How to Add a New Job Portal to the ai-job-search CLI: The Complete Guide
Adding a new job portal to the ai-job-search CLI requires running the /add-portal command to scaffold a zero-dependency portal skill in .agents/skills/ that implements the required search and detail commands, passes a live query test, and automatically integrates into the /scrape workflow.
The ai-job-search framework by MadsLorentzen discovers job listings through modular portal skills—self-contained CLIs located in .agents/skills/. Each portal skill follows a strict contract defined in .claude/commands/add-portal.md so that the core workflow can invoke any job portal transparently without modifying the underlying scraper code.
Understanding the Portal-Skill Contract
Every portal skill must conform to a strict interface contract documented in the Add-Portal command specification. This contract ensures that the core /scrape workflow can discover and execute any portal skill uniformly.
Required CLI Commands and Flags
Each portal skill must expose two primary commands: search and detail. Both commands must support the following flag set:
--query(search term)--jobage(maximum age of postings)--page(pagination)--limit(results per page)--format(output format)
According to the contract in .claude/commands/add-portal.md, these flags enable the generic scraper to pass consistent parameters across all portals.
JSON Output Specification
The search command must emit JSON shaped exactly as { "meta":…, "results":[…] }, where each result object contains the fields id, title, company, location, date, and url. Errors must be written to stderr as JSON with exit code 1. This standardized output allows the generic scraper in .claude/skills/job-scraper/SKILL.md to parse results from any portal without custom adapters.
Step-by-Step Guide to Adding a New Portal
The /add-portal command orchestrates a guided interview process to scaffold a new portal skill. The workflow copies the canonical structure from the reference linkedin-search skill, which serves as the zero-dependency example implementation.
Step 1: Initialize the Interview
Run the /add-portal command with a target URL to begin the guided process:
claude /add-portal https://www.seek.com.au
If you omit the URL, the command starts an interactive interview. During this phase, you provide:
- A kebab-case skill name ending in
-search(e.g.,seek-search) - Market and language (e.g.,
Australia (English)) - A realistic test query for validation
Step 2: Investigate the Portal Structure
The command prompts you to investigate the portal's architecture using WebFetch or curl. You must document:
- The search URL pattern and query parameters
- Result field mappings (
id,title,company,location,date,url) - The detail page URL pattern
- Access constraints (robots.txt, authentication requirements, ToS)
These findings are recorded in url-reference.md for later reference during implementation.
Step 3: Scaffold the Skill Files
The generator creates the directory .agents/skills/<name>/ with the following structure:
.agents/skills/<name>/
├── SKILL.md # Skill definition and trigger phrases
├── url-reference.md # Endpoint documentation
└── cli/
├── package.json
├── tsconfig.json
├── README.md
├── src/
│ ├── cli.ts # Entry point and argument dispatch
│ ├── helpers.ts # Fetching with exponential back-off
│ └── commands/
│ ├── search.ts # Implements search logic
│ └── detail.ts # Implements detail fetching
└── tests/
└── helpers.ts
This scaffold includes a full TypeScript CLI skeleton with zero runtime dependencies, mirroring the linkedin-search reference implementation.
Step 4: Run Live Tests
After scaffolding, install dev dependencies and type-check the code:
cd .agents/skills/seek-search/cli
bun install
bun run typecheck
Execute a live search using your test query to verify the implementation:
bun run src/cli.ts search -q "software engineer" --limit 5 --format table
The result set must contain non-null values for id, title, and url. Next, verify the detail command:
bun run src/cli.ts detail 12345678 --format plain
Finally, run the generated test suite:
bun run test
Step 5: Register the Portal
Once the live test passes, append the portal's domain to .claude/skills/job-scraper/search-queries.md so that /scrape includes it in future searches:
echo "seek.com.au" >> .claude/skills/job-scraper/search-queries.md
The core scraper automatically discovers installed portal skills by reading every SKILL.md under .agents/skills/*/, requiring no further code changes.
Testing Requirements and Validation
The ai-job-search framework enforces strict validation before a portal skill can be used in production. The live query test in Step 4 is mandatory and validates that:
- The
searchcommand returns at least one result with validid,title, andurlfields - The
detailcommand successfully retrieves full posting information - Error handling writes JSON to stderr with exit code
1 - Zero runtime dependencies are maintained (only dev types allowed)
These requirements ensure that the core scraper in .claude/skills/job-scraper/SKILL.md can orchestrate parallel searches across all portals without compatibility issues.
Integration with the Core Scraper
Once registered, the portal skill integrates automatically with the main workflow. The core scraper reads the declarative SKILL.md files from .agents/skills/*/ to discover available portals. When you invoke claude /scrape, the system:
- Identifies all installed portal skills
- Invokes their
searchcommands with the appropriate flags - Aggregates the standardized JSON outputs
- Executes
detailcommands for specific postings when needed
This architecture means that adding a new job portal requires no modifications to the core scraper code—only the creation of a conforming portal skill in .agents/skills/.
Summary
- Run
/add-portalto start the guided interview process for creating a new job portal skill - Portal skills must implement
searchanddetailcommands with specific flags (--query,--jobage,--page,--limit,--format) and JSON output - The scaffold generates a zero-dependency TypeScript CLI in
.agents/skills/<name>/based on thelinkedin-searchreference - Mandatory live testing ensures the portal returns valid
id,title, andurlfields before registration - Register the portal by adding its domain to
.claude/skills/job-scraper/search-queries.mdfor automatic inclusion in/scrapeworkflows
Frequently Asked Questions
What is the portal-skill contract in ai-job-search?
The portal-skill contract is a strict interface specification defined in .claude/commands/add-portal.md that requires every portal skill to expose search and detail commands with standardized flags (--query, --jobage, --page, --limit, --format). The contract mandates JSON output shaped as { "meta":…, "results":[…] } containing fields id, title, company, location, date, and url, with errors written to stderr as JSON. This standardization allows the core scraper to invoke any portal skill transparently.
Where are portal skills stored in the ai-job-search repository?
Portal skills are stored in the .agents/skills/ directory, with each skill residing in its own subdirectory named using kebab-case (e.g., .agents/skills/linkedin-search/). Each skill directory contains a SKILL.md file defining its capabilities, a url-reference.md documenting the portal's endpoints, and a cli/ subdirectory containing the TypeScript implementation with src/commands/search.ts and src/commands/detail.ts.
How does the core scraper discover new portal skills?
The core scraper, defined in .claude/skills/job-scraper/SKILL.md, automatically discovers portal skills by reading the SKILL.md files in every subdirectory of .agents/skills/*/. It uses the declarative metadata in these files to determine available trigger phrases and invokes the respective CLI commands with the standardized flag set. No manual registration in the scraper code is required—only adding the domain to search-queries.md and ensuring the skill exists in .agents/skills/.
What are the testing requirements for a new job portal skill?
Every new portal skill must pass a mandatory live query test before integration. You must run a real search using bun run src/cli.ts search -q "<query>" and verify that the returned JSON contains non-null values for id, title, and url. Additionally, you must test the detail command with a specific job ID and run the full test suite with bun run test. The skill must maintain zero runtime dependencies and handle errors by writing JSON to stderr with exit code 1.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →