Skip to main content

Adding an AI coding tool

chat-recall reads seven tools today: Claude Code, Codex, OpenCode, Antigravity, Cursor, Hermes Agent and Pi. Adding an eighth means writing one format adapter and then extending the places that enumerate the seven by name.

The shared engine in packages/engine/src/core/generic-engine.ts runs the same extractTurnsFromEvents / liveScanEditsFromEvents / replayFromEvents against every backend's readEvents() output. Turning a transcript into CanonicalEvent[] is therefore the whole job; everything downstream already works once that function does.

1. The id​

AiTool in packages/engine/src/core/live-session-scan.ts:

export type AiTool = 'claude' | 'opencode' | 'codex' | 'agy' | 'cursor' | 'hermes' | 'pi';

Add yours. The compiler will now point at most of the work in step 3.

Session ids carry a prefix so one id space spans every tool: claude uses the empty prefix, the rest use <id>_. Pick a prefix nothing else starts with.

2. The backend​

Create packages/engine/src/core/backends/<tool>.ts, implementing ToolBackend from ../tool-backend.js:

MemberWhat it does
id, idPrefix, displayNameidentity
homeDir(), isAvailable()where this tool writes, and whether it is installed here
matchesId(), toRawId(), toPrefixedId()id translation
findSession(), listSessions()locate one session, or enumerate them
readEvents(), fileToolMapthe format adapter the generic engine consumes
extractTurns(), liveScanEdits(), replay(), computeOutcome(), getCommits(), collectRecentEdits(), exportRawSession()delegate these to the generic engine

Read backends/codex.ts first: it is the smallest complete adapter. Read backends/hermes.ts if your tool keeps its chats in SQLite rows in the OpenAI message shape. Read backends/pi.ts if your tool writes one JSONL file per session with a header line. Read backends/cursor.ts if your tool has more than one surface — Cursor covers both the cursor-agent CLI and the desktop IDE, and splits its readers into cursor-store.ts and cursor-ide.ts.

Make the home directory overridable by an environment variable, the way the others are: CHAT_RECALL_<TOOL>_HOME. core/tool-paths.ts holds the defaults and the overrides so backends and the dispatcher read one source.

3. The enumerations​

Register the backend in packages/engine/src/core/backends/index.ts — import the singleton, add one registerBackend(...) line, and re-export it.

That is not the end of it. Thirty other files list the five ids by name: per-tool skill and MCP paths, sync policy, artifact encoding, the toolkit matrix, several server routes. A tool missing from one of them is not a compile error and not a crash; it silently reads as "this tool has nothing", which is the failure mode this section exists to prevent.

Find them, rather than trusting a list in a document that will rot:

for f in $(grep -rln "'cursor'" packages/*/src --include='*.ts' | grep -v '\.test\.'); do
grep -q "'claude'" "$f" && grep -q "'codex'" "$f" && grep -q "'agy'" "$f" \
&& grep -q "'opencode'" "$f" && echo "$f"
done

Work through every file it prints. npx tsc -b catches the ones whose lists are typed as Record<AiTool, …>; the rest are arrays and string comparisons that accept a short list without complaint.

4. Tests​

backends/backends.test.ts runs the contract every backend must satisfy, and backends/integration.test.ts runs the generic engine against real fixture transcripts. Add fixtures for your format to both.

Several tests point every tool at an empty folder, so that only their fixtures are counted. Add your tool's home variable to each of them, or they read the real data of the machine that runs them:

grep -rl CHAT_RECALL_CURSOR_HOME packages | grep '\.test\.ts$'

Fixtures use invented paths and invented project names — /home/user/code/example-app, acme, owner/repo. See the rule at the foot of CLAUDE.md: a path test asserts a string transformation, so a real home directory adds no coverage and publishes a username.

Run the suite:

npx tsc -b
npx vitest run packages/engine