context-mode 1.0.89 → 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 (128) 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 +12 -140
  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 +13 -0
  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/runtime.js +24 -9
  54. package/build/security.d.ts +17 -1
  55. package/build/security.js +40 -6
  56. package/build/server.js +41 -9
  57. package/build/session/analytics.d.ts +8 -7
  58. package/build/session/analytics.js +95 -75
  59. package/build/session/db.d.ts +10 -1
  60. package/build/session/db.js +67 -8
  61. package/build/session/extract.js +10 -2
  62. package/build/session/project-attribution.d.ts +73 -0
  63. package/build/session/project-attribution.js +231 -0
  64. package/build/store.d.ts +4 -0
  65. package/build/store.js +58 -9
  66. package/build/types.d.ts +8 -0
  67. package/cli.bundle.mjs +135 -121
  68. package/configs/antigravity/GEMINI.md +31 -36
  69. package/configs/claude-code/CLAUDE.md +31 -37
  70. package/configs/codex/AGENTS.md +35 -49
  71. package/configs/cursor/context-mode.mdc +24 -25
  72. package/configs/gemini-cli/GEMINI.md +30 -36
  73. package/configs/jetbrains-copilot/copilot-instructions.md +59 -0
  74. package/configs/jetbrains-copilot/hooks.json +16 -0
  75. package/configs/jetbrains-copilot/mcp.json +8 -0
  76. package/configs/kilo/AGENTS.md +30 -36
  77. package/configs/kiro/KIRO.md +30 -36
  78. package/configs/kiro/agent.json +1 -1
  79. package/configs/openclaw/AGENTS.md +30 -36
  80. package/configs/opencode/AGENTS.md +30 -36
  81. package/configs/pi/AGENTS.md +31 -36
  82. package/configs/qwen-code/QWEN.md +63 -0
  83. package/configs/vscode-copilot/copilot-instructions.md +30 -36
  84. package/configs/zed/AGENTS.md +31 -36
  85. package/hooks/codex/posttooluse.mjs +7 -7
  86. package/hooks/codex/pretooluse.mjs +3 -3
  87. package/hooks/codex/sessionstart.mjs +2 -1
  88. package/hooks/core/formatters.mjs +24 -0
  89. package/hooks/core/routing.mjs +40 -15
  90. package/hooks/core/tool-naming.mjs +2 -0
  91. package/hooks/cursor/posttooluse.mjs +7 -7
  92. package/hooks/cursor/pretooluse.mjs +3 -3
  93. package/hooks/cursor/sessionstart.mjs +2 -1
  94. package/hooks/cursor/stop.mjs +2 -2
  95. package/hooks/ensure-deps.mjs +22 -10
  96. package/hooks/gemini-cli/aftertool.mjs +8 -8
  97. package/hooks/gemini-cli/beforetool.mjs +3 -2
  98. package/hooks/gemini-cli/precompress.mjs +2 -2
  99. package/hooks/gemini-cli/sessionstart.mjs +12 -4
  100. package/hooks/jetbrains-copilot/posttooluse.mjs +61 -0
  101. package/hooks/jetbrains-copilot/precompact.mjs +54 -0
  102. package/hooks/jetbrains-copilot/pretooluse.mjs +27 -0
  103. package/hooks/jetbrains-copilot/sessionstart.mjs +119 -0
  104. package/hooks/kiro/posttooluse.mjs +6 -7
  105. package/hooks/kiro/pretooluse.mjs +3 -2
  106. package/hooks/posttooluse.mjs +8 -8
  107. package/hooks/precompact.mjs +3 -4
  108. package/hooks/pretooluse.mjs +5 -4
  109. package/hooks/routing-block.mjs +35 -33
  110. package/hooks/session-attribution.bundle.mjs +1 -0
  111. package/hooks/session-db.bundle.mjs +27 -8
  112. package/hooks/session-extract.bundle.mjs +2 -1
  113. package/hooks/session-helpers.mjs +44 -3
  114. package/hooks/session-loaders.mjs +37 -0
  115. package/hooks/sessionstart.mjs +5 -5
  116. package/hooks/userpromptsubmit.mjs +26 -9
  117. package/hooks/vscode-copilot/posttooluse.mjs +8 -8
  118. package/hooks/vscode-copilot/precompact.mjs +2 -2
  119. package/hooks/vscode-copilot/pretooluse.mjs +3 -2
  120. package/hooks/vscode-copilot/sessionstart.mjs +2 -2
  121. package/insight/server.mjs +237 -25
  122. package/insight/src/lib/api.ts +2 -1
  123. package/insight/src/routes/index.tsx +16 -3
  124. package/insight/src/routes/search.tsx +1 -1
  125. package/openclaw.plugin.json +1 -1
  126. package/package.json +11 -2
  127. package/server.bundle.mjs +94 -80
  128. package/skills/ctx-insight/SKILL.md +1 -1
@@ -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
  *
@@ -15,11 +15,29 @@
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 { existsSync } from "node:fs";
20
21
  import { resolve } from "node:path";
