Skip to content

Wire up your coding agent

Terminal window
npx memhtml@latest integrations install # every host whose home directory exists
memhtml integrations install claude # one host
memhtml integrations install --dry-run # every file and entry it would write, and the content
memhtml integrations doctor # check the wiring, including a live handshake

memhtml integrations install wires a coding agent to this store in one command: the host’s MCP server entry, its hooks, a fenced block in its instruction file, and a skill. Every one of those is recorded in a receipt with the SHA-256 of the bytes it owns, so uninstall removes exactly what install wrote and nothing a human added beside it. The four hosts are claude (Claude Code), codex (Codex CLI), cursor, and opencode, in that order everywhere the command reports them.

With no host argument, install detects one by home directory: ~/.claude, ~/.codex, ~/.cursor, and ~/.config/opencode. A host whose directory is absent isn’t installed, so a machine with two agents on it gets two integrations and no guesses.

Four components per host, plus the receipt that owns them:

Component What it does
MCP entry Registers memhtml-mcp over stdio, so the host gets the fifteen tools and three resources
Hooks Session-start recall, per-prompt recall where the host can inject it, transcript indexing where a trace root is known
Instruction block A fenced block in the host’s instruction file carrying the recall discipline
Skill A SKILL.md the host loads on demand, carrying the same discipline
Receipt ~/.config/memhtml/integrations/<host>.json: the SHA-256 of every owned file or fragment

Two kinds of thing get written, and the difference is how ownership is checked. A file is wholly memhtml’s, so the receipt claims the hash of the whole thing: the skill, the OpenCode plugin, the Cursor rule. A fragment lives inside a file other tools also own, so the receipt claims only the rendered bytes of the fragment: a JSON key, a fenced TOML table, a Markdown block, one entry in a hooks array. Your own entries, comments, key order, and line breaking survive both directions byte for byte: the editor splices only the owned bytes and never reformats a neighbor.

One file is shared on purpose. Codex, Cursor, and OpenCode all read ~/.agents/skills/memhtml/SKILL.md, so install adopts a byte-identical twin instead of refusing it as somebody else’s file, and uninstall leaves it on disk while another host’s receipt still claims it.

Terminal window
memhtml integrations install claude
What Where
MCP entry ~/.claude.json, mcpServers.memhtml (project scope: .mcp.json)
Hooks ~/.claude/settings.json: SessionStart, UserPromptSubmit, PreCompact, SessionEnd
Instruction block ~/.claude/CLAUDE.md, inside <!-- MEMHTML:START --> / <!-- MEMHTML:END -->
Skill ~/.claude/skills/memhtml/SKILL.md

Claude Code watches its settings, hooks, and skills files and picks up a change live, so a user-scope install asks nothing of you and its nextSteps is empty. A project install carries exactly one step, because a project .mcp.json server needs the approval prompt the host shows on the next launch: Approve the project .mcp.json prompt the next time Claude Code opens this repository.

This is the one host with a default trace root, ~/.claude, so it’s the one host that gets the two indexing hooks with no configuration. MEMHTML_TRACE_ROOT names a trace root for any host, and on OpenCode it’s what arms the plugin’s indexing handler; Codex and Cursor have no indexing event to write one into. See index session transcripts for what those hooks feed.

Terminal window
memhtml integrations install codex
What Where
MCP entry ~/.codex/config.toml, [mcp_servers.memhtml] inside # MEMHTML:START / # MEMHTML:END
Hooks ~/.codex/hooks.json: SessionStart, UserPromptSubmit
Instruction block ~/.codex/AGENTS.md, inside the same Markdown fence
Skill ~/.agents/skills/memhtml/SKILL.md

Codex needs two steps from you, and the report names both. Restart Codex, because an edit to config.toml is read at startup. Then trust the hooks in /hooks: Codex keys trust to a hook’s hash and skips an untrusted one, so a hook an installer wrote does nothing at all until you approve it there. Neither step can be automated, and nothing outside Codex can read the trust decision, so doctor carries it as an informational green hook-trust row rather than a verdict.

config.toml is TOML that you also edit, which is why the MCP table lives inside a comment fence: the fence is the fragment install owns, and everything outside it is yours.

Terminal window
memhtml integrations install cursor
memhtml integrations install cursor --project .
What Where
MCP entry ~/.cursor/mcp.json (project scope: .cursor/mcp.json)
Hooks ~/.cursor/hooks.json: sessionStart only
Rule .cursor/rules/memhtml.mdc, a project file
Skill ~/.agents/skills/memhtml/SKILL.md

Cursor gets one hook. Its sessionStart returns additional_context, which is a context-injection channel, and its beforeSubmitPrompt can only stop a turn, so there’s nothing for a per-prompt recall hook to inject into and install writes none. Mid-turn recall on Cursor is the MCP tools.

