mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-09-17 00:26:26 +00:00
chore(freehire-search): point at freehire.me (domain migrated from freehire.dev) (#229)
freehire moved its primary domain from freehire.dev to freehire.me. Update the freehire-search skill's default API base URL, help text, docs, and examples. Backward-compatible: FREEHIRE_API_URL still overrides the base (self-hosting), and normalizeSlug is host-agnostic so pasted freehire.dev/jobs/<slug> URLs still resolve. The GitHub repo link (github.com/strelov1/freehire) is unchanged. All 27 CLI tests pass; the freehire.me API answers 200 for /jobs/search + /jobs/facets.
This commit is contained in:
@@ -3,7 +3,7 @@ name: freehire-search
|
|||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
description: >
|
description: >
|
||||||
Use this skill to search live software / tech / data / engineering job listings
|
Use this skill to search live software / tech / data / engineering job listings
|
||||||
across many countries and markets (and remote) via the freehire.dev aggregator's
|
across many countries and markets (and remote) via the freehire.me aggregator's
|
||||||
public API, or to look up a specific posting. It aggregates roles from ~50 ATS
|
public API, or to look up a specific posting. It aggregates roles from ~50 ATS
|
||||||
platforms into one schema, so a single skill covers many markets — but its faceted
|
platforms into one schema, so a single skill covers many markets — but its faceted
|
||||||
filtering (skills, category, seniority) is tuned tech-first, so scope triggers to
|
filtering (skills, category, seniority) is tuned tech-first, so scope triggers to
|
||||||
@@ -17,7 +17,7 @@ allowed-tools: Bash(bun run .agents/skills/freehire-search/cli/src/cli.ts *)
|
|||||||
|
|
||||||
# freehire Search Skill
|
# freehire Search Skill
|
||||||
|
|
||||||
Search live job listings from the **[freehire.dev](https://freehire.dev)** job
|
Search live job listings from the **[freehire.me](https://freehire.me)** job
|
||||||
aggregator — an open-source IT job board that normalizes postings from ~50 ATS
|
aggregator — an open-source IT job board that normalizes postings from ~50 ATS
|
||||||
platforms across many countries into one schema. No authentication, no API key,
|
platforms across many countries into one schema. No authentication, no API key,
|
||||||
and **zero runtime dependencies** — it runs with just `bun`. The market is chosen
|
and **zero runtime dependencies** — it runs with just `bun`. The market is chosen
|
||||||
@@ -40,10 +40,10 @@ coverage exists but is still maturing; don't rely on this skill for general
|
|||||||
|
|
||||||
## ℹ️ Hosted-service dependency (best-effort, no SLA)
|
## ℹ️ Hosted-service dependency (best-effort, no SLA)
|
||||||
|
|
||||||
This skill depends on a third-party hosted service, freehire.dev. Reads are
|
This skill depends on a third-party hosted service, freehire.me. Reads are
|
||||||
**public and unauthenticated** — the same zero-signup bar as `linkedin-search`.
|
**public and unauthenticated** — the same zero-signup bar as `linkedin-search`.
|
||||||
|
|
||||||
**freehire.dev is a personal project but actively maintained; it runs on a
|
**freehire.me is a personal project but actively maintained; it runs on a
|
||||||
best-effort basis (no formal SLA).** If the API is unreachable, the CLI fails
|
best-effort basis (no formal SLA).** If the API is unreachable, the CLI fails
|
||||||
gracefully — a non-zero exit with a clear error message — so an outage degrades
|
gracefully — a non-zero exit with a clear error message — so an outage degrades
|
||||||
this source rather than breaking the surrounding workflow.
|
this source rather than breaking the surrounding workflow.
|
||||||
@@ -52,7 +52,7 @@ this source rather than breaking the surrounding workflow.
|
|||||||
MIT-licensed repo — [`strelov1/freehire`](https://github.com/strelov1/freehire)
|
MIT-licensed repo — [`strelov1/freehire`](https://github.com/strelov1/freehire)
|
||||||
(Go + PostgreSQL + Meilisearch) — that stands up with one command via Docker
|
(Go + PostgreSQL + Meilisearch) — that stands up with one command via Docker
|
||||||
Compose (`make up` → API on `:8080`, same `/api/v1/...` paths). The skill honors a
|
Compose (`make up` → API on `:8080`, same `/api/v1/...` paths). The skill honors a
|
||||||
base-URL env var, `FREEHIRE_API_URL` (default `https://freehire.dev`), so pointing
|
base-URL env var, `FREEHIRE_API_URL` (default `https://freehire.me`), so pointing
|
||||||
it at a local instance is a one-line change:
|
it at a local instance is a one-line change:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -99,7 +99,7 @@ Facet filters (values come from freehire's controlled vocabularies; comma-separa
|
|||||||
> **Location is a facet, not free text.** Unlike `linkedin-search`'s `--location`,
|
> **Location is a facet, not free text.** Unlike `linkedin-search`'s `--location`,
|
||||||
> freehire filters geography through the structured `--region`/`--country`/`--city`
|
> freehire filters geography through the structured `--region`/`--country`/`--city`
|
||||||
> facets. Discover the live values for a market at
|
> facets. Discover the live values for a market at
|
||||||
> [`/api/v1/jobs/facets`](https://freehire.dev/api/v1/jobs/facets) (append `?q=<role>`
|
> [`/api/v1/jobs/facets`](https://freehire.me/api/v1/jobs/facets) (append `?q=<role>`
|
||||||
> to scope it) — never invent facet values.
|
> to scope it) — never invent facet values.
|
||||||
|
|
||||||
### Fetch full job detail
|
### Fetch full job detail
|
||||||
@@ -109,7 +109,7 @@ bun run .agents/skills/freehire-search/cli/src/cli.ts detail <slug|url> [--forma
|
|||||||
```
|
```
|
||||||
|
|
||||||
`slug` is the `id` from a `search` result (e.g. `golang-zensar-2bxu6dxm`). You may
|
`slug` is the `id` from a `search` result (e.g. `golang-zensar-2bxu6dxm`). You may
|
||||||
also pass a full `https://freehire.dev/jobs/<slug>` URL. Returns the full (HTML-stripped)
|
also pass a full `https://freehire.me/jobs/<slug>` URL. Returns the full (HTML-stripped)
|
||||||
description, skills, region/country, and — when the posting is enriched — seniority,
|
description, skills, region/country, and — when the posting is enriched — seniority,
|
||||||
category, employment type, and salary.
|
category, employment type, and salary.
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ dictionaries never guess). So:
|
|||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- Data is from freehire.dev's public API — no credentials required. Only per-user
|
- Data is from freehire.me's public API — no credentials required. Only per-user
|
||||||
tracking (apply/save) needs a key, and this skill deliberately does not touch it:
|
tracking (apply/save) needs a key, and this skill deliberately does not touch it:
|
||||||
it is **search + detail only**.
|
it is **search + detail only**.
|
||||||
- `id` in search results is the freehire `public_slug` — pass it as-is to `detail`.
|
- `id` in search results is the freehire `public_slug` — pass it as-is to `detail`.
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
# freehire-cli
|
# freehire-cli
|
||||||
|
|
||||||
CLI for searching the [freehire.dev](https://freehire.dev) job aggregator across
|
CLI for searching the [freehire.me](https://freehire.me) job aggregator across
|
||||||
**many markets** (tech-focused), via its public JSON API.
|
**many markets** (tech-focused), via its public JSON API.
|
||||||
|
|
||||||
**Data source**: freehire.dev REST API (`/api/v1/jobs/search`, `/api/v1/jobs/facets`, `/api/v1/jobs/{slug}`).
|
**Data source**: freehire.me REST API (`/api/v1/jobs/search`, `/api/v1/jobs/facets`, `/api/v1/jobs/{slug}`).
|
||||||
**Authentication**: None required — reads are public (only tracking mutations need a key, and those are out of scope here).
|
**Authentication**: None required — reads are public (only tracking mutations need a key, and those are out of scope here).
|
||||||
**Dependencies**: None (plain `bun` + `fetch`). `bun install` is optional and only pulls dev type defs.
|
**Dependencies**: None (plain `bun` + `fetch`). `bun install` is optional and only pulls dev type defs.
|
||||||
|
|
||||||
> **Hosted-service dependency.** This skill talks to freehire.dev, a personal
|
> **Hosted-service dependency.** This skill talks to freehire.me, a personal
|
||||||
> project maintained on a **best-effort basis with no formal SLA**. If the API is
|
> project maintained on a **best-effort basis with no formal SLA**. If the API is
|
||||||
> unreachable the CLI exits non-zero with a clear error rather than hanging, so an
|
> unreachable the CLI exits non-zero with a clear error rather than hanging, so an
|
||||||
> outage degrades gracefully instead of breaking the caller. Point `FREEHIRE_API_URL`
|
> outage degrades gracefully instead of breaking the caller. Point `FREEHIRE_API_URL`
|
||||||
@@ -25,7 +25,7 @@ The CLI runs without any install because it has zero runtime dependencies.
|
|||||||
|
|
||||||
## Self-hosting / base URL
|
## Self-hosting / base URL
|
||||||
|
|
||||||
The base URL defaults to `https://freehire.dev` and is overridable with an env var:
|
The base URL defaults to `https://freehire.me` and is overridable with an env var:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
FREEHIRE_API_URL=http://localhost:8080 bun run src/cli.ts search -q "go"
|
FREEHIRE_API_URL=http://localhost:8080 bun run src/cli.ts search -q "go"
|
||||||
@@ -83,5 +83,5 @@ See `../SKILL.md` for the full flag reference and the hosted-dependency note.
|
|||||||
|
|
||||||
Facet values come from freehire's controlled vocabularies. Discover the live
|
Facet values come from freehire's controlled vocabularies. Discover the live
|
||||||
values (with counts) for a market at
|
values (with counts) for a market at
|
||||||
[`/api/v1/jobs/facets`](https://freehire.dev/api/v1/jobs/facets), or narrow it,
|
[`/api/v1/jobs/facets`](https://freehire.me/api/v1/jobs/facets), or narrow it,
|
||||||
e.g. `https://freehire.dev/api/v1/jobs/facets?q=react`.
|
e.g. `https://freehire.me/api/v1/jobs/facets?q=react`.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "freehire-cli",
|
"name": "freehire-cli",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "CLI for searching the freehire.dev job aggregator's public JSON API across many markets (tech-focused) — no authentication, zero runtime dependencies. Base URL is swappable via FREEHIRE_API_URL for self-hosting.",
|
"description": "CLI for searching the freehire.me job aggregator's public JSON API across many markets (tech-focused) — no authentication, zero runtime dependencies. Base URL is swappable via FREEHIRE_API_URL for self-hosting.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/cli.ts",
|
"main": "src/cli.ts",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
#!/usr/bin/env bun
|
#!/usr/bin/env bun
|
||||||
// Self-contained CLI for searching the freehire.dev aggregator's public JSON API.
|
// Self-contained CLI for searching the freehire.me aggregator's public JSON API.
|
||||||
// No external CLI framework and zero runtime dependencies, so it runs anywhere
|
// No external CLI framework and zero runtime dependencies, so it runs anywhere
|
||||||
// `bun` is available with nothing installed beyond the repo clone.
|
// `bun` is available with nothing installed beyond the repo clone.
|
||||||
//
|
//
|
||||||
// Hosted-service dependency: reads are public (no API key), but they hit
|
// Hosted-service dependency: reads are public (no API key), but they hit
|
||||||
// freehire.dev — a personal project maintained best-effort (no formal SLA). Point
|
// freehire.me — a personal project maintained best-effort (no formal SLA). Point
|
||||||
// FREEHIRE_API_URL at a self-hosted freehire backend to swap the source.
|
// FREEHIRE_API_URL at a self-hosted freehire backend to swap the source.
|
||||||
|
|
||||||
import { runSearch, type SearchOpts } from "./commands/search.js"
|
import { runSearch, type SearchOpts } from "./commands/search.js"
|
||||||
@@ -69,7 +69,7 @@ function commaList(raw: FlagValue): string[] {
|
|||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
}
|
}
|
||||||
|
|
||||||
const HELP = `freehire-cli — search the freehire.dev job aggregator (many markets, tech-focused)
|
const HELP = `freehire-cli — search the freehire.me job aggregator (many markets, tech-focused)
|
||||||
|
|
||||||
USAGE
|
USAGE
|
||||||
bun run src/cli.ts search [-q "<keywords>"] [facet flags] [--format json|table|plain]
|
bun run src/cli.ts search [-q "<keywords>"] [facet flags] [--format json|table|plain]
|
||||||
@@ -82,7 +82,7 @@ SEARCH FLAGS
|
|||||||
--limit, -n <n> Results per page (API limit). Default 25.
|
--limit, -n <n> Results per page (API limit). Default 25.
|
||||||
--format <fmt> json (default) | table | plain.
|
--format <fmt> json (default) | table | plain.
|
||||||
|
|
||||||
FACET FILTERS (values from freehire.dev's controlled vocabularies; comma = OR)
|
FACET FILTERS (values from freehire.me's controlled vocabularies; comma = OR)
|
||||||
--region <codes> Macro-region: global, eu, us, apac, latam, cis, ... e.g. --region eu,us
|
--region <codes> Macro-region: global, eu, us, apac, latam, cis, ... e.g. --region eu,us
|
||||||
--country <codes> ISO-3166 alpha-2, e.g. --country DE,GB
|
--country <codes> ISO-3166 alpha-2, e.g. --country DE,GB
|
||||||
--city <names> City name(s), e.g. --city Berlin
|
--city <names> City name(s), e.g. --city Berlin
|
||||||
@@ -95,7 +95,7 @@ FACET FILTERS (values from freehire.dev's controlled vocabularies; comma = OR)
|
|||||||
|
|
||||||
DETAIL
|
DETAIL
|
||||||
<slug|url> A freehire public slug (from a search result's id/slug)
|
<slug|url> A freehire public slug (from a search result's id/slug)
|
||||||
or a full https://freehire.dev/jobs/<slug> URL.
|
or a full https://freehire.me/jobs/<slug> URL.
|
||||||
|
|
||||||
EXAMPLES
|
EXAMPLES
|
||||||
bun run src/cli.ts search -q "backend engineer" --seniority senior --limit 10 --format table
|
bun run src/cli.ts search -q "backend engineer" --seniority senior --limit 10 --format table
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
// Data source: the freehire.dev public REST API (JSON, `{data, meta}` envelope).
|
// Data source: the freehire.me public REST API (JSON, `{data, meta}` envelope).
|
||||||
// Reads are unauthenticated — no API key, the same bar as linkedin-search — and
|
// Reads are unauthenticated — no API key, the same bar as linkedin-search — and
|
||||||
// unlike the HTML-scraping portals there is no markup to parse: we fetch JSON and
|
// unlike the HTML-scraping portals there is no markup to parse: we fetch JSON and
|
||||||
// reshape it into the portal-skill contract's result fields. The base URL is
|
// reshape it into the portal-skill contract's result fields. The base URL is
|
||||||
// swappable via FREEHIRE_API_URL for self-hosting.
|
// swappable via FREEHIRE_API_URL for self-hosting.
|
||||||
|
|
||||||
export const DEFAULT_BASE_URL = "https://freehire.dev"
|
export const DEFAULT_BASE_URL = "https://freehire.me"
|
||||||
|
|
||||||
/** API base URL: FREEHIRE_API_URL (for a self-hosted instance) or the default. */
|
/** API base URL: FREEHIRE_API_URL (for a self-hosted instance) or the default. */
|
||||||
export function baseUrl(): string {
|
export function baseUrl(): string {
|
||||||
@@ -16,7 +16,7 @@ export function writeError(error: string, code: string): void {
|
|||||||
process.stderr.write(JSON.stringify({ error, code }) + "\n")
|
process.stderr.write(JSON.stringify({ error, code }) + "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
const UA = "freehire-search-skill/1.0 (+https://freehire.dev)"
|
const UA = "freehire-search-skill/1.0 (+https://freehire.me)"
|
||||||
|
|
||||||
/** The shared API response envelope: {data, meta, error}. */
|
/** The shared API response envelope: {data, meta, error}. */
|
||||||
export interface Envelope<T> {
|
export interface Envelope<T> {
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ describe("normalizeSlug", () => {
|
|||||||
expect(normalizeSlug("golang-zensar-2bxu6dxm")).toBe("golang-zensar-2bxu6dxm");
|
expect(normalizeSlug("golang-zensar-2bxu6dxm")).toBe("golang-zensar-2bxu6dxm");
|
||||||
});
|
});
|
||||||
test("extracts the slug from a /jobs/<slug> URL", () => {
|
test("extracts the slug from a /jobs/<slug> URL", () => {
|
||||||
expect(normalizeSlug("https://freehire.dev/jobs/golang-zensar-2bxu6dxm")).toBe("golang-zensar-2bxu6dxm");
|
expect(normalizeSlug("https://freehire.me/jobs/golang-zensar-2bxu6dxm")).toBe("golang-zensar-2bxu6dxm");
|
||||||
});
|
});
|
||||||
test("rejects a non-slug string", () => {
|
test("rejects a non-slug string", () => {
|
||||||
expect(normalizeSlug("not a slug!")).toBeNull();
|
expect(normalizeSlug("not a slug!")).toBeNull();
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# freehire.dev API reference
|
# freehire.me API reference
|
||||||
|
|
||||||
The endpoints, parameters, and response shapes this skill depends on. This is the
|
The endpoints, parameters, and response shapes this skill depends on. This is the
|
||||||
file to update if the freehire API changes. Base URL defaults to
|
file to update if the freehire API changes. Base URL defaults to
|
||||||
`https://freehire.dev` and is overridable via the `FREEHIRE_API_URL` env var.
|
`https://freehire.me` and is overridable via the `FREEHIRE_API_URL` env var.
|
||||||
|
|
||||||
## Authentication
|
## Authentication
|
||||||
|
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ ai-job-search/
|
|||||||
│ ├── jobindex-search/ # Jobindex.dk (Denmark)
|
│ ├── jobindex-search/ # Jobindex.dk (Denmark)
|
||||||
│ ├── jobnet-search/ # Jobnet.dk (Denmark, government portal)
|
│ ├── jobnet-search/ # Jobnet.dk (Denmark, government portal)
|
||||||
│ ├── linkedin-search/ # LinkedIn public job listings (country-agnostic)
|
│ ├── linkedin-search/ # LinkedIn public job listings (country-agnostic)
|
||||||
│ └── freehire-search/ # freehire.dev tech job aggregator (multi-market, REST API)
|
│ └── freehire-search/ # freehire.me tech job aggregator (multi-market, REST API)
|
||||||
├── cv/
|
├── cv/
|
||||||
│ └── main_example.tex # moderncv LaTeX template
|
│ └── main_example.tex # moderncv LaTeX template
|
||||||
├── cover_letters/
|
├── cover_letters/
|
||||||
@@ -300,7 +300,7 @@ Maintaining a fork adapted to your market or language? Add it to the [Community
|
|||||||
For **country-agnostic** starting points outside Denmark, the repo ships two portal skills alongside the Danish demos:
|
For **country-agnostic** starting points outside Denmark, the repo ships two portal skills alongside the Danish demos:
|
||||||
|
|
||||||
- **`linkedin-search`** — built on LinkedIn's public, unauthenticated `jobs-guest` endpoints. Field-agnostic, **zero runtime dependencies** (runs with just `bun`), and takes the search location as an explicit flag, so it works for any market out of the box (`-l "Berlin, Germany"`, `-l "Mumbai, Maharashtra, India"`, `-l "Remote"`, …). Intended for **personal use only** — automated access is against LinkedIn's Terms of Service, so keep volume low. See `.agents/skills/linkedin-search/SKILL.md`.
|
- **`linkedin-search`** — built on LinkedIn's public, unauthenticated `jobs-guest` endpoints. Field-agnostic, **zero runtime dependencies** (runs with just `bun`), and takes the search location as an explicit flag, so it works for any market out of the box (`-l "Berlin, Germany"`, `-l "Mumbai, Maharashtra, India"`, `-l "Remote"`, …). Intended for **personal use only** — automated access is against LinkedIn's Terms of Service, so keep volume low. See `.agents/skills/linkedin-search/SKILL.md`.
|
||||||
- **`freehire-search`** — queries the [freehire.dev](https://freehire.dev) aggregator's public REST API (JSON, no API key). Tech-focused (software, data, engineering, DevOps, remote), multi-market via facet flags (`--region`, `--country`, `--remote`), and **zero runtime dependencies**. Unlike the HTML-scraping Danish portals, results come back structured (skills, seniority, category). The backend is MIT-licensed and [self-hostable](https://github.com/strelov1/freehire) — point `FREEHIRE_API_URL` at your own instance if you prefer. See `.agents/skills/freehire-search/SKILL.md`.
|
- **`freehire-search`** — queries the [freehire.me](https://freehire.me) aggregator's public REST API (JSON, no API key). Tech-focused (software, data, engineering, DevOps, remote), multi-market via facet flags (`--region`, `--country`, `--remote`), and **zero runtime dependencies**. Unlike the HTML-scraping Danish portals, results come back structured (skills, seniority, category). The backend is MIT-licensed and [self-hostable](https://github.com/strelov1/freehire) — point `FREEHIRE_API_URL` at your own instance if you prefer. See `.agents/skills/freehire-search/SKILL.md`.
|
||||||
|
|
||||||
### Salary benchmarking
|
### Salary benchmarking
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user