@mxalbert/context-mode 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +33 -0
- package/.claude-plugin/plugin.json +31 -0
- package/.codex-plugin/hooks.json +65 -0
- package/.codex-plugin/mcp.json +13 -0
- package/.codex-plugin/plugin.json +31 -0
- package/.openclaw-plugin/index.ts +12 -0
- package/.openclaw-plugin/openclaw.plugin.json +38 -0
- package/.openclaw-plugin/package.json +28 -0
- package/LICENSE +94 -0
- package/README.md +1619 -0
- package/bin/statusline.mjs +378 -0
- package/build/adapters/antigravity/index.d.ts +51 -0
- package/build/adapters/antigravity/index.js +169 -0
- package/build/adapters/antigravity-cli/index.d.ts +51 -0
- package/build/adapters/antigravity-cli/index.js +342 -0
- package/build/adapters/base.d.ts +92 -0
- package/build/adapters/base.js +135 -0
- package/build/adapters/claude-code/config.d.ts +8 -0
- package/build/adapters/claude-code/config.js +8 -0
- package/build/adapters/claude-code/hooks.d.ts +106 -0
- package/build/adapters/claude-code/hooks.js +183 -0
- package/build/adapters/claude-code/index.d.ts +80 -0
- package/build/adapters/claude-code/index.js +569 -0
- package/build/adapters/claude-code-base.d.ts +49 -0
- package/build/adapters/claude-code-base.js +113 -0
- package/build/adapters/client-map.d.ts +10 -0
- package/build/adapters/client-map.js +42 -0
- package/build/adapters/codex/config.d.ts +8 -0
- package/build/adapters/codex/config.js +8 -0
- package/build/adapters/codex/hooks.d.ts +55 -0
- package/build/adapters/codex/hooks.js +64 -0
- package/build/adapters/codex/index.d.ts +87 -0
- package/build/adapters/codex/index.js +995 -0
- package/build/adapters/codex/paths.d.ts +1 -0
- package/build/adapters/codex/paths.js +12 -0
- package/build/adapters/codex/usage.d.ts +107 -0
- package/build/adapters/codex/usage.js +227 -0
- package/build/adapters/copilot-base.d.ts +94 -0
- package/build/adapters/copilot-base.js +297 -0
- package/build/adapters/copilot-cli/hooks.d.ts +33 -0
- package/build/adapters/copilot-cli/hooks.js +64 -0
- package/build/adapters/copilot-cli/index.d.ts +48 -0
- package/build/adapters/copilot-cli/index.js +341 -0
- package/build/adapters/cursor/config.d.ts +4 -0
- package/build/adapters/cursor/config.js +4 -0
- package/build/adapters/cursor/hooks.d.ts +55 -0
- package/build/adapters/cursor/hooks.js +82 -0
- package/build/adapters/cursor/index.d.ts +64 -0
- package/build/adapters/cursor/index.js +517 -0
- package/build/adapters/detect.d.ts +136 -0
- package/build/adapters/detect.js +623 -0
- package/build/adapters/gemini-cli/config.d.ts +8 -0
- package/build/adapters/gemini-cli/config.js +8 -0
- package/build/adapters/gemini-cli/hooks.d.ts +83 -0
- package/build/adapters/gemini-cli/hooks.js +115 -0
- package/build/adapters/gemini-cli/index.d.ts +80 -0
- package/build/adapters/gemini-cli/index.js +483 -0
- package/build/adapters/jetbrains-copilot/config.d.ts +8 -0
- package/build/adapters/jetbrains-copilot/config.js +8 -0
- package/build/adapters/jetbrains-copilot/hooks.d.ts +59 -0
- package/build/adapters/jetbrains-copilot/hooks.js +87 -0
- package/build/adapters/jetbrains-copilot/index.d.ts +31 -0
- package/build/adapters/jetbrains-copilot/index.js +131 -0
- package/build/adapters/kimi/config.d.ts +8 -0
- package/build/adapters/kimi/config.js +8 -0
- package/build/adapters/kimi/hooks.d.ts +28 -0
- package/build/adapters/kimi/hooks.js +34 -0
- package/build/adapters/kimi/index.d.ts +66 -0
- package/build/adapters/kimi/index.js +537 -0
- package/build/adapters/kimi/paths.d.ts +21 -0
- package/build/adapters/kimi/paths.js +52 -0
- package/build/adapters/kimi/usage.d.ts +82 -0
- package/build/adapters/kimi/usage.js +217 -0
- package/build/adapters/kiro/hooks.d.ts +61 -0
- package/build/adapters/kiro/hooks.js +94 -0
- package/build/adapters/kiro/index.d.ts +61 -0
- package/build/adapters/kiro/index.js +301 -0
- package/build/adapters/omp/index.d.ts +65 -0
- package/build/adapters/omp/index.js +182 -0
- package/build/adapters/omp/plugin.d.ts +81 -0
- package/build/adapters/omp/plugin.js +331 -0
- package/build/adapters/omp/usage.d.ts +49 -0
- package/build/adapters/omp/usage.js +110 -0
- package/build/adapters/openclaw/config.d.ts +8 -0
- package/build/adapters/openclaw/config.js +8 -0
- package/build/adapters/openclaw/hooks.d.ts +50 -0
- package/build/adapters/openclaw/hooks.js +61 -0
- package/build/adapters/openclaw/index.d.ts +74 -0
- package/build/adapters/openclaw/index.js +439 -0
- package/build/adapters/openclaw/mcp-tools.d.ts +54 -0
- package/build/adapters/openclaw/mcp-tools.js +207 -0
- package/build/adapters/openclaw/plugin.d.ts +141 -0
- package/build/adapters/openclaw/plugin.js +818 -0
- package/build/adapters/openclaw/session-db.d.ts +55 -0
- package/build/adapters/openclaw/session-db.js +88 -0
- package/build/adapters/openclaw/usage.d.ts +34 -0
- package/build/adapters/openclaw/usage.js +52 -0
- package/build/adapters/openclaw/workspace-router.d.ts +29 -0
- package/build/adapters/openclaw/workspace-router.js +64 -0
- package/build/adapters/opencode/config.d.ts +8 -0
- package/build/adapters/opencode/config.js +8 -0
- package/build/adapters/opencode/hooks.d.ts +38 -0
- package/build/adapters/opencode/hooks.js +50 -0
- package/build/adapters/opencode/index.d.ts +144 -0
- package/build/adapters/opencode/index.js +638 -0
- package/build/adapters/opencode/plugin.d.ts +249 -0
- package/build/adapters/opencode/plugin.js +1536 -0
- package/build/adapters/opencode/v2.d.ts +117 -0
- package/build/adapters/opencode/v2.js +224 -0
- package/build/adapters/opencode/zod3tov4.d.ts +1 -0
- package/build/adapters/opencode/zod3tov4.js +132 -0
- package/build/adapters/pi/extension.d.ts +81 -0
- package/build/adapters/pi/extension.js +861 -0
- package/build/adapters/pi/index.d.ts +57 -0
- package/build/adapters/pi/index.js +173 -0
- package/build/adapters/pi/mcp-bridge.d.ts +268 -0
- package/build/adapters/pi/mcp-bridge.js +894 -0
- package/build/adapters/qwen-code/hooks.d.ts +26 -0
- package/build/adapters/qwen-code/hooks.js +29 -0
- package/build/adapters/qwen-code/index.d.ts +40 -0
- package/build/adapters/qwen-code/index.js +361 -0
- package/build/adapters/qwen-code/usage.d.ts +90 -0
- package/build/adapters/qwen-code/usage.js +222 -0
- package/build/adapters/types.d.ts +360 -0
- package/build/adapters/types.js +118 -0
- package/build/adapters/vscode-copilot/config.d.ts +8 -0
- package/build/adapters/vscode-copilot/config.js +8 -0
- package/build/adapters/vscode-copilot/hooks.d.ts +71 -0
- package/build/adapters/vscode-copilot/hooks.js +98 -0
- package/build/adapters/vscode-copilot/index.d.ts +32 -0
- package/build/adapters/vscode-copilot/index.js +222 -0
- package/build/adapters/zed/index.d.ts +40 -0
- package/build/adapters/zed/index.js +163 -0
- package/build/cli.d.ts +34 -0
- package/build/cli.js +1828 -0
- package/build/db-base.d.ts +250 -0
- package/build/db-base.js +756 -0
- package/build/executor.d.ts +72 -0
- package/build/executor.js +678 -0
- package/build/exit-classify.d.ts +19 -0
- package/build/exit-classify.js +12 -0
- package/build/fetch/blocks.d.ts +145 -0
- package/build/fetch/blocks.js +244 -0
- package/build/fetch/extract.d.ts +68 -0
- package/build/fetch/extract.js +123 -0
- package/build/fetch/page-store.d.ts +66 -0
- package/build/fetch/page-store.js +174 -0
- package/build/fetch-cache.d.ts +13 -0
- package/build/fetch-cache.js +15 -0
- package/build/lifecycle.d.ts +124 -0
- package/build/lifecycle.js +278 -0
- package/build/package-identity.d.ts +26 -0
- package/build/package-identity.js +28 -0
- package/build/runPool.d.ts +36 -0
- package/build/runPool.js +51 -0
- package/build/runtime.d.ts +71 -0
- package/build/runtime.js +659 -0
- package/build/search/auto-memory.d.ts +47 -0
- package/build/search/auto-memory.js +170 -0
- package/build/search/ctx-search-schema.d.ts +90 -0
- package/build/search/ctx-search-schema.js +135 -0
- package/build/search/flood-guard.d.ts +57 -0
- package/build/search/flood-guard.js +80 -0
- package/build/search/unified.d.ts +56 -0
- package/build/search/unified.js +104 -0
- package/build/security.d.ts +206 -0
- package/build/security.js +780 -0
- package/build/server.d.ts +240 -0
- package/build/server.js +5034 -0
- package/build/session/analytics.d.ts +773 -0
- package/build/session/analytics.js +2420 -0
- package/build/session/db.d.ts +468 -0
- package/build/session/db.js +1330 -0
- package/build/session/error-classifier.d.ts +87 -0
- package/build/session/error-classifier.js +303 -0
- package/build/session/event-emit.d.ts +48 -0
- package/build/session/event-emit.js +101 -0
- package/build/session/extract.d.ts +333 -0
- package/build/session/extract.js +2688 -0
- package/build/session/model-prices.json +429 -0
- package/build/session/persist-tool-calls.d.ts +54 -0
- package/build/session/persist-tool-calls.js +105 -0
- package/build/session/pricing.d.ts +64 -0
- package/build/session/pricing.js +151 -0
- package/build/session/project-attribution.d.ts +73 -0
- package/build/session/project-attribution.js +245 -0
- package/build/session/purge.d.ts +138 -0
- package/build/session/purge.js +240 -0
- package/build/session/retrieval-marker.d.ts +39 -0
- package/build/session/retrieval-marker.js +65 -0
- package/build/session/snapshot.d.ts +46 -0
- package/build/session/snapshot.js +532 -0
- package/build/store-directory.d.ts +56 -0
- package/build/store-directory.js +254 -0
- package/build/store.d.ts +147 -0
- package/build/store.js +1714 -0
- package/build/truncate.d.ts +57 -0
- package/build/truncate.js +142 -0
- package/build/types.d.ts +117 -0
- package/build/types.js +20 -0
- package/build/util/claude-config.d.ts +32 -0
- package/build/util/claude-config.js +84 -0
- package/build/util/hook-config.d.ts +27 -0
- package/build/util/hook-config.js +76 -0
- package/build/util/jsonc.d.ts +14 -0
- package/build/util/jsonc.js +104 -0
- package/build/util/plugin-cache-integrity.d.ts +51 -0
- package/build/util/plugin-cache-integrity.js +146 -0
- package/build/util/project-dir.d.ts +149 -0
- package/build/util/project-dir.js +340 -0
- package/build/util/sibling-mcp.d.ts +79 -0
- package/build/util/sibling-mcp.js +181 -0
- package/cli.bundle.mjs +1495 -0
- package/configs/antigravity/GEMINI.md +68 -0
- package/configs/antigravity/mcp_config.json +7 -0
- package/configs/antigravity-cli/hooks/hooks.json +37 -0
- package/configs/antigravity-cli/hooks.json +37 -0
- package/configs/antigravity-cli/mcp_config.json +10 -0
- package/configs/antigravity-cli/plugin.json +14 -0
- package/configs/antigravity-cli/rules/context-mode.md +77 -0
- package/configs/antigravity-cli/skills/context-mode/SKILL.md +77 -0
- package/configs/claude-code/CLAUDE.md +91 -0
- package/configs/codex/AGENTS.md +99 -0
- package/configs/codex/config.toml +8 -0
- package/configs/codex/hooks.json +47 -0
- package/configs/copilot-cli/.github/plugin/plugin.json +23 -0
- package/configs/copilot-cli/.mcp.json +12 -0
- package/configs/copilot-cli/README.md +47 -0
- package/configs/copilot-cli/hooks.json +41 -0
- package/configs/copilot-cli/skills/context-mode/SKILL.md +38 -0
- package/configs/cursor/context-mode.mdc +76 -0
- package/configs/cursor/hooks.json +21 -0
- package/configs/cursor/mcp.json +7 -0
- package/configs/gemini-cli/GEMINI.md +88 -0
- package/configs/gemini-cli/mcp.json +7 -0
- package/configs/gemini-cli/settings.json +60 -0
- package/configs/jetbrains-copilot/copilot-instructions.md +80 -0
- package/configs/jetbrains-copilot/hooks.json +16 -0
- package/configs/jetbrains-copilot/mcp.json +7 -0
- package/configs/kilo/AGENTS.md +84 -0
- package/configs/kilo/kilo.json +6 -0
- package/configs/kimi/hooks.json +54 -0
- package/configs/kiro/KIRO.md +84 -0
- package/configs/kiro/agent.json +18 -0
- package/configs/kiro/mcp.json +7 -0
- package/configs/omp/SYSTEM.md +85 -0
- package/configs/omp/mcp.json +7 -0
- package/configs/openclaw/AGENTS.md +87 -0
- package/configs/openclaw/openclaw.json +13 -0
- package/configs/opencode/AGENTS.md +84 -0
- package/configs/opencode/opencode.json +6 -0
- package/configs/pi/AGENTS.md +3 -0
- package/configs/qwen-code/QWEN.md +91 -0
- package/configs/vscode-copilot/copilot-instructions.md +80 -0
- package/configs/vscode-copilot/hooks.json +16 -0
- package/configs/vscode-copilot/mcp.json +7 -0
- package/configs/zed/AGENTS.md +68 -0
- package/hooks/antigravity-cli/payload.mjs +98 -0
- package/hooks/antigravity-cli/posttooluse.mjs +138 -0
- package/hooks/antigravity-cli/pretooluse.mjs +78 -0
- package/hooks/antigravity-cli/stop.mjs +58 -0
- package/hooks/auto-injection.mjs +102 -0
- package/hooks/cache-heal-utils.mjs +379 -0
- package/hooks/codex/platform.mjs +1 -0
- package/hooks/codex/posttooluse.mjs +66 -0
- package/hooks/codex/precompact.mjs +70 -0
- package/hooks/codex/pretooluse.mjs +42 -0
- package/hooks/codex/sessionstart.mjs +121 -0
- package/hooks/codex/stop.mjs +138 -0
- package/hooks/codex/userpromptsubmit.mjs +75 -0
- package/hooks/copilot-cli/posttooluse.mjs +79 -0
- package/hooks/copilot-cli/precompact.mjs +66 -0
- package/hooks/copilot-cli/pretooluse.mjs +41 -0
- package/hooks/copilot-cli/sessionstart.mjs +121 -0
- package/hooks/copilot-cli/stop.mjs +59 -0
- package/hooks/copilot-cli/userpromptsubmit.mjs +77 -0
- package/hooks/core/codex-caps.mjs +112 -0
- package/hooks/core/formatters.mjs +356 -0
- package/hooks/core/mcp-ready.mjs +109 -0
- package/hooks/core/platform-detect.mjs +49 -0
- package/hooks/core/routing.mjs +1050 -0
- package/hooks/core/stdin.mjs +90 -0
- package/hooks/core/tool-naming.mjs +57 -0
- package/hooks/cursor/afteragentresponse.mjs +74 -0
- package/hooks/cursor/hooks.json +31 -0
- package/hooks/cursor/posttooluse.mjs +72 -0
- package/hooks/cursor/pretooluse.mjs +26 -0
- package/hooks/cursor/sessionstart.mjs +97 -0
- package/hooks/cursor/stop.mjs +48 -0
- package/hooks/ensure-deps.mjs +254 -0
- package/hooks/formatters/claude-code.mjs +113 -0
- package/hooks/formatters/cursor.mjs +37 -0
- package/hooks/formatters/gemini-cli.mjs +55 -0
- package/hooks/formatters/vscode-copilot.mjs +55 -0
- package/hooks/gemini-cli/aftermodel.mjs +70 -0
- package/hooks/gemini-cli/aftertool.mjs +61 -0
- package/hooks/gemini-cli/beforeagent.mjs +99 -0
- package/hooks/gemini-cli/beforetool.mjs +27 -0
- package/hooks/gemini-cli/precompress.mjs +55 -0
- package/hooks/gemini-cli/sessionstart.mjs +137 -0
- package/hooks/heal-partial-install.mjs +712 -0
- package/hooks/hooks.json +143 -0
- package/hooks/jetbrains-copilot/posttooluse.mjs +61 -0
- package/hooks/jetbrains-copilot/precompact.mjs +55 -0
- package/hooks/jetbrains-copilot/pretooluse.mjs +27 -0
- package/hooks/jetbrains-copilot/sessionstart.mjs +124 -0
- package/hooks/kimi/platform.mjs +1 -0
- package/hooks/kimi/posttooluse.mjs +72 -0
- package/hooks/kimi/precompact.mjs +80 -0
- package/hooks/kimi/pretooluse.mjs +42 -0
- package/hooks/kimi/sessionend.mjs +61 -0
- package/hooks/kimi/sessionstart.mjs +113 -0
- package/hooks/kimi/stop.mjs +132 -0
- package/hooks/kimi/userpromptsubmit.mjs +90 -0
- package/hooks/kiro/agentspawn.mjs +97 -0
- package/hooks/kiro/posttooluse.mjs +51 -0
- package/hooks/kiro/pretooluse.mjs +64 -0
- package/hooks/kiro/userpromptsubmit.mjs +88 -0
- package/hooks/normalize-hooks.mjs +323 -0
- package/hooks/platform-bridge.mjs +340 -0
- package/hooks/posttooluse.mjs +225 -0
- package/hooks/precompact.mjs +100 -0
- package/hooks/pretooluse.mjs +227 -0
- package/hooks/qwen-code/platform.mjs +1 -0
- package/hooks/qwen-code/stop.mjs +168 -0
- package/hooks/routing-block.mjs +105 -0
- package/hooks/run-hook.mjs +95 -0
- package/hooks/security.bundle.mjs +2 -0
- package/hooks/session-attribution.bundle.mjs +1 -0
- package/hooks/session-db.bundle.mjs +145 -0
- package/hooks/session-directive.mjs +520 -0
- package/hooks/session-extract.bundle.mjs +3 -0
- package/hooks/session-helpers.mjs +428 -0
- package/hooks/session-loaders.mjs +448 -0
- package/hooks/session-snapshot.bundle.mjs +32 -0
- package/hooks/sessionstart.mjs +464 -0
- package/hooks/stop.mjs +82 -0
- package/hooks/suppress-stderr.mjs +25 -0
- package/hooks/userpromptsubmit.mjs +108 -0
- package/hooks/vscode-copilot/posttooluse.mjs +61 -0
- package/hooks/vscode-copilot/precompact.mjs +55 -0
- package/hooks/vscode-copilot/pretooluse.mjs +27 -0
- package/hooks/vscode-copilot/sessionstart.mjs +129 -0
- package/openclaw.plugin.json +38 -0
- package/package.json +132 -0
- package/scripts/heal-better-sqlite3.mjs +430 -0
- package/scripts/heal-installed-plugins.mjs +635 -0
- package/scripts/plugin-cache-integrity.mjs +248 -0
- package/scripts/postinstall.mjs +397 -0
- package/server.bundle.mjs +1421 -0
- package/server.js +5 -0
- package/skills/.ignore +7 -0
- package/skills/context-mode/SKILL.md +300 -0
- package/skills/context-mode/references/anti-patterns.md +283 -0
- package/skills/context-mode/references/patterns-javascript.md +298 -0
- package/skills/context-mode/references/patterns-python.md +304 -0
- package/skills/context-mode/references/patterns-shell.md +277 -0
- package/skills/ctx-doctor/SKILL.md +22 -0
- package/skills/ctx-index/SKILL.md +46 -0
- package/skills/ctx-insight/SKILL.md +24 -0
- package/skills/ctx-purge/SKILL.md +49 -0
- package/skills/ctx-search/SKILL.md +35 -0
- package/skills/ctx-stats/SKILL.md +26 -0
- package/skills/ctx-upgrade/SKILL.md +31 -0
- package/start.mjs +616 -0
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adapters/cursor — Cursor platform adapter.
|
|
3
|
+
*
|
|
4
|
+
* Native Cursor hooks use lower-camel hook names and flat command entries in
|
|
5
|
+
* `.cursor/hooks.json` / `~/.cursor/hooks.json`.
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync, writeFileSync, mkdirSync, accessSync, chmodSync, constants, existsSync, readdirSync, } from "node:fs";
|
|
8
|
+
import { execSync } from "node:child_process";
|
|
9
|
+
import { resolve, join } from "node:path";
|
|
10
|
+
import { homedir } from "node:os";
|
|
11
|
+
import { BaseAdapter } from "../base.js";
|
|
12
|
+
import { resolveClaudeConfigDir } from "../../util/claude-config.js";
|
|
13
|
+
import { HOOK_TYPES as CURSOR_HOOK_NAMES, HOOK_SCRIPTS as CURSOR_HOOK_SCRIPTS, PRE_TOOL_USE_MATCHER_PATTERN, REQUIRED_HOOKS, OPTIONAL_HOOKS, isContextModeHook, buildHookCommand, } from "./hooks.js";
|
|
14
|
+
const CURSOR_ENTERPRISE_HOOKS_PATH = "/Library/Application Support/Cursor/hooks.json";
|
|
15
|
+
export class CursorAdapter extends BaseAdapter {
|
|
16
|
+
constructor() {
|
|
17
|
+
super([".cursor"]);
|
|
18
|
+
}
|
|
19
|
+
name = "Cursor";
|
|
20
|
+
paradigm = "json-stdio";
|
|
21
|
+
capabilities = {
|
|
22
|
+
preToolUse: true,
|
|
23
|
+
postToolUse: true,
|
|
24
|
+
preCompact: false,
|
|
25
|
+
// Cursor v1 ships native sessionStart and the matching hook script
|
|
26
|
+
// (hooks/cursor/sessionstart.mjs) is wired through the dispatcher
|
|
27
|
+
// (src/cli.ts HOOK_MAP). Capability flag must reflect script presence.
|
|
28
|
+
sessionStart: true,
|
|
29
|
+
canModifyArgs: true,
|
|
30
|
+
canModifyOutput: false,
|
|
31
|
+
canInjectSessionContext: true,
|
|
32
|
+
};
|
|
33
|
+
parsePreToolUseInput(raw) {
|
|
34
|
+
const input = raw;
|
|
35
|
+
return {
|
|
36
|
+
toolName: input.tool_name ?? "",
|
|
37
|
+
toolInput: input.tool_input ?? {},
|
|
38
|
+
sessionId: this.extractSessionId(input),
|
|
39
|
+
projectDir: this.getProjectDir(input),
|
|
40
|
+
raw,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
parsePostToolUseInput(raw) {
|
|
44
|
+
const input = raw;
|
|
45
|
+
return {
|
|
46
|
+
toolName: input.tool_name ?? "",
|
|
47
|
+
toolInput: input.tool_input ?? {},
|
|
48
|
+
toolOutput: input.tool_output ?? input.error_message,
|
|
49
|
+
isError: Boolean(input.error_message),
|
|
50
|
+
sessionId: this.extractSessionId(input),
|
|
51
|
+
projectDir: this.getProjectDir(input),
|
|
52
|
+
raw,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
parseSessionStartInput(raw) {
|
|
56
|
+
const input = raw;
|
|
57
|
+
const rawSource = input.source ?? input.trigger ?? "startup";
|
|
58
|
+
let source;
|
|
59
|
+
switch (rawSource) {
|
|
60
|
+
case "compact":
|
|
61
|
+
source = "compact";
|
|
62
|
+
break;
|
|
63
|
+
case "resume":
|
|
64
|
+
source = "resume";
|
|
65
|
+
break;
|
|
66
|
+
case "clear":
|
|
67
|
+
source = "clear";
|
|
68
|
+
break;
|
|
69
|
+
default:
|
|
70
|
+
source = "startup";
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
sessionId: this.extractSessionId(input),
|
|
74
|
+
source,
|
|
75
|
+
projectDir: this.getProjectDir(input),
|
|
76
|
+
raw,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
formatPreToolUseResponse(response) {
|
|
80
|
+
if (response.decision === "deny") {
|
|
81
|
+
return {
|
|
82
|
+
permission: "deny",
|
|
83
|
+
user_message: response.reason ?? "Blocked by context-mode hook",
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
if (response.decision === "modify" && response.updatedInput) {
|
|
87
|
+
return { updated_input: response.updatedInput };
|
|
88
|
+
}
|
|
89
|
+
if (response.decision === "context" && response.additionalContext) {
|
|
90
|
+
return { agent_message: response.additionalContext };
|
|
91
|
+
}
|
|
92
|
+
if (response.decision === "ask") {
|
|
93
|
+
return {
|
|
94
|
+
permission: "ask",
|
|
95
|
+
user_message: response.reason ?? "Action requires user confirmation (security policy)",
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
// Cursor rejects empty stdout as "no valid response", so adapter callers
|
|
99
|
+
// need the same explicit no-op payload the hook scripts emit at runtime.
|
|
100
|
+
return { agent_message: "" };
|
|
101
|
+
}
|
|
102
|
+
formatPostToolUseResponse(response) {
|
|
103
|
+
// Cursor rejects empty stdout as "no valid response", so emit a no-op
|
|
104
|
+
// additional_context payload when there is nothing to inject.
|
|
105
|
+
return { additional_context: response.additionalContext ?? "" };
|
|
106
|
+
}
|
|
107
|
+
formatSessionStartResponse(response) {
|
|
108
|
+
// SessionStart follows the same rule: always emit valid JSON, even when
|
|
109
|
+
// the payload is effectively a no-op.
|
|
110
|
+
return { additional_context: response.context ?? "" };
|
|
111
|
+
}
|
|
112
|
+
parseStopInput(raw) {
|
|
113
|
+
const input = raw;
|
|
114
|
+
return {
|
|
115
|
+
sessionId: input.conversation_id ?? `pid-${process.ppid}`,
|
|
116
|
+
status: input.status ?? "completed",
|
|
117
|
+
loopCount: input.loop_count ?? 0,
|
|
118
|
+
generationId: input.generation_id,
|
|
119
|
+
transcriptPath: input.transcript_path ?? undefined,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
formatStopResponse(response) {
|
|
123
|
+
if (response.followupMessage) {
|
|
124
|
+
return { followup_message: response.followupMessage };
|
|
125
|
+
}
|
|
126
|
+
return {};
|
|
127
|
+
}
|
|
128
|
+
parseAfterAgentResponseInput(raw) {
|
|
129
|
+
const input = raw;
|
|
130
|
+
return { text: input.text ?? "" };
|
|
131
|
+
}
|
|
132
|
+
getSettingsPath() {
|
|
133
|
+
return resolve(".cursor", "hooks.json");
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Cursor stores conventions per project under .cursor/. Always returned
|
|
137
|
+
* as an absolute path resolved against `projectDir` (or `process.cwd()`
|
|
138
|
+
* when omitted) per the HookAdapter.getConfigDir contract.
|
|
139
|
+
*/
|
|
140
|
+
getConfigDir(projectDir) {
|
|
141
|
+
return resolve(projectDir ?? process.cwd(), ".cursor");
|
|
142
|
+
}
|
|
143
|
+
getInstructionFiles() {
|
|
144
|
+
return ["context-mode.mdc"];
|
|
145
|
+
}
|
|
146
|
+
generateHookConfig(_pluginRoot) {
|
|
147
|
+
const hooks = {
|
|
148
|
+
[CURSOR_HOOK_NAMES.PRE_TOOL_USE]: [
|
|
149
|
+
{
|
|
150
|
+
type: "command",
|
|
151
|
+
command: buildHookCommand(CURSOR_HOOK_NAMES.PRE_TOOL_USE),
|
|
152
|
+
matcher: PRE_TOOL_USE_MATCHER_PATTERN,
|
|
153
|
+
loop_limit: null,
|
|
154
|
+
failClosed: false,
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
[CURSOR_HOOK_NAMES.POST_TOOL_USE]: [
|
|
158
|
+
{
|
|
159
|
+
type: "command",
|
|
160
|
+
command: buildHookCommand(CURSOR_HOOK_NAMES.POST_TOOL_USE),
|
|
161
|
+
loop_limit: null,
|
|
162
|
+
failClosed: false,
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
[CURSOR_HOOK_NAMES.SESSION_START]: [
|
|
166
|
+
{
|
|
167
|
+
type: "command",
|
|
168
|
+
command: buildHookCommand(CURSOR_HOOK_NAMES.SESSION_START),
|
|
169
|
+
loop_limit: null,
|
|
170
|
+
failClosed: false,
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
[CURSOR_HOOK_NAMES.STOP]: [
|
|
174
|
+
{
|
|
175
|
+
type: "command",
|
|
176
|
+
command: buildHookCommand(CURSOR_HOOK_NAMES.STOP),
|
|
177
|
+
loop_limit: null,
|
|
178
|
+
failClosed: false,
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
[CURSOR_HOOK_NAMES.AFTER_AGENT_RESPONSE]: [
|
|
182
|
+
{
|
|
183
|
+
type: "command",
|
|
184
|
+
command: buildHookCommand(CURSOR_HOOK_NAMES.AFTER_AGENT_RESPONSE),
|
|
185
|
+
loop_limit: null,
|
|
186
|
+
failClosed: false,
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
};
|
|
190
|
+
return hooks;
|
|
191
|
+
}
|
|
192
|
+
readSettings() {
|
|
193
|
+
for (const configPath of this.getCandidateHookConfigPaths()) {
|
|
194
|
+
try {
|
|
195
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
196
|
+
return JSON.parse(raw);
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
writeSettings(settings) {
|
|
205
|
+
const configPath = this.getSettingsPath();
|
|
206
|
+
mkdirSync(resolve(".cursor"), { recursive: true });
|
|
207
|
+
writeFileSync(configPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
208
|
+
}
|
|
209
|
+
validateHooks(_pluginRoot) {
|
|
210
|
+
const results = [];
|
|
211
|
+
const loaded = this.loadNativeHookConfig();
|
|
212
|
+
if (!loaded) {
|
|
213
|
+
results.push({
|
|
214
|
+
check: "Native hook config",
|
|
215
|
+
status: "fail",
|
|
216
|
+
message: "No readable native Cursor hook config found in .cursor/hooks.json or ~/.cursor/hooks.json",
|
|
217
|
+
fix: "context-mode upgrade",
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
const hooks = loaded.config.hooks ?? {};
|
|
222
|
+
results.push({
|
|
223
|
+
check: "Native hook config",
|
|
224
|
+
status: "pass",
|
|
225
|
+
message: `Loaded ${loaded.path}`,
|
|
226
|
+
});
|
|
227
|
+
for (const hookType of REQUIRED_HOOKS) {
|
|
228
|
+
const entries = hooks[hookType];
|
|
229
|
+
const hasHook = Array.isArray(entries)
|
|
230
|
+
&& entries.some((entry) => isContextModeHook(entry, hookType));
|
|
231
|
+
results.push({
|
|
232
|
+
check: hookType,
|
|
233
|
+
status: hasHook ? "pass" : "fail",
|
|
234
|
+
message: hasHook
|
|
235
|
+
? `${hookType} hook configured`
|
|
236
|
+
: `${hookType} hook not configured in ${loaded.path}`,
|
|
237
|
+
fix: hasHook ? undefined : "context-mode upgrade",
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
for (const hookType of OPTIONAL_HOOKS) {
|
|
241
|
+
const entries = hooks[hookType];
|
|
242
|
+
const hasHook = Array.isArray(entries)
|
|
243
|
+
&& entries.some((entry) => isContextModeHook(entry, hookType));
|
|
244
|
+
results.push({
|
|
245
|
+
check: hookType,
|
|
246
|
+
status: hasHook ? "pass" : "warn",
|
|
247
|
+
message: hasHook
|
|
248
|
+
? `${hookType} hook configured`
|
|
249
|
+
: `${hookType} hook missing — session event capture will be reduced`,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (existsSync(CURSOR_ENTERPRISE_HOOKS_PATH)) {
|
|
254
|
+
results.push({
|
|
255
|
+
check: "Enterprise hook config",
|
|
256
|
+
status: "warn",
|
|
257
|
+
message: "Enterprise Cursor hook config detected at /Library/Application Support/Cursor/hooks.json (read-only informational layer)",
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
if (this.hasClaudeCompatibilityHooks()) {
|
|
261
|
+
results.push({
|
|
262
|
+
check: "Claude compatibility",
|
|
263
|
+
status: "warn",
|
|
264
|
+
message: "Claude-compatible hooks detected; native Cursor hooks are the supported configuration",
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
const pluginInstalls = this.detectPluginInstalls();
|
|
268
|
+
if (pluginInstalls.length > 0) {
|
|
269
|
+
const nativeHasContextMode = loaded
|
|
270
|
+
? Object.entries(loaded.config.hooks ?? {}).some(([type, entries]) => Array.isArray(entries) && entries.some((entry) => isContextModeHook(entry, type)))
|
|
271
|
+
: false;
|
|
272
|
+
if (nativeHasContextMode && loaded) {
|
|
273
|
+
results.push({
|
|
274
|
+
check: "Plugin/native hook duplication",
|
|
275
|
+
status: "warn",
|
|
276
|
+
message: `context-mode plugin detected at ${pluginInstalls[0]} alongside native hooks in ${loaded.path} — ` +
|
|
277
|
+
`each event will fire twice. Remove one configuration to avoid duplicate routing.`,
|
|
278
|
+
fix: "Remove the native .cursor/hooks.json entries OR uninstall the plugin",
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
results.push({
|
|
283
|
+
check: "Plugin install",
|
|
284
|
+
status: "pass",
|
|
285
|
+
message: `context-mode plugin installed at ${pluginInstalls[0]}`,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return results;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Detects context-mode plugin installations under Cursor's plugin directories.
|
|
293
|
+
* Returns absolute paths to any `.cursor-plugin/plugin.json` files whose
|
|
294
|
+
* `name` matches `context-mode`.
|
|
295
|
+
*/
|
|
296
|
+
detectPluginInstalls() {
|
|
297
|
+
const roots = [
|
|
298
|
+
join(homedir(), ".cursor", "plugins", "local"),
|
|
299
|
+
join(homedir(), ".cursor", "plugins", "cache"),
|
|
300
|
+
];
|
|
301
|
+
const found = [];
|
|
302
|
+
for (const root of roots) {
|
|
303
|
+
try {
|
|
304
|
+
accessSync(root, constants.F_OK);
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
// Plugins live one directory deep: <root>/<name>/.cursor-plugin/plugin.json
|
|
310
|
+
let entries = [];
|
|
311
|
+
try {
|
|
312
|
+
entries = readdirSync(root);
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
for (const name of entries) {
|
|
318
|
+
const manifestPath = join(root, name, ".cursor-plugin", "plugin.json");
|
|
319
|
+
try {
|
|
320
|
+
const raw = readFileSync(manifestPath, "utf-8");
|
|
321
|
+
const parsed = JSON.parse(raw);
|
|
322
|
+
if (parsed?.name === "context-mode") {
|
|
323
|
+
found.push(manifestPath);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return found;
|
|
332
|
+
}
|
|
333
|
+
checkPluginRegistration() {
|
|
334
|
+
const mcpPaths = [resolve(".cursor", "mcp.json"), join(homedir(), ".cursor", "mcp.json")];
|
|
335
|
+
for (const configPath of mcpPaths) {
|
|
336
|
+
try {
|
|
337
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
338
|
+
const config = JSON.parse(raw);
|
|
339
|
+
const servers = (config.mcpServers ?? config.servers);
|
|
340
|
+
if (!servers)
|
|
341
|
+
continue;
|
|
342
|
+
const hasContextMode = Object.entries(servers).some(([name, value]) => {
|
|
343
|
+
if (name.includes("context-mode"))
|
|
344
|
+
return true;
|
|
345
|
+
if (!value || typeof value !== "object")
|
|
346
|
+
return false;
|
|
347
|
+
const server = value;
|
|
348
|
+
return server.command === "context-mode";
|
|
349
|
+
});
|
|
350
|
+
if (hasContextMode) {
|
|
351
|
+
return {
|
|
352
|
+
check: "MCP registration",
|
|
353
|
+
status: "pass",
|
|
354
|
+
message: `context-mode found in ${configPath}`,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
catch {
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
// #489 round-3 — pure plugin install (Marketplace) bundles MCP registration
|
|
363
|
+
// inside the plugin package. No native mcp.json exists, but the plugin
|
|
364
|
+
// manifest under ~/.cursor/plugins/{local,cache}/<name>/.cursor-plugin/plugin.json
|
|
365
|
+
// is enough to consider context-mode registered. Without this, doctor
|
|
366
|
+
// self-contradicts: `Plugin install: pass` alongside `MCP registration: warn`.
|
|
367
|
+
const pluginInstalls = this.detectPluginInstalls();
|
|
368
|
+
if (pluginInstalls.length > 0) {
|
|
369
|
+
return {
|
|
370
|
+
check: "MCP registration",
|
|
371
|
+
status: "pass",
|
|
372
|
+
message: `context-mode registered via plugin manifest at ${pluginInstalls[0]}`,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
return {
|
|
376
|
+
check: "MCP registration",
|
|
377
|
+
status: "warn",
|
|
378
|
+
message: "Could not find context-mode in .cursor/mcp.json or ~/.cursor/mcp.json",
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
getInstalledVersion() {
|
|
382
|
+
try {
|
|
383
|
+
const output = execSync("cursor --version", {
|
|
384
|
+
encoding: "utf-8",
|
|
385
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
386
|
+
}).trim();
|
|
387
|
+
return output.split(/\r?\n/)[0] || "unknown";
|
|
388
|
+
}
|
|
389
|
+
catch {
|
|
390
|
+
return "not installed";
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
configureAllHooks(_pluginRoot) {
|
|
394
|
+
const settings = this.readSettings() ?? { version: 1, hooks: {} };
|
|
395
|
+
const hooks = (settings.hooks ?? {});
|
|
396
|
+
const changes = [];
|
|
397
|
+
this.upsertHookEntry(hooks, CURSOR_HOOK_NAMES.PRE_TOOL_USE, {
|
|
398
|
+
type: "command",
|
|
399
|
+
command: buildHookCommand(CURSOR_HOOK_NAMES.PRE_TOOL_USE),
|
|
400
|
+
matcher: PRE_TOOL_USE_MATCHER_PATTERN,
|
|
401
|
+
loop_limit: null,
|
|
402
|
+
failClosed: false,
|
|
403
|
+
}, changes);
|
|
404
|
+
this.upsertHookEntry(hooks, CURSOR_HOOK_NAMES.POST_TOOL_USE, {
|
|
405
|
+
type: "command",
|
|
406
|
+
command: buildHookCommand(CURSOR_HOOK_NAMES.POST_TOOL_USE),
|
|
407
|
+
loop_limit: null,
|
|
408
|
+
failClosed: false,
|
|
409
|
+
}, changes);
|
|
410
|
+
this.upsertHookEntry(hooks, CURSOR_HOOK_NAMES.SESSION_START, {
|
|
411
|
+
type: "command",
|
|
412
|
+
command: buildHookCommand(CURSOR_HOOK_NAMES.SESSION_START),
|
|
413
|
+
loop_limit: null,
|
|
414
|
+
failClosed: false,
|
|
415
|
+
}, changes);
|
|
416
|
+
this.upsertHookEntry(hooks, CURSOR_HOOK_NAMES.STOP, {
|
|
417
|
+
type: "command",
|
|
418
|
+
command: buildHookCommand(CURSOR_HOOK_NAMES.STOP),
|
|
419
|
+
loop_limit: null,
|
|
420
|
+
failClosed: false,
|
|
421
|
+
}, changes);
|
|
422
|
+
this.upsertHookEntry(hooks, CURSOR_HOOK_NAMES.AFTER_AGENT_RESPONSE, {
|
|
423
|
+
type: "command",
|
|
424
|
+
command: buildHookCommand(CURSOR_HOOK_NAMES.AFTER_AGENT_RESPONSE),
|
|
425
|
+
loop_limit: null,
|
|
426
|
+
failClosed: false,
|
|
427
|
+
}, changes);
|
|
428
|
+
settings.version = 1;
|
|
429
|
+
settings.hooks = hooks;
|
|
430
|
+
this.writeSettings(settings);
|
|
431
|
+
changes.push(`Wrote native Cursor hooks to ${this.getSettingsPath()}`);
|
|
432
|
+
return changes;
|
|
433
|
+
}
|
|
434
|
+
setHookPermissions(pluginRoot) {
|
|
435
|
+
const set = [];
|
|
436
|
+
const hooksDir = join(pluginRoot, "hooks", "cursor");
|
|
437
|
+
for (const scriptName of Object.values(CURSOR_HOOK_SCRIPTS)) {
|
|
438
|
+
const scriptPath = resolve(hooksDir, scriptName);
|
|
439
|
+
try {
|
|
440
|
+
accessSync(scriptPath, constants.R_OK);
|
|
441
|
+
chmodSync(scriptPath, 0o755);
|
|
442
|
+
set.push(scriptPath);
|
|
443
|
+
}
|
|
444
|
+
catch {
|
|
445
|
+
/* skip missing scripts */
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
return set;
|
|
449
|
+
}
|
|
450
|
+
updatePluginRegistry(_pluginRoot, _version) {
|
|
451
|
+
// Cursor manages extensions and native hooks internally.
|
|
452
|
+
}
|
|
453
|
+
getCandidateHookConfigPaths() {
|
|
454
|
+
const paths = [this.getSettingsPath(), join(homedir(), ".cursor", "hooks.json")];
|
|
455
|
+
if (process.platform === "darwin") {
|
|
456
|
+
paths.push(CURSOR_ENTERPRISE_HOOKS_PATH);
|
|
457
|
+
}
|
|
458
|
+
return paths;
|
|
459
|
+
}
|
|
460
|
+
getProjectDir(input) {
|
|
461
|
+
return input.cwd
|
|
462
|
+
|| input.workspace_roots?.[0]
|
|
463
|
+
|| process.env.CURSOR_CWD
|
|
464
|
+
|| process.cwd();
|
|
465
|
+
}
|
|
466
|
+
extractSessionId(input) {
|
|
467
|
+
if (input.conversation_id)
|
|
468
|
+
return input.conversation_id;
|
|
469
|
+
if (input.session_id)
|
|
470
|
+
return input.session_id;
|
|
471
|
+
if (process.env.CURSOR_SESSION_ID)
|
|
472
|
+
return process.env.CURSOR_SESSION_ID;
|
|
473
|
+
if (process.env.CURSOR_TRACE_ID)
|
|
474
|
+
return process.env.CURSOR_TRACE_ID;
|
|
475
|
+
return `pid-${process.ppid}`;
|
|
476
|
+
}
|
|
477
|
+
loadNativeHookConfig() {
|
|
478
|
+
for (const configPath of this.getCandidateHookConfigPaths()) {
|
|
479
|
+
try {
|
|
480
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
481
|
+
const config = JSON.parse(raw);
|
|
482
|
+
if (config && typeof config === "object") {
|
|
483
|
+
return { path: configPath, config };
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
catch {
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
492
|
+
hasClaudeCompatibilityHooks() {
|
|
493
|
+
// Issue #460 round-3: probe the resolved CC config dir (honors
|
|
494
|
+
// $CLAUDE_CONFIG_DIR) instead of the literal ~/.claude so users
|
|
495
|
+
// who relocated their CC config still trigger the compat path.
|
|
496
|
+
const compatPaths = [
|
|
497
|
+
resolve(".claude", "settings.json"),
|
|
498
|
+
resolve(".claude", "settings.local.json"),
|
|
499
|
+
join(resolveClaudeConfigDir(), "settings.json"),
|
|
500
|
+
];
|
|
501
|
+
return compatPaths.some((configPath) => existsSync(configPath));
|
|
502
|
+
}
|
|
503
|
+
upsertHookEntry(hooks, hookType, entry, changes) {
|
|
504
|
+
const existingRaw = hooks[hookType];
|
|
505
|
+
const existing = Array.isArray(existingRaw) ? [...existingRaw] : [];
|
|
506
|
+
const idx = existing.findIndex((candidate) => isContextModeHook(candidate, hookType));
|
|
507
|
+
if (idx >= 0) {
|
|
508
|
+
existing[idx] = entry;
|
|
509
|
+
changes.push(`Updated existing ${hookType} hook entry`);
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
existing.push(entry);
|
|
513
|
+
changes.push(`Added ${hookType} hook entry`);
|
|
514
|
+
}
|
|
515
|
+
hooks[hookType] = existing;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adapters/detect — Auto-detect which platform is running.
|
|
3
|
+
*
|
|
4
|
+
* Detection priority:
|
|
5
|
+
* 1. Environment variables (high confidence)
|
|
6
|
+
* 2. Config directory existence (medium confidence)
|
|
7
|
+
* 3. Fallback to Claude Code (low confidence — most common)
|
|
8
|
+
*
|
|
9
|
+
* Verified env vars per platform (from source code audit):
|
|
10
|
+
* - Claude Code: CLAUDE_CODE_ENTRYPOINT, CLAUDE_PLUGIN_ROOT,
|
|
11
|
+
* CLAUDE_PROJECT_DIR, CLAUDE_SESSION_ID | ~/.claude/
|
|
12
|
+
* - Gemini CLI: GEMINI_PROJECT_DIR (hooks), GEMINI_CLI (MCP) | ~/.gemini/
|
|
13
|
+
* - KiloCode: KILO, KILO_PID | ~/.config/kilo/
|
|
14
|
+
* - OpenCode: OPENCODE_PROJECT_DIR, OPENCODE_CLIENT,
|
|
15
|
+
* OPENCODE_TERMINAL, OPENCODE, OPENCODE_PID |
|
|
16
|
+
* ~/.config/opencode/
|
|
17
|
+
* - OpenClaw: OPENCLAW_HOME, OPENCLAW_CLI | ~/.openclaw/
|
|
18
|
+
* - Codex CLI: CODEX_CI, CODEX_THREAD_ID | ~/.codex/
|
|
19
|
+
* - Cursor: CURSOR_TRACE_ID (MCP), CURSOR_CLI (terminal) | ~/.cursor/
|
|
20
|
+
* - VS Code Copilot: VSCODE_PID, VSCODE_CWD | ~/.vscode/
|
|
21
|
+
* - JetBrains Copilot: IDEA_INITIAL_DIRECTORY, IDEA_HOME, JETBRAINS_CLIENT_ID | ~/.config/JetBrains/
|
|
22
|
+
*/
|
|
23
|
+
import type { PlatformId, DetectionSignal, HookAdapter } from "./types.js";
|
|
24
|
+
/** Test-only: reset the installed_plugins.json memo so each test starts cold. */
|
|
25
|
+
export declare function __resetClaudeCodePluginCacheForTests(): void;
|
|
26
|
+
/**
|
|
27
|
+
* Test-only: pretend installed_plugins.json does not exist (or has no
|
|
28
|
+
* context-mode entry). Lets tests that exercise the genuine vscode-copilot
|
|
29
|
+
* env-var path run on a developer machine that actually has context-mode
|
|
30
|
+
* installed as a Claude Code plugin.
|
|
31
|
+
*/
|
|
32
|
+
export declare function __seedClaudeCodePluginCacheMissForTests(): void;
|
|
33
|
+
/**
|
|
34
|
+
* Tag for each PLATFORM_ENV_VARS row.
|
|
35
|
+
* - `workspace`: env var names a project/working directory. Used by
|
|
36
|
+
* `resolveProjectDir({ strictPlatform })` to form the candidate list,
|
|
37
|
+
* and by Pi's bridge to scrub foreign workspace vars on child spawn.
|
|
38
|
+
* - `identification`: env var only signals which host is running; carries
|
|
39
|
+
* no project path. PRESERVED in normal operation (some are load-bearing
|
|
40
|
+
* for hook integrations on the host that owns them, e.g. CLAUDE_PLUGIN_ROOT
|
|
41
|
+
* for Claude Code's hook context).
|
|
42
|
+
*
|
|
43
|
+
* Issue #545 — algorithmic env-leak fix. The split allows resolveProjectDir
|
|
44
|
+
* to derive ALLOW (own workspace vars) and BAN (other platforms' workspace
|
|
45
|
+
* vars) sets from a single registry, satisfying MUST-3 (17 adapters equal).
|
|
46
|
+
*
|
|
47
|
+
* Issue #561 — FOREIGN identification vars MUST be scrubbed when spawning a
|
|
48
|
+
* child under a different host (e.g. Pi spawning context-mode child must
|
|
49
|
+
* scrub Claude Code identification vars CLAUDE_CODE_ENTRYPOINT /
|
|
50
|
+
* CLAUDE_PLUGIN_ROOT to prevent detectPlatform() in the child from
|
|
51
|
+
* misidentifying the host as claude-code and writing Pi's data into
|
|
52
|
+
* ~/.claude/context-mode/). See `foreignIdentificationEnv()` below.
|
|
53
|
+
*/
|
|
54
|
+
export type EnvVarRole = "workspace" | "identification";
|
|
55
|
+
export interface PlatformEnvEntry {
|
|
56
|
+
readonly name: string;
|
|
57
|
+
readonly role: EnvVarRole;
|
|
58
|
+
/**
|
|
59
|
+
* When `false`, this entry is NOT used as a high-confidence detection
|
|
60
|
+
* signal — only consumed by `workspaceEnvVarsFor`/`foreignWorkspaceEnv`
|
|
61
|
+
* (project-dir cascade and bridge env scrub). Use for consumer-set
|
|
62
|
+
* workspace vars that the host runtime never emits itself, so that a
|
|
63
|
+
* stale env var on an unrelated host does not misclassify the platform.
|
|
64
|
+
* Default: `true` (entry participates in detection).
|
|
65
|
+
*
|
|
66
|
+
* Issue #542 — PI_PROJECT_DIR / PI_WORKSPACE_DIR are consumer-set and
|
|
67
|
+
* MUST NOT trigger Pi detection on their own.
|
|
68
|
+
*/
|
|
69
|
+
readonly detect?: boolean;
|
|
70
|
+
}
|
|
71
|
+
export declare const PLATFORM_ENV_VARS: ReadonlyMap<PlatformId, readonly PlatformEnvEntry[]>;
|
|
72
|
+
/**
|
|
73
|
+
* Backwards-compat shim: legacy `string[]` shape used by detection logic and
|
|
74
|
+
* by tests that iterate the registry to clear env vars. Always returns the
|
|
75
|
+
* names in registry order.
|
|
76
|
+
*/
|
|
77
|
+
export declare function getEnvVarNames(platform: PlatformId): string[];
|
|
78
|
+
/**
|
|
79
|
+
* Issue #545 — return only role=workspace env var names for a platform, in
|
|
80
|
+
* registry order. Empty array for adapters with no workspace var (e.g.
|
|
81
|
+
* codex, kilo, zed, antigravity, openclaw, kiro). Consumed by
|
|
82
|
+
* `resolveProjectDir({ strictPlatform })` to build the cascade.
|
|
83
|
+
*/
|
|
84
|
+
export declare function workspaceEnvVarsFor(platform: PlatformId): string[];
|
|
85
|
+
/**
|
|
86
|
+
* Issue #545 — return the union of workspace env vars from ALL platforms
|
|
87
|
+
* EXCEPT the given one. Consumed by Pi's bridge env scrub (strip foreign
|
|
88
|
+
* workspace vars from spawned MCP child) and by the matrix regression test.
|
|
89
|
+
*/
|
|
90
|
+
export declare function foreignWorkspaceEnv(platform: PlatformId): Set<string>;
|
|
91
|
+
/**
|
|
92
|
+
* Issue #561 — return the union of identification env vars from ALL
|
|
93
|
+
* platforms EXCEPT the given one. Sibling of `foreignWorkspaceEnv`,
|
|
94
|
+
* filtered on `role === "identification"` instead of "workspace".
|
|
95
|
+
*
|
|
96
|
+
* Consumed by Pi's bridge env scrub: when Pi spawns the context-mode
|
|
97
|
+
* MCP child, the child inherits the host shell env including any
|
|
98
|
+
* identification vars set by a co-resident Claude Code session
|
|
99
|
+
* (CLAUDE_CODE_ENTRYPOINT / CLAUDE_PLUGIN_ROOT). Without scrubbing,
|
|
100
|
+
* `detectPlatform()` in the child falls through env priority order and
|
|
101
|
+
* resolves to claude-code first — Pi's session data then writes into
|
|
102
|
+
* `~/.claude/context-mode/` instead of Pi's own dir. Scrubbing FOREIGN
|
|
103
|
+
* identification vars (everyone else's) preserves Pi's OWN identification
|
|
104
|
+
* vars (PI_CONFIG_DIR / PI_SESSION_FILE / PI_COMPILED) so the child still
|
|
105
|
+
* detects pi correctly.
|
|
106
|
+
*
|
|
107
|
+
* Algorithmic, registry-driven — adding adapter #16 grows the scrub
|
|
108
|
+
* automatically (no edit to mcp-bridge.ts).
|
|
109
|
+
*/
|
|
110
|
+
export declare function foreignIdentificationEnv(platform: PlatformId): Set<string>;
|
|
111
|
+
/**
|
|
112
|
+
* Sync map from platform identifier → home-relative path segments where that
|
|
113
|
+
* platform stores its config. Mirrors the `super([...])` argument passed by
|
|
114
|
+
* each adapter — kept in sync as the single source of truth used when we need
|
|
115
|
+
* a session dir BEFORE an adapter has been instantiated (race window between
|
|
116
|
+
* MCP server start and `initialize` handshake completion).
|
|
117
|
+
*
|
|
118
|
+
* Returns `null` for "unknown" or any string outside the supported set so the
|
|
119
|
+
* caller can decide on a safe fallback.
|
|
120
|
+
*/
|
|
121
|
+
export declare function getSessionDirSegments(platform: string): string[] | null;
|
|
122
|
+
/**
|
|
123
|
+
* Detect the current platform by checking env vars and config dirs.
|
|
124
|
+
*
|
|
125
|
+
* @param clientInfo - Optional MCP clientInfo from initialize handshake.
|
|
126
|
+
* When provided, takes highest priority (zero-config detection).
|
|
127
|
+
*/
|
|
128
|
+
export declare function detectPlatform(clientInfo?: {
|
|
129
|
+
name: string;
|
|
130
|
+
version?: string;
|
|
131
|
+
}): DetectionSignal;
|
|
132
|
+
/**
|
|
133
|
+
* Get the adapter instance for a given platform.
|
|
134
|
+
* Lazily imports platform-specific adapter modules.
|
|
135
|
+
*/
|
|
136
|
+
export declare function getAdapter(platform?: PlatformId): Promise<HookAdapter>;
|