# Can the AI Job Search Framework Be Extended with Custom Skills? A Complete Guide

> Extend the AI Job Search framework with custom skills using its thin-pointer architecture. Add new capabilities to the .claude/skills/ directory without core code changes. Get the complete guide.

- Repository: [Mads Lorentzen/ai-job-search](https://github.com/MadsLorentzen/ai-job-search)
- Tags: how-to-guide
- Published: 2026-08-30

---

**Yes, the AI Job Search framework supports custom skills through a thin-pointer architecture that auto-discovers new capabilities placed in the `.claude/skills/` directory without modifying core code.**

The AI Job Search framework, maintained at `MadsLorentzen/ai-job-search`, uses a filesystem-based discovery mechanism that makes extending with custom skills straightforward. Rather than editing Python modules, you create new folders with declarative metadata files that the framework reads at startup.

## How Custom Skill Discovery Works

The framework implements a **thin-pointer architecture** where all logic lives in the hidden `.claude/` hierarchy. At startup, it recursively scans `*.md` files inside `.claude/skills/` to identify available capabilities.

| Component | Role | Location | Discovery Mechanism |
|-----------|------|----------|---------------------|
| **Skill definition** | Declares name, description, parameters, UI hints | `.claude/skills/<skill-name>/SKILL.md` | Recursive scan of `.claude/skills/` at startup |
| **Skill implementation** | Executable code (Python, shell, or external tools) | `.claude/skills/<skill-name>/` (e.g., [`run.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/run.py), [`cli.sh`](https://github.com/MadsLorentzen/ai-job-search/blob/main/cli.sh)) | `run` entry in [`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md) specifies the entry point |
| **Global settings** | API keys, default options, shared configuration | [`.claude/settings.json`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/settings.json) | Loaded once and injected into every skill |
| **Command bindings** | CLI shortcuts that invoke one or more skills | `.claude/commands/*.md` | Executed when matching command name is called |

Because discovery depends only on file structure and markdown metadata, you can extend the AI Job Search framework with custom skills by creating files in the correct locations.

## Creating a Custom Skill: Step-by-Step

This example adds `example-portal`, a skill that scrapes job listings from a fictional job board.

### Step 1: Create the Directory Structure

```bash
mkdir -p .claude/skills/example-portal

```

The resulting layout:

```

.claude/
└─ skills/
   └─ example-portal/
      ├─ SKILL.md
      └─ run.py

```

### Step 2: Define Skill Metadata in SKILL.md

The [`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md) file ([source](https://github.com/MadsLorentzen/ai-job-search/blob/master/.claude/skills/example-portal/SKILL.md)) contains declarative configuration:

```markdown

# Example Portal Skill

**name:** example-portal  
**description:** Scrape job listings from https://example.com and return a JSON payload.  

**run:** python run.py "{{ query }}"  

**parameters:**  
- `query` (string) – Search term to use on the portal.  

**output:** JSON array of objects containing `title`, `company`, `location`, and `url`.

```

**Required fields:**
- `name` – unique identifier matching the folder name
- `description` – human-readable purpose
- `run` – command template executed when the skill is invoked
- `parameters` – input variables referenced in `run`

### Step 3: Implement the Executable Script

The [`run.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/run.py) implementation ([source](https://github.com/MadsLorentzen/ai-job-search/blob/master/.claude/skills/example-portal/run.py)):

```python
#!/usr/bin/env python3
import sys, json, requests, bs4

def scrape(query: str):
    url = f"https://example.com/search?q={query}"
    resp = requests.get(url, timeout=10)
    soup = bs4.BeautifulSoup(resp.text, "html.parser")
    results = []
    for card in soup.select(".job-card"):
        results.append({
            "title":    card.select_one(".title").get_text(strip=True),
            "company":  card.select_one(".company").get_text(strip=True),
            "location": card.select_one(".location").get_text(strip=True),
            "url":      card.select_one("a")["href"]
        })
    return results

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Missing query argument", file=sys.stderr)
        sys.exit(1)
    query = sys.argv[1]
    print(json.dumps(scrape(query), ensure_ascii=False, indent=2))

```

This script:
- Accepts `query` as a positional argument
- Returns structured JSON to stdout
- Exits with code 1 on error

### Step 4: Invoke the Custom Skill

```bash
ai-job-search skill example-portal "data scientist"

```

The framework:
1. Locates [`.claude/skills/example-portal/SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/skills/example-portal/SKILL.md)
2. Parses the `run` template: `python run.py "{{ query }}"`
3. Substitutes `{{ query }}` with `"data scientist"`
4. Executes the resolved command in the skill's directory
5. Renders the JSON output

## Binding Custom Skills to CLI Commands

For cleaner invocation, create a command alias in [`.claude/commands/example-search.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/commands/example-search.md):

```markdown

# Example Search Command

## description

Run the Example Portal skill with a single argument.

## execute

skill example-portal "{{ args[0] }}"

```

Now use the shorthand:

```bash
ai-job-search example-search "data scientist"

```

Command files are optional—skills remain accessible through the generic `skill` subcommand.

## Configuration and Shared Resources

### Accessing Global Settings

Skills automatically receive values from [`.claude/settings.json`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/settings.json) ([source](https://github.com/MadsLorentzen/ai-job-search/blob/master/.claude/settings.json)). Add custom keys for your skill:

```json
{
  "example-portal": {
    "api_key": "sk-...",
    "rate_limit": 10
  }
}

```

The framework loads this once and makes it available to all skill executions.

### Reference Implementations

Study existing skills for patterns:

- **Basic skill**: [`.claude/skills/job-scraper/SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/skills/job-scraper/SKILL.md) ([source](https://github.com/MadsLorentzen/ai-job-search/blob/master/.claude/skills/job-scraper/SKILL.md)) – minimal required fields
- **Multi-step skill**: [`.claude/skills/upskill/SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/skills/upskill/SKILL.md) ([source](https://github.com/MadsLorentzen/ai-job-search/blob/master/.claude/skills/upskill/SKILL.md)) – complex orchestration example
- **Command binding**: [`.claude/commands/search.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/commands/search.md) ([source](https://github.com/MadsLorentzen/ai-job-search/blob/master/.claude/commands/search.md)) – how to chain skills

## Key Documentation Files

| File | Purpose | Link |
|------|---------|------|
| [`README.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/README.md) | Installation and basic usage | [source](https://github.com/MadsLorentzen/ai-job-search/blob/master/README.md) |
| [`AGENTS.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/AGENTS.md) | Thin-pointer architecture explanation | [source](https://github.com/MadsLorentzen/ai-job-search/blob/master/AGENTS.md) |
| [`.claude/settings.json`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/settings.json) | Global configuration schema | [source](https://github.com/MadsLorentzen/ai-job-search/blob/master/.claude/settings.json) |

## Summary

- The AI Job Search framework uses **filesystem-based auto-discovery**—new skills appear immediately when placed in `.claude/skills/`
- Each skill requires a [`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md) metadata file and an executable script referenced by the `run` field
- No core code modifications are needed; extensions are isolated and version-controllable
- Optional command bindings in `.claude/commands/` provide CLI shortcuts
- Global configuration in [`.claude/settings.json`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/settings.json) is automatically available to all skills

## Frequently Asked Questions

### What programming languages can I use for skill implementations?

Any language executable from the shell. The `run` field in [`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md) accepts arbitrary commands—`python run.py`, `node script.js`, `bash cli.sh`, or compiled binaries. The framework treats the `run` template as a shell command and executes it directly.

### Do custom skills require restarting the framework?

No. The AI Job Search framework scans `.claude/skills/` at each invocation, so new or modified skills are discovered immediately. This enables rapid iteration during development without service restarts.

### Can my skill depend on external Python packages?

Yes. Install dependencies in the environment where `ai-job-search` runs—either system-wide, in a virtual environment, or via a [`requirements.txt`](https://github.com/MadsLorentzen/ai-job-search/blob/main/requirements.txt) managed alongside your skill. The framework does not isolate skill dependencies; they share the execution environment.

### How do I debug a custom skill that fails silently?

Run the skill with verbose logging enabled, or test the `run` command directly in the skill's directory. Since [`SKILL.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SKILL.md) templates resolve to standard shell commands, you can manually execute `python run.py "test query"` to see stderr and exit codes.