# How Portal Skills Are Defined and Discovered in the AI Job Search Framework

> Discover how portal skills are defined and discovered in the AI Job Search Framework. Learn about modular YAML integrations and automated discovery for efficient job scraping.

- Repository: [Mads Lorentzen/ai-job-search](https://github.com/MadsLorentzen/ai-job-search)
- Tags: deep-dive
- Published: 2026-08-31

---

**Portal skills in the AI Job Search Framework are modular job-portal integrations defined by YAML-front-matter [`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md) files stored under `.agents/skills/`, which the system auto-discovers via glob patterns and orchestrates through the job-scraper skill during `/scrape` execution.**

The **MadsLorentzen/ai-job-search** repository implements a plug-and-play architecture where each job portal exists as a standalone skill. Understanding how these portal skills are structured and discovered is essential for customizing or extending the framework's data aggregation capabilities.

## What Are Portal Skills?

A **portal skill** is a self-contained integration package that encapsulates everything needed to scrape a specific job board. Rather than hardcoding portal logic into the core application, the framework treats each integration as a portable module living in its own directory under `.agents/skills/`.

Each portal skill is described by a **[`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md)** file that combines machine-readable metadata with human-readable documentation. This design allows the AI agent to understand how to invoke the portal without requiring changes to the orchestration code.

## SKILL.md Schema and Structure

The [`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md) file serves as both configuration manifest and technical documentation. It uses **YAML front-matter** surrounded by triple dashes (`---`) followed by a Markdown body.

### Required YAML Front-matter Fields

Every [`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md) must declare these fields:

- **`name`**: The human-readable portal identifier (e.g., `linkedin-search`)
- **`version`**: Semantic version string (e.g., `1.0.0`)
- **`description`**: Concise summary used for trigger matching and UI display
- **`enabled`**: Boolean toggle (`true` by default) that determines whether the portal participates in `/scrape` operations
- **`allowed-tools`**: Declares the exact CLI command the framework may execute (e.g., `Bash(bun run skills/<name>/cli/src/cli.ts *)`)
- **`context`**: Origin marker (e.g., `fork`, `official`) indicating the skill's provenance

### Body Documentation

Following the front-matter, the Markdown body documents:
- Exact CLI flags and argument patterns
- Usage examples and query transformation rules
- Personal-use warnings or rate-limit notices
- Links to supplementary files like [`url-reference.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/url-reference.md)

## The Auto-Discovery Mechanism

When the `/scrape` command executes, 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) performs a four-step discovery process:

1. **Glob the directory**: The framework searches for files matching the pattern `.agents/skills/*/SKILL.md` using filesystem globbing.

2. **Parse front-matter**: Each discovered file is read and its YAML header parsed. The **`enabled`** field is evaluated immediately—portals are active unless explicitly set to `false`.

3. **Extract CLI invocation**: The **`allowed-tools`** entry provides the exact shell command template (typically `bun run` invocations pointing to TypeScript CLI entry points).

4. **Execute portal CLI**: The scraper transforms the user's query into portal-specific flags as documented in the skill, then invokes the extracted command.

Because discovery relies solely on filesystem presence and valid [`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md) syntax, **adding a new portal requires zero code changes** to the core framework.

## Enabling and Disabling Portals

The **`enabled`** field in [`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md) front-matter acts as a soft-delete mechanism. When set to `true` (the default), the portal is included in the next `/scrape` run. When set to `false`, the framework skips the portal entirely without removing its files from `.agents/skills/`.

This toggle allows users to temporarily disable unreliable or rate-limited portals while preserving the integration for future use. The job-scraper skill explicitly honors this flag during its parsing phase, as documented in its own [`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md) implementation.

## Real-World Example: LinkedIn Search

The LinkedIn portal skill demonstrates the complete structure. Located at [`.agents/skills/linkedin-search/SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.agents/skills/linkedin-search/SKILL.md), the file defines:

```yaml
---
name: linkedin-search
version: 1.0.0
description: Search LinkedIn job listings using the public `jobs-guest` endpoint.
enabled: true
allowed-tools: Bash(bun run skills/linkedin-search/cli/src/cli.ts *)
context: fork
---

```

When `/scrape` runs, the framework reads this file, confirms `enabled: true`, extracts the `Bash(bun run ...)` command, and executes it with appropriately translated query parameters. The body of the file contains the flag reference specifying how to map search terms to the CLI's expected arguments.

The same pattern appears in [`.agents/skills/freehire-search/SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.agents/skills/freehire-search/SKILL.md), demonstrating how different API-based portals follow identical structural conventions.

## Extending the Framework with New Portals

The **`/add-portal`** command utilizes the same discovery logic. When invoked with `--list`, it glob-reads all `.agents/skills/*/SKILL.md` files and renders a table displaying name, market focus, and data source for each installed portal.

To add a new portal, create a directory under `.agents/skills/` containing:
1. A properly formatted [`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md) with valid front-matter
2. An optional CLI implementation (typically TypeScript executed via `bun`)

The next invocation of `/scrape` automatically detects and executes the new skill without requiring registration in any central configuration file. This architecture is documented in [`.claude/commands/add-portal.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/commands/add-portal.md).

## Summary

- **Portal skills** are modular integrations stored as subdirectories under `.agents/skills/`, each defined by a [`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md) file.
- **Auto-discovery** works via glob pattern matching (`.agents/skills/*/SKILL.md`) performed by the job-scraper skill during `/scrape` execution.
- The **`enabled`** boolean in YAML front-matter controls participation in scraping without requiring file deletion.
- **Zero-code deployment** is possible because the framework extracts CLI commands from the **`allowed-tools`** field and executes them dynamically.
- New portals are automatically picked up on the next run once their [`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md) is placed in the correct directory structure.

## Frequently Asked Questions

### What file defines a portal skill in the AI Job Search Framework?

Each portal skill is defined by a **[`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md)** file located at `.agents/skills/<portal-name>/SKILL.md`. This file contains YAML front-matter specifying metadata, enabled status, and the CLI command to execute, followed by Markdown documentation explaining usage and parameters.

### How does the framework decide which portals to run during a scrape?

The **job-scraper skill** ([`.claude/skills/job-scraper/SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/skills/job-scraper/SKILL.md)) glob-reads all [`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md) files under `.agents/skills/` and checks the **`enabled`** field in each file's front-matter. Only portals with `enabled: true` (the default) are invoked; those with `enabled: false` are skipped without deletion.

### Can I add a new job portal without modifying the core codebase?

Yes. The framework uses **drop-in discovery**: create a new directory under `.agents/skills/` containing a valid [`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md) file and optional CLI code. The next `/scrape` command automatically detects and executes the new portal by reading its configuration—no central registry or code changes are required.

### What is the purpose of the `allowed-tools` field in SKILL.md?

The **`allowed-tools`** field declares the exact shell command the AI agent is permitted to run for that portal, typically formatted as `Bash(bun run skills/<name>/cli/src/cli.ts *)`. This declaration serves as both execution template and security boundary, ensuring the scraper only runs explicitly authorized binaries with the correct path structure.