context-mode 1.0.88 → 1.0.90

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.
Files changed (132) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.openclaw-plugin/openclaw.plugin.json +1 -1
  4. package/.openclaw-plugin/package.json +1 -1
  5. package/README.md +184 -60
  6. package/build/adapters/antigravity/index.d.ts +3 -5
  7. package/build/adapters/antigravity/index.js +7 -35
  8. package/build/adapters/base.d.ts +27 -0
  9. package/build/adapters/base.js +59 -0
  10. package/build/adapters/claude-code/index.d.ts +9 -25
  11. package/build/adapters/claude-code/index.js +27 -141
  12. package/build/adapters/claude-code-base.d.ts +49 -0
  13. package/build/adapters/claude-code-base.js +113 -0
  14. package/build/adapters/client-map.js +5 -0
  15. package/build/adapters/codex/hooks.d.ts +21 -14
  16. package/build/adapters/codex/hooks.js +22 -15
  17. package/build/adapters/codex/index.d.ts +6 -10
  18. package/build/adapters/codex/index.js +13 -43
  19. package/build/adapters/copilot-base.d.ts +78 -0
  20. package/build/adapters/copilot-base.js +281 -0
  21. package/build/adapters/cursor/index.d.ts +3 -5
  22. package/build/adapters/cursor/index.js +6 -34
  23. package/build/adapters/detect.d.ts +7 -0
  24. package/build/adapters/detect.js +57 -56
  25. package/build/adapters/gemini-cli/index.d.ts +3 -5
  26. package/build/adapters/gemini-cli/index.js +7 -35
  27. package/build/adapters/jetbrains-copilot/config.d.ts +8 -0
  28. package/build/adapters/jetbrains-copilot/config.js +8 -0
  29. package/build/adapters/jetbrains-copilot/hooks.d.ts +51 -0
  30. package/build/adapters/jetbrains-copilot/hooks.js +82 -0
  31. package/build/adapters/jetbrains-copilot/index.d.ts +24 -0
  32. package/build/adapters/jetbrains-copilot/index.js +119 -0
  33. package/build/adapters/kiro/hooks.d.ts +14 -0
  34. package/build/adapters/kiro/hooks.js +23 -0
  35. package/build/adapters/kiro/index.d.ts +3 -5
  36. package/build/adapters/kiro/index.js +10 -38
  37. package/build/adapters/openclaw/index.d.ts +3 -4
  38. package/build/adapters/openclaw/index.js +6 -22
  39. package/build/adapters/opencode/index.d.ts +2 -3
  40. package/build/adapters/opencode/index.js +5 -16
  41. package/build/adapters/qwen-code/index.d.ts +39 -0
  42. package/build/adapters/qwen-code/index.js +199 -0
  43. package/build/adapters/types.d.ts +1 -1
  44. package/build/adapters/vscode-copilot/index.d.ts +16 -46
  45. package/build/adapters/vscode-copilot/index.js +29 -320
  46. package/build/adapters/zed/index.d.ts +3 -5
  47. package/build/adapters/zed/index.js +7 -35
  48. package/build/cli.js +113 -47
  49. package/build/lifecycle.d.ts +23 -0
  50. package/build/lifecycle.js +54 -13
  51. package/build/opencode-plugin.d.ts +19 -7
  52. package/build/opencode-plugin.js +19 -7
  53. package/build/pi-extension.js +24 -7
  54. package/build/runtime.js +24 -9
  55. package/build/security.d.ts +17 -1
  56. package/build/security.js +40 -6
  57. package/build/server.js +129 -21
  58. package/build/session/analytics.d.ts +8 -7
  59. package/build/session/analytics.js +95 -75
  60. package/build/session/db.d.ts +10 -1
  61. package/build/session/db.js +67 -8
  62. package/build/session/extract.js +10 -2
  63. package/build/session/project-attribution.d.ts +73 -0
  64. package/build/session/project-attribution.js +231 -0
  65. package/build/store.d.ts +7 -0
  66. package/build/store.js +117 -18
  67. package/build/truncate.d.ts +6 -0
  68. package/build/truncate.js +51 -29
  69. package/build/types.d.ts +8 -0
  70. package/cli.bundle.mjs +157 -136
  71. package/configs/antigravity/GEMINI.md +31 -36
  72. package/configs/claude-code/CLAUDE.md +31 -37
  73. package/configs/codex/AGENTS.md +35 -49
  74. package/configs/cursor/context-mode.mdc +24 -25
  75. package/configs/gemini-cli/GEMINI.md +30 -36
  76. package/configs/jetbrains-copilot/copilot-instructions.md +59 -0
  77. package/configs/jetbrains-copilot/hooks.json +16 -0
  78. package/configs/jetbrains-copilot/mcp.json +8 -0
  79. package/configs/kilo/AGENTS.md +30 -36
  80. package/configs/kiro/KIRO.md +30 -36
  81. package/configs/kiro/agent.json +1 -1
  82. package/configs/openclaw/AGENTS.md +30 -36
  83. package/configs/opencode/AGENTS.md +30 -36
  84. package/configs/pi/AGENTS.md +31 -36
  85. package/configs/qwen-code/QWEN.md +63 -0
  86. package/configs/vscode-copilot/copilot-instructions.md +30 -36
  87. package/configs/zed/AGENTS.md +31 -36
  88. package/hooks/codex/posttooluse.mjs +7 -7
  89. package/hooks/codex/pretooluse.mjs +3 -3
  90. package/hooks/codex/sessionstart.mjs +2 -1
  91. package/hooks/core/formatters.mjs +24 -0
  92. package/hooks/core/routing.mjs +40 -15
  93. package/hooks/core/tool-naming.mjs +2 -0
  94. package/hooks/cursor/posttooluse.mjs +7 -7
  95. package/hooks/cursor/pretooluse.mjs +3 -3
  96. package/hooks/cursor/sessionstart.mjs +2 -1
  97. package/hooks/cursor/stop.mjs +2 -2
  98. package/hooks/ensure-deps.mjs +22 -10
  99. package/hooks/gemini-cli/aftertool.mjs +8 -8
  100. package/hooks/gemini-cli/beforetool.mjs +3 -2
  101. package/hooks/gemini-cli/precompress.mjs +2 -2
  102. package/hooks/gemini-cli/sessionstart.mjs +12 -4
  103. package/hooks/jetbrains-copilot/posttooluse.mjs +61 -0
  104. package/hooks/jetbrains-copilot/precompact.mjs +54 -0
  105. package/hooks/jetbrains-copilot/pretooluse.mjs +27 -0
  106. package/hooks/jetbrains-copilot/sessionstart.mjs +119 -0
  107. package/hooks/kiro/posttooluse.mjs +6 -7
  108. package/hooks/kiro/pretooluse.mjs +3 -2
  109. package/hooks/posttooluse.mjs +8 -8
  110. package/hooks/precompact.mjs +3 -4
  111. package/hooks/pretooluse.mjs +43 -20
  112. package/hooks/routing-block.mjs +35 -33
  113. package/hooks/session-attribution.bundle.mjs +1 -0
  114. package/hooks/session-db.bundle.mjs +27 -8
  115. package/hooks/session-extract.bundle.mjs +2 -1
  116. package/hooks/session-helpers.mjs +44 -3
  117. package/hooks/session-loaders.mjs +37 -0
  118. package/hooks/session-snapshot.bundle.mjs +14 -14
  119. package/hooks/sessionstart.mjs +5 -5
  120. package/hooks/userpromptsubmit.mjs +26 -9
  121. package/hooks/vscode-copilot/posttooluse.mjs +8 -8
  122. package/hooks/vscode-copilot/precompact.mjs +2 -2
  123. package/hooks/vscode-copilot/pretooluse.mjs +3 -2
  124. package/hooks/vscode-copilot/sessionstart.mjs +2 -2
  125. package/insight/server.mjs +262 -32
  126. package/insight/src/lib/api.ts +2 -1
  127. package/insight/src/routes/index.tsx +16 -3
  128. package/insight/src/routes/search.tsx +1 -1
  129. package/openclaw.plugin.json +1 -1
  130. package/package.json +11 -2
  131. package/server.bundle.mjs +117 -99
  132. package/skills/ctx-insight/SKILL.md +1 -1
