From 7a753f3cd40ae8561cc13b2bbb5663fb06a121fb Mon Sep 17 00:00:00 2001 From: Mads Lorentzen Date: Thu, 30 Jul 2026 11:04:45 +0200 Subject: [PATCH] docs(readme): document the extension model - portals, templates, criteria, borrowing from forks Prompted by the extension-system question in discussion #249: the three extension points existed as folklore across #78, /add-portal, and closed PRs. Now stated plainly, with a read-the-code-first checklist for borrowing portal skills and the rationale for why there is no installer. Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 9 +++++++++ README.md | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 473a7c3..f9cd4b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,15 @@ per-file diff commands. ## [Unreleased] +- **README: the extension model, documented** - new Customization subsection "Extending the + framework: portals, templates, criteria - and borrowing from other forks". States plainly + what was previously folklore: the three extension points (portal skills with their + auto-discovered contract, `/add-template` toolchains, free-form evaluation criteria), the + copy-one-folder pattern for borrowing a portal skill from another fork with a + read-the-code-first checklist, and why there is deliberately no installer (the manual copy + is the security model). Documentation only - no behavior changes. Prompted by the + extension-system question in discussion #249. + - **freehire-search: full descriptions come back with the search** - `search` now calls freehire's agent search endpoint (`/api/v1/agent/jobs/search`), which serves each hit's complete description instead of the search index's truncated preview. A 20-role search is diff --git a/README.md b/README.md index 9f287a7..48a771a 100644 --- a/README.md +++ b/README.md @@ -302,6 +302,24 @@ For **country-agnostic** starting points outside Denmark, the repo ships two por - **`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.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`. +### Extending the framework: portals, templates, criteria - and borrowing from other forks + +Everything above adds up to an extension model, so here it is stated plainly. The framework has three extension points, and none of them require touching upstream: + +1. **Portal skills** - the module system for job boards. Every `*-search` skill is a self-contained folder under `.agents/skills/` with the same contract (a `search`/`detail` CLI, `--format json|table|plain` output, an `enabled:` flag in its `SKILL.md`, its own tests). `/scrape` auto-discovers any installed skill that follows the contract - nothing to register, nothing to wire up. `/add-portal` generates new ones; the [community portal index](https://github.com/MadsLorentzen/ai-job-search/discussions/78) catalogs the ones other forks have built. +2. **Document templates** - `/add-template` registers any CV or cover-letter toolchain that compiles to PDF from the command line, LaTeX or otherwise. +3. **Evaluation criteria** - deal-breakers and preferences in your profile are free-form, and the evaluation rubric scores against whatever you put there. "Strong parental-leave terms", "minimum salary X per my union's scale", "no on-call" - each is one profile line, no code, and it carries real weight in `/rank` and `/apply` fit evaluations. + +**Borrowing a portal skill from another fork** is the intended way to get a board that upstream doesn't ship: find it in the [portal index](https://github.com/MadsLorentzen/ai-job-search/discussions/78), open that fork, and copy the one folder into your own `.agents/skills/`. Before you run it: + +- **Read the code.** All of it - these CLIs run pre-approved on your machine (`.claude/settings.json` allowlists them) against your career data. Check that the only network calls go to the job board it claims to search, that `package.json` has no `dependencies` and no lifecycle scripts (`postinstall` etc.), and that nothing reads or writes outside its own folder. +- **Run its tests offline** (`bun test` in the skill's `cli/` directory) - a well-built skill's tests pass with no network access. +- Check the `enabled:` flag and the skill's own ToS notes. + +The copy step is manual on purpose. Your settings already allow installed portal skills to run without asking each time - so an installer that fetched them from third-party repos for you would skip the one check that matters: you, reading the code first. There isn't one, and that's a security decision rather than a missing feature. + +Market-specific *data sources* (a national salary database, local award-rate tables) follow the same pattern as portals: they belong in a market fork, shared via [#78](https://github.com/MadsLorentzen/ai-job-search/discussions/78), not upstream. + ### Salary benchmarking The salary tool works with any salary data you provide (union statistics, Glassdoor exports, personal research, etc.). See `tools/README_SALARY_TOOL.md` for the expected format and setup. If you don't have salary data, the salary step is simply skipped.