The instruction surface is the other asymmetry. Cursor reads .cursor/rules/*.mdc and a project AGENTS.md, and it has no user-level instruction file this build writes, so the discipline block lands in a project rule rather than in your home directory. Run install once per repository where you want it, or rely on the skill, which Cursor 2.4 and newer read from ~/.agents/skills/.

Cursor reloads hooks.json on save. Restart Cursor after the MCP entry changes.

Terminal window
memhtml integrations install opencode
What Where
MCP entry ~/.config/opencode/opencode.json, mcp.memhtml
Plugin ~/.config/opencode/plugins/memhtml.js, generated by install
Instruction block ~/.config/opencode/AGENTS.md, inside the same Markdown fence
Skill ~/.agents/skills/memhtml/SKILL.md

OpenCode’s hook system is its plugin system, so install generates a JavaScript file rather than a config entry, and the receipt claims the whole file. The plugin wires two handlers by default: chat.message pushes recall onto each user message, and experimental.session.compacting pushes the session-start pack into the compaction prompt. A third handler, session.idle, indexes the session’s transcript at turn end, and install writes it only when a trace root is known, which for OpenCode means MEMHTML_TRACE_ROOT. Restart OpenCode after install, because it loads plugins and config at startup.

install answers one integrations.report envelope, keyed by host. This is a real memhtml integrations install claude --dry-run answer with the contents map trimmed off:

{
"apiVersion": "1",
"type": "integrations.report",
"data": {
"action": "install",
"scope": "user",
"root": "/home/you",
"memhtmlRoot": "/home/you/memhtml",
"dryRun": true,
"changed": true,
"hosts": [
{
"host": "claude",
"changed": true,
"memhtmlRoot": "/home/you/memhtml",
"entries": [
{ "path": "/home/you/.claude.json", "role": "mcp-entry", "kind": "fragment", "action": "planned" },
{ "path": "/home/you/.claude/settings.json", "role": "hooks", "kind": "fragment", "action": "planned" },
{ "path": "/home/you/.claude/CLAUDE.md", "role": "instruction-block", "kind": "fragment", "action": "planned" },
{ "path": "/home/you/.claude/skills/memhtml/SKILL.md", "role": "skill", "kind": "file", "action": "planned" }
],
"backups": [],
"receiptPath": "/home/you/.config/memhtml/integrations/claude.json",
"nextSteps": []
}
]
}
}

The report is keyed by host because the host argument is optional: memhtml integrations install wires every host it detects, and one flat report could describe only the last of them. A row’s action is created, updated, or unchanged on a real run, planned on a dry run, and removed on an uninstall. backups names the prior bytes --force kept beside a file. nextSteps is that host’s manual half, and it’s the empty array for a host that asks nothing of you.

Read changed rather than counting entries: at the top it’s true when any host changed, and each host carries its own. A re-run that finds every managed fragment already correct reports changed: false and writes nothing, which is what makes install safe on a cron line or in a machine-setup script. Uninstall answers the same type with action: "uninstall" and one host row, which carries a state of installed or not-installed beside the entries it removed.

--dry-run adds contents, a map from every path the install would touch to that file’s whole new text, and writes nothing. Run it first when you want to read the block and the hook lines before they land in your home directory.

By default the MCP entry and every hook line name an absolute node executable and an absolute entry script, and the receipt records the version the binary reported. Two reasons, and both are about hosts rather than about taste. A GUI host doesn’t inherit your shell’s PATH, so a bare memhtml-mcp in its config is a command it can’t find. And a version-manager alias that moves leaves a bare command pointing at nothing, with the failure showing up as a server that won’t start rather than as a message about a path.

The cost is that an upgrade moves the entry script, so run install again after one. doctor re-resolves the path on every run and tells an upgrade from a move, so you find out before a session does.

--bare-command writes memhtml and memhtml-mcp instead and leaves PATH to you. Choose it when you manage the toolchain yourself and would rather the config not name a version.

The entry also carries MEMHTML_ROOT in its env, absolute, rather than relying on your shell profile. A host launches the server from its own environment, and the default ~/memhtml may not be the store you meant.

--project <path> installs at repository scope instead of user scope. The path is resolved to its git top level, and the host’s project files are written there: .mcp.json, .claude/settings.json, .cursor/mcp.json, .cursor/rules/memhtml.mdc, .codex/config.toml, .codex/hooks.json, opencode.json, .opencode/plugins/memhtml.js, a root AGENTS.md or CLAUDE.md, and the skill under .claude/skills/memhtml/ for Claude Code or .agents/skills/memhtml/ for the other three. The receipt moves with them, to <root>/.memhtml-integrations/<host>.json.

Claude Code’s block goes where the repository already keeps its instructions. A repository with an AGENTS.md gets the block there and a fenced @AGENTS.md import in CLAUDE.md, so the prose exists once and both readers see it; a CLAUDE.md that is already only that import is left alone.

Terminal window
memhtml integrations install claude --project .
memhtml integrations list --project .
memhtml integrations uninstall claude --project .

/ and $HOME are refused as a project root, because a git top level that resolves to either of those is a mistake whose blast radius is every repository under it.

Re-running, and what a modification refuses

Section titled “Re-running, and what a modification refuses”

Install is idempotent. Run it twice and the second run reports changed: false.

A managed fragment a human edited is a different case, and install refuses it:

ERR_INTEGRATION_MODIFIED

The refusal is per entry, and nothing is written when one fires. It fires when the bytes install owns no longer hash to what the receipt claims, which means either you edited the block or another tool rewrote the file around it. Read the entry, decide which version you want, then either keep your edit by removing the receipt’s claim on it with uninstall, or overwrite it with --force. --force keeps the prior bytes beside the file as a timestamped backup, and the report names the backup path.

The same refusal covers a receipt that is present and can’t be read, whether it’s truncated, of the wrong shape, copied in from another host or root, or claims a path outside its own root. Install won’t plan over it as if the host were never installed, and uninstall won’t guess at what to remove from it; --force on install proceeds as a fresh install and keeps the unreadable bytes beside it. Uninstall also stops when another host’s receipt at the same scope can’t be read, because that receipt may claim the skill the three non-Claude hosts share.

With no host named, install plans every detected host before it writes to any of them, so a refusal on the third host leaves the first two untouched. If a disk error interrupts the real pass, the failure names the hosts that did land and the uninstall command for each.

Re-running install with a narrower --hooks mode removes the events it no longer wants, and --hooks none removes every hook the previous install wrote, including OpenCode’s plugin file, in the same transaction that drops them from the receipt. The report lists those rows as removed. Your own hook entries beside ours stay where they are.

uninstall refuses a modified entry with the same code and has no --force. That asymmetry is deliberate: overwriting your edit is recoverable from the backup, and deleting a file you had made your own isn’t.

integrations list answers an integrations.list envelope: home, a projectRoot that is null without --project, and rows, one row per host per scope:

Terminal window
memhtml integrations list
memhtml integrations list --project .

A row carries host, scope, root, state (not-installed, installed, or modified), and receiptPath, plus version and memhtmlRoot when the receipt was readable and a detail sentence when it wasn’t or when something drifted.

integrations doctor checks a host’s wiring end to end and answers an integrations.doctor envelope: scope, root, healthy, and hosts, each with its own healthy and a checks array of {name, ok, detail, suggestions}:

Terminal window
memhtml integrations doctor # every host with a receipt
memhtml integrations doctor claude # one host

The check names, in the order a healthy host reports them: receipt, binary-node, binary-cli, binary-mcp, cli-version, store-root, one entry:<role> per receipt entry, hooks, instruction-block, skill, trace-root when an installed hook names one, mcp-handshake, and hook-trust on Codex. An absent or unreadable receipt answers with that one receipt row and stops, because nothing else is knowable without it. trace-root passes on a root that exists and holds no transcript yet, which is every machine before its first session, and fails only on a root that cannot be listed.

cli-version runs the recorded CLI’s manifest and compares the version it prints with the version the receipt records, so an upgrade reads differently from a binary that moved. mcp-handshake spawns the server the entry names, with the env the entry carries, and completes a live initialize. That last check is the one that catches a config which reads correctly and starts nothing.

The store-root check reads the directory rather than calling memhtml status, because status on a bare directory creates a store, and a diagnostic that creates the thing it’s checking always passes. The same rule gates the handshake: when store-root failed, mcp-handshake reads not attempted, because starting the server would scaffold the .memhtml/ the row above just reported missing and the next run would read green.

The MCP entries carry their own environment, so nothing about the host integration needs your shell. integrations shell is for you, running the CLI by hand:

Terminal window
memhtml integrations shell # print the snippet
memhtml integrations shell --write # append it to your rc file

It exports MEMHTML_ROOT and puts this binary’s directory on PATH. --write appends the snippet inside # MEMHTML:START / # MEMHTML:END markers, replacing a prior fenced block rather than stacking a second one, to ~/.zshrc when $SHELL ends in zsh and ~/.bashrc otherwise. --rc names a different file. The integrations.shell envelope carries rc, snippet, written, and changed, so a script can tell a fresh append from a no-op.

Hooks and recall covers what each installed hook injects, the fail-open rule, the time bounds, and how to run a hook by hand to see what a host sees. Wire up the MCP server is the tutorial for the server the entry points at, including the hand-written config for a host this command doesn’t know. Configure the environment lists the variables the entry sets for you.