codiedev 0.6.0 → 0.7.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.
@@ -76,6 +76,14 @@ function hasCursorMcpEntry() {
76
76
  return false;
77
77
  }
78
78
  }
79
+ // Match both legacy `npx codiedev-hook ...` and absolute-path forms
80
+ // `<node> <.../codiedev/dist/hook.js> ...` — installer switched to absolute
81
+ // paths in 0.6.1 to work in GUI-launched contexts where shell PATH is missing.
82
+ function isCodiedevHookCommand(cmd) {
83
+ if (!cmd)
84
+ return false;
85
+ return cmd.includes("codiedev-hook") || /codiedev[\\/]dist[\\/]hook/.test(cmd);
86
+ }
79
87
  // Cursor's hooks.json schema is { hooks: { sessionEnd: [{ command, ... }] } }
80
88
  // — flat array of objects with `command`, not the Claude/Codex nested
81
89
  // `{ hooks: [{ command }] }` wrapper.
@@ -88,10 +96,7 @@ function hasCursorHook() {
88
96
  const arr = parsed.hooks?.sessionEnd;
89
97
  if (!Array.isArray(arr))
90
98
  return false;
91
- return arr.some((h) => {
92
- const cmd = h.command;
93
- return (cmd ?? "").includes("codiedev-hook");
94
- });
99
+ return arr.some((h) => isCodiedevHookCommand(h.command));
95
100
  }
96
101
  catch {
97
102
  return false;
@@ -110,7 +115,7 @@ function hasCodiedevHook(settingsPath, hookKey) {
110
115
  const inner = h.hooks;
111
116
  if (!Array.isArray(inner))
112
117
  return false;
113
- return inner.some((x) => (x.command ?? "").includes("codiedev-hook"));
118
+ return inner.some((x) => isCodiedevHookCommand(x.command));
114
119
  });
115
120
  }