@@ -9,21 +9,22 @@
9
9
  * - Config: ~/.codex/hooks.json + ~/.codex/config.toml (TOML for MCP/features)
10
10
  * - Session dir: ~/.codex/context-mode/sessions/
11
11
  *
12
- * IMPORTANT: Hook dispatch is NOT yet active in Codex CLI (v0.118.0).
13
- * codex_hooks feature flag is Stage::UnderDevelopment hooks are implemented
14
- * in codex-rs/hooks/ but not wired into the tool execution pipeline.
15
- * Our adapter is ready; it will work once Codex enables dispatch.
16
- * Track: https://github.com/openai/codex/issues/16685
12
+ * Hook dispatch is stable in Codex CLI. PreToolUse deny decisions work,
13
+ * while input rewriting remains blocked on upstream updatedInput support.
14
+ * Track: https://github.com/openai/codex/issues/18491
17
15
  */
18
- import { createHash } from "node:crypto";
19
- import { readFileSync, mkdirSync, copyFileSync, accessSync, constants, } from "node:fs";
20
- import { resolve, join, dirname } from "node:path";
16
+ import { readFileSync, } from "node:fs";
17
+ import { resolve, dirname } from "node:path";
21
18
  import { fileURLToPath } from "node:url";
22
19
  import { homedir } from "node:os";
20
+ import { BaseAdapter } from "../base.js";
23
21
  // ─────────────────────────────────────────────────────────
24
22
  // Adapter implementation
25
23
  // ─────────────────────────────────────────────────────────
26
- export class CodexAdapter {
24
+ export class CodexAdapter extends BaseAdapter {
25
+ constructor() {
26
+ super([".codex"]);
27
+ }
27
28
  name = "Codex CLI";
28
29
  paradigm = "json-stdio";
29
30
  capabilities = {
@@ -146,30 +147,11 @@ export class CodexAdapter {
146
147
  getSettingsPath() {
147
148
  return resolve(homedir(), ".codex", "config.toml");
148
149
  }
149
- getSessionDir() {
150
- const dir = join(homedir(), ".codex", "context-mode", "sessions");
151
- mkdirSync(dir, { recursive: true });
152
- return dir;
153
- }
154
- getSessionDBPath(projectDir) {
155
- const hash = createHash("sha256")
156
- .update(projectDir)
157
- .digest("hex")
158
- .slice(0, 16);
159
- return join(this.getSessionDir(), `${hash}.db`);
160
- }
161
- getSessionEventsPath(projectDir) {
162
- const hash = createHash("sha256")
163
- .update(projectDir)
164
- .digest("hex")
165
- .slice(0, 16);
166
- return join(this.getSessionDir(), `${hash}-events.md`);
167
- }
168
150
  generateHookConfig(pluginRoot) {
169
151
  return {
170
152
  PreToolUse: [
171
153
  {
172
- matcher: "",
154
+ matcher: "local_shell|shell|shell_command|exec_command|container.exec|Bash|Shell|grep_files|mcp__plugin_context-mode_context-mode__ctx_execute|mcp__plugin_context-mode_context-mode__ctx_execute_file|mcp__plugin_context-mode_context-mode__ctx_batch_execute",
173
155
  hooks: [
174
156
  {
175
157
  type: "command",
@@ -225,8 +207,8 @@ export class CodexAdapter {
225
207
  return [
226
208
  {
227
209
  check: "Hook support",
228
- status: "warn",
229
- message: "Codex CLI hooks are implemented but dispatch is not yet active (Stage::UnderDevelopment, v0.118.0). Enable flag: [features] codex_hooks = true in ~/.codex/config.toml. Track: openai/codex#16685",
210
+ status: "pass",
211
+ message: "Codex CLI hooks are stable. Configure ~/.codex/hooks.json for PreToolUse, PostToolUse, and SessionStart.",
230
212
  },
231
213
  ];
232
214
  }
@@ -275,18 +257,6 @@ export class CodexAdapter {
275
257
  // Codex CLI hook configuration is done via hooks.json, not config.toml
276
258
  return [];
277
259
  }
278
- backupSettings() {
279
- const settingsPath = this.getSettingsPath();
280
- try {
281
- accessSync(settingsPath, constants.R_OK);
282
- const backupPath = settingsPath + ".bak";
283
- copyFileSync(settingsPath, backupPath);
284
- return backupPath;
285
- }
286
- catch {
287
- return null;
288
- }
289
- }
290
260
  setHookPermissions(_pluginRoot) {
291
261
  // Hook permissions are set during plugin install
292
262
  return [];
@@ -0,0 +1,78 @@
1
+ /**
2
+ * CopilotBaseAdapter — shared implementation for VS Code Copilot and JetBrains Copilot.
3
+ *
4
+ * Both platforms share the SAME Copilot agent runtime:
5
+ * - hookSpecificOutput wrapper with hookEventName
6
+ * - Same hook events (PreToolUse, PostToolUse, PreCompact, SessionStart)
7
+ * - Same .github/hooks/ config location
8
+ * - Same configureHooks logic
9
+ * - Same generateHookConfig format
10
+ * - Same parse/format methods
11
+ *
12
+ * Platform-specific differences handled by subclasses:
13
+ * - extractSessionId() — different env var fallbacks
14
+ * - getProjectDir() — different env vars for project root
15
+ * - getSessionDir() — different default session directories
16
+ * - checkPluginRegistration() — VS Code reads .vscode/mcp.json, JetBrains uses IDE UI
17
+ * - getInstalledVersion() — VS Code checks extensions dir, JetBrains checks hook config
18
+ * - validateHooks() — different warning messages
19
+ */
20
+ import { BaseAdapter } from "./base.js";
21
+ import type { HookAdapter, HookParadigm, PlatformCapabilities, DiagnosticResult, PreToolUseEvent, PostToolUseEvent, PreCompactEvent, SessionStartEvent, PreToolUseResponse, PostToolUseResponse, PreCompactResponse, SessionStartResponse, HookRegistration } from "./types.js";
22
+ export interface CopilotHookInput {
23
+ tool_name?: string;
24
+ tool_input?: Record<string, unknown>;
25
+ tool_output?: string;
26
+ is_error?: boolean;
27
+ /** Copilot uses camelCase sessionId (NOT session_id). */
28
+ sessionId?: string;
29
+ source?: string;
30
+ }
31
+ export interface CopilotHookModule {
32
+ HOOK_TYPES: {
33
+ readonly PRE_TOOL_USE: string;
34
+ readonly POST_TOOL_USE: string;
35
+ readonly PRE_COMPACT: string;
36
+ readonly SESSION_START: string;
37
+ readonly STOP: string;
38
+ readonly SUBAGENT_START: string;
39
+ readonly SUBAGENT_STOP: string;
40
+ };
41
+ HOOK_SCRIPTS: Record<string, string>;
42
+ buildHookCommand: (hookType: any, pluginRoot?: string) => string;
43
+ }
44
+ export declare abstract class CopilotBaseAdapter extends BaseAdapter implements HookAdapter {
45
+ readonly paradigm: HookParadigm;
46
+ readonly capabilities: PlatformCapabilities;
47
+ /** Subclasses must provide their platform name. */
48
+ abstract readonly name: string;
49
+ /** Subclasses must provide their hook module (HOOK_TYPES, HOOK_SCRIPTS, buildHookCommand). */
50
+ protected abstract readonly hookModule: CopilotHookModule;
51
+ /** Subclasses must provide the hook scripts subdirectory name (e.g., "vscode-copilot"). */
52
+ protected abstract readonly hookSubdir: string;
53
+ /** Extract session ID from Copilot hook input — env var fallbacks differ per platform. */
54
+ protected abstract extractSessionId(input: CopilotHookInput): string;
55
+ /** Get the project directory — env vars differ per platform. */
56
+ protected abstract getProjectDir(): string;
57
+ /** Validate that hooks are properly configured for this platform. */
58
+ abstract validateHooks(pluginRoot: string): DiagnosticResult[];
59
+ /** Check if the plugin is registered/enabled on this platform. */
60
+ abstract checkPluginRegistration(): DiagnosticResult;
61
+ /** Get the installed version from this platform's registry/marketplace. */
62
+ abstract getInstalledVersion(): string;
63
+ parsePreToolUseInput(raw: unknown): PreToolUseEvent;
64
+ parsePostToolUseInput(raw: unknown): PostToolUseEvent;
65
+ parsePreCompactInput(raw: unknown): PreCompactEvent;
66
+ parseSessionStartInput(raw: unknown): SessionStartEvent;
67
+ formatPreToolUseResponse(response: PreToolUseResponse): unknown;
68
+ formatPostToolUseResponse(response: PostToolUseResponse): unknown;
69
+ formatPreCompactResponse(response: PreCompactResponse): unknown;
70
+ formatSessionStartResponse(response: SessionStartResponse): unknown;
71
+ getSettingsPath(): string;
72
+ generateHookConfig(pluginRoot: string): HookRegistration;
73
+ readSettings(): Record<string, unknown> | null;
74
+ writeSettings(settings: Record<string, unknown>): void;
75
+ configureAllHooks(pluginRoot: string): string[];
76
+ setHookPermissions(pluginRoot: string): string[];
77
+ updatePluginRegistry(_pluginRoot: string, _version: string): void;
78
+ }
@@ -0,0 +1,281 @@
1
+ /**
2
+ * CopilotBaseAdapter — shared implementation for VS Code Copilot and JetBrains Copilot.
3
+ *
4
+ * Both platforms share the SAME Copilot agent runtime:
5
+ * - hookSpecificOutput wrapper with hookEventName
6
+ * - Same hook events (PreToolUse, PostToolUse, PreCompact, SessionStart)
7
+ * - Same .github/hooks/ config location
8
+ * - Same configureHooks logic
9
+ * - Same generateHookConfig format
10
+ * - Same parse/format methods
11
+ *
12
+ * Platform-specific differences handled by subclasses:
13
+ * - extractSessionId() — different env var fallbacks
14
+ * - getProjectDir() — different env vars for project root
15
+ * - getSessionDir() — different default session directories
16
+ * - checkPluginRegistration() — VS Code reads .vscode/mcp.json, JetBrains uses IDE UI
17
+ * - getInstalledVersion() — VS Code checks extensions dir, JetBrains checks hook config
18
+ * - validateHooks() — different warning messages
19
+ */
20
+ import { readFileSync, writeFileSync, mkdirSync, accessSync, chmodSync, constants, } from "node:fs";
21
+ import { resolve, join } from "node:path";
22
+ import { BaseAdapter } from "./base.js";
23
+ // ─────────────────────────────────────────────────────────
24
+ // Abstract base adapter for Copilot platforms
25
+ // ─────────────────────────────────────────────────────────
26
+ export class CopilotBaseAdapter extends BaseAdapter {
27
+ paradigm = "json-stdio";
28
+ capabilities = {
29
+ preToolUse: true,
30
+ postToolUse: true,
31
+ preCompact: true,
32
+ sessionStart: true,
33
+ canModifyArgs: true,
34
+ canModifyOutput: true,
35
+ canInjectSessionContext: true,
36
+ };
37
+ // ── Input parsing (shared) ─────────────────────────────
38
+ parsePreToolUseInput(raw) {
39
+ const input = raw;
40
+ return {
41
+ toolName: input.tool_name ?? "",
42
+ toolInput: input.tool_input ?? {},
43
+ sessionId: this.extractSessionId(input),
44
+ projectDir: this.getProjectDir(),
45
+ raw,
46
+ };
47
+ }
48
+ parsePostToolUseInput(raw) {
49
+ const input = raw;
50
+ return {
51
+ toolName: input.tool_name ?? "",
52
+ toolInput: input.tool_input ?? {},
53
+ toolOutput: input.tool_output,
54
+ isError: input.is_error,
55
+ sessionId: this.extractSessionId(input),
56
+ projectDir: this.getProjectDir(),
57
+ raw,
58
+ };
59
+ }
60
+ parsePreCompactInput(raw) {
61
+ const input = raw;
62
+ return {
63
+ sessionId: this.extractSessionId(input),
64
+ projectDir: this.getProjectDir(),
65
+ raw,
66
+ };
67
+ }
68
+ parseSessionStartInput(raw) {
69
+ const input = raw;
70
+ const rawSource = input.source ?? "startup";
71
+ let source;
72
+ switch (rawSource) {
73
+ case "compact":
74
+ source = "compact";
75
+ break;
76
+ case "resume":
77
+ source = "resume";
78
+ break;
79
+ case "clear":
80
+ source = "clear";
81
+ break;
82
+ default:
83
+ source = "startup";
84
+ }
85
+ return {
86
+ sessionId: this.extractSessionId(input),
87
+ source,
88
+ projectDir: this.getProjectDir(),
89
+ raw,
90
+ };
91
+ }
92
+ // ── Response formatting (shared) ───────────────────────
93
+ formatPreToolUseResponse(response) {
94
+ if (response.decision === "deny") {
95
+ return {
96
+ permissionDecision: "deny",
97
+ reason: response.reason ?? "Blocked by context-mode hook",
98
+ };
99
+ }
100
+ if (response.decision === "modify" && response.updatedInput) {
101
+ return {
102
+ hookSpecificOutput: {
103
+ hookEventName: this.hookModule.HOOK_TYPES.PRE_TOOL_USE,
104
+ updatedInput: response.updatedInput,
105
+ },
106
+ };
107
+ }
108
+ if (response.decision === "context" && response.additionalContext) {
109
+ return {
110
+ hookSpecificOutput: {
111
+ hookEventName: this.hookModule.HOOK_TYPES.PRE_TOOL_USE,
112
+ additionalContext: response.additionalContext,
113
+ },
114
+ };
115
+ }
116
+ if (response.decision === "ask") {
117
+ return {
118
+ permissionDecision: "deny",
119
+ reason: response.reason ?? "Action requires user confirmation (security policy)",
120
+ };
121
+ }
122
+ // "allow" — return undefined for passthrough
123
+ return undefined;
124
+ }
125
+ formatPostToolUseResponse(response) {
126
+ if (response.updatedOutput) {
127
+ return {
128
+ hookSpecificOutput: {
129
+ hookEventName: this.hookModule.HOOK_TYPES.POST_TOOL_USE,
130
+ decision: "block",
131
+ reason: response.updatedOutput,
132
+ },
133
+ };
134
+ }
135
+ if (response.additionalContext) {
136
+ return {
137
+ hookSpecificOutput: {
138
+ hookEventName: this.hookModule.HOOK_TYPES.POST_TOOL_USE,
139
+ additionalContext: response.additionalContext,
140
+ },
141
+ };
142
+ }
143
+ return undefined;
144
+ }
145
+ formatPreCompactResponse(response) {
146
+ return response.context ?? "";
147
+ }
148
+ formatSessionStartResponse(response) {
149
+ return response.context ?? "";
150
+ }
151
+ // ── Configuration (shared) ─────────────────────────────
152
+ getSettingsPath() {
153
+ return resolve(".github", "hooks", "context-mode.json");
154
+ }
155
+ generateHookConfig(pluginRoot) {
156
+ const { HOOK_TYPES, buildHookCommand } = this.hookModule;
157
+ return {
158
+ [HOOK_TYPES.PRE_TOOL_USE]: [
159
+ {
160
+ matcher: "",
161
+ hooks: [
162
+ {
163
+ type: "command",
164
+ command: buildHookCommand(HOOK_TYPES.PRE_TOOL_USE, pluginRoot),
165
+ },
166
+ ],
167
+ },
168
+ ],
169
+ [HOOK_TYPES.POST_TOOL_USE]: [
170
+ {
171
+ matcher: "",
172
+ hooks: [
173
+ {
174
+ type: "command",
175
+ command: buildHookCommand(HOOK_TYPES.POST_TOOL_USE, pluginRoot),
176
+ },
177
+ ],
178
+ },
179
+ ],
180
+ [HOOK_TYPES.PRE_COMPACT]: [
181
+ {
182
+ matcher: "",
183
+ hooks: [
184
+ {
185
+ type: "command",
186
+ command: buildHookCommand(HOOK_TYPES.PRE_COMPACT, pluginRoot),
187
+ },
188
+ ],
189
+ },
190
+ ],
191
+ [HOOK_TYPES.SESSION_START]: [
192
+ {
193
+ matcher: "",
194
+ hooks: [
195
+ {
196
+ type: "command",
197
+ command: buildHookCommand(HOOK_TYPES.SESSION_START, pluginRoot),
198
+ },
199
+ ],
200
+ },
201
+ ],
202
+ };
203
+ }
204
+ readSettings() {
205
+ // Primary: .github/hooks/context-mode.json
206
+ try {
207
+ const raw = readFileSync(this.getSettingsPath(), "utf-8");
208
+ return JSON.parse(raw);
209
+ }
210
+ catch {
211
+ /* fall through */
212
+ }
213
+ // Fallback: .claude/settings.json
214
+ try {
215
+ const raw = readFileSync(resolve(".claude", "settings.json"), "utf-8");
216
+ return JSON.parse(raw);
217
+ }
218
+ catch {
219
+ return null;
220
+ }
221
+ }
222
+ writeSettings(settings) {
223
+ const configPath = this.getSettingsPath();
224
+ mkdirSync(resolve(".github", "hooks"), { recursive: true });
225
+ writeFileSync(configPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
226
+ }
227
+ // ── Upgrade (shared) ──────────────────────────────────
228
+ configureAllHooks(pluginRoot) {
229
+ const changes = [];
230
+ const settings = this.readSettings() ?? {};
231
+ const hooks = settings.hooks ?? {};
232
+ const { HOOK_TYPES, HOOK_SCRIPTS, buildHookCommand } = this.hookModule;
233
+ const hookTypes = [
234
+ HOOK_TYPES.PRE_TOOL_USE,
235
+ HOOK_TYPES.POST_TOOL_USE,
236
+ HOOK_TYPES.PRE_COMPACT,
237
+ HOOK_TYPES.SESSION_START,
238
+ ];
239
+ for (const hookType of hookTypes) {
240
+ const script = HOOK_SCRIPTS[hookType];
241
+ if (!script)
242
+ continue;
243
+ hooks[hookType] = [
244
+ {
245
+ matcher: "",
246
+ hooks: [
247
+ {
248
+ type: "command",
249
+ command: buildHookCommand(hookType, pluginRoot),
250
+ },
251
+ ],
252
+ },
253
+ ];
254
+ changes.push(`Configured ${hookType} hook`);
255
+ }
256
+ settings.hooks = hooks;
257
+ this.writeSettings(settings);
258
+ changes.push(`Wrote hook config to ${this.getSettingsPath()}`);
259
+ return changes;
260
+ }
261
+ setHookPermissions(pluginRoot) {
262
+ const set = [];
263
+ const hooksDir = join(pluginRoot, "hooks", this.hookSubdir);
264
+ for (const scriptName of Object.values(this.hookModule.HOOK_SCRIPTS)) {
265
+ const scriptPath = resolve(hooksDir, scriptName);
266
+ try {
267
+ accessSync(scriptPath, constants.R_OK);
268
+ chmodSync(scriptPath, 0o755);
269
+ set.push(scriptPath);
270
+ }
271
+ catch {
272
+ /* skip missing scripts */
273
+ }
274
+ }
275
+ return set;
276
+ }
277
+ updatePluginRegistry(_pluginRoot, _version) {
278
+ // Copilot platforms manage plugins through their own marketplaces.
279
+ // No manual registry update needed.
280
+ }
281
+ }
@@ -4,6 +4,7 @@
4
4
  * Native Cursor hooks use lower-camel hook names and flat command entries in
5
5
  * `.cursor/hooks.json` / `~/.cursor/hooks.json`.
6
6
  */
7
+ import { BaseAdapter } from "../base.js";
7
8
  import type { HookAdapter, HookParadigm, PlatformCapabilities, DiagnosticResult, PreToolUseEvent, PostToolUseEvent, SessionStartEvent, PreToolUseResponse, PostToolUseResponse, SessionStartResponse, HookRegistration } from "../types.js";
8
9
  interface StopEvent {
9
10
  sessionId: string;
@@ -12,7 +13,8 @@ interface StopEvent {
12
13
  generationId?: string;
13
14
  transcriptPath?: string;
14
15
  }
15
- export declare class CursorAdapter implements HookAdapter {
16
+ export declare class CursorAdapter extends BaseAdapter implements HookAdapter {
17
+ constructor();
16
18
  readonly name = "Cursor";
17
19
  readonly paradigm: HookParadigm;
18
20
  readonly capabilities: PlatformCapabilities;
@@ -30,9 +32,6 @@ export declare class CursorAdapter implements HookAdapter {
30
32
  text: string;
31
33
  };
32
34
  getSettingsPath(): string;
33
- getSessionDir(): string;
34
- getSessionDBPath(projectDir: string): string;
35
- getSessionEventsPath(projectDir: string): string;
36
35
  generateHookConfig(_pluginRoot: string): HookRegistration;
37
36
  readSettings(): Record<string, unknown> | null;
38
37
  writeSettings(settings: Record<string, unknown>): void;
@@ -40,7 +39,6 @@ export declare class CursorAdapter implements HookAdapter {
40
39
  checkPluginRegistration(): DiagnosticResult;
41
40
  getInstalledVersion(): string;
42
41
  configureAllHooks(_pluginRoot: string): string[];
43
- backupSettings(): string | null;
44
42
  setHookPermissions(pluginRoot: string): string[];
45
43
  updatePluginRegistry(_pluginRoot: string, _version: string): void;
46
44
  private getCandidateHookConfigPaths;
@@ -4,14 +4,17 @@
4
4
  * Native Cursor hooks use lower-camel hook names and flat command entries in
5
5
  * `.cursor/hooks.json` / `~/.cursor/hooks.json`.
6
6
  */
7
- import { createHash } from "node:crypto";
8
- import { readFileSync, writeFileSync, mkdirSync, copyFileSync, accessSync, chmodSync, constants, existsSync, } from "node:fs";
7
+ import { readFileSync, writeFileSync, mkdirSync, accessSync, chmodSync, constants, existsSync, } from "node:fs";
9
8
  import { execSync } from "node:child_process";
10
9
  import { resolve, join } from "node:path";
11
10
  import { homedir } from "node:os";
11
+ import { BaseAdapter } from "../base.js";
12
12
  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";
13
13
  const CURSOR_ENTERPRISE_HOOKS_PATH = "/Library/Application Support/Cursor/hooks.json";
14
- export class CursorAdapter {
14
+ export class CursorAdapter extends BaseAdapter {
15
+ constructor() {
16
+ super([".cursor"]);
17
+ }
15
18
  name = "Cursor";
16
19
  paradigm = "json-stdio";
17
20
  capabilities = {
@@ -125,25 +128,6 @@ export class CursorAdapter {
125
128
  getSettingsPath() {
126
129
  return resolve(".cursor", "hooks.json");
127
130
  }
128
- getSessionDir() {
129
- const dir = join(homedir(), ".cursor", "context-mode", "sessions");
130
- mkdirSync(dir, { recursive: true });
131
- return dir;
132
- }
133
- getSessionDBPath(projectDir) {
134
- const hash = createHash("sha256")
135
- .update(projectDir)
136
- .digest("hex")
137
- .slice(0, 16);
138
- return join(this.getSessionDir(), `${hash}.db`);
139
- }
140
- getSessionEventsPath(projectDir) {
141
- const hash = createHash("sha256")
142
- .update(projectDir)
143
- .digest("hex")
144
- .slice(0, 16);
145
- return join(this.getSessionDir(), `${hash}-events.md`);
146
- }
147
131
  generateHookConfig(_pluginRoot) {
148
132
  const hooks = {
149
133
  [CURSOR_HOOK_NAMES.PRE_TOOL_USE]: [
@@ -355,18 +339,6 @@ export class CursorAdapter {
355
339
  changes.push(`Wrote native Cursor hooks to ${this.getSettingsPath()}`);
356
340
  return changes;
357
341
  }
358
- backupSettings() {
359
- const settingsPath = this.getSettingsPath();
360
- try {
361
- accessSync(settingsPath, constants.R_OK);
362
- const backupPath = settingsPath + ".bak";
363
- copyFileSync(settingsPath, backupPath);
364
- return backupPath;
365
- }
366
- catch {
367
- return null;
368
- }
369
- }
370
342
  setHookPermissions(pluginRoot) {
371
343
  const set = [];
372
344
  const hooksDir = join(pluginRoot, "hooks", "cursor");
@@ -15,8 +15,15 @@
15
15
  * - Codex CLI: CODEX_CI, CODEX_THREAD_ID | ~/.codex/
16
16
  * - Cursor: CURSOR_TRACE_ID (MCP), CURSOR_CLI (terminal) | ~/.cursor/
17
17
  * - VS Code Copilot: VSCODE_PID, VSCODE_CWD | ~/.vscode/
18
+ * - JetBrains Copilot: IDEA_INITIAL_DIRECTORY, IDEA_HOME, JETBRAINS_CLIENT_ID | ~/.config/JetBrains/
18
19
  */
19
20
  import type { PlatformId, DetectionSignal, HookAdapter } from "./types.js";
21
+ /**
22
+ * High-confidence env vars per platform, checked in priority order.
23
+ * Single source of truth — consumed by detectPlatform() below and by
24
+ * tests that need to clear platform-related env vars deterministically.
25
+ */
26
+ export declare const PLATFORM_ENV_VARS: readonly [readonly ["claude-code", readonly ["CLAUDE_PROJECT_DIR", "CLAUDE_SESSION_ID"]], readonly ["gemini-cli", readonly ["GEMINI_PROJECT_DIR", "GEMINI_CLI"]], readonly ["openclaw", readonly ["OPENCLAW_HOME", "OPENCLAW_CLI"]], readonly ["kilo", readonly ["KILO", "KILO_PID"]], readonly ["opencode", readonly ["OPENCODE", "OPENCODE_PID"]], readonly ["codex", readonly ["CODEX_CI", "CODEX_THREAD_ID"]], readonly ["cursor", readonly ["CURSOR_TRACE_ID", "CURSOR_CLI"]], readonly ["vscode-copilot", readonly ["VSCODE_PID", "VSCODE_CWD"]], readonly ["jetbrains-copilot", readonly ["IDEA_INITIAL_DIRECTORY", "IDEA_HOME", "JETBRAINS_CLIENT_ID"]], readonly ["qwen-code", readonly ["QWEN_PROJECT_DIR", "QWEN_SESSION_ID"]]];
20
27
  /**
21
28
  * Detect the current platform by checking env vars and config dirs.
22
29
  *