21
22
  import { homedir } from "node:os";
22
23
  import { CLIENT_NAME_TO_PLATFORM } from "./client-map.js";
24
+ /**
25
+ * High-confidence env vars per platform, checked in priority order.
26
+ * Single source of truth — consumed by detectPlatform() below and by
27
+ * tests that need to clear platform-related env vars deterministically.
28
+ */
29
+ export const PLATFORM_ENV_VARS = [
30
+ ["claude-code", ["CLAUDE_PROJECT_DIR", "CLAUDE_SESSION_ID"]],
31
+ ["gemini-cli", ["GEMINI_PROJECT_DIR", "GEMINI_CLI"]],
32
+ ["openclaw", ["OPENCLAW_HOME", "OPENCLAW_CLI"]],
33
+ ["kilo", ["KILO", "KILO_PID"]],
34
+ ["opencode", ["OPENCODE", "OPENCODE_PID"]],
35
+ ["codex", ["CODEX_CI", "CODEX_THREAD_ID"]],
36
+ ["cursor", ["CURSOR_TRACE_ID", "CURSOR_CLI"]],
37
+ ["vscode-copilot", ["VSCODE_PID", "VSCODE_CWD"]],
38
+ ["jetbrains-copilot", ["IDEA_INITIAL_DIRECTORY", "IDEA_HOME", "JETBRAINS_CLIENT_ID"]],
39
+ ["qwen-code", ["QWEN_PROJECT_DIR", "QWEN_SESSION_ID"]],
40
+ ];
23
41
  /**
24
42
  * Detect the current platform by checking env vars and config dirs.
25
43
  *
@@ -37,13 +55,21 @@ export function detectPlatform(clientInfo) {
37
55
  reason: `MCP clientInfo.name="${clientInfo.name}"`,
38
56
  };
39
57
  }
58
+ // Qwen Code uses dynamic client names: qwen-cli-mcp-client-<serverName>
59
+ if (clientInfo.name.startsWith("qwen-cli-mcp-client")) {
60
+ return {
61
+ platform: "qwen-code",
62
+ confidence: "high",
63
+ reason: `MCP clientInfo.name="${clientInfo.name}" (qwen-cli pattern)`,
64
+ };
65
+ }
40
66
  }
41
67
  // ── Explicit platform override ────────────────────────
42
68
  const platformOverride = process.env.CONTEXT_MODE_PLATFORM;
43
69
  if (platformOverride) {
44
70
  const validPlatforms = [
45
71
  "claude-code", "gemini-cli", "kilo", "opencode", "codex",
46
- "vscode-copilot", "cursor", "antigravity", "kiro", "pi", "zed",
72
+ "vscode-copilot", "jetbrains-copilot", "cursor", "antigravity", "kiro", "pi", "zed", "qwen-code",
47
73
  ];
48
74
  if (validPlatforms.includes(platformOverride)) {
49
75
  return {
@@ -54,61 +80,14 @@ export function detectPlatform(clientInfo) {
54
80
  }
55
81
  }
56
82
  // ── High confidence: environment variables ─────────────
57
- if (process.env.CLAUDE_PROJECT_DIR || process.env.CLAUDE_SESSION_ID) {
58
- return {
59
- platform: "claude-code",
60
- confidence: "high",
61
- reason: "CLAUDE_PROJECT_DIR or CLAUDE_SESSION_ID env var set",
62
- };
63
- }
64
- if (process.env.GEMINI_PROJECT_DIR || process.env.GEMINI_CLI) {
65
- return {
66
- platform: "gemini-cli",
67
- confidence: "high",
68
- reason: "GEMINI_PROJECT_DIR or GEMINI_CLI env var set",
69
- };
70
- }
71
- if (process.env.OPENCLAW_HOME || process.env.OPENCLAW_CLI) {
72
- return {
73
- platform: "openclaw",
74
- confidence: "high",
75
- reason: "OPENCLAW_HOME or OPENCLAW_CLI env var set",
76
- };
77
- }
78
- if (process.env.KILO || process.env.KILO_PID) {
79
- return {
80
- platform: "kilo",
81
- confidence: "high",
82
- reason: "KILO or KILO_PID env var set",
83
- };
84
- }
85
- if (process.env.OPENCODE || process.env.OPENCODE_PID) {
86
- return {
87
- platform: "opencode",
88
- confidence: "high",
89
- reason: "OPENCODE or OPENCODE_PID env var set",
90
- };
91
- }
92
- if (process.env.CODEX_CI || process.env.CODEX_THREAD_ID) {
93
- return {
94
- platform: "codex",
95
- confidence: "high",
96
- reason: "CODEX_CI or CODEX_THREAD_ID env var set",
97
- };
98
- }
99
- if (process.env.CURSOR_TRACE_ID || process.env.CURSOR_CLI) {
100
- return {
101
- platform: "cursor",
102
- confidence: "high",
103
- reason: "CURSOR_TRACE_ID or CURSOR_CLI env var set",
104
- };
105
- }
106
- if (process.env.VSCODE_PID || process.env.VSCODE_CWD) {
107
- return {
108
- platform: "vscode-copilot",
109
- confidence: "high",
110
- reason: "VSCODE_PID or VSCODE_CWD env var set",
111
- };
83
+ for (const [platform, vars] of PLATFORM_ENV_VARS) {
84
+ if (vars.some((v) => process.env[v])) {
85
+ return {
86
+ platform,
87
+ confidence: "high",
88
+ reason: `${vars.join(" or ")} env var set`,
89
+ };
90
+ }
112
91
  }
113
92
  // ── Medium confidence: config directory existence ──────
114
93
  const home = homedir();
@@ -154,6 +133,13 @@ export function detectPlatform(clientInfo) {
154
133
  reason: "~/.pi/ directory exists",
155
134
  };
156
135
  }
136
+ if (existsSync(resolve(home, ".qwen"))) {
137
+ return {
138
+ platform: "qwen-code",
139
+ confidence: "medium",
140
+ reason: "~/.qwen/ directory exists",
141
+ };
142
+ }
157
143
  if (existsSync(resolve(home, ".openclaw"))) {
158
144
  return {
159
145
  platform: "openclaw",
@@ -168,6 +154,13 @@ export function detectPlatform(clientInfo) {
168
154
  reason: "~/.config/kilo/ directory exists",
169
155
  };
170
156
  }
157
+ if (existsSync(resolve(home, ".config", "JetBrains"))) {
158
+ return {
159
+ platform: "jetbrains-copilot",
160
+ confidence: "medium",
161
+ reason: "~/.config/JetBrains/ directory exists",
162
+ };
163
+ }
171
164
  if (existsSync(resolve(home, ".config", "opencode"))) {
172
165
  return {
173
166
  platform: "opencode",
@@ -221,6 +214,10 @@ export async function getAdapter(platform) {
221
214
  const { VSCodeCopilotAdapter } = await import("./vscode-copilot/index.js");
222
215
  return new VSCodeCopilotAdapter();
223
216
  }
217
+ case "jetbrains-copilot": {
218
+ const { JetBrainsCopilotAdapter } = await import("./jetbrains-copilot/index.js");
219
+ return new JetBrainsCopilotAdapter();
220
+ }
224
221
  case "cursor": {
225
222
  const { CursorAdapter } = await import("./cursor/index.js");
226
223
  return new CursorAdapter();
@@ -237,6 +234,10 @@ export async function getAdapter(platform) {
237
234
  const { ZedAdapter } = await import("./zed/index.js");
238
235
  return new ZedAdapter();
239
236
  }
237
+ case "qwen-code": {
238
+ const { QwenCodeAdapter } = await import("./qwen-code/index.js");
239
+ return new QwenCodeAdapter();
240
+ }
240
241
  default: {
241
242
  // Unsupported platform — fall back to Claude Code adapter
242
243
  // (MCP server works everywhere, hooks may not)
@@ -18,8 +18,10 @@
18
18
  * - Project dir env: GEMINI_PROJECT_DIR (also CLAUDE_PROJECT_DIR alias)
19
19
  * - Session dir: ~/.gemini/context-mode/sessions/
20
20
  */
21
+ import { BaseAdapter } from "../base.js";
21
22
  import type { HookAdapter, HookParadigm, PlatformCapabilities, DiagnosticResult, PreToolUseEvent, PostToolUseEvent, PreCompactEvent, SessionStartEvent, PreToolUseResponse, PostToolUseResponse, PreCompactResponse, SessionStartResponse, HookRegistration } from "../types.js";
22
- export declare class GeminiCLIAdapter implements HookAdapter {
23
+ export declare class GeminiCLIAdapter extends BaseAdapter implements HookAdapter {
24
+ constructor();
23
25
  readonly name = "Gemini CLI";
24
26
  readonly paradigm: HookParadigm;
25
27
  readonly capabilities: PlatformCapabilities;
@@ -32,9 +34,6 @@ export declare class GeminiCLIAdapter implements HookAdapter {
32
34
  formatPreCompactResponse(response: PreCompactResponse): unknown;
33
35
  formatSessionStartResponse(response: SessionStartResponse): unknown;
34
36
  getSettingsPath(): string;
35
- getSessionDir(): string;
36
- getSessionDBPath(projectDir: string): string;
37
- getSessionEventsPath(projectDir: string): string;
38
37
  generateHookConfig(pluginRoot: string): HookRegistration;
39
38
  readSettings(): Record<string, unknown> | null;
40
39
  writeSettings(settings: Record<string, unknown>): void;
@@ -42,7 +41,6 @@ export declare class GeminiCLIAdapter implements HookAdapter {
42
41
  checkPluginRegistration(): DiagnosticResult;
43
42
  getInstalledVersion(): string;
44
43
  configureAllHooks(pluginRoot: string): string[];
45
- backupSettings(): string | null;
46
44
  setHookPermissions(pluginRoot: string): string[];
47
45
  updatePluginRegistry(pluginRoot: string, version: string): void;
48
46
  /** Get the project directory from environment variables. */