116
121
  catch {
package/dist/connect.js CHANGED
@@ -142,7 +142,9 @@ async function runConnect() {
142
142
  const hasClaude = (0, utils_1.claudeCodeInstalled)();
143
143
  const hasCodex = (0, utils_1.codexInstalled)();
144
144
  const hasCursor = (0, utils_1.cursorInstalled)();
145
+ const hasVSCodeCopilot = (0, utils_1.vscodeCopilotInstalled)();
145
146
  const installed = [];
147
+ let vscodeMcpInstalled = false;
146
148
  if (hasClaude) {
147
149
  try {
148
150
  (0, utils_1.installHook)();
@@ -212,8 +214,18 @@ async function runConnect() {
212
214
  console.error(`\nWarning: Failed to install Cursor MCP server — ${err.message}`);
213
215
  }
214
216
  }
215
- if (!hasClaude && !hasCodex && !hasCursor) {
216
- console.warn("\nNo Claude Code (~/.claude), Codex (~/.codex), or Cursor (~/.cursor) install detected.");
217
+ if (hasVSCodeCopilot) {
218
+ try {
219
+ (0, utils_1.installVSCodeMcp)();
220
+ installed.push("VS Code Copilot MCP server (~/Library/Application Support/Code/User/mcp.json)");
221
+ vscodeMcpInstalled = true;
222
+ }
223
+ catch (err) {
224
+ console.error(`\nWarning: Failed to install VS Code Copilot MCP server — ${err.message}`);
225
+ }
226
+ }
227
+ if (!hasClaude && !hasCodex && !hasCursor && !hasVSCodeCopilot) {
228
+ console.warn("\nNo Claude Code (~/.claude), Codex (~/.codex), Cursor (~/.cursor), or VS Code Copilot install detected.");
217
229
  console.warn("Config saved. Install one, then re-run `npx codiedev connect` to wire up capture hooks.");
218
230
  }
219
231
  console.log(`\nConnected to ${companyName}`);
@@ -225,6 +237,13 @@ async function runConnect() {
225
237
  }
226
238
  console.log("Sessions will be captured automatically.");
227
239
  }
240
+ if (vscodeMcpInstalled) {
241
+ console.log();
242
+ console.log("VS Code: open Copilot Chat — it will prompt you to trust the codiedev MCP server.");
243
+ console.log("Click Allow once, then ask Copilot: \"create a reverse ticket from my current changes\".");
244
+ console.log("Note: Copilot doesn't expose chat transcripts to MCP, so on-demand tools (reverse_ticket,");
245
+ console.log("push, pull, ask, ping) work fully — auto-captured decisions need Claude Code or Codex.");
246
+ }
228
247
  console.log();
229
248
  console.log("Run `codiedev doctor` to verify everything's wired up.");
230
249
  console.log();
package/dist/utils.d.ts CHANGED
@@ -27,6 +27,12 @@ export declare function hashToken(token: string): string;
27
27
  export declare function claudeCodeInstalled(): boolean;
28
28
  export declare function codexInstalled(): boolean;
29
29
  export declare function cursorInstalled(): boolean;
30
+ /**
31
+ * VS Code with the GitHub Copilot Chat extension installed. The extension
32
+ * directory pattern is `~/.vscode/extensions/github.copilot-chat-*`. We treat
33
+ * the presence of any `github.copilot*` directory as the Copilot fingerprint.
34
+ */
35
+ export declare function vscodeCopilotInstalled(): boolean;
30
36
  export declare function installHook(): void;
31
37
  export declare function installClaudeCodeInstructions(): void;
32
38
  export declare function installCodexInstructions(): void;
@@ -43,6 +49,18 @@ export declare function installClaudeCodeMcp(): void;
43
49
  * <project>/.cursor/mcp.json — we only manage the user-scope one.
44
50
  */
45
51
  export declare function installCursorMcp(): void;
52
+ /**
53
+ * Install the CodieDev MCP server into VS Code's user-scope MCP config so
54
+ * GitHub Copilot Chat picks it up. VS Code uses a different schema than
55
+ * Claude / Cursor: top-level key is `servers` (not `mcpServers`). On first
56
+ * Copilot Chat run after install, VS Code prompts the user to trust the
57
+ * server — that's expected and documented in the connect success message.
58
+ *
59
+ * Reverse-ticket and the other on-demand verbs work in Copilot identically
60
+ * to Claude Code since the MCP server's reverse_ticket handler is already
61
+ * 100% diff-driven (no transcript dependency).
62
+ */
63
+ export declare function installVSCodeMcp(): void;
46
64
  /**
47
65
  * Best-effort append of the CodieDev MCP server block to ~/.codex/config.toml.
48
66
  *
package/dist/utils.js CHANGED
@@ -42,12 +42,14 @@ exports.hashToken = hashToken;
42
42
  exports.claudeCodeInstalled = claudeCodeInstalled;
43
43
  exports.codexInstalled = codexInstalled;
44
44
  exports.cursorInstalled = cursorInstalled;
45
+ exports.vscodeCopilotInstalled = vscodeCopilotInstalled;
45
46
  exports.installHook = installHook;
46
47
  exports.installClaudeCodeInstructions = installClaudeCodeInstructions;
47
48
  exports.installCodexInstructions = installCodexInstructions;
48
49
  exports.installCursorInstructions = installCursorInstructions;
49
50
  exports.installClaudeCodeMcp = installClaudeCodeMcp;
50
51
  exports.installCursorMcp = installCursorMcp;
52
+ exports.installVSCodeMcp = installVSCodeMcp;
51
53
  exports.installCodexMcp = installCodexMcp;
52
54
  exports.installCodexHook = installCodexHook;
53
55
  exports.installCursorHook = installCursorHook;
@@ -142,6 +144,52 @@ const CURSOR_HOOKS_PATH = path.join(CURSOR_DIR, "hooks.json");
142
144
  const CURSOR_MCP_PATH = path.join(CURSOR_DIR, "mcp.json");
143
145
  const CURSOR_RULES_DIR = path.join(CURSOR_DIR, "rules");
144
146
  const CURSOR_RULES_PATH = path.join(CURSOR_RULES_DIR, "codiedev.mdc");
147
+ // VS Code Copilot Chat: detected by the github.copilot* extension dir, then
148
+ // MCP wired via VS Code's user-scope mcp.json (top-level key is "servers",
149
+ // not "mcpServers" like Claude/Cursor).
150
+ const VSCODE_EXTENSIONS_DIR = path.join(os.homedir(), ".vscode", "extensions");
151
+ const VSCODE_USER_DIR = (() => {
152
+ const home = os.homedir();
153
+ if (process.platform === "darwin") {
154
+ return path.join(home, "Library", "Application Support", "Code", "User");
155
+ }
156
+ if (process.platform === "win32") {
157
+ const appData = process.env.APPDATA ?? path.join(home, "AppData", "Roaming");
158
+ return path.join(appData, "Code", "User");
159
+ }
160
+ return path.join(home, ".config", "Code", "User");
161
+ })();
162
+ const VSCODE_USER_MCP_PATH = path.join(VSCODE_USER_DIR, "mcp.json");
163
+ // GUI-launched agents (Cursor.app, future JetBrains plugins) don't source the
164
+ // user's shell rc, so nvm-managed `npx` and `node` aren't on PATH. Resolve the
165
+ // absolute path to the current node binary and our hook.js at install time and
166
+ // write that into hook configs so execution doesn't depend on the spawned
167
+ // shell's PATH. Falls back to `npx` if we can't resolve dist/hook.js.
168
+ function shellQuote(s) {
169
+ return `'${s.replace(/'/g, `'\\''`)}'`;
170
+ }
171
+ function resolveHookCommand(subcommand) {
172
+ try {
173
+ const cliEntry = process.argv[1];
174
+ if (cliEntry) {
175
+ const realCli = fs.realpathSync(cliEntry);
176
+ const distDir = path.dirname(realCli);
177
+ const hookScript = path.join(distDir, "hook.js");
178
+ if (fs.existsSync(hookScript)) {
179
+ return `${shellQuote(process.execPath)} ${shellQuote(hookScript)} ${subcommand}`;
180
+ }
181
+ }
182
+ }
183
+ catch {
184
+ // Fall through to npx fallback.
185
+ }
186
+ return `npx codiedev-hook ${subcommand}`;
187
+ }
188
+ function isCodiedevHookCommand(cmd) {
189
+ if (!cmd)
190
+ return false;
191
+ return cmd.includes("codiedev-hook") || /codiedev[\\/]dist[\\/]hook/.test(cmd);
192
+ }
145
193
  function claudeCodeInstalled() {
146
194
  return fs.existsSync(CLAUDE_DIR);
147
195
  }
@@ -151,6 +199,23 @@ function codexInstalled() {
151
199
  function cursorInstalled() {
152
200
  return fs.existsSync(CURSOR_DIR);
153
201
  }
202
+ /**
203
+ * VS Code with the GitHub Copilot Chat extension installed. The extension
204
+ * directory pattern is `~/.vscode/extensions/github.copilot-chat-*`. We treat
205
+ * the presence of any `github.copilot*` directory as the Copilot fingerprint.
206
+ */
207
+ function vscodeCopilotInstalled() {
208
+ if (!fs.existsSync(VSCODE_EXTENSIONS_DIR))
209
+ return false;
210
+ try {
211
+ return fs
212
+ .readdirSync(VSCODE_EXTENSIONS_DIR)
213
+ .some((name) => name.startsWith("github.copilot"));
214
+ }
215
+ catch {
216
+ return false;
217
+ }
218
+ }
154
219
  function installHook() {
155
220
  let settings = {};
156
221
  try {
@@ -169,30 +234,27 @@ function installHook() {
169
234
  if (!hooks.SessionEnd) {
170
235
  hooks.SessionEnd = [];
171
236
  }
172
- const sessionEndHooks = hooks.SessionEnd;
173
- const alreadyInstalled = sessionEndHooks.some((hook) => {
174
- const matcher = hook.matcher;
175
- const hooks2 = hook.hooks;
176
- if (hooks2) {
177
- return hooks2.some((h) => {
178
- const cmd = h.command;
179
- return cmd && cmd.includes("codiedev-hook");
180
- });
237
+ // Drop any prior codiedev entries (legacy `npx` form or older absolute
238
+ // paths) before re-adding so connect re-runs upgrade the resolved binary
239
+ // path instead of leaving stale entries behind.
240
+ const existing = hooks.SessionEnd;
241
+ const filtered = existing.filter((hook) => {
242
+ const inner = hook.hooks;
243
+ if (Array.isArray(inner)) {
244
+ return !inner.some((h) => isCodiedevHookCommand(h.command));
181
245
  }
182
- return matcher && matcher.includes("codiedev-hook");
246
+ return !isCodiedevHookCommand(hook.matcher);
183
247
  });
184
- if (alreadyInstalled) {
185
- return;
186
- }
187
- sessionEndHooks.push({
248
+ filtered.push({
188
249
  matcher: ".*",
189
250
  hooks: [
190
251
  {
191
252
  type: "command",
192
- command: "npx codiedev-hook capture",
253
+ command: resolveHookCommand("capture"),
193
254
  },
194
255
  ],
195
256
  });
257
+ hooks.SessionEnd = filtered;
196
258
  if (!fs.existsSync(CLAUDE_DIR)) {
197
259
  fs.mkdirSync(CLAUDE_DIR, { recursive: true });
198
260
  }
@@ -420,6 +482,33 @@ function installCursorMcp() {
420
482
  config.mcpServers = mcpServers;
421
483
  fs.writeFileSync(CURSOR_MCP_PATH, JSON.stringify(config, null, 2), "utf8");
422
484
  }
485
+ /**
486
+ * Install the CodieDev MCP server into VS Code's user-scope MCP config so
487
+ * GitHub Copilot Chat picks it up. VS Code uses a different schema than
488
+ * Claude / Cursor: top-level key is `servers` (not `mcpServers`). On first
489
+ * Copilot Chat run after install, VS Code prompts the user to trust the
490
+ * server — that's expected and documented in the connect success message.
491
+ *
492
+ * Reverse-ticket and the other on-demand verbs work in Copilot identically
493
+ * to Claude Code since the MCP server's reverse_ticket handler is already
494
+ * 100% diff-driven (no transcript dependency).
495
+ */
496
+ function installVSCodeMcp() {
497
+ if (!fs.existsSync(VSCODE_USER_DIR)) {
498
+ fs.mkdirSync(VSCODE_USER_DIR, { recursive: true });
499
+ }
500
+ const config = readMcpConfigSafely(VSCODE_USER_MCP_PATH);
501
+ const servers = config.servers ?? {};
502
+ if (servers.codiedev)
503
+ return;
504
+ servers.codiedev = {
505
+ type: "stdio",
506
+ command: "npx",
507
+ args: ["codiedev-mcp"],
508
+ };
509
+ config.servers = servers;
510
+ fs.writeFileSync(VSCODE_USER_MCP_PATH, JSON.stringify(config, null, 2), "utf8");
511
+ }
423
512
  /**
424
513
  * Best-effort append of the CodieDev MCP server block to ~/.codex/config.toml.
425
514
  *
@@ -469,28 +558,23 @@ function installCodexHook() {
469
558
  if (!hooks.Stop) {
470
559
  hooks.Stop = [];
471
560
  }
472
- const stopHooks = hooks.Stop;
473
- const alreadyInstalled = stopHooks.some((hook) => {
561
+ const existing = hooks.Stop;
562
+ const filtered = existing.filter((hook) => {
474
563
  const inner = hook.hooks;
475
- if (!inner)
476
- return false;
477
- return inner.some((h) => {
478
- const cmd = h.command;
479
- return cmd && cmd.includes("codiedev-hook");
480
- });
564
+ if (!Array.isArray(inner))
565
+ return true;
566
+ return !inner.some((h) => isCodiedevHookCommand(h.command));
481
567
  });
482
- if (alreadyInstalled) {
483
- return;
484
- }
485
- stopHooks.push({
568
+ filtered.push({
486
569
  hooks: [
487
570
  {
488
571
  type: "command",
489
- command: "npx codiedev-hook capture-codex",
572
+ command: resolveHookCommand("capture-codex"),
490
573
  timeout: 30,
491
574
  },
492
575
  ],
493
576
  });
577
+ hooks.Stop = filtered;
494
578
  if (!fs.existsSync(CODEX_DIR)) {
495
579
  fs.mkdirSync(CODEX_DIR, { recursive: true });
496
580
  }
@@ -524,19 +608,14 @@ function installCursorHook() {
524
608
  if (!hooks.sessionEnd) {
525
609
  hooks.sessionEnd = [];
526
610
  }
527
- const sessionEndHooks = hooks.sessionEnd;
528
- const alreadyInstalled = sessionEndHooks.some((h) => {
529
- const cmd = h.command;
530
- return cmd && cmd.includes("codiedev-hook");
531
- });
532
- if (alreadyInstalled) {
533
- return;
534
- }
535
- sessionEndHooks.push({
536
- command: "npx codiedev-hook capture-cursor",
611
+ const existing = hooks.sessionEnd;
612
+ const filtered = existing.filter((h) => !isCodiedevHookCommand(h.command));
613
+ filtered.push({
614
+ command: resolveHookCommand("capture-cursor"),
537
615
  type: "command",
538
616
  timeout: 30,
539
617
  });
618
+ hooks.sessionEnd = filtered;
540
619
  fs.writeFileSync(CURSOR_HOOKS_PATH, JSON.stringify(hooksFile, null, 2), "utf8");
541
620
  }
542
621
  function parseClaudeCodeStats(content) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "codiedev",
3
- "version": "0.6.0",
4
- "description": "Connect Claude Code, Codex, or Cursor to CodieDev for org-wide session capture and artifact collaboration",
3
+ "version": "0.7.0",
4
+ "description": "Connect Claude Code, Codex, Cursor, or VS Code Copilot to CodieDev for org-wide session capture and artifact collaboration",
5
5
  "bin": {
6
6
  "codiedev": "./dist/cli.js",
7
7
  "codiedev-hook": "./dist/hook.js"
@@ -22,6 +22,10 @@
22
22
  "codex",
23
23
  "openai",
24
24
  "cursor",
25
+ "copilot",
26
+ "github-copilot",
27
+ "vscode",
28
+ "mcp",
25
29
  "ai",
26
30
  "session-capture"
27
31
  ],