How to Add Custom Portal Skills to the AI-Job-Search Framework

Adding a custom portal skill to the AI-Job-Search framework involves running the add-portal command to scaffold a new skill directory under .agents/skills/, implementing the search and detail commands in TypeScript, and validating the integration through live tests before automatic CI registration.

The MadsLorentzen/ai-job-search repository follows a thin-pointer architectural pattern where reusable framework logic remains in the core repository, while market-specific portal implementations reside in user forks. This design allows you to extend job search capabilities to any regional or niche portal by creating self-contained skill packages that the framework discovers and orchestrates automatically.

The Three-Phase Architecture for Custom Portal Skills

The framework enforces a rigorous three-phase lifecycle when you add custom portal skills. Each phase ensures that new integrations maintain consistency with the existing codebase and respect portal terms of service.

Phase 1: Interview and Investigation

Before generating code, the add-portal command conducts an interactive interview to gather essential metadata. You provide the portal's base URL, market name, primary language, and a realistic test query. The command also supports a --list flag to enumerate existing portal skills by scanning .agents/skills/*/SKILL.md files.


# List existing skills

add-portal --list

# Start interactive skill creation

add-portal

# Prompt: Portal URL → https://www.example.com/jobs

# Prompt: Skill name → example-search

# Prompt: Market & language → Denmark, Danish

# Prompt: Test query → "Software Engineer"

Phase 2: Scaffolding from the Canonical Reference

Using .agents/skills/linkedin-search/ as the canonical reference implementation, the generator creates a standardized directory structure that adheres to the portal-skill contract. This contract mandates specific command interfaces, JSON output shapes, error handling patterns, and a zero-runtime-dependency policy.

.agents/skills/<skill-name>/
├── SKILL.md                # Skill metadata and trigger phrases

├── url-reference.md       # Documented endpoints & parameters

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

    │   ├── helpers.ts    # Fetching, back-off, parsers

    │   └── commands/
    │       ├── search.ts
    │       └── detail.ts
    └── tests/
        └── helpers.ts    # runCLI & JSON parsing utilities

The scaffolding logic is specified in .claude/commands/add-portal.md, which defines how the generator mirrors the LinkedIn skill's architecture while replacing portal-specific strings.

Phase 3: Live Validation and Registration

The generator enforces a live test run before completing registration. You must execute the CLI against the actual portal to verify data extraction works correctly:

cd .agents/skills/example-search/cli

# Test search functionality

bun run src/cli.ts search -q "Software Engineer" --limit 5

# Verify detail fetching

bun run src/cli.ts detail 12345

# Run auto-generated test suite

bun test

Validation requires that the search command returns at least one result containing non-null id, title, and url fields, and that the detail command returns a clean description object. Only after these checks pass does the command prompt you to finalize registration.

Required Skill Structure and Portal-Skill Contract

Every custom portal skill must adhere to strict architectural constraints defined in the framework's specification files and enforced by tools/lint_skills.py.

Zero-Dependency Runtime Policy

Skills must rely exclusively on bun with native fetch and regex-based parsing. This mirrors the linkedin-search implementation and ensures that the CI pipeline (.github/workflows/ci.yml) can type-check and test every skill without resolving external HTTP client libraries.

Credential Management

If a portal requires authentication, the skill must read the API token exclusively from an environment variable named <SERVICE>_API_TOKEN. The CLI must fail with a structured JSON error if this variable is unset, allowing the framework to handle credential provisioning gracefully.

Content and Compliance Markers

When robots.txt or terms of service restrict automated access, the generated SKILL.md must include a prominent warning (⚠️ Personal use only). The tools/security_guards.py script validates that skills contain proper .gitignore settings to prevent credential leakage and appropriate manifest configurations.

Implementing Search and Detail Commands

The core functionality resides in src/commands/search.ts and src/commands/detail.ts. These files must export functions that parse arguments, execute HTTP requests with exponential back-off for rate limiting, and output strictly formatted JSON.

Running Development Commands

During implementation, use the following patterns to test your skill:


# Table-formatted search results for debugging

bun run src/cli.ts search -q "Software Engineer" --limit 5 --format table

# Plain text detail view

bun run src/cli.ts detail <job-id> --format plain

# JSON output for framework integration

bun run src/cli.ts search -q "Developer" --format json

The helpers.ts file should contain portal-specific parsers that extract structured data from HTML or JSON responses using native fetch and regex operations, avoiding DOM manipulation libraries.

CI Integration and Automated Discovery

The framework automatically discovers and validates all portal skills through the GitHub Actions workflow defined in .github/workflows/ci.yml.

Discovery Mechanism

The discover-clis job executes:

find .agents/skills -mindepth 3 -maxdepth 3 -path '*/cli/package.json'

This pattern identifies every skill's CLI entry point without requiring manual registration lists. When you add custom portal skills following the directory convention, the CI pipeline immediately includes them in its matrix.

Automated Checks

The cli-checks job runs bun install and bun run typecheck for every discovered CLI. It also executes fixture tests from the tests/ directory to validate argument parsing and error handling. Live smoke tests are deliberately excluded from CI to respect portal rate limits and terms of service.

Summary

  • Use the add-portal command to scaffold new skills under .agents/skills/ using the linkedin-search directory as a canonical template.
  • Follow the portal-skill contract by implementing search and detail commands with zero external dependencies and structured JSON output.
  • Validate live integration by running bun run src/cli.ts search and bun test before completing registration.
  • Manage credentials exclusively through <SERVICE>_API_TOKEN environment variables with structured error handling.
  • Rely on automated CI discovery via .github/workflows/ci.yml, which type-checks and tests every skill matching the */cli/package.json pattern on every push.

Frequently Asked Questions

What are the naming conventions for custom portal skills?

Skill names must use kebab-case and reflect the portal's identity (e.g., linkedin-search, example-portal). The directory name becomes the skill identifier used by the framework's discovery mechanisms. According to the specification in .claude/commands/add-portal.md, the name should be lowercase and avoid special characters except hyphens.

How does the framework handle API rate limiting in custom skills?

Each skill implements exponential back-off logic in src/helpers.ts, mirroring the pattern in .agents/skills/linkedin-search/. The CLI must respect portal rate limits during the live validation phase, and the framework deliberately excludes live smoke tests from CI to prevent automated traffic violations against portal terms of service.

Can I use external libraries like Axios or Cheerio in my portal skill?

No. The portal-skill contract enforces a zero-dependency runtime policy. Skills must use native fetch for HTTP requests and regex-based parsing for HTML extraction. This constraint ensures that the cli-checks job in .github/workflows/ci.yml executes quickly without resolving complex dependency trees or native Node.js addons.

Where should I store API keys for my custom portal skill?

Store portal credentials exclusively in environment variables following the pattern <SERVICE>_API_TOKEN (e.g., EXAMPLE_API_TOKEN). The skill's cli.ts must check for this variable and output a structured JSON error if missing. Never commit credentials to the repository; the tools/security_guards.py linter validates that .gitignore properly excludes local environment files.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →