-
Notifications
You must be signed in to change notification settings - Fork 2.4k
feat: klaatcode support (cli adapter, native hooks) #1222
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
shivaylamba
wants to merge
5
commits into
rohitg00:main
Choose a base branch
from
shivaylamba:feat/klaatcode
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
7c01a81
feat: klaatcode support (cli adapter, native hooks)
shivaylamba 4e554ae
docs: add Klaat Code logo to README grid and website
shivaylamba 0178ec9
fix: take first non-blank project dir env var
shivaylamba 6da0e4b
style: drop what-explaining comments per AGENTS.md
shivaylamba 0789f27
feat: write the memory guideline for klaatcode too
shivaylamba File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
| } | ||
| ] | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. | ||
| */ | ||
|
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; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 }), | ||
| }; | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.