Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,7 @@ The agentmemory entry is the **same MCP server block** across every host that us
| **Antigravity** (replaces Gemini CLI) | `mcp_config.json` (in Antigravity's User dir) | `agentmemory connect antigravity` writes the standard `mcpServers` block. macOS: `~/Library/Application Support/Antigravity/User/`. Linux: `~/.config/Antigravity/User/`. Use after the 2026-06-18 Gemini CLI sunset. |
| **Antigravity CLI** (`agy`) | `~/.gemini/config/mcp_config.json` | `agentmemory connect antigravity-cli`. The `agy` CLI keeps its own config under `~/.gemini/`, separate from the Antigravity IDE above. Pass `--with-hooks` for native auto-capture via `~/.gemini/config/hooks.json`. |
| **Kiro** | `~/.kiro/settings/mcp.json` | `agentmemory connect kiro` writes the user-level config. Workspace overrides go in `.kiro/settings/mcp.json` next to your code. |
| **Klaat Code (MCP + hooks)** | `~/.klaatai/mcp.json` | `agentmemory connect klaatcode` merges the MCP entry under Klaat Code's `servers` key (not `mcpServers`); `--with-hooks` adds six native auto-capture hooks (`session_start`, `before_message`, `before_tool`, `after_tool`, `after_message`, `session_end`) into `~/.klaatai/hooks.json` with Klaat Code's snake_case tool matchers. Verify with `/mcp` and `/hooks` inside klaatcode. Hooks fire in the interactive TUI only — `klaatai run` (headless) and ACP sessions are not captured. Klaat Code also auto-loads `.claude/skills/`, so the 17 agentmemory skills work with no extra step. |
| **Warp** | `~/.warp/.mcp.json` | `agentmemory connect warp` writes the standard `mcpServers` block. Warp also auto-discovers skills from `.claude/skills/`; once the Claude Code plugin is installed the 8 agentmemory skills (`remember`, `recall`, `recap`, `handoff`, `forget`, `commit-context`, `commit-history`, `session-history`) appear natively in Warp's slash-command palette. |
| **Cline (CLI)** | `~/.cline/mcp.json` | `agentmemory connect cline` writes the standard `mcpServers` block. VS Code extension users: paste the same block via Cline Settings → MCP Servers → Edit JSON. |
| **Continue.dev** | `~/.continue/config.yaml` (preferred) or `config.json` (legacy) | `agentmemory connect continue` creates `config.yaml` from scratch when neither exists, or modifies existing `config.json`. **If you already have `config.yaml`** the adapter prints the exact block to paste under `mcpServers:`; it won't silently rewrite your yaml because preserving comments and anchors safely needs a YAML parser the package doesn't ship. Continue uses array form (not object) for `mcpServers`. |
Expand Down
39 changes: 39 additions & 0 deletions plugin/hooks/hooks.klaatcode.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"session_start": [
{
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/session-start.mjs\"",
"timeout": 5
}
],
"before_message": [
{
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/prompt-submit.mjs\"",
"timeout": 5
}
],
"before_tool": [
{
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/pre-tool-use.mjs\"",
"matcher": "^(run_command|edit_file|multi_edit|write_file|read_file|apply_patch|grep|glob)$",
"timeout": 5
}
],
"after_tool": [
{
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/post-tool-use.mjs\"",
"timeout": 5
}
],
"after_message": [
{
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/stop.mjs\"",
"timeout": 5
}
],
"session_end": [
{
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/session-end.mjs\"",
"timeout": 5
}
]
}
3 changes: 2 additions & 1 deletion plugin/scripts/notification.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ function hookCwd(data) {
if (Array.isArray(roots)) {
for (const root of roots) if (typeof root === "string" && root.trim()) return root;
}
const projectDir = process.env["DEVIN_PROJECT_DIR"] || process.env["CLAUDE_PROJECT_DIR"];
if (typeof data.project_root === "string" && data.project_root.trim()) return data.project_root;
const projectDir = process.env["DEVIN_PROJECT_DIR"] || process.env["CLAUDE_PROJECT_DIR"] || process.env["KLAATAI_PROJECT_ROOT"];
if (projectDir && projectDir.trim()) return projectDir;
}
//#endregion
Expand Down
3 changes: 2 additions & 1 deletion plugin/scripts/post-commit.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ function hookCwd(data) {
if (Array.isArray(roots)) {
for (const root of roots) if (typeof root === "string" && root.trim()) return root;
}
const projectDir = process.env["DEVIN_PROJECT_DIR"] || process.env["CLAUDE_PROJECT_DIR"];
if (typeof data.project_root === "string" && data.project_root.trim()) return data.project_root;
const projectDir = process.env["DEVIN_PROJECT_DIR"] || process.env["CLAUDE_PROJECT_DIR"] || process.env["KLAATAI_PROJECT_ROOT"];
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
if (projectDir && projectDir.trim()) return projectDir;
}
//#endregion
Expand Down
3 changes: 2 additions & 1 deletion plugin/scripts/post-tool-failure.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ function hookCwd(data) {
if (Array.isArray(roots)) {
for (const root of roots) if (typeof root === "string" && root.trim()) return root;
}
const projectDir = process.env["DEVIN_PROJECT_DIR"] || process.env["CLAUDE_PROJECT_DIR"];
if (typeof data.project_root === "string" && data.project_root.trim()) return data.project_root;
const projectDir = process.env["DEVIN_PROJECT_DIR"] || process.env["CLAUDE_PROJECT_DIR"] || process.env["KLAATAI_PROJECT_ROOT"];
if (projectDir && projectDir.trim()) return projectDir;
}
//#endregion
Expand Down
3 changes: 2 additions & 1 deletion plugin/scripts/post-tool-use.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ function hookCwd(data) {
if (Array.isArray(roots)) {
for (const root of roots) if (typeof root === "string" && root.trim()) return root;
}
const projectDir = process.env["DEVIN_PROJECT_DIR"] || process.env["CLAUDE_PROJECT_DIR"];
if (typeof data.project_root === "string" && data.project_root.trim()) return data.project_root;
const projectDir = process.env["DEVIN_PROJECT_DIR"] || process.env["CLAUDE_PROJECT_DIR"] || process.env["KLAATAI_PROJECT_ROOT"];
if (projectDir && projectDir.trim()) return projectDir;
}
//#endregion
Expand Down
3 changes: 2 additions & 1 deletion plugin/scripts/pre-compact.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ function hookCwd(data) {
if (Array.isArray(roots)) {
for (const root of roots) if (typeof root === "string" && root.trim()) return root;
}
const projectDir = process.env["DEVIN_PROJECT_DIR"] || process.env["CLAUDE_PROJECT_DIR"];
if (typeof data.project_root === "string" && data.project_root.trim()) return data.project_root;
const projectDir = process.env["DEVIN_PROJECT_DIR"] || process.env["CLAUDE_PROJECT_DIR"] || process.env["KLAATAI_PROJECT_ROOT"];
if (projectDir && projectDir.trim()) return projectDir;
}
//#endregion
Expand Down
3 changes: 2 additions & 1 deletion plugin/scripts/prompt-submit.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ function hookCwd(data) {
if (Array.isArray(roots)) {
for (const root of roots) if (typeof root === "string" && root.trim()) return root;
}
const projectDir = process.env["DEVIN_PROJECT_DIR"] || process.env["CLAUDE_PROJECT_DIR"];
if (typeof data.project_root === "string" && data.project_root.trim()) return data.project_root;
const projectDir = process.env["DEVIN_PROJECT_DIR"] || process.env["CLAUDE_PROJECT_DIR"] || process.env["KLAATAI_PROJECT_ROOT"];
if (projectDir && projectDir.trim()) return projectDir;
}
//#endregion
Expand Down
3 changes: 2 additions & 1 deletion plugin/scripts/session-end.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ function hookCwd(data) {
if (Array.isArray(roots)) {
for (const root of roots) if (typeof root === "string" && root.trim()) return root;
}
const projectDir = process.env["DEVIN_PROJECT_DIR"] || process.env["CLAUDE_PROJECT_DIR"];
if (typeof data.project_root === "string" && data.project_root.trim()) return data.project_root;
const projectDir = process.env["DEVIN_PROJECT_DIR"] || process.env["CLAUDE_PROJECT_DIR"] || process.env["KLAATAI_PROJECT_ROOT"];
if (projectDir && projectDir.trim()) return projectDir;
}
//#endregion
Expand Down
3 changes: 2 additions & 1 deletion plugin/scripts/session-start.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ function hookCwd(data) {
if (Array.isArray(roots)) {
for (const root of roots) if (typeof root === "string" && root.trim()) return root;
}
const projectDir = process.env["DEVIN_PROJECT_DIR"] || process.env["CLAUDE_PROJECT_DIR"];
if (typeof data.project_root === "string" && data.project_root.trim()) return data.project_root;
const projectDir = process.env["DEVIN_PROJECT_DIR"] || process.env["CLAUDE_PROJECT_DIR"] || process.env["KLAATAI_PROJECT_ROOT"];
if (projectDir && projectDir.trim()) return projectDir;
}
//#endregion
Expand Down
3 changes: 2 additions & 1 deletion plugin/scripts/subagent-start.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ function hookCwd(data) {
if (Array.isArray(roots)) {
for (const root of roots) if (typeof root === "string" && root.trim()) return root;
}
const projectDir = process.env["DEVIN_PROJECT_DIR"] || process.env["CLAUDE_PROJECT_DIR"];
if (typeof data.project_root === "string" && data.project_root.trim()) return data.project_root;
const projectDir = process.env["DEVIN_PROJECT_DIR"] || process.env["CLAUDE_PROJECT_DIR"] || process.env["KLAATAI_PROJECT_ROOT"];
if (projectDir && projectDir.trim()) return projectDir;
}
//#endregion
Expand Down
3 changes: 2 additions & 1 deletion plugin/scripts/subagent-stop.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ function hookCwd(data) {
if (Array.isArray(roots)) {
for (const root of roots) if (typeof root === "string" && root.trim()) return root;
}
const projectDir = process.env["DEVIN_PROJECT_DIR"] || process.env["CLAUDE_PROJECT_DIR"];
if (typeof data.project_root === "string" && data.project_root.trim()) return data.project_root;
const projectDir = process.env["DEVIN_PROJECT_DIR"] || process.env["CLAUDE_PROJECT_DIR"] || process.env["KLAATAI_PROJECT_ROOT"];
if (projectDir && projectDir.trim()) return projectDir;
}
//#endregion
Expand Down
3 changes: 2 additions & 1 deletion plugin/scripts/task-completed.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ function hookCwd(data) {
if (Array.isArray(roots)) {
for (const root of roots) if (typeof root === "string" && root.trim()) return root;
}
const projectDir = process.env["DEVIN_PROJECT_DIR"] || process.env["CLAUDE_PROJECT_DIR"];
if (typeof data.project_root === "string" && data.project_root.trim()) return data.project_root;
const projectDir = process.env["DEVIN_PROJECT_DIR"] || process.env["CLAUDE_PROJECT_DIR"] || process.env["KLAATAI_PROJECT_ROOT"];
if (projectDir && projectDir.trim()) return projectDir;
}
//#endregion
Expand Down
3 changes: 2 additions & 1 deletion plugin/skills/agentmemory-agents/REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Generated from `src/cli/connect/index.ts`. Do not edit the block below by hand; run `npm run skills:gen` after adding or removing an adapter.

