mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-09-17 00:26:26 +00:00
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.
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
# freehire-cli
|
||||
|
||||
CLI for searching the [freehire.dev](https://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](https://github.com/strelov1/freehire) backend to swap
|
||||
> the source.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
# 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`](https://freehire.dev/api/v1/jobs/facets), or narrow it,
|
||||
e.g. `https://freehire.dev/api/v1/jobs/facets?q=react`.
|
||||
Reference in New Issue
Block a user