Architecture for Job Portal Search Skills and CLI Tool Pattern in AI Job Search
The architecture employs a modular skill-based design where each job portal is encapsulated as a self-contained CLI tool under .agents/skills/<portal-name>/, driven by metadata in SKILL.md and implemented in TypeScript using the Bun runtime.
This repository implements a skill-first architecture that standardizes how AI agents interact with diverse job portals. By isolating each portal's logic into discrete, testable CLI modules, the system enables rapid integration of new sources while maintaining consistent invocation patterns across LinkedIn, Jobnet, Jobindex, and other platforms.
Skill-First Architecture Overview
The repository organizes every job portal integration as a skill located under .agents/skills/<portal-name>/. This directory structure creates a thin-pointer architecture that separates metadata, execution logic, and testing into three distinct components.
SKILL.md Metadata Declaration
Each skill begins with a SKILL.md file containing YAML front-matter that declares how the AI framework should invoke the tool. According to the source code in .agents/skills/linkedin-search/SKILL.md, the metadata specifies:
---
name: linkedin-search
version: 1.0.0
description: >-
Use this skill whenever the user wants to search for jobs …
context: fork
enabled: true
allowed-tools: Bash(bun run .agents/skills/linkedin-search/cli/src/cli.ts *)
---
The allowed-tools field is critical: it tells the core framework exactly how to execute the skill via Bash command. This declarative approach enables the agent to dynamically select and invoke the correct portal based on user intent, without hard-coding integration logic.
CLI Directory Structure
Inside each skill directory, the cli/ folder contains a zero-dependency TypeScript implementation designed to run exclusively with Bun. The structure follows a consistent pattern across all portals:
cli/src/cli.ts– Entry point for flag parsing and command dispatchcli/src/commands/– Individual command modules (search.ts,detail.ts)cli/src/helpers.ts– Shared utilities for HTTP requests and formattingcli/tests/– Unit tests validating behavior and error handling
This self-contained design requires no npm install or external runtime dependencies, making skills lightweight and portable across environments.
The CLI Tool Pattern Anatomy
The CLI tool pattern follows a repeatable four-layer architecture that standardizes how commands are parsed, executed, and output. This pattern is implemented identically across LinkedIn, Jobnet, and other portal skills.
Entry Point and Flag Parsing (cli.ts)
The executable entry point in cli/src/cli.ts handles argument validation and command routing. As implemented in .agents/skills/linkedin-search/cli/src/cli.ts, the main function follows this flow:
#!/usr/bin/env bun
import { runSearch, type SearchOpts } from "./commands/search.js"
import { runDetail, type DetailOpts } from "./commands/detail.js"
function parseFlags(argv: string[]): Flags { /* … */ }
async function main(): Promise<number> {
const argv = process.argv.slice(2)
const flags = parseFlags(argv)
const cmd = (flags._ as string[])[0]
if (cmd === "search") {
const opts: SearchOpts = { /* validated flags */ }
return runSearch(opts)
}
if (cmd === "detail") {
const id = (flags._ as string[])[1]
const opts: DetailOpts = { /* validated flags */ }
return runDetail(opts)
}
// Unknown command → error JSON on stderr
}
Key architectural decisions include:
- Custom flag parsing – A lightweight parser supporting long (
--location) and short (-l) aliases without external libraries - Exit code discipline – Returns
0for success,1for any failure, enabling reliable shell scripting - Validation logic – Required arguments (like
--locationfor searches) are validated before dispatching to command modules
Command Modules (search.ts, detail.ts)
Each sub-command lives in its own module under cli/src/commands/, exposing a single run<Command>(opts) function that returns Promise<number>. The search command in .agents/skills/jobnet-search/cli/src/commands/search.ts builds the portal-specific query URL, performs HTTP requests, and handles pagination.
The detail command in .agents/skills/linkedin-search/cli/src/commands/detail.ts fetches individual job postings by ID and extracts structured fields like title, company, and application links. These modules encapsulate all portal-specific logic, ensuring that changes to one portal's API do not affect others.
Shared Helpers and Utilities
Common functionality resides in cli/src/helpers.ts, providing:
fetchJson(url, headers)– HTTP wrapper with exponential back-off for 429/5xx responsesparseHtml(html)– Lightweight DOM parsing for extracting job cards (e.g.,data-entity-urn="urn:li:jobPosting:<id>")formatTable(data)– ASCII table generation for human-readable output
These utilities ensure consistent behavior across portals while handling platform-specific quirks like rate limiting or HTML structure variations.
Error Handling and Output Formats
The CLI pattern enforces strict error handling: all errors are emitted as JSON objects to stderr with a code field (e.g., NO_LOCATION, FETCH_ERROR). This standardized error format allows the AI agent to programmatically handle failures.
Output supports three formats controlled by the --format flag:
json(default) – Machine-readable structured datatable– ASCII formatted for terminal displayplain– Human-readable text without formatting
Implementation Examples
LinkedIn Search Skill
The LinkedIn skill demonstrates the full pattern implementation. To search for data engineering roles:
bun run .agents/skills/linkedin-search/cli/src/cli.ts search \
-q "data engineer" \
-l "Bengaluru, Karnataka, India" \
--jobage 30 \
--format table
Retrieving specific job details uses the detail command:
bun run .agents/skills/linkedin-search/cli/src/cli.ts detail \
4426311357 \
--format plain
Jobnet Search Skill
The Danish Jobnet portal follows the identical architecture with portal-specific parameters:
bun run .agents/skills/jobnet-search/cli/src/cli.ts search \
--search-string "sygeplejerske" \
--work-hours FullTime \
--duration Permanent \
--order BestMatch \
--format table
The command modules in .agents/skills/jobnet-search/cli/src/commands/search.ts translate these flags into Jobnet's proprietary query parameters while maintaining the same CLI interface.
Extending the Architecture
Adding a new job portal requires only three steps:
- Create the directory structure:
mkdir -p .agents/skills/<portal>-search/cli/src/commands - Write
SKILL.mdwith theallowed-toolspointing to the new CLI entry point - Implement
cli.tsand command modules following the established pattern ofrunSearch()andrunDetail()functions
Because each skill is self-contained and uses only Bun's standard library, new portals can be developed and tested independently without modifying the core framework.
Summary
- The skill-first architecture isolates each job portal under
.agents/skills/<portal-name>/with metadata inSKILL.mdand executable logic incli/ - CLI tools follow a uniform pattern: entry point (
cli.ts), command modules (commands/search.ts,commands/detail.ts), and shared helpers - Zero-dependency design using Bun ensures portability without
npm installrequirements - Standardized error handling writes JSON to stderr with specific error codes, while output supports
json,table, andplainformats - Extensible structure allows new portals to be added by creating a directory, declaring metadata, and implementing portal-specific HTTP logic in the command modules
Frequently Asked Questions
How does the AI agent know which skill to invoke for a job search?
The agent reads the SKILL.md file in each skill directory, specifically the description and context fields, to determine relevance. The allowed-tools field provides the exact Bash command (e.g., bun run .agents/skills/linkedin-search/cli/src/cli.ts) needed to execute the skill, enabling dynamic tool selection based on user intent and portal availability.
Why does the CLI pattern use Bun instead of Node.js?
The CLI tools use Bun to achieve a zero-dependency runtime environment. Unlike Node.js applications that require npm install to download dependencies, Bun bundles the TypeScript execution environment natively. This design keeps each skill lightweight and ensures immediate execution without package management overhead, critical for AI agent toolchains that invoke commands ephemerally.
What happens when a job portal API returns an error or rate limit?
The helper utilities in cli/src/helpers.ts implement exponential back-off for HTTP 429 and 5xx status codes. If the request ultimately fails, the command module writes a structured JSON error object to stderr containing a machine-readable code field (such as FETCH_ERROR or RATE_LIMITED) and exits with code 1. This allows calling scripts to detect failures programmatically and handle retries or fallbacks appropriately.
How are new skills tested to ensure they follow the CLI pattern?
Each skill includes a cli/tests/ directory with unit tests (e.g., search.test.ts) that validate flag parsing, argument validation, and response handling. The repository's CI workflow (.github/workflows/ci.yml) runs these tests across all skills to enforce consistency in error handling, output formatting, and exit codes, ensuring that new portals adhere to the established architectural contract before integration.
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 →