# How to Add a Custom Job Portal for Local Markets Using `/add-portal`

> Use /add-portal in the AI Job Search framework to quickly scaffold a new portal-skill CLI tool. Auto-discover search patterns and integrate with /scrape seamlessly without code changes.

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

---

**Run `/add-portal` in the AI Job Search framework to scaffold a new portal-skill CLI tool that auto-discovers search patterns, respects robots.txt, and integrates immediately with `/scrape` without any core code changes.**

The AI Job Search framework by MadsLorentzen treats every job board as an independent **portal-skill**—a self-contained, zero-dependency CLI tool that follows a strict contract for commands, output format, and error handling. The `/add-portal` command automates the creation of this skill for any public job portal, letting you extend coverage to your local market without touching the framework's core.

## What Is a Portal-Skill?

A portal-skill is the fundamental abstraction that makes the AI Job Search framework extensible. Each skill lives under `.agents/skills/<name>/` and implements a standardized interface consumed by the `/scrape` command. The contract is defined in [`/.claude/commands/add-portal.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main//.claude/commands/add-portal.md) and enforced by the auto-discovery mechanism in [`/.claude/skills/job-scraper/SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main//.claude/skills/job-scraper/SKILL.md).

Every portal-skill must provide:

- **`search` and `detail <id|url>` commands**
- **Standard flags**: `--query/-q`, `--jobage`, `--page`, `--limit`, `--format json|table|plain`, plus optional `--location`
- **JSON output shape** with `meta` and `results` containing `id`, `title`, `company`, `location`, `date`, `url` (missing values as `null`)
- **Errors to stderr** as JSON with exit code 1
- **Zero runtime dependencies** (plain `bun` + `fetch` only)
- **Back-off on 429/5xx responses** and graceful 404 handling

## The `/add-portal` Generator Workflow

The `/add-portal` command implements a five-step guided interview that transforms a job board URL into a fully functional portal-skill. According to the specification in [`/.claude/commands/add-portal.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main//.claude/commands/add-portal.md), the workflow handles argument parsing (`--list`, URL, or none) and proceeds through automated discovery and scaffolding.

### Step 1: Interview and Target Selection

The generator collects:
- Portal URL (homepage or search page)
- Skill name (kebab-case, e.g., `seek-search`)
- Market/language for documentation
- Realistic test query for validation

### Step 2: Portal Investigation

The tool automatically analyzes:
- Search URL patterns and pagination
- Result field extraction (HTML or JSON)
- Detail page structure
- [`robots.txt`](https://github.com/MadsLorentzen/ai-job-search/blob/main/robots.txt) and Terms of Service compliance

**The generator aborts if authentication is required or crawling is disallowed.**

### Step 3: Skill Scaffolding

The generator copies the canonical structure from `.agents/skills/linkedin-search/`—the zero-dependency reference implementation. This creates:

```

.agents/skills/<name>/
├── SKILL.md            # Front-matter, description, trigger phrases

├── url-reference.md   # Documented endpoints & field mappings

└── cli/
    ├── package.json
    ├── tsconfig.json
    ├── README.md
    ├── src/
    │   ├── cli.ts      # Argument parsing, dispatch

    │   ├── helpers.ts  # fetch with back-off, HTML/JSON parsers

    │   └── commands/
    │       ├── search.ts
    │       └── detail.ts
    └── tests/
        └── helpers.ts  # CLI test utilities (from jobindex-search)

```

### Step 4: Live Testing and Iteration

The generator executes the test query against the live portal, iterating on parsers until valid results are returned. This validates the extraction logic before the skill is finalized.

### Step 5: Automatic Registration

No manual wiring is required. The `/scrape` command auto-discovers any [`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md) under `.agents/skills/*/`, making the new portal immediately available.

## How Auto-Discovery Works in `/scrape`

The discovery mechanism is implemented in [`/.claude/skills/job-scraper/SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main//.claude/skills/job-scraper/SKILL.md). At runtime, `/scrape` scans `.agents/skills/*/SKILL.md` and registers any skill conforming to the portal-skill contract. This means:

- **No configuration files to edit**
- **No imports to add**
- **No framework restarts needed**

Optional WebSearch placeholders can be added to [`search-queries.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/search-queries.md) for fallback when the CLI is temporarily unavailable.

## Practical Code Examples

After generating your portal-skill, use these commands to operate it directly or through the framework.

### List Installed Portal Skills

```bash
/add-portal --list

```

Example output:

```

┌───────────────┬─────────────┬───────────────────────┐
│ Name          │ Market      │ Data source (url-reference.md) │
├───────────────┼─────────────┼───────────────────────┤
│ seek-search   │ Australia   │ https://www.seek.com.au │
│ stepstone-search │ Germany  │ https://www.stepstone.de │
└───────────────┴─────────────┴───────────────────────┘

```

### Search Your Custom Portal

```bash

# Replace <name> with your skill name

bun run .agents/skills/<name>/cli/src/cli.ts search -q "data scientist" --limit 5 --format table

```

Typical table output:

| id | title | company | location | date | url |
|:---|:------|:--------|:---------|:-----|:----|
| 12345 | Data Scientist | XYZ Corp | Sydney, NSW | 2026-07-30 | https://www.seek.com.au/job/12345 |

### Fetch Job Details

```bash
bun run .agents/skills/<name>/cli/src/cli.ts detail 12345 --format plain

```

Outputs the full job description with HTML tags stripped.

### Run the Test Suite

```bash
cd .agents/skills/<name>/cli
bun install      # Dev-only types, zero runtime deps

bun run test

```

All tests pass when live search and detail commands work correctly.

## Compliance and Safety Guarantees

The `/add-portal` generator embeds several safeguards:

- **Authentication block**: Aborts if login is required
- **robots.txt check**: Respects crawler restrictions
- **Terms of Service detection**: Adds `personal-use-only` disclaimer to [`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md) when restrictions apply (mirroring the LinkedIn skill's approach)
- **Local execution only**: Generated code never sends data to external services or requires secrets

## Extending to New Markets: Reference Implementations

Study these existing skills for implementation patterns:

| Skill | Market | Key Technique |
|:------|:-------|:--------------|
| `.agents/skills/linkedin-search/` | Global | HTML scraping with back-off |
| `.agents/skills/jobindex-search/` | Denmark | JSON API with test utilities |

The `linkedin-search` folder serves as the **canonical template** for all new portal-skills, demonstrating pure `fetch`-based extraction without external dependencies.

## Summary

- **`/add-portal` scaffolds complete portal-skills** through a five-step interview and live validation process
- **Portal-skills follow a strict contract** defined in [`/.claude/commands/add-portal.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main//.claude/commands/add-portal.md) covering commands, flags, output format, and error handling
- **File structure is copied from `linkedin-search/`**, ensuring zero-dependency, `bun`-based implementations
- **`/scrape` auto-discovers skills** via [`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md) scanning—no registration steps required
- **Compliance checks** enforce robots.txt respect and personal-use limitations
- **Test locally with `bun run`** commands before framework integration

## Frequently Asked Questions

### Can I add a portal that requires login or API keys?

No. The `/add-portal` generator explicitly aborts when authentication is detected. The framework's security model requires all portal-skills to operate without external secrets. For authenticated sources, create a manual skill outside the generator workflow, though this sacrifices the zero-dependency guarantee.

### How does `/scrape` find my new portal without configuration?

The discovery mechanism in [`/.claude/skills/job-scraper/SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main//.claude/skills/job-scraper/SKILL.md) recursively scans `.agents/skills/*/SKILL.md` at runtime. Each [`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md) contains front-matter declaring trigger phrases and capability metadata. This file-based registration requires no central registry or configuration file updates.

### What happens if the job portal changes its HTML structure?

Regenerate the skill with `/add-portal` using the same name, or manually edit `.agents/skills/<name>/cli/src/helpers.ts` to update parsing selectors. The test suite in [`cli/tests/helpers.ts`](https://github.com/MadsLorentzen/ai-job-search/blob/main/cli/tests/helpers.ts) catches structural changes during CI. For production stability, pin your skill to a specific date and monitor for failures.

### Can I use Python or another runtime instead of Bun?

The portal-skill contract specifies Bun as the default runtime for consistency with the framework's zero-dependency goal. However, the contract only mandates the CLI interface and output format. If you replace the generated code while preserving the command structure and [`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md) metadata, `/scrape` will still register your skill—though you lose the reference implementation's back-off and caching utilities.