Docs

Supported tools

One ledger, cost, and grade across every coding agent your team runs - not just Claude Code.

Adapters shipped today

RepoOps registers a session-capture adapter per tool. Every adapter feeds the SAME pipeline (the normalized SessionRecord, cost normalization, the accountability loop) - no per-tool silo.

The verification state is part of the claim. Live-verified means the adapter ran against a real install on a real machine and returned real sessions, on the date shown. Pending live verification means the parser ships and passes fixture tests, but no live install with session data has been available to run it against yet.

  • Claude Code - first-class, the native path; reads the local transcript store with zero setup.
  • Cursor - live-verified 2026-07-07: listSessions() against a real Cursor install returned 5 real sessions (checkpoint metadata plus a state.vscdb prompt record), and the checkpoint schema matched the mapper field for field.
  • Codex - supported, pending live verification: the rollout parser ships and passes fixture tests, but the last check (2026-07-22) found a Codex CLI install with no logged-in session and no local session data, so no live round-trip has run yet.
  • GitHub Copilot, Continue, Xcode - parsers shipped, fixture-tested; pending live verification.
  • Cline, Aider, Roo Code, Windsurf - editor-store adapters, fixture-tested; pending live verification.
  • Claude Cowork - parses Claude Desktop's local-agent session store; live-verified 2026-06-06 against a real Desktop install.
  • Claude Routines - cloud routines via a manual-import drop directory.
  • Claude Desktop chat - registered, but honestly yields nothing today: the chat store is a Chromium leveldb the adapter deliberately does not parse. Use the generic ingest below instead.
  • Entire - interop adapter for entire.io's open Checkpoints format (the entire/checkpoints/v1 branch), mapped tolerantly since Entire publishes no byte-exact schema.
  • Generic ingest - POST /v1/traces (OpenTelemetry) or POST /api/sessions/ingest (plain JSON) for anything else, plus the external-adapter protocol below.

Bring your own agent: the external-adapter protocol

Not on the list above? Write a small standalone program that speaks one protocol, and RepoOps captures its sessions without a code change on either side. Your adapter can be in any language; it only has to read one JSON request on stdin and write one JSON array on stdout.

Declare your adapters in .repoops-external-adapters.json at your repo root (this file is safe to commit; keep secrets out of it):

[
  { "name": "my-agent", "command": "node", "args": ["scripts/my-agent-adapter.mjs"] }
]

Point command at the interpreter, not the script. RepoOps spawns command with args directly (it does not go through a shell), so a script needs its runtime as the command: use { "command": "node", "args": ["adapter.mjs"] } or { "command": "python", "args": ["adapter.py"] }. Point command straight at a file only when it is a native executable, or a shebang script that is marked executable on macOS or Linux (that form does not run on Windows). The interpreter form works on every platform.

The protocol: request in, sessions out

For each configured adapter, RepoOps runs command [...args] with the working directory set to your repo root, then:

  1. Writes one JSON request object to the adapter's stdin and closes stdin:
    { "repoRoot": "/abs/path/to/your/repo", "sinceMs": 1750000000000 }
    repoRoot is the absolute path being scanned. sinceMs is a Unix epoch in milliseconds, or null on a full pull. When it is set, emit only sessions that ended at or after it (an incremental pull); when it is null, return everything you have.
  2. Reads a JSON array of session objects from the adapter's stdout. Return [] when there is nothing new.

Rules the runner enforces (all fail-soft, so a broken adapter never affects the others or the aggregator):

  • The adapter must exit 0 and print a JSON array. A non-zero exit, non-array output, or unparseable JSON yields zero sessions for that adapter and is silently ignored.
  • There is a 10 second timeout per adapter run. If it has not exited by then it is killed and yields nothing, so do your slow work incrementally and return fast.
  • Only stdout is read. stderr is ignored, so log progress or debug output to stderr freely without corrupting the response.
  • A missing .repoops-external-adapters.json, a missing binary, or a bad config entry simply contributes nothing. Entries need a string name and a string command to be considered.

The session object RepoOps reads

Each element of your stdout array is mapped tolerantly: RepoOps accepts several field-name spellings per concept and fills honest defaults for anything it does not recognize, so you only emit what you have.

  • id (required) - a stable unique id for the session. Accepts id, sessionId, or session_id. An object with no id is skipped. RepoOps stores it as external:<name>:<id> under source tool external:<name>.
  • prompts - accepts prompts, transcript, or messages: an array of strings, or an array of objects carrying prompt, content, or text.
  • tokens - accepts tokens, token_usage, or tokenUsage: an object with input (or input_tokens / prompt), output (or output_tokens / completion), and optionally cache_read and cache_creation. The total is computed for you.
  • cost - accepts cost, costUsd, or cost_usd: a number in US dollars. Omit it and RepoOps normalizes cost from tokens plus model where it can.
  • files - accepts files, files_touched, or filesTouched: an array of repo-relative paths the session changed.
  • model and provider - strings (for example claude-sonnet-5 and anthropic).
  • startedAt and endedAt - accept the _at spellings too; any date string Date can parse (ISO 8601 recommended). If endedAt is missing it defaults to startedAt.

A complete adapter (Node)

A minimal but complete adapter. Swap loadMyAgentSessions for however you actually read your agent's history (a log directory under repoRoot, a local file, an API call):

#!/usr/bin/env node
// scripts/my-agent-adapter.mjs - emit RepoOps sessions for "my-agent".
let input = "";
process.stdin.on("data", (c) => (input += c));
process.stdin.on("end", () => {
  const { repoRoot, sinceMs } = JSON.parse(input || "{}");

  // 1. Gather your agent's sessions however you like.
  const all = loadMyAgentSessions(repoRoot); // <- you implement this

  // 2. When sinceMs is set, emit only newer sessions (incremental pull).
  const sessions = all
    .filter((s) => sinceMs == null || Date.parse(s.endISO) >= sinceMs)
    .map((s) => ({
      id: s.id,                 // required, unique per session
      prompts: s.userPrompts,   // array of strings
      model: s.model,
      tokens: { input: s.inTokens, output: s.outTokens },
      cost: s.costUsd,          // US dollars
      files: s.filesTouched,    // repo-relative paths
      startedAt: s.startISO,    // ISO 8601
      endedAt: s.endISO,
    }));

  // 3. One JSON array to stdout, exit 0. Send logs to stderr only.
  process.stdout.write(JSON.stringify(sessions));
});

A reference implementation that emits one hardcoded demo session lives in the repo at scripts/external-adapters/reference-adapter.mjs. It doubles as the fixture the protocol's own tests spawn as a real child process, so it is a known-good starting template.

Test it before you trust it

Your adapter is just a program that reads stdin and writes stdout, so you can exercise the exact contract by hand before RepoOps ever runs it:

echo '{"repoRoot":".","sinceMs":null}' | node scripts/my-agent-adapter.mjs

It should print a single JSON array. Confirm it parses (... | node -e "JSON.parse(require('fs').readFileSync(0))") and that each object has an id. Then drop the config file in place, and the next capture picks it up: your sessions come back from GET /api/providers/sessions?source=external, tagged external:<name>, with cost normalized the same way as every native adapter.

Where the cost shows up

The Signal tab's cost and acceptance by tool table reads every adapter above, native and external, over GET /api/providers/sessions, with no extra setup. The Activity Ledger is built from the local Claude Code telemetry store only, so sessions from other tools do not appear there.

Last updated