# How to Add a New Job Portal to the ai-job-search CLI: The Complete Guide

> Learn to add a new job portal to the ai-job-search CLI. This guide details using the /add-portal command to scaffold a new skill and integrate it into the scraping workflow.

- Repository: [Mads Lorentzen/ai-job-search](https://github.com/MadsLorentzen/ai-job-search)
- Tags: how-to-guide
- Published: 2026-07-12

---

**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`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.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`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.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`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.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:

```bash
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`](https://github.com/MadsLorentzen/ai-job-search/blob/main/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:

```text
.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:

```bash
cd .agents/skills/seek-search/cli
bun install
bun run typecheck

```

Execute a live search using your test query to verify the implementation:

```bash
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:

```bash
bun run src/cli.ts detail 12345678 --format plain

```

Finally, run the generated test suite:

```bash
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`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/skills/job-scraper/search-queries.md) so that `/scrape` includes it in future searches:

```bash
echo "seek.com.au" >> .claude/skills/job-scraper/search-queries.md

```

The core scraper automatically discovers installed portal skills by reading every [`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/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:

1. The `search` command returns at least one result with valid `id`, `title`, and `url` fields
2. The `detail` command successfully retrieves full posting information
3. Error handling writes JSON to stderr with exit code `1`
4. Zero runtime dependencies are maintained (only dev types allowed)

These requirements ensure that the core scraper in [`.claude/skills/job-scraper/SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.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`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md) files from `.agents/skills/*/` to discover available portals. When you invoke `claude /scrape`, the system:

1. Identifies all installed portal skills
2. Invokes their `search` commands with the appropriate flags
3. Aggregates the standardized JSON outputs
4. Executes `detail` commands 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-portal` to start the guided interview process for creating a new job portal skill
- Portal skills must implement `search` and `detail` commands 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 the `linkedin-search` reference
- Mandatory live testing ensures the portal returns valid `id`, `title`, and `url` fields before registration
- Register the portal by adding its domain to [`.claude/skills/job-scraper/search-queries.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/skills/job-scraper/search-queries.md) for automatic inclusion in `/scrape` workflows

## 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`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.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`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md) file defining its capabilities, a [`url-reference.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/url-reference.md) documenting the portal's endpoints, and a `cli/` subdirectory containing the TypeScript implementation with [`src/commands/search.ts`](https://github.com/MadsLorentzen/ai-job-search/blob/main/src/commands/search.ts) and [`src/commands/detail.ts`](https://github.com/MadsLorentzen/ai-job-search/blob/main/src/commands/detail.ts).

### How does the core scraper discover new portal skills?

The core scraper, defined in [`.claude/skills/job-scraper/SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/skills/job-scraper/SKILL.md), automatically discovers portal skills by reading the [`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/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`](https://github.com/MadsLorentzen/ai-job-search/blob/main/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`.