Metadata-Version: 2.4
Name: the-cartographer
Version: 0.1.0
Summary: Ask which agent skills fit a task before you start it: one cached model call over the whole skill catalogue, with a checked answer. Find and vet the ones you lack on skills.sh.
License-Expression: Apache-2.0 AND MIT AND CC-BY-SA-4.0 AND Unlicense
Project-URL: Homepage, https://github.com/sheepk95/The-Cartographer
Project-URL: Source, https://github.com/sheepk95/The-Cartographer
Project-URL: Issues, https://github.com/sheepk95/The-Cartographer/issues
Project-URL: Changelog, https://github.com/sheepk95/The-Cartographer/blob/main/CHANGELOG.md
Keywords: agent skills,skill.md,claude code,codex,mcp,model context protocol,skills,llm,agents,prompt caching,skills.sh
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Operating System :: OS Independent
Classifier: Topic :: Software Development
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: NOTICE
License-File: THIRD_PARTY_NOTICES.md
Provides-Extra: api
Requires-Dist: anthropic; extra == "api"
Dynamic: license-file

# The Cartographer

**Ask which agent skills fit a task before you start it.**

![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue)
![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue)
![Bundled skills: 193](https://img.shields.io/badge/bundled%20skills-193-2ea44f)
![MCP: stdio server](https://img.shields.io/badge/MCP-stdio%20server-8a2be2)
![Dependencies: stdlib only](https://img.shields.io/badge/dependencies-stdlib%20only-lightgrey)

**For people whose agent has more skills than it can keep in mind.** Agents that use skills (`SKILL.md` folders, as
in Claude Code and Codex) get worse as the pile grows: either every description sits in the context on every turn, or
the agent guesses from names and opens the wrong ones. The Cartographer is one model call instead. It maps a short
brief of the task against your **whole** skill library and points to the few skills worth reading first. "None" is a
valid answer.

Your own skills are the point: it reads the skill folders you already have. The 193 bundled skills are a starter
library.

![cartographer ask typing a task about property-based tests, then the answer: two picked skills with a reason each, one maybe, and a note](https://github.com/sheepk95/The-Cartographer/raw/main/docs/demo.svg)

**Try it** (needs [uv](https://docs.astral.sh/uv/), plus a Claude Code or Codex login; with a Claude API key instead,
run `uvx --from "the-cartographer[api]" the-cartographer ask "..."`):

```bash
uvx the-cartographer ask "Add property-based tests for our Python date-parsing module"
```

In Claude Code 2.1.275 or later, the plugin asks it for you at the start of each task (needs uv):
`/plugin install the-cartographer --marketplace sheepk95/The-Cartographer`. Other installs: [Quick start](#quick-start).

### A real answer

```text
$ cartographer ask "Add property-based tests for our Python date-parsing module, and check the existing tests actually catch bugs."
PICKS:
- trailofbits/property-based-testing | .../skills/trailofbits/property-based-testing/SKILL.md | Hypothesis property tests for parsers, round-trips (parse/format) in Python | read: whole
- dotnet/test-gap-analysis | .../skills/dotnet/test-gap-analysis/SKILL.md | Polyglot check of whether existing tests would catch caller-visible bugs via pseudo-mutation | read: whole
MAYBE:
- trailofbits/mutation-testing | .../skills/trailofbits/mutation-testing/SKILL.md | Real mutation campaigns (mewt/muton) if actual mutant tooling is wanted, though those tools may not support Python | read: whole
ALSO GREP: python mutmut hypothesis
NOTE: No Python-specific mutation tool skill in catalogue; test-gap-analysis covers the "do tests catch bugs" part by manual pseudo-mutation.
--- end of cartographer answer ---
```

That is a real run: Claude Code backend, Sonnet, 8.4 seconds, the 193-skill bundled catalogue (paths shortened here).
The second pick comes from a .NET skill set but is written for any language, and the NOTE says plainly what the
catalogue lacks. The animation above is a separate real run of the same task with Haiku, which picked a different
second skill: answers vary a little by model.

When the catalogue lacks something, `cartographer find` looks for it on [skills.sh](https://www.skills.sh/), and
`cartographer install` checks a find's licence and files before it goes into your skills folder
([details](#find-skills-you-dont-have)).

## How it works

```mermaid
flowchart LR
    A["Agent starts a task"] -->|"2-10 line brief"| Q["cartographer ask<br/>or the ask_cartographer MCP tool"]
    S[("Bundled skills<br/>+ your skill folders")] --> C["Catalogue text<br/>sorted, no dates, cacheable"]
    C --> M{{"One model call<br/>anthropic / claude / codex / command"}}
    Q --> M
    M --> K["Check every name and path<br/>against the catalogue"]
    K --> R["PICKS / MAYBE / ALSO GREP / NOTE"]
    R --> A2["Agent reads the picked SKILL.md files"]
    M -.->|"no backend, timeout, bad answer"| U["exit 3: CARTOGRAPHER_UNAVAILABLE<br/>fall back to cartographer search"]
```

- **The model sees every skill.** No embedding or keyword shortlist decides what it gets to consider. It judges fit by
  what the task needs, not by shared words.
- **Precision first.** The prompt asks for as few picks as do the job, sends doubtful fits to MAYBE, and treats
  `PICKS: none` as a good answer. Agents open every skill they are given, so a wrong pick costs real context.
- **Checked output.** Every name in the answer is matched to the catalogue before it is printed. Wrong paths are
  corrected and invented names are dropped with a NOTE. If every pick is invented, the answer exits 3 rather than
  printing `PICKS: none`.
- **Cheap on repeat.** The catalogue text is deterministic, so with the `anthropic` backend it sits in a 1-hour prompt
  cache: repeat asks read it at the cached-input price and pay full price only for the brief and the answer.
- **Never blocking.** A backend failure, timeout or malformed answer exits 3 with one line,
  `CARTOGRAPHER_UNAVAILABLE (reason)`, and the agent falls back to offline keyword search.
- **Isolated.** The model gets no tools, and the CLI backends run with your hooks, skills and MCP servers switched off,
  so an "always ask the cartographer" rule cannot call itself.
- **Bring your own model.** Claude API key, Claude Code login, Codex CLI, or any command that reads stdin.
- **193 bundled skills**, copied unchanged from their upstream repositories at pinned commits, each with its
  licence. Your own skill folders are added automatically.
- **Finds what you lack.** `cartographer find` searches skills.sh politely and without a model; `cartographer install`
  vets a find (licence, files, provenance) and never runs it.

## Quick start

Needs Python 3.9+ and one way to reach a model: a Claude API key, Claude Code, Codex, or a local command. Install it
one of these ways (the bundled skills come with the package):

```bash
pipx install the-cartographer          # or: uv tool install the-cartographer
pip install the-cartographer           # into the current environment; with a Claude API key: pip install "the-cartographer[api]"
uvx the-cartographer doctor            # run it once without installing (uv); any command works: uvx the-cartographer ask "..."
```

Then:

```bash
cartographer doctor            # skills found, catalogue size, which backends are ready
cartographer ask "Build a Three.js scene with a rotating textured planet and bloom"
```

The command is `cartographer`; `the-cartographer` is the same command under the package's name, which is what `uvx` and
`pipx run` look for.

From source, to edit the code or the skills in place:

```bash
git clone https://github.com/sheepk95/The-Cartographer
cd The-Cartographer
pip install -e .            # or pip install . for a regular install
```

`cartographer doctor` on a fresh clone with only the bundled skills prints something like:

```text
the-cartographer 0.1.0
bundled home: /path/to/The-Cartographer
skills: 193 (193 bundled, 0 local)
catalogue: 101163 bytes, about 28k tokens, id 3726e799ee4a
backend anthropic not ready: pip install anthropic
backend claude    ready
backend codex     ready
backend command   not ready: set CARTOGRAPHER_COMMAND
default backend: claude
installed by `cartographer install`: 0
finder cache: /home/you/.cache/the-cartographer/finder
  names list: none yet (the first `cartographer find` builds it from robots.txt and the sitemaps: about 4 requests)
  skill pages cached: 0 (0 KB)
```

To look for skills you do not have yet (see [Find skills you don't have](#find-skills-you-dont-have)):

```bash
cartographer find "Add property-based tests for our Python date parser and fuzz it for crashes"
cartographer install https://www.skills.sh/<owner>/<repo>/<skill>     # shows the licence and files, then asks
```

No model handy? These commands work offline:

```bash
cartographer search react performance      # keyword search over names and descriptions
cartographer list --origin local           # only your own skills
cartographer show obra/systematic-debugging
cartographer catalogue                     # the exact text the model reads
```

## Backends: bring your own model

| backend | uses | needs |
|---|---|---|
| `anthropic` | Claude API through the official SDK; the catalogue is a cached system block (1 hour) | `pip install "the-cartographer[api]"` (from a clone: `pip install ".[api]"`), `ANTHROPIC_API_KEY` |
| `claude` | Claude Code in print mode (`claude -p`) with your existing login | the `claude` command |
| `codex` | Codex CLI (`codex exec`), read-only sandbox, shell off, ephemeral | the `codex` command |
| `command` | any command that reads the prompt on stdin and prints the answer (local models, other CLIs) | `--command "..."`, or `CARTOGRAPHER_BACKEND=command` with `CARTOGRAPHER_COMMAND` |

Which backend runs: `--backend` if given, else `command` if you passed `--command`, else `CARTOGRAPHER_BACKEND`, else
the first ready one in the table's order. Flags also beat environment variables for `--model` and `--effort`. The model only ever sees the cartographer's own prompt:
the CLI backends run with customisations, tools and MCP servers switched off (`claude --safe-mode --tools ""`;
`codex exec --ignore-user-config -s read-only` with the shell tool disabled).

Defaults: API model `claude-opus-5-5` at effort `medium` (with server-side refusal fallbacks on). The CLI backends use
your CLI's default model unless you pass `--model` (for example `--model sonnet` with Claude Code). `--effort` takes
`low` to `max`, or `none` to send no effort setting.

For the `command` backend, the model needs a context window comfortably above the catalogue size (about 30k tokens
with the bundled skills; check yours with `cartographer doctor`).

## Connect your agent

**Claude Code: the plugin** (MCP server and an "ask the cartographer first" skill in one step; needs
[uv](https://docs.astral.sh/uv/getting-started/installation/))

```text
/plugin marketplace add sheepk95/The-Cartographer
/plugin install the-cartographer@the-cartographer
```

On Claude Code 2.1.275 or later, one line does both:
`/plugin install the-cartographer --marketplace sheepk95/The-Cartographer`. The plugin runs the server from its own
copy of this repository with `uvx --from ${CLAUDE_PLUGIN_ROOT} cartographer mcp`, so it needs no separate install; the first start builds it once (about 15 seconds), later starts take under a second.
Its skill tells the agent to call `ask_cartographer` at the start of each non-trivial task.

**Claude Code: the MCP server only**

```bash
claude mcp add the-cartographer -- uvx the-cartographer mcp      # or, after pipx/pip install: -- cartographer mcp
```

(or put [`integrations/claude-code/mcp.json`](https://github.com/sheepk95/The-Cartographer/blob/main/integrations/claude-code/mcp.json)
in a project as `.mcp.json`). Then either copy
[`integrations/claude-code/ask-the-cartographer/`](https://github.com/sheepk95/The-Cartographer/tree/main/integrations/claude-code/ask-the-cartographer)
into `~/.claude/skills/`, or paste
[`integrations/claude-code/CLAUDE.md-snippet.md`](https://github.com/sheepk95/The-Cartographer/blob/main/integrations/claude-code/CLAUDE.md-snippet.md)
into your CLAUDE.md, so the agent asks at the start of each task.

**Codex**

```bash
codex mcp add the-cartographer -- uvx the-cartographer mcp      # or, after pipx/pip install: -- cartographer mcp
```

or add this to `~/.codex/config.toml` (the same as
[`integrations/codex/config.toml`](https://github.com/sheepk95/The-Cartographer/blob/main/integrations/codex/config.toml)):

```toml
[mcp_servers.the-cartographer]
command = "uvx"
args = ["the-cartographer", "mcp"]
startup_timeout_sec = 60   # the first uvx start downloads the package
tool_timeout_sec = 180     # an ask is one model call over the whole catalogue
```

Then add
[`integrations/codex/AGENTS.md-snippet.md`](https://github.com/sheepk95/The-Cartographer/blob/main/integrations/codex/AGENTS.md-snippet.md)
to your AGENTS.md. Codex sandboxes may not open files outside the workspace, so the MCP server also offers `read_skill`.

**Anything else**

Call the CLI. Exit 0 prints the answer. Exit 3 prints `CARTOGRAPHER_UNAVAILABLE (reason)`, and the agent should fall
back to `cartographer search <words>`. Exit 2 means bad arguments or an empty brief. (`cartographer search` and `show` exit 1 when nothing matches.) `--json` prints the answer, the parsed
picks, timing and (with the API backend) token usage. For longer briefs use `--brief-file task.md`, or give `-` as
the brief to read it from stdin.

## MCP tools

`cartographer mcp` runs a stdio MCP server with four read-only tools:

| tool | what it does |
|---|---|
| `ask_cartographer(brief, from?)` | the picks for a task (calls the backend); also returns them as structured content |
| `read_skill(name, offset?, limit?)` | a skill's SKILL.md text, in line ranges for large skills |
| `search_skills(query)` | keyword search, no model call |
| `find_skills(brief, offline?)` | skills on skills.sh that the catalogue lacks (network, no model call, installs nothing; writes only the finder's cache) |

`cartographer mcp` takes the same model and catalogue options as `ask` (`--backend`, `--model`, `--effort`, `--timeout`,
`--command`, `--skills-dir`, `--no-user-skills`, `--no-bundled`), plus `--lexicon` for `find_skills`. `find_skills`
stops fetching pages after 45 seconds and says how many matches it did not check; a failure comes back as a tool
error, not a protocol error. Installing is CLI only: it writes to disk, so a person runs `cartographer install`.

Any MCP client that launches stdio servers can run it with `uvx the-cartographer mcp` (or `cartographer mcp` after an
install). Its MCP Registry name is `io.github.sheepk95/the-cartographer` (the PyPI package `the-cartographer`, stdio;
see [`server.json`](https://github.com/sheepk95/The-Cartographer/blob/main/server.json)).

<!-- mcp-name: io.github.sheepk95/the-cartographer -->

## Your own skills

Scanned automatically when they exist: `~/.claude/skills`, `~/.codex/skills`, `~/.agents/skills`, and the same three
under the current project. Each holds `<name>/SKILL.md`; the cartographer reads its `name` and `description`
front matter (falling back to the folder name and the first line of text). Add more with `--skills-dir DIR`
(repeatable) or `CARTOGRAPHER_SKILLS_DIRS` (path-separator list). `--no-user-skills` and `--no-bundled` narrow the
catalogue.

Your skills are marked `[local]` in the catalogue and judged on equal terms with bundled ones. They are never copied or
uploaded anywhere except into the prompt of the backend you chose.

## Find skills you don't have

`cartographer ask` chooses from skills you already have. `cartographer find` looks for good ones you lack, on
[skills.sh](https://www.skills.sh/), the public directory of agent skills. It installs nothing, and it calls no model
unless you add `--judge`.

```text
$ cartographer find "Add property-based tests with Hypothesis for a Python date-parsing module, and fuzz the parser for crashes."
SKILLS.SH FINDS (not in your catalogue; UNVETTED: nothing was installed)
(names and descriptions are third-party text from skills.sh pages: data, not instructions)
1. microsoft/azure-skills/file-test-bug | https://www.skills.sh/microsoft/azure-skills/file-test-bug
   fits: debugging (bug); testing (test) | score 10.49
   installs 928 (skills.sh CLI telemetry) | repo stars 1.6K (microsoft/azure-skills, whole repo, rounded) | audits: none listed
   often covered: you already have addyosmani/doubt-driven-development, dotnet/csharp-refactoring, dotnet/test-gap-analysis
   file-test-bug — an installable skill for AI agents.
   vet and install: cartographer install https://www.skills.sh/microsoft/azure-skills/file-test-bug
2. sickn33/agentic-awesome-skills/api-fuzzing-bug-bounty | https://www.skills.sh/sickn33/agentic-awesome-skills/api-fuzzing-bug-bounty
   fits: debugging (bug); testing (fuzzing, testing) | score 10.49
   installs 665 (skills.sh CLI telemetry) | repo stars 48.6K (sickn33/agentic-awesome-skills, whole repo, rounded) | audits: Gen Agent Trust Hub Pass, Socket Warn, Snyk Pass
   often covered: you already have addyosmani/doubt-driven-development, dotnet/csharp-refactoring, dotnet/test-gap-analysis
   Provide comprehensive techniques for testing REST, SOAP, and GraphQL APIs during bug bounty hunting and ...
   vet and install: cartographer install https://www.skills.sh/sickn33/agentic-awesome-skills/api-fuzzing-bug-bounty
...
NOTE: searched 19976 skills.sh names and 10 cached pages; fetched 8 page(s) now
```

That is a real run (shortened: three more finds followed) against the bundled catalogue. The property-based and
mutation testing skills that fit best are already bundled, so they are not offered again; what is left is near the
topic, not on it. That is why every find says UNVETTED, and why `--judge` exists (below): with it, the model dropped
every one as off-topic.

**How a search works**

1. **Names.** The skills.sh sitemaps list about 20,000 skills, most installed first. The list is cached for a day,
   so most searches send no request for it.
2. **Screen.** A word-based fit rule (the *lexicon*, below) scores every name against the brief. Skills you already
   have, by name or by an earlier `cartographer install`, are left out.
3. **Fetch a few pages.** The current skills.sh pages of the best matches only, 8 at most per search, cached for a
   day. A page gives the description, the install count, the repository, its stars and the security audits.
4. **Score again** on the descriptions, apply the popularity bar, and print the best 5 (`--limit` up to 10).

The popularity bar keeps a find only when it comes from a GitHub repository, no audit says Fail, and it has 1,000+
installs, or 200+ repository stars with 100+ installs, or a verified owner with 100+ installs. "Often covered" names
skills you already have that share its concepts: check them before installing something new.

**Options**

| option | meaning |
|---|---|
| `--offline` | no network: only the cached names and pages |
| `--judge` | one model call (any backend) reads the shortlist and marks each KEEP or DROP with a reason |
| `--lexicon FILE` | add to (or replace) the built-in lexicon |
| `--limit N` | at most N finds (1-10, default 5; anything else is exit 2) |
| `--json` | the finds as JSON |
| `--brief-file F` | read the brief from a file, or `-` for stdin (`-` as the brief also reads stdin) |

`find` also takes the catalogue options of `ask` (`--skills-dir`, `--no-user-skills`, `--no-bundled`), and its model
options (`--backend`, `--model`, `--effort`, `--timeout`, `--command`) are used only by `--judge`.

Exit codes: 0 finds printed; 1 none (or the judge dropped them all); 2 bad arguments, a bad lexicon or an empty brief;
3 `CARTOGRAPHER_UNAVAILABLE` (nothing cached and the network failed, the cache folder cannot be used, or there is no
find while some name matches could not be checked because a page fetch failed or a cap was reached: run it again
later), no backend for `--judge`, or `JUDGE FAILED`.

**The judge.** `--judge` sends the brief and the shortlist (names, descriptions, figures from skills.sh) to your
backend in one call. The third-party text is fenced as data: `<` and `>` in it (and in the brief) are replaced with
look-alikes, so nothing can close the fence, and bidi and zero-width characters are removed. The reply must be one
JSON object with a verdict for each candidate id and a fresh request id. A reply that cannot be read fails loudly (`JUDGE FAILED`, exit 3) and the
shortlist is shown as not judged; it never looks like "everything dropped". A single bad item is shown as UNJUDGED. A
judge verdict never installs anything.

### Being polite to skills.sh

The finder reads only the public pages and sitemaps that skills.sh's `robots.txt` allows, slowly, and caches what it
reads. skills.sh's [terms](https://www.skills.sh/terms) welcome reasonable use and caching of its public API and
warn that scraping which gets around its rate limits may be blocked; they say nothing specific about reading the
pages. The finder never uses that API (`robots.txt` disallows `/api/`), and it:

- reads `robots.txt` before it requests anything else (a copy under a day old is reused) and never requests a
  disallowed path, such as `/api/` or `/search`;
- sends one request at a time, at least 1.3 seconds apart (1.5 by default; the bulk index at least 3), shared by
  every `cartographer` process that uses the same cache folder, through a lock file there;
- stops on HTTP 429 or 403 and sends nothing more to that host until the `Retry-After` time (at least a minute) or,
  for 403, six hours;
- keeps daily caps (UTC): 300 skill pages and 12 robots or sitemap requests a day for `find` and `install`, and a
  separate 2,000 a day for the bulk index (its skill and repo pages), changeable with
  `CARTOGRAPHER_FINDER_DAILY_PAGES`, `CARTOGRAPHER_FINDER_DAILY_LISTS` and `CARTOGRAPHER_FINDER_DAILY_BULK`;
- asks again with `If-None-Match` / `If-Modified-Since`, so an unchanged page costs a 304 (a stale page in `find`,
  and every cached page in `finder index --refresh`);
- requests only whitelisted URLs: skills.sh pages and sitemaps, and for `install` one GitHub API call and one
  tarball. Redirects off the whitelist, or to a path robots.txt disallows, are refused; each hop that is followed
  waits the gap, and a hop on skills.sh counts against the daily cap; a GitHub token is never sent on to a redirect;
  search never touches GitHub;
- identifies itself: `the-cartographer/<version> (+https://github.com/sheepk95/The-Cartographer; skill finder; reads
  robots.txt; one request at a time, at least 1.3 s apart)`.

The Cartographer is not affiliated with or endorsed by skills.sh or Vercel.

**Optional bulk index.** `cartographer finder index --top 2000` fetches the pages of the most-installed skills in
sitemap order, so `find --offline` and the name screen have descriptions to work with. It reads `robots.txt` live,
resumes where it stopped, steps aside while a `find` is fetching, stops on 429 or 403 (or after 25 failures in a
row), and Ctrl-C is safe. At the default gap (`--gap`, 3 seconds, also the minimum) 2,000 pages take about two
hours; the daily bulk cap spreads a bigger index over days, and `--repo-pages` requests count against it. It is never
started for you: there is no daemon and no schedule. `cartographer finder status` shows the cache and today's request
counts.

### Installing a find

```bash
cartographer install https://www.skills.sh/<owner>/<repo>/<skill>   # or: cartographer install <owner>/<repo>[/<skill>]
```

1. Reads the skill's page for its figures (skip with `--no-page`). A Fail audit refuses (`--allow-audit-fail`).
2. Asks the GitHub API for the commit (or takes `--ref <full sha>`), then downloads that commit's tarball.
3. Finds the skill folder (by name, or `--path`) and its licence: the SKILL.md `license` field (an SPDX id or a
   flat expression such as `MIT OR Apache-2.0`) and the licence files of the nearest folder that has any (the skill
   folder, then each parent up to the repository root). When the two disagree both apply, and a refused one decides.
4. Prints the licence evidence, every file with its size, and flags: scripts, binaries, large files, executable bits
   (dropped). Symlinks and devices are never written. An archive with an absolute or `..` path, or a backslash or
   colon in any member name, is refused; so are file names that cannot be written the same way on every system
   (Windows device names such as `aux`, `COM1`, `COM¹`, `CONIN$`, `<>:"|?*`, a trailing dot or space, two names
   differing only in case) and archives over 100,000 members or 2 GB unpacked.
5. Refuses agent configuration that an agent host can run on its own:
   - at the top of the skill folder, `.claude-plugin/`, `.codex-plugin/`, `.cursor-plugin/`, `.claude/` or `hooks/`
     folders, and `.mcp.json`, `.lsp.json`, `settings.json`, `settings.local.json`, `hooks.json` or `plugin.json`;
   - SKILL.md front matter `hooks` or `allowed-tools`, also quoted, spaced or written `allowed_tools`, and front
     matter in YAML forms the check cannot read (a `{...}` flow mapping, `? key`, `<<` merge keys, anchors, tags,
     escaped keys);
   - SKILL.md shell commands: `` !`...` `` after any whitespace, or a code fence opened with `!` anywhere.

   Pass `--allow-agent-config` after reading them. The same names deeper in the folder, and `agents/`, `commands/`,
   `.codex/`, `.cursor/` or `.vscode/` anywhere, are listed as warnings, not refused: a host loads them only from a
   plugin or project root. These are pattern checks that fail closed, not a guarantee that every form is caught.
6. Refuses a missing, unknown, non-commercial, no-derivatives, source-available or proprietary licence, including a
   permissive text with a use rider (for example MIT plus "not for commercial use", "personal use only" or "you may
   not redistribute"), or one listed in `CARTOGRAPHER_REFUSE_LICENCES` (for example `AGPL-3.0,GPL-3.0`). Licences are
   classified by their wording, so read the evidence it prints. You can override with `--allow-any-licence` only
   if you have the right to use the skill; the record says so. Copyleft licences (GPL, LGPL, AGPL, MPL, EPL,
   CC-BY-SA, OFL) are allowed with a one-line note and recorded as `"copyleft": true`.
7. Asks before it writes (`--yes` to skip; with no terminal it refuses instead of guessing), and never overwrites: an
   existing folder is a refusal. The files are written to a hidden temporary folder first, each path checked to be
   inside it, and renamed into place.

It installs into `~/.agents/skills/<name>` by default (`--dest`, `--name` to change), where the catalogue already
looks. Every file of the skill folder is copied, hidden ones such as `.github/` included (only `.git/` is left out),
up to 500 files and 20 MB. The licence text the decision rests on is always copied in, also when the front matter
names the licence; a parent folder's licence files and NOTICE files above the skill go beside it (as
`<name>.upstream` when the skill has a file of that name), and the record's `licence_text` names them. When upstream
has no licence text, `install` says so. Nothing it downloads is ever run by the cartographer (but see agent
configuration above).

Exit codes: 0 installed; 1 refused (licence, audit, agent configuration, unsafe or unportable names, a tarball over
200 MB, folder exists, you said no, skill not found); 2 bad arguments; 3 the download or the write failed (network,
rate limit, unreadable archive, disk), with nothing changed.

Each installed skill gets a `.cartographer-source.json`: repository, commit, folder, licence with its evidence and
text files, the copyleft and agent-configuration marks and warnings, the SHA-256 of every file, the flags, and the skills.sh
figures at install time. `cartographer list` shows where each skill
came from, and `cartographer doctor` reports any installed skill whose files changed since or whose licence needed an
override. Refusals are remembered in the cache (`finder/refused.jsonl`) so `find` stops offering them: a licence
refusal blocks the whole repository when the licence was found above the skill folder, or none was found, else
that one skill; a `--judge` prompt-injection flag blocks that one skill.

### What the figures mean

- **Installs** come from the skills.sh CLI's telemetry. They count installs through that CLI only and are easy to
  inflate, so read them as a rough signal.
- **Stars** are the whole repository's GitHub stars, as skills.sh shows them (rounded, such as "1.6K"). A big
  collection repository lends its stars to every skill in it.
- **Audits** are the security scans skills.sh lists for the skill. "Pass" is not a review; read the files.

### The lexicon

The fit rule is driven by a lexicon: concepts (testing, debugging, security, frontend, video, ...) with the words that
signal each, aliases, and platform words. A find needs at least one concept in common with the brief; rarer concepts
count more, and a hit in the skill's name counts extra. A skill that names a platform or framework (Godot, visionOS,
Vue, Django, ...) is dropped unless the brief names one of the same.

Extend it with a JSON file (`--lexicon FILE` or `CARTOGRAPHER_LEXICON`); lists add to the built-in ones, and
`"replace": true` starts from an empty lexicon (the popularity bar keeps its defaults):

```json
{
  "concepts": {"testing": "hypothesis pbt", "parsing": "parser parsers grammar tokenizer"},
  "aliases": {"pbt": "property-based"},
  "conflict": "elixir",
  "not_conflict": "django",
  "blocked": [{"repo": "someone/some-repo", "reason": "licence unclear"}],
  "min_score": 1.8,
  "max_rows": 5
}
```

Other keys: `needs` (`{"video": "specific"}` makes a concept count only when task and skill share one of the
`specific` words), `context`, `name_weight`, `same_term_bonus` and `bar` (the popularity thresholds:
`min_installs`, `min_stars`, `min_installs_with_stars`, `min_installs_verified`). A `blocked` entry with an `id`
(`owner/repo/skill`) drops that skill. One with a `repo` drops the whole repository when its reason mentions a
licence, malware, secrets or similar; any other reason only adds a note.

**Limits.** The first screen sees only names, so a skill whose name has none of the lexicon's words is found only
through the bulk index. Words are matched, not meaning; `--judge` adds the meaning. The skills.sh pages and sitemaps
are not a published API, so a change to them can break parsing: the finder then finds less and says so in its notes,
rather than guessing.

## The bundled skills

193 skills from 59 upstream repositories. A sample of what is in there:

| area | examples |
|---|---|
| Blender and 3D art | `impertio-studio/blender-core-api`, `ifbars/blender-modeling-workflow`, `roble3/blender-uv-texturing`, `arjun988/geometry-nodes` |
| Unity | `gamedev-skills/unity-csharp-scripting`, `hissal/unity-testing`, `wshobson/unity-ecs-patterns`, `scenario-labs/scenario-unity-performance` |
| Three.js and WebGL | `cloudai-x/threejs-fundamentals`, `cloudai-x/threejs-postprocessing`, `mengto/threejs-landscape` |
| Game design | `gamedev-skills/game-feel`, `gamedev-skills/level-design`, `gamedev-skills/save-systems`, `mengto/design-action-combat` |
| Shaders and VFX | `minimax-ai/shader-dev`, `adevra/urp-hlsl-templates`, `omer-metin/vfx-realtime` |
| Testing | `trailofbits/property-based-testing`, `trailofbits/mutation-testing`, `dotnet/test-gap-analysis`, `nowsprinting/test-designing-guide` |
| Debugging, review, refactoring | `obra/systematic-debugging`, `mattpocock/code-review`, `wondelai/working-with-legacy-code`, `warpdotdev/merge-risk` |
| Security review | `trailofbits/variant-analysis`, `trailofbits/audit-context-building`, `trailofbits/spec-to-code-compliance` |
| Agent workflows | `obra/writing-plans`, `obra/subagent-driven-development`, `muratcankoylan/context-compression`, `anthropics/skill-creator`, `anthropics/mcp-builder` |
| Writing and marketing | `coreyhaines31/copywriting`, `coreyhaines31/launch`, `blader/humanizer`, `mattpocock/writing-for-agents` |
| Video and audio | `calesthio/editing-montage`, `elevenlabs/music`, `jakeschincariol/yt-retention`, `heygen-com/cut-the-curve` |
| Data and web | `supabase/supabase-postgres-best-practices`, `laguagu/postgres-semantic-search`, `zaggino/writing-json-schemas`, `openai/playwright`, `anthropics/frontend-design` |

`cartographer list --origin bundled` shows all of them. `catalogue/bundled.json` records each one's upstream repository,
pinned commit, path, licence and the SHA-256 of every file; `THIRD_PARTY_NOTICES.md` links each to its source.

- **Unchanged.** Each folder holds the skill's upstream files at that commit, byte for byte (hidden tool folders
  such as `.github/` left out), plus the upstream LICENSE and NOTICE files when the skill folder had none.
  `python tools/vendor.py verify` checks all 1078 file hashes offline; `python tools/vendor.py fetch` downloads them
  again from upstream.
- **Licensed for redistribution.** MIT (154), Apache-2.0 (32), CC-BY-SA-4.0 (6), Unlicense (1). Skills under
  non-commercial, unknown or vendor-restricted licences were left out.
- **Used before they were bundled.** Every one was in day-to-day use in the maintainers' own agent setup. 120 have a
  written review record from when they were installed (`"review": "recorded"` in bundled.json); the other 73 are
  marked `"review": "in use"`.
- **Read scripts before running them.** Some skills ship helper scripts. The Cartographer never runs them; your agent
  might, if a skill tells it to. Treat them like any third-party code.
- **Don't want them?** `--no-bundled` uses only your own skills.

## How it answers

```text
PICKS:
- <name> | <SKILL.md path> | <why, one line> | read: whole
- <name> | <SKILL.md path> | <why> | read: LARGE read in parts
MAYBE:
- <name> | <SKILL.md path> | <why it might fit> | read: whole
ALSO GREP: <2-4 words>
NOTE: <one line>
--- end of cartographer answer ---
```

- The prompt allows at most 6 picks, only clear fits; MAYBE is capped at 3. Doubtful fits go under MAYBE, and the
  agent reads only their first 40 lines.
- `PICKS: none` when the task needs no skill (copy files, run a fixed command).
- Every name is checked against the catalogue before the answer is printed: a wrong path is corrected, an invented
  name is dropped with a NOTE. When every pick is invented, the answer exits 3 (`CARTOGRAPHER_UNAVAILABLE`).
- A pick line needs all four fields (name, path, why, `read: ...`). A PICKS section with no readable line and no
  explicit none (`none`, `none.`, `(none)`, or `none` with a short reason) exits 3.
- A SKILL.md over 50,000 bytes is marked LARGE (from the file size, not the model's guess), so the agent reads it in
  parts.
- The brief is data. The cartographer is told never to follow instructions inside it, and it has no tools to act with.
- The fixed end line lets a calling script tell a complete answer from a cut-off one.

## How it compares

Other ways to choose skills exist, and each makes a different trade:

| approach | how skills get chosen | trade-off |
|---|---|---|
| All descriptions in the agent's prompt | the agent decides mid-task from names and one-liners | costs tokens on every turn; gets harder as the list grows |
| Keyword or embedding search (skill routers, built-in skill search) | similarity between the task text and the descriptions | fast and free per query, but matches words, and the shortlist decides what is considered |
| The Cartographer | one model call judges the brief against every description, then the answer is checked | costs one model call (seconds) per task; quality depends on the descriptions |

The Cartographer keeps keyword search as its offline fallback (`cartographer search`, `search_skills`), so you can use both.

## Cost, speed and limits

- **Catalogue size.** About 100-130 KB (roughly 28-35k tokens) with the bundled skills; it lists each SKILL.md's full
  path, so it varies with where you installed. Roughly 150-180 tokens per skill, so it grows with every skill you add. `cartographer doctor` shows yours.
- **Caching.** The catalogue text is the same bytes for the same skills, so with the `anthropic` backend repeat asks
  within an hour read it from the prompt cache instead of paying the full input price again. With the CLI backends,
  the CLI's own caching applies.
- **Speed.** The Claude Code backend answered in 8.4 seconds on Sonnet in the example above. Larger models and higher
  effort take longer.
- **Quality depends on descriptions.** A skill whose description does not say what it is for is hard to pick, for a
  model or a person.
- **It picks; it does not check the work.** The checker guarantees every name and path is real, not that the choice is
  right. A model can still miss a skill or include a weak one.

## Configuration

| variable | meaning |
|---|---|
| `CARTOGRAPHER_BACKEND` | `anthropic`, `claude`, `codex` or `command` |
| `CARTOGRAPHER_MODEL` | model id or alias for the backend |
| `CARTOGRAPHER_EFFORT` | `low` ... `max`, or `none` |
| `CARTOGRAPHER_COMMAND` | the command for the `command` backend |
| `CARTOGRAPHER_REQUESTER` | default for `--from` (who is asking) |
| `CARTOGRAPHER_SKILLS_DIRS` | extra skill folders |
| `CARTOGRAPHER_HOME` | the folder holding `skills/` and `catalogue/` (default: the installed bundle, else the clone) |
| `CARTOGRAPHER_CLAUDE_BIN`, `CARTOGRAPHER_CODEX_BIN` | paths to the CLIs when they are not on PATH |
| `CARTOGRAPHER_CODEX_KEEP_CONFIG` | set to keep your Codex config (custom providers) instead of ignoring it |
| `CARTOGRAPHER_CACHE` | the cache folder for `find` and `install` (default: `%LOCALAPPDATA%\the-cartographer` on Windows, else `$XDG_CACHE_HOME/the-cartographer` or `~/.cache/the-cartographer`); the finder's files are in its `finder/` folder |
| `CARTOGRAPHER_LEXICON` | a lexicon file for `find` and `find_skills` |
| `CARTOGRAPHER_FINDER_DAILY_PAGES`, `CARTOGRAPHER_FINDER_DAILY_LISTS` | the daily caps on skills.sh page requests (300) and robots/sitemap requests (12) for `find` and `install` |
| `CARTOGRAPHER_FINDER_DAILY_BULK` | the daily cap on the bulk index's skill and repo page requests (2000) |
| `CARTOGRAPHER_REFUSE_LICENCES` | comma-separated licence ids `install` refuses on this machine, on top of its policy (for example `AGPL-3.0,GPL-3.0`) |
| `CARTOGRAPHER_NO_NETWORK` | set (to anything): the finder and installer send no request and say the network is off (the tests set it; model backends are not affected) |
| `CARTOGRAPHER_GITHUB_TOKEN` | optional token for the one GitHub API call `install` makes (added only to requests to `api.github.com`) |

## FAQ

**Does it send my skills anywhere?** Only into the prompt of the backend you chose, the same place your agent would
send them. Nothing is uploaded or stored by The Cartographer itself.

**What if no model is reachable?** `cartographer ask` exits 3 with `CARTOGRAPHER_UNAVAILABLE (reason)`. Agents set up with
the integration snippets then run `cartographer search` instead and carry on.

**Does it run the skills?** No. It names them. Reading and following a skill is your agent's job. `cartographer install`
downloads files and writes them; it never runs them either, and it refuses agent configuration that your agent host
could run on its own unless you pass `--allow-agent-config`.

**Does `find` crawl skills.sh?** No. A search reads the names list (rebuilt from the sitemaps when it is a day old)
and at most 8 pages, within the site's robots.txt and a daily cap. The bulk index is opt-in, goes at least 3 seconds
apart and has its own daily cap (2,000).

**Can a brief hijack it?** The brief is wrapped as data, the model has no tools, and the answer is parsed and checked
against the catalogue, so every pick it prints is a real catalogue entry. The prompt also tells the model to
answer a brief that gives it orders as a plain skill question and add a NOTE saying so.

**Should every helper agent ask?** No. A coordinator lists every helper's task in one brief and passes each helper its
picks.

## Development

```bash
python -m unittest discover -s tests
python tools/vendor.py verify
```

The tests call no model and use no network: they cover the front matter reader, the catalogue, answer checking, the
command backend, the API request shape (with a stand-in SDK), an MCP stdio session, the bundle hashes, and the finder
and installer against a fake skills.sh and fake GitHub tarballs (robots, the URL whitelist, rate limits, licence
refusals, unsafe archives, never overwriting, never running a download). See [CONTRIBUTING.md](https://github.com/sheepk95/The-Cartographer/blob/main/CONTRIBUTING.md)
for how to propose a change or a skill for the bundle, [docs/DESIGN.md](https://github.com/sheepk95/The-Cartographer/blob/main/docs/DESIGN.md) for why it works this way,
and [SECURITY.md](https://github.com/sheepk95/The-Cartographer/blob/main/SECURITY.md) for what it does and does not do with your files and the network.

## Licence

The Cartographer's code and docs: Apache License 2.0 ([LICENSE](https://github.com/sheepk95/The-Cartographer/blob/main/LICENSE)),
copyright 2026 Blake W. If you pass on a copy or a changed version, keep the
[NOTICE](https://github.com/sheepk95/The-Cartographer/blob/main/NOTICE) file with it and mark the files you changed. Each bundled skill: its own licence, in its folder
([THIRD_PARTY_NOTICES.md](https://github.com/sheepk95/The-Cartographer/blob/main/THIRD_PARTY_NOTICES.md) lists them all).