<!-- AUTOGEN:agents START - generated by scripts/skills/generate.ts, do not edit by hand -->
`agentmemory connect <agent>` wires the memory server into a host agent. 21 adapters:
`agentmemory connect <agent>` wires the memory server into a host agent. 22 adapters:

| Agent | Name | Protocol |
| --- | --- | --- |
Expand All @@ -21,6 +21,7 @@ Generated from `src/cli/connect/index.ts`. Do not edit the block below by hand;
| Gemini CLI | `gemini-cli` | Using MCP (the only protocol Gemini CLI speaks). Memory bridge runs at :3111 underneath. |
| Hermes Agent | `hermes` | Using MCP. Hooks are also available, see https://github.com/rohitg00/agentmemory/tree/main/integrations/hermes. |
| Kiro | `kiro` | Using MCP via ~/.kiro/settings/mcp.json (user-level). Workspace overrides live in .kiro/settings/mcp.json. |
| Klaat Code | `klaatcode` | Using MCP via ~/.klaatai/mcp.json (key: servers). Pass --with-hooks for native auto-capture. |
| OpenClaw | `openclaw` | Using MCP. Hooks are also available, see https://github.com/rohitg00/agentmemory/tree/main/integrations/openclaw. |
| OpenCode | `opencode` | Using MCP via ~/.config/opencode/opencode.json (top-level `mcp` key). For full auto-capture, also install the bundled plugin in plugin/opencode/. |
| OpenHuman | `openhuman` | Using native hooks (REST API at :3111). MCP not required. |
Expand Down
2 changes: 2 additions & 0 deletions src/cli/connect/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { adapter as dsh } from "./dsh.js";
import { adapter as geminiCli } from "./gemini-cli.js";
import { adapter as hermes } from "./hermes.js";
import { adapter as kiro } from "./kiro.js";
import { adapter as klaatcode } from "./klaatcode.js";
import { adapter as openclaw } from "./openclaw.js";
import { adapter as opencode } from "./opencode.js";
import { adapter as openhuman } from "./openhuman.js";
Expand All @@ -36,6 +37,7 @@ export const ADAPTERS: readonly ConnectAdapter[] = [
antigravity,
antigravityCli,
kiro,
klaatcode,
warp,
cline,
continueDev,
Expand Down
115 changes: 115 additions & 0 deletions src/cli/connect/klaatcode-hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { readFileSync } from "node:fs";
import { join } from "node:path";

/**
* Merge engine for Klaat Code's hooks file (`~/.klaatai/hooks.json`).
*
* Klaat Code does not use the nested Claude-Code hook shape that
* `codex-hooks.ts#buildMergedHooks` handles. Its config is flat — event
* name maps straight to a list of entries, where an entry is either a bare
* shell string (v1) or `{ command, matcher?, timeout? }` (v2):
*
* { "session_start": ["cmd"],
* "before_tool": [{ "command": "…", "matcher": "^edit_file$", "timeout": 5 }] }
*
* Event names differ too (`session_start` / `before_tool` / … rather than
* `SessionStart` / `PreToolUse` / …), so the bundled manifest for Klaat Code
* is authored in its native shape and this module only resolves paths and
* de-duplicates. See KlaatAI/klaatcode `src/screens/repl.ts` for the loader.
*
* The two guarantees match the Codex/Claude engine:
* 1. `${CLAUDE_PLUGIN_ROOT}` is rewritten to the absolute bundled
* `plugin/` path, so the written file needs no env expansion (Klaat
* Code does not inject that variable — it is used here purely as the
* internal placeholder token every bundled manifest shares).
* 2. Re-installs are idempotent: any pre-existing entry whose command
* points under `<pluginRoot>/scripts/` is stripped before ours are
* appended, so upgrading never leaves stale duplicates and never
* touches the user's own hooks.
*/
Comment thread
coderabbitai[bot] marked this conversation as resolved.

export type KlaatcodeHookEvent =
| "before_tool"
| "after_tool"
| "before_message"
| "after_message"
| "session_start"
| "session_end";

export type KlaatcodeHookEntry =
| string
| { command: string; matcher?: string; timeout?: number };

export type KlaatcodeHooksConfig = Partial<
Record<KlaatcodeHookEvent, KlaatcodeHookEntry[]>
>;

function entryCommand(entry: KlaatcodeHookEntry): string {
return typeof entry === "string" ? entry : entry.command;
}

function normalizePathForCommandMatch(value: string): string {
return value.replace(/\\/g, "/");
}

function isAgentmemoryEntry(
entry: KlaatcodeHookEntry,
scriptsDir: string,
): boolean {
return normalizePathForCommandMatch(entryCommand(entry)).includes(
normalizePathForCommandMatch(scriptsDir),
);
}

function resolveEntry(
entry: KlaatcodeHookEntry,
pluginRoot: string,
): KlaatcodeHookEntry {
const command = entryCommand(entry).replace(
/\$\{CLAUDE_PLUGIN_ROOT\}/g,
pluginRoot,
);
if (typeof entry === "string") return command;
const next: { command: string; matcher?: string; timeout?: number } = {
command,
};
if (entry.matcher !== undefined) next.matcher = entry.matcher;
if (entry.timeout !== undefined) next.timeout = entry.timeout;
return next;
}

export function buildMergedKlaatcodeHooks(
existing: KlaatcodeHooksConfig | null,
pluginRoot: string,
manifestFile = "hooks.klaatcode.json",
): KlaatcodeHooksConfig {
const ours = JSON.parse(
readFileSync(join(pluginRoot, "hooks", manifestFile), "utf-8"),
) as KlaatcodeHooksConfig;
const scriptsDir = join(pluginRoot, "scripts");

const out: KlaatcodeHooksConfig = {};

if (existing) {
for (const [event, entries] of Object.entries(existing) as [
KlaatcodeHookEvent,
KlaatcodeHookEntry[],
][]) {
if (!Array.isArray(entries)) continue;
const kept = entries.filter(
(entry) => !isAgentmemoryEntry(entry, scriptsDir),
);
if (kept.length > 0) out[event] = kept;
}
}

for (const [event, entries] of Object.entries(ours) as [
KlaatcodeHookEvent,
KlaatcodeHookEntry[],
][]) {
const resolved = entries.map((entry) => resolveEntry(entry, pluginRoot));
out[event] = [...(out[event] ?? []), ...resolved];
}

return out;
}
92 changes: 92 additions & 0 deletions src/cli/connect/klaatcode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { existsSync, mkdirSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import * as p from "@clack/prompts";
import { createJsonMcpAdapter } from "./json-mcp-adapter.js";
import type { ConnectOptions, ConnectResult } from "./types.js";
import { findPluginRoot } from "./codex-hooks.js";
import {
buildMergedKlaatcodeHooks,
type KlaatcodeHooksConfig,
} from "./klaatcode-hooks.js";
import {
backupFile,
logBackup,
logInstalled,
readJsonSafe,
writeJsonAtomic,
} from "./util.js";

// Klaat Code keeps everything under ~/.klaatai (relocatable via KLAATAI_DIR),
// with MCP servers and lifecycle hooks in separate files rather than one
// settings blob. MCP entries live under the top-level "servers" key — the
// same situation as Zed's "context_servers", handled by wrapperKey.
function klaatDir(): string {
const override = process.env["KLAATAI_DIR"];
if (override && override.trim()) return override;
return join(homedir(), ".klaatai");
}

const KLAAT_DIR = klaatDir();
const KLAAT_MCP = join(KLAAT_DIR, "mcp.json");
const KLAAT_HOOKS = join(KLAAT_DIR, "hooks.json");

export const adapter = createJsonMcpAdapter({
name: "klaatcode",
displayName: "Klaat Code",
detectDir: KLAAT_DIR,
configPath: KLAAT_MCP,
wrapperKey: "servers",
category: "native",
docs: "https://github.com/rohitg00/agentmemory#other-agents",
protocolNote:
"→ Using MCP via ~/.klaatai/mcp.json (key: servers). Pass --with-hooks for native auto-capture.",
installHooks: installKlaatcodeHooks,
});

function installKlaatcodeHooks(opts: ConnectOptions): ConnectResult {
let pluginRoot: string;
try {
pluginRoot = findPluginRoot();
} catch (err) {
return {
kind: "skipped",
reason: err instanceof Error ? err.message : String(err),
};
}

const existing = readJsonSafe<KlaatcodeHooksConfig>(KLAAT_HOOKS);
const merged = buildMergedKlaatcodeHooks(
existing,
pluginRoot,
"hooks.klaatcode.json",
);

if (opts.dryRun) {
p.log.info(
`[dry-run] Would write ${Object.keys(merged).length} hook event(s) into ${KLAAT_HOOKS}`,
);
return { kind: "installed", mutatedPath: KLAAT_HOOKS };
}

let backupPath: string | undefined;
if (existsSync(KLAAT_HOOKS)) {
backupPath = backupFile(KLAAT_HOOKS, "klaatcode-hooks", "json");
logBackup(backupPath);
} else {
mkdirSync(KLAAT_DIR, { recursive: true });
}

writeJsonAtomic(KLAAT_HOOKS, merged);

logInstalled("Klaat Code hooks", KLAAT_HOOKS);
p.log.info(
"Verify with `/hooks` inside klaatcode. Hooks fire in the interactive TUI only — headless (`klaatai run`) and ACP sessions are not captured. Re-run `agentmemory connect klaatcode --with-hooks` after upgrading agentmemory so the plugin paths stay current.",
);

return {
kind: "installed",
mutatedPath: KLAAT_HOOKS,
...(backupPath !== undefined && { backupPath }),
};
}
Loading