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:
Ilya Strelov
2026-07-23 10:17:10 +02:00
committed by GitHub
parent 7db231c680
commit 1ae66ad094
8 changed files with 28 additions and 28 deletions
+8 -8
View File
@@ -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`.
+6 -6
View File
@@ -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
+2 -2
View File
@@ -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