π§ Fourteen coding CLIs, one map. You ask where Claude keeps skills, you get the path.
Warning
@agntn/harnesses is pre-1.0. Paths follow upstream CLIs that still move. Building on it now means pinning the version.
Claude stores transcripts under a mangled copy of your cwd. Codex keeps TOML with comments you wanted to keep. Ask a model where Codex lives and it invents .claude/. So this is one registry: fourteen harnesses, same object, paths for your machine.
The rest of it sits on harnesses.agntn.dev.
- π§© Fourteen harnesses, one class. Same fields on Claude, Codex, Pi and the rest.
- π Every path has a receipt. Scope, evidence level, and a platform tag when the OS actually differs.
- π Detects which CLI you're inside. Environment variables first. Two project markers in one directory is
null, not a guess. βΆοΈ Headless runs with real modes. Advisor without tools, full agent, or a native read-only sandbox. A mode the CLI cannot enforce is rejected.- π MCP across the dialects. One master list at
~/.config/agntn/mcp.jsonc. TOML edits keep the comments. - π One AGENTS.md behind the global files. Symlinks, so an edit through Claude or Gemini is the same bytes.
- ποΈ One prompt folder, supported harnesses. Markdown lives under XDG data; Gemini TOML is generated from it.
- π Session types when the format is stable. JSONL, SQLite, JSON. Unstable shapes stay
unknown. - π€ Eleven tools, three doors. MCP, Pi and OMP call the same executors.
pnpm add @agntn/harnessesNode.js 26 or newer.
npx @agntn/harnesses detect System Scan
β antigravity Google Antigravity CLI v1.2.14 agent
β claude Anthropic Claude Code v2.1.289 advisor Β· readOnly Β· agent
β codex OpenAI Codex CLI v0.160.0 readOnly Β· agent
β cursor Cursor
β freebuff Freebuff v0.0.204 no headless mode
β gemini Google Gemini CLI
β github-copilot GitHub Copilot
β grok xAI Grok CLI v1.0.41 readOnly Β· agent
β mastracode Mastra Code v0.10.2 no headless mode
β omp OMP (oh-my-pi) v18.6.1 agent
β opencode OpenCode CLI v2.0.22 agent
β prime-agent Prime Agent v0.9.5 advisor Β· agent
β pi Pi Coding Agent v1.0.2 advisor Β· readOnly Β· agent
β openbot OpenBot
No key, no config. No network either. detect looks at PATH. Filled dot is installed, hollow is not. The tail says how run can drive it: advisor without tools, readOnly behind the harness's own read-only lock, agent with everything. Codex has no advisor, and now you learn that here, not from a refused run. After pnpm add, the same command is pnpm exec harnesses, or install it once with pnpm add -g @agntn/harnesses.
Same binary, more commands:
harnesses list
harnesses info claude
harnesses paths pi
harnesses models pi gpt-5.4 --json
harnesses run claude "review this design"
harnesses run codex --read-only "review this"
harnesses mcp-servers list
harnesses agents sync --check
harnesses prompts sync --check
harnesses skills sync --checkrun without --tools is the advisor. --tools is the full agent. --read-only asks the CLI for a sandbox and implies tools, so pairing it with --no-tools is an error. Timeouts, --cwd, --model and --effort sit in the CLI guide.
| Command | What it does | Example |
|---|---|---|
list |
Every known harness, id and name | harnesses list |
detect |
Which ones are installed, and how each one runs | harnesses detect |
info |
Registry entry: modes, capabilities, path templates | harnesses info claude |
paths |
Those templates expanded for this machine | harnesses paths pi |
models |
Models the harness can use, through its native listing | harnesses models pi |
run |
One prompt through headless mode | harnesses run claude "review this design" |
mcp-servers |
MCP servers across the config dialects | harnesses mcp-servers list |
agents sync |
Link global instructions files to one master | harnesses agents sync --check |
prompts sync |
Sync one Markdown prompt directory across harnesses | harnesses prompts sync --check |
skills sync |
Link every skills directory to one shared folder | harnesses skills sync --check |
mcp |
The MCP server on stdio | harnesses mcp |
list, detect, info, paths and models take --json or --toon. run --json is different: that one is the harness's own structured output.
import { getHarness, detectHarness } from "@agntn/harnesses";
const claude = getHarness("claude");
const paths = claude.resolve({ platform: "linux", homeDir: "/home/dev" });
console.log(paths.skills);
console.log(paths.temp, claude.envOverrides);
const active = detectHarness();
if (active) console.log(active.id);
await getHarness("codex").invoke("Review this patch", { readOnly: true, effort: "low" });
getHarness("grok").readOnlyNetwork(); // false on Linux, so no web for its MCP servers under readOnly
getHarness("pi").readOnlyTools(); // ["read", "grep", "find", "ls"], no extension or MCP tools
getHarness("claude").invocation?.modelHint; // what model takes, since Claude can't list them
const agy = getHarness("antigravity");
const [model] = (await agy.listModels({ search: "gemini" })).models;
if (model) await agy.invoke("Review this patch", { tools: true, model: agy.modelSelector(model) });temp is where a harness drops its own temp files, and envOverrides names the variables that move its directories. claude.resolve().temp follows CLAUDE_CODE_TMPDIR when it's set, which is handy when /tmp is tmpfs and Claude's scratchpads are quietly eating RAM.
That's most of it, really. getHarness wants an exact id. detectHarness uses env vars first, then a single project marker. invoke() talks to the CLI, and effort decides how hard it thinks for that one run. listModels() asks it what it can run, and modelSelector() turns one of those into what model takes, since Pi wants provider/id while Antigravity and Codex want the bare id. A mode the CLI cannot run comes back as an error, not a quieter one. An advisor that writes a tool call as text anyway comes back with toolCallsAsText: true, because nothing ran it. The rest: Registry, Invoke, MCP servers, Instructions files.
One list in ~/.config/agntn/mcp.jsonc, every harness synced to it:
import { getHarness, syncMcpServers } from "@agntn/harnesses";
syncMcpServers([getHarness("codex")]).targets[0]?.results;
// [{ name: "harnesses", action: "added" }, { name: "node_repl", action: "kept" }]Anything not on the list gets removed. That's the point. But what about a server only one harness can run? Name it under keep and it stays. And a server that has to get past the global excludes? Give it its own. The rest is in the MCP servers guide.
Put canonical Markdown templates in $XDG_DATA_HOME/agntn/prompts/, or
~/.local/share/agntn/prompts/ when XDG_DATA_HOME is unset. Then sync every
supported harness:
harnesses prompts sync --check
harnesses prompts syncFor a Markdown harness, the whole prompt directory becomes one symlink to that
directory, so a template added through any harness shows up in all of them.
Gemini gets generated TOML commands, with $ARGUMENTS translated to {{args}},
and its commands directory keeps nothing else. Whatever was there before is
backed up under the same XDG data tree first.
getHarness("pi").promptTemplates still lists every verified reusable prompt
location. promptTemplateSyncTarget is the single stable user destination the
sync owns for that harness. Formats, frontmatter and CLI limits are in the
registry guide.
Skills live in $XDG_DATA_HOME/agntn/skills/, next to the prompts, one folder
with a SKILL.md per skill:
harnesses skills sync --check
harnesses skills syncimport { getAllHarnesses, syncSkills } from "@agntn/harnesses";
const report = syncSkills(getAllHarnesses(), true);
console.log(report.skills, report.targets);Every harness's user skills directory becomes one symlink to that folder. That
includes ~/.claude/skills/, ~/.agents/skills/ for Codex,
~/.pi/agent/skills/ and the rest. A skill written through Claude is already
there when Pi looks.
Old setups with a symlink per skill get replaced without ceremony. A directory
with real skills gets backed up to diverged/skills/ first. Already keep skills
in a dotfiles repo? Make the XDG folder a symlink to it. The sync leaves a
harness directory alone if the source points into it. Your only copy stays put.
skillsSyncTarget names the linked directory for each harness. Each harness
gets its own target, never a shared one. It's null for Copilot and Freebuff.
Neither has a user skills directory in the registry. It's also null for
OpenBot, but for another reason. OpenBot reads only ~/.agents/skills/, Codex's
target. Syncing Codex already links it.
| ID | Name | Project skills |
|---|---|---|
antigravity |
Google Antigravity CLI | .agents/skills/ |
claude |
Anthropic Claude Code | .claude/skills/ |
codex |
OpenAI Codex CLI | .codex/skills/ |
cursor |
Cursor | .cursor/skills/ |
freebuff |
Freebuff | .agents/skills/ |
gemini |
Google Gemini CLI | .gemini/skills/ |
github-copilot |
GitHub Copilot | .github/skills/ |
grok |
xAI Grok CLI | .grok/skills/ |
mastracode |
Mastra Code | .mastracode/skills/ |
omp |
OMP (oh-my-pi) | .omp/skills/ |
openbot |
OpenBot | none |
opencode |
OpenCode CLI | .opencode/skills/ |
prime-agent |
Prime Agent | .prime/agent/skills/ |
pi |
Pi Coding Agent | .pi/skills/ |
That's the project directory. Most of them also keep a copy under your home directory, and a few read someone else's skills folder on purpose. Sessions, hooks, audio, video, the whole sheet: Harnesses.
harnesses mcp
pi install npm:@agntn/harnesses
omp install @agntn/harnesses{
"mcpServers": {
"harnesses": { "command": "npx", "args": ["-y", "@agntn/harnesses", "mcp"] }
}
}Eleven tools, the same eleven on MCP, Pi and OMP. harnesses_detect, harnesses_info and harnesses_mcp_list only read. harnesses_run is the one that can spend tokens. tools is required, so the model has to pick advisor or agent. What each call returns is on the Agents page.
It does not install skills, drive a browser, or run a sandbox of its own. invoke() is the harness CLI plus process cleanup. The wide, thin agent list is unagent.
Want a fourteenth? One class extending Harness, then registerHarness. getHarness, the CLI and the tools pick it up. How to write that class: Custom harnesses.
pnpm install # docs/ too: one workspace, one lockfile
pnpm lint # builds first, then vp lint and vp fmt --check
pnpm lint:fix
pnpm typecheck
pnpm test:run
pnpm build # obuild
pnpm run docs # the Docus site; plain `pnpm docs` is a pnpm 12 builtinAnthropic and OpenAI both run programs this package was built with. Claude for Open Source and Codex for Open Source. Thank you <3
{ "keep": { "codex": ["node_repl"] }, "mcpServers": { "harnesses": { "command": "npx", "args": ["-y", "@agntn/harnesses", "mcp"] }, }, }