# Portal CLIs Component in the AI Job Search Framework: Architecture and Usage

> Explore the Portal CLIs component in the AI Job Search Framework. Discover how it enables universal job board querying via a uniform CLI contract for automatic, parallel job discovery.

- Repository: [Mads Lorentzen/ai-job-search](https://github.com/MadsLorentzen/ai-job-search)
- Tags: architecture
- Published: 2026-09-01

---

**The Portal CLIs component provides a standardized, plug-and-play interface that allows the AI Job Search framework to query any job board through a uniform CLI contract, enabling automatic discovery and parallel execution across multiple portals.**

The **Portal CLIs** subsystem in the [MadsLorentzen/ai-job-search](https://github.com/MadsLorentzen/ai-job-search) repository serves as the abstraction engine that unifies disparate job board APIs into a consistent query interface. This component eliminates bespoke wiring for each portal by enforcing a strict contract that every job board integration must follow. By living under `.agents/skills/*/`, these command-line interfaces enable the framework to scrape listings from LinkedIn, FreeHire, and custom portals without modifying core logic.

## Core Architecture of the Portal CLIs Component

The architecture centers on a **standardized search contract** that every portal must implement. This contract ensures that regardless of the underlying job board's API structure, the framework receives data in a predictable format.

### Standardized Search Contract

Each portal CLI must provide two primary commands: `search` and `detail`. The `search` command accepts location, keywords, and result limits, while the `detail` command retrieves full posting information via URL. According to the source code in [`.agents/skills/linkedin-search/SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.agents/skills/linkedin-search/SKILL.md) and similar files, each CLI exposes a `--format` flag supporting `json`, `table`, or `plain` output modes.

Portals declare their availability through an `enabled:` flag within their respective [`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md) files. This boolean flag allows users to toggle specific job boards without removing code, as the `job-scraper` skill reads these declarations during initialization.

### Command Structure and Flags

Every Portal CLI follows identical invocation patterns. The framework calls these CLIs using `bun run` commands, passing standardized arguments that map to the job board's query parameters. For example, the LinkedIn search CLI accepts `--keywords`, `-l` for location, and `--limit` to cap result counts.

```bash

# Execute a search against the LinkedIn portal CLI

bun run .agents/skills/linkedin-search/cli/src/cli.ts search \
    --format json \
    -l "Berlin, Germany" \
    --keywords "data scientist" \
    --limit 20

```

The detail command operates similarly, requiring only a `--url` parameter to fetch specific posting data:

```bash

# Retrieve detailed job posting information

bun run .agents/skills/linkedin-search/cli/src/cli.ts detail \
    --format json \
    --url "https://www.linkedin.com/jobs/view/1234567890"

```

## Automatic Discovery and Execution

The framework eliminates manual registration through an **automatic discovery** mechanism that scans the skills directory at runtime.

### SKILL.md Parsing

The `job-scraper` skill, defined in [`.claude/skills/job-scraper/SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/skills/job-scraper/SKILL.md), implements the discovery logic. During the `/scrape` command execution, the system scans every [`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md) file located under `.agents/skills/`. It parses these markdown files to locate the correct `bun run` invocation for each enabled portal, then executes them in parallel without requiring explicit configuration lists.

This design means adding a new job board requires only creating a new subdirectory under `.agents/skills/` with a compliant [`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md) file. The scraper automatically detects and includes it in the next execution cycle.

### Parallel Execution Model

The scraper orchestrates multiple portal CLIs concurrently. According to the job-scraper skill documentation, Step 1b outlines how the framework reads portal configurations and executes corresponding CLIs simultaneously. This parallelization reduces total scrape time when querying multiple job boards, while the standardized JSON output ensures the ranking and application modules receive uniform data structures regardless of the source.

## Extensibility via the Add-Portal Command

New job board integrations require minimal setup thanks to the `/add-portal` command infrastructure. Documented in [`.claude/commands/add-portal.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/commands/add-portal.md), this generator scaffolds a complete portal CLI folder that automatically conforms to the framework's contract.

The command inspects the target portal's search URL patterns, result structure, and [`robots.txt`](https://github.com/MadsLorentzen/ai-job-search/blob/main/robots.txt) rules to generate a self-contained CLI implementation. This scaffolding includes the mandatory `search` and `detail` command handlers, proper argument parsing, and the required [`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md) with the `enabled:` flag. Once generated, the new portal participates immediately in future scrape operations without additional wiring.

## Zero-Runtime-Dependency Design

Security and portability drive the **zero-runtime-dependency** architecture of the Portal CLIs component. Most shipped implementations, such as `linkedin-search` and `freehire-search`, execute using only the Bun runtime with no external package dependencies.

As noted in the repository's [`README.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/README.md) under the country-agnostic portals section, these CLIs run with "just `bun`", eliminating the security surface area associated with npm package trees. This design choice ensures that job board queries execute in isolated, dependency-free environments that resist supply-chain attacks while maintaining fast startup times.

## Fail-Safe Behavior and Error Handling

The Portal CLIs component implements robust **fail-safe behavior** to prevent individual portal failures from crashing the entire scrape operation. According to Steps 1a through 1c in the job-scraper skill documentation, the system handles multiple edge cases gracefully.

If a portal CLI exits with a non-zero status code, the scraper logs the specific failure and continues processing results from other portals. When the Bun runtime is unavailable on the host system, the framework automatically falls back to a plain WebSearch implementation. This fallback chain ensures that users receive job search results even when specific portal integrations malfunction or runtime prerequisites are missing.

## Summary

- The **Portal CLIs component** provides a standardized command-line interface for querying any job board, requiring implementations to support `search` and `detail` commands with `--format` flags.
- **Automatic discovery** occurs through scanning `.agents/skills/*/SKILL.md` files, enabling parallel execution of enabled portals without manual registration.
- The **zero-dependency design** ensures most CLIs run using only Bun, minimizing security risks and installation complexity.
- **Fail-safe mechanisms** prevent individual portal failures from halting the entire job search pipeline, with automatic fallback to web search when necessary.
- New portals can be added via the `/add-portal` command, which generates compliant scaffolding in `.agents/skills/` that immediately participates in the next scrape cycle.

## Frequently Asked Questions

### What commands must a Portal CLI implement to integrate with the framework?

Every Portal CLI must implement exactly two commands: `search` and `detail`. The `search` command accepts parameters for location, keywords, and result limits, while the `detail` command requires a URL parameter to fetch comprehensive posting information. Both commands must support a `--format` flag accepting `json`, `table`, or `plain` values, and the CLI must declare an `enabled:` boolean in its [`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md) file for discovery purposes.

### How does the framework automatically discover new portal CLIs?

The `job-scraper` skill, located at [`.claude/skills/job-scraper/SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/skills/job-scraper/SKILL.md), implements recursive scanning of the `.agents/skills/` directory during the `/scrape` command execution. It parses every [`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md) file to extract the `enabled:` status and the corresponding `bun run` invocation path. This discovery mechanism requires no central registry; simply adding a compliant portal folder under `.agents/skills/` makes it available for the next scrape cycle.

### What happens when a Portal CLI encounters an error during execution?

The framework implements a fail-safe execution model where individual portal failures do not terminate the scrape operation. If a portal CLI exits with an error code, the job-scraper logs the specific failure and continues processing results from other enabled portals. Additionally, if the Bun runtime is unavailable on the host system, the framework automatically falls back to a plain WebSearch implementation to maintain job search functionality.

### How can developers add support for a new job board to the AI Job Search framework?

Developers use the `/add-portal` command, documented in [`.claude/commands/add-portal.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/commands/add-portal.md), to scaffold a new portal integration. This command generates a complete CLI structure under `.agents/skills/` that includes the mandatory command handlers, argument parsing, and [`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md) configuration. The generator analyzes the target job board's search patterns and robots.txt rules to create a compliant implementation that automatically participates in future scrape operations without additional configuration.