Files
ai-job-search/.agents/skills/freehire-search/cli
Ilya Strelov b8d35a4b69 Add freehire-search: country-agnostic freehire.dev aggregator skill (#85)
* feat(freehire-search): add country-agnostic freehire.dev aggregator skill

Adds a portal-search skill over the freehire.dev public JSON API — an
open-source IT job aggregator normalizing ~50 ATS platforms across many
markets into one schema. Like linkedin-search it is country-agnostic and
zero-dependency (plain bun + fetch), but it queries a JSON API rather than
scraping HTML, so results carry structured facets (skills/seniority/region).

Honors the portal-skill contract: search + detail commands, --format
json|table|plain, stderr JSON errors with exit 1, backoff on 429/5xx. Reads
are public (no API key) — the same zero-signup bar as linkedin-search. The
hosted-service dependency (best-effort, no SLA) is labeled prominently in
SKILL.md, and FREEHIRE_API_URL swaps the base URL for a self-hosted backend.

Scoped tech-first: triggers cover software/data/engineering roles, where the
faceted filtering is strong; non-tech coverage exists but is still maturing.

Network-free tests (mocked fetch + pure reshape/parse functions); CI matrix
updated to typecheck the new CLI.

* refactor(freehire-search): clarity pass on cli flag parsing

No behavior change. Replace a nested ternary and a comma-operator side effect
in a ternary with explicit if/else, and fix a comment that described facets
while sitting on the alias map.

* refactor(freehire-search): tighten to boundary contracts, trim comments

- Validate/normalize at boundaries, trust the declared types inside: drop the
  redundant '?? []' guards on facet arrays the wire contract already guarantees,
  and the re-filter in buildQuery (commaList already stripped empties).
- Model enrichment as always-present (an unenriched job serializes it as {}),
  removing the '?? {}' guard.
- Replace the positional table-row builder with a declarative column list; add a
  shared shortDate and a labeled-field helper for detail's plain output.
- Extract stringFlag for the string-or-bare-boolean flags (--remote/--query/...).
- Dedup the response parse in apiGet to a single tolerant read (drop safeJson).
- SKILL.md: document partial data + the 'none' unspecified-region facet.
- Trim restating comments to the reference skills' density.
2026-07-09 06:04:35 +02:00
..

freehire-cli

CLI for searching the freehire.dev job aggregator across 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}). 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.

Hosted-service dependency. This skill talks to freehire.dev, a personal 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 outage degrades gracefully instead of breaking the caller. Point FREEHIRE_API_URL at a self-hosted freehire backend to swap the source.

Installation

cd .agents/skills/freehire-search/cli
bun install   # optional — only installs TypeScript dev types

The CLI runs without any install because it has zero runtime dependencies.

Self-hosting / base URL

The base URL defaults to https://freehire.dev and is overridable with an env var:

FREEHIRE_API_URL=http://localhost:8080 bun run src/cli.ts search -q "go"

The freehire backend is MIT-licensed and stands up with one command via Docker Compose (make up → API on :8080, same /api/v1/... paths).

Commands

Command Description
search Search jobs by keyword and facet filters
detail Fetch full detail for a single job by its slug

search accepts --format json|table|plain (default json); detail accepts --format json|plain. All errors are written to stderr as { "error": "...", "code": "..." } with exit code 1.

Quick examples

# Senior backend roles, table view
bun run src/cli.ts search -q "backend engineer" --seniority senior --limit 10 --format table

# Remote React roles in the EU
bun run src/cli.ts search -q "react" --remote remote --region eu --format table

# DevOps roles in Germany posted in the last 14 days
bun run src/cli.ts search --category devops --country DE --jobage 14 --format table

# Full detail for one job (slug from a search result's id)
bun run src/cli.ts detail golang-zensar-2bxu6dxm --format plain

See ../SKILL.md for the full flag reference and the hosted-dependency note.

Search flags

Flag Alias Description
--query -q Keywords (title / skill / role). Full-text; optional.
--jobage Posted within N days (posted_within_days).
--page 1-indexed page. Default 1.
--limit -n Results per page (API limit). Default 25.
--region Macro-region(s), comma = OR (e.g. eu,us).
--country ISO-3166 alpha-2 code(s).
--city City name(s).
--seniority Seniority level(s).
--category Role category(ies).
--skill Canonical skill(s).
--company Company slug.
--remote remote | hybrid | onsite (work_mode).
--facet Any other facet as key=value (repeatable).
--format json | table | plain.

Facet values come from freehire's controlled vocabularies. Discover the live values (with counts) for a market at /api/v1/jobs/facets, or narrow it, e.g. https://freehire.dev/api/v1/jobs/facets?q=react.