Job Portal Search CLI Tools Architecture: Modular Design for the ai-job-search Repository
The job portal search CLI tools in ai-job-search implement a zero-dependency, six-layer architecture where each portal skill in .agents/skills/<portal>/cli exposes standardized commands via src/cli.ts, transforms raw data through helpers.ts, and returns unified JSON consumed by the .claude/skills/job-scraper orchestrator.
The ai-job-search repository by MadsLorentzen provides a specialized framework for aggregating job listings across disparate portals through a consistent command-line interface. Understanding the job portal search CLI tools architecture reveals how individual skills for LinkedIn, Jobbank, Jobindex, and government portals integrate into a unified workflow. Each portal follows a strict structural convention that enables the Claude orchestrator to treat every source identically, regardless of whether the underlying implementation uses API calls or HTML scraping.
The Six-Layer Skill Architecture
Each job portal skill resides in .agents/skills/<portal-name>/cli and implements a consistent six-layer pattern:
1. CLI Entry Point (src/cli.ts)
The bootstrap layer handles argument parsing and command dispatch. In .agents/skills/linkedin-search/cli/src/cli.ts, the code creates a createCLI instance, registers available sub-commands, and executes cli.run() to dispatch operations like search or detail according to the source code lines 7-18.
2. Commands Layer (src/commands/*.ts)
Concrete actions that interact with portal APIs or scrape HTML live in separate command files. Each skill exports specific functions—search, detail, occupations, and suggestions—from individual files such as linkedin-search/cli/src/commands/search.ts. This separation ensures that CLI logic remains distinct from portal-specific implementation details.
3. Data Transformation (helpers.ts)
Raw HTTP responses undergo normalization in helpers.ts files. Each skill contains a dedicated helper module—for example, .agents/skills/linkedin-search/cli/src/helpers.ts—that parses job cards or API payloads and maps them to a unified JSON shape containing id, title, company, location, url, and publishedAt fields.
4. Output Formatting (src/format.ts)
The formatResult utility in src/format.ts renders unified results as JSON, table, or plain text based on the --format flag. This layer is reused across every command within a skill, ensuring consistent presentation regardless of the data source.
5. Package Configuration and Testing
Each skill declares its entry point and scripts in package.json. For instance, .agents/skills/linkedin-search/cli/package.json defines start, test, and typecheck scripts, and specifies the CLI entry file. Testing relies on bun test, with tests/helpers.ts providing a runCLI wrapper for smoke-testing commands against live queries and verifying JSON error handling.
6. Skill Metadata Contract (SKILL.md)
The integration contract resides in SKILL.md at the skill root—such as .agents/skills/linkedin-search/SKILL.md. This file contains front-matter describing the portal, usage examples, and the critical allowed-tools declaration (lines 14-15) that tells the Claude orchestrator exactly which commands it may invoke.
Integration with the Job Scraper Orchestrator
The .claude/skills/job-scraper orchestrator treats all portal skills as interchangeable modules through a standardized invocation pattern. According to the source code, the orchestrator invokes a skill via Bun by executing bun run .agents/skills/<portal>/cli/src/cli.ts search … as declared in each skill's allowed-tools contract.
The integration follows a three-stage pipeline:
- Invocation: The orchestrator calls the CLI tool with the appropriate arguments, receiving JSON output via stdout.
- Normalization: It reads the unified JSON structure—where
searchreturns an array of job objects anddetailreturns a rich job description—deduplicating results across multiple portals. - Consumption: Down-stream commands such as
/apply,/rank, and/interviewoperate on the consolidated data completely agnostic of the original portal source.
This design ensures that adding LinkedIn, Jobnet, or niche government portals requires no changes to the orchestrator logic.
Scaffolding New Portal Skills
The repository includes an /add-portal command that automates the creation of new skills. When invoked with a target URL, such as /add-portal https://example.com/jobs, the generator scaffolds a complete .agents/skills/<name>/cli tree including src/cli.ts, src/commands/search.ts, src/helpers.ts, package.json, and SKILL.md. The scaffold automatically follows the six-layer architecture, runs a live test query to verify functionality, and registers the skill for immediate use by the orchestrator.
Code Examples
Running a search against the LinkedIn skill:
cd .agents/skills/linkedin-search/cli
bun run src/cli.ts search -q "data engineer" -l "Berlin, Germany" -n 10 --format table
Fetching detailed information for a specific job ID:
bun run src/cli.ts detail 4300011451 --format json
Using a skill programmatically from the orchestrator (Python example):
import subprocess, json
def run_linkedin_search(query, location):
result = subprocess.run(
["bun", "run", ".agents/skills/linkedin-search/cli/src/cli.ts", "search",
"-q", query, "-l", location, "--format", "json"],
capture_output=True, text=True, check=True
)
return json.loads(result.stdout)
jobs = run_linkedin_search("backend engineer", "Copenhagen, Denmark")
print(jobs[0]["title"], jobs[0]["company"])
Scaffolding a new portal automatically:
/add-portal https://example.com/jobs
# Creates: .agents/skills/example-search/cli/ with full layer structure
Key Implementation Files
| File Path | Role |
|---|---|
.agents/skills/linkedin-search/cli/src/cli.ts |
CLI bootstrap and command registration |
.agents/skills/linkedin-search/cli/src/helpers.ts |
HTML parsing and result normalization |
.agents/skills/linkedin-search/cli/src/format.ts |
Output formatting utility (formatResult) |
.agents/skills/linkedin-search/cli/package.json |
NPM metadata and script definitions |
.agents/skills/linkedin-search/SKILL.md |
Skill contract and allowed-tools declaration |
.agents/skills/jobnet-search/cli/src/cli.ts |
Multi-command layout example for government portals |
.claude/skills/job-scraper/SKILL.md |
Orchestrator configuration calling individual CLI skills |
Summary
- The job portal search CLI tools architecture relies on a six-layer pattern (CLI, commands, helpers, formatting, tests, metadata) implemented in
.agents/skills/<portal>/cli. - Each skill is zero-dependency for core functionality, requiring only Bun to execute.
- A unified JSON contract ensures that
searchreturns standardized job arrays anddetailreturns rich descriptions, enabling cross-portal deduplication. - The
.claude/skills/job-scraperorchestrator invokes skills viabun runcommands declared in eachSKILL.mdfile, treating all portals as interchangeable modules. - The
/add-portalcommand generates fully functional new skills following the established architecture, automatically registering them for orchestrator use.
Frequently Asked Questions
How does the orchestrator invoke individual portal skills?
The orchestrator invokes skills by executing bun run against the specific CLI entry point—for example, bun run .agents/skills/linkedin-search/cli/src/cli.ts search …—as defined in the allowed-tools section of each skill's SKILL.md file. This subprocess-based approach captures stdout JSON output while maintaining strict isolation between the orchestrator and portal-specific implementations.
What is the unified JSON contract between skills and the orchestrator?
The contract specifies that the search command must return an array of objects containing id, title, company, location, url, and publishedAt fields, while the detail command returns a single rich job description object. This standardization allows the job-scraper to aggregate results from LinkedIn, Jobbank, government portals, and custom sources without parsing portal-specific formats.
How do I add support for a new job portal?
Use the /add-portal command followed by the target URL, such as /add-portal https://example.com/jobs. This scaffold creates a complete skill directory at .agents/skills/<name>/cli with the six-layer architecture, runs a live test query to verify functionality, and updates the orchestrator's available tools list automatically.
What testing framework validates the CLI tools?
All skills use Bun's built-in test runner (bun test), configured in package.json. The tests/helpers.ts file in each skill directory provides a runCLI wrapper function that smoke-tests commands against live queries and validates JSON error handling, ensuring reliability before the orchestrator consumes the skill.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →