codiedev 0.5.6 → 0.6.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.
@@ -44,6 +44,9 @@ const CLAUDE_SETTINGS_PATH = path.join(os.homedir(), ".claude", "settings.json")
44
44
  const CLAUDE_INSTRUCTIONS_PATH = path.join(os.homedir(), ".claude", "CLAUDE.md");
45
45
  const CODEX_HOOKS_PATH = path.join(os.homedir(), ".codex", "hooks.json");
46
46
  const CODEX_INSTRUCTIONS_PATH = path.join(os.homedir(), ".codex", "AGENTS.md");
47
+ const CURSOR_HOOKS_PATH = path.join(os.homedir(), ".cursor", "hooks.json");
48
+ const CURSOR_INSTRUCTIONS_PATH = path.join(os.homedir(), ".cursor", "rules", "codiedev.mdc");
49
+ const CURSOR_MCP_PATH = path.join(os.homedir(), ".cursor", "mcp.json");
47
50
  function symbol(status) {
48
51
  if (status === "pass")
49
52
  return "✓";
@@ -57,6 +60,43 @@ function claudeCodeInstalled() {
57
60
  function codexInstalled() {
58
61
  return fs.existsSync(path.join(os.homedir(), ".codex"));
59
62
  }
63
+ function cursorInstalled() {
64
+ return fs.existsSync(path.join(os.homedir(), ".cursor"));
65
+ }
66
+ function hasCursorMcpEntry() {
67
+ try {
68
+ if (!fs.existsSync(CURSOR_MCP_PATH))
69
+ return false;
70
+ const raw = fs.readFileSync(CURSOR_MCP_PATH, "utf8");
71
+ const parsed = JSON.parse(raw);
72
+ const servers = parsed.mcpServers;
73
+ return !!servers && "codiedev" in servers;
74
+ }
75
+ catch {
76
+ return false;
77
+ }
78
+ }
79
+ // Cursor's hooks.json schema is { hooks: { sessionEnd: [{ command, ... }] } }
80
+ // — flat array of objects with `command`, not the Claude/Codex nested
81
+ // `{ hooks: [{ command }] }` wrapper.
82
+ function hasCursorHook() {
83
+ try {
84
+ if (!fs.existsSync(CURSOR_HOOKS_PATH))
85
+ return false;
86
+ const raw = fs.readFileSync(CURSOR_HOOKS_PATH, "utf8");
87
+ const parsed = JSON.parse(raw);
88
+ const arr = parsed.hooks?.sessionEnd;
89
+ if (!Array.isArray(arr))
90
+ return false;
91
+ return arr.some((h) => {
92
+ const cmd = h.command;
93
+ return (cmd ?? "").includes("codiedev-hook");
94
+ });
95
+ }
96
+ catch {
97
+ return false;
98
+ }
99
+ }
60
100
  function hasCodiedevHook(settingsPath, hookKey) {
61
101
  try {
62
102
  if (!fs.existsSync(settingsPath))
@@ -229,7 +269,43 @@ async function runDoctor(_args) {
229
269
  detail: "not installed on this machine",
230
270
  });
231
271
  }
232
- // 7. gh CLI (optional — only needed for reverse-ticket)
272
+ // 7. Cursor setup (if present)
273
+ if (cursorInstalled()) {
274
+ checks.push({
275
+ name: "Cursor detected",
276
+ status: "pass",
277
+ detail: "~/.cursor",
278
+ });
279
+ checks.push({
280
+ name: "Cursor sessionEnd hook",
281
+ status: hasCursorHook() ? "pass" : "fail",
282
+ detail: hasCursorHook()
283
+ ? "~/.cursor/hooks.json"
284
+ : "missing — re-run `codiedev connect`",
285
+ });
286
+ checks.push({
287
+ name: "Cursor agent instructions",
288
+ status: hasCodiedevInstructions(CURSOR_INSTRUCTIONS_PATH) ? "pass" : "fail",
289
+ detail: hasCodiedevInstructions(CURSOR_INSTRUCTIONS_PATH)
290
+ ? "~/.cursor/rules/codiedev.mdc"
291
+ : "missing — re-run `codiedev connect`",
292
+ });
293
+ checks.push({
294
+ name: "Cursor MCP server",
295
+ status: hasCursorMcpEntry() ? "pass" : "fail",
296
+ detail: hasCursorMcpEntry()
297
+ ? "~/.cursor/mcp.json"
298
+ : "missing — re-run `codiedev connect`",
299
+ });
300
+ }
301
+ else {
302
+ checks.push({
303
+ name: "Cursor",
304
+ status: "warn",
305
+ detail: "not installed on this machine",
306
+ });
307
+ }
308
+ // 8. gh CLI (optional — only needed for reverse-ticket)
233
309
  checks.push({
234
310
  name: "GitHub CLI (`gh`) for reverse-ticket command",
235
311
  status: hasGhCli() ? "pass" : "warn",
package/dist/connect.js CHANGED
@@ -141,6 +141,7 @@ async function runConnect() {
141
141
  }
142
142
  const hasClaude = (0, utils_1.claudeCodeInstalled)();
143
143
  const hasCodex = (0, utils_1.codexInstalled)();
144
+ const hasCursor = (0, utils_1.cursorInstalled)();
144
145
  const installed = [];
145
146
  if (hasClaude) {
146
147
  try {
@@ -157,6 +158,13 @@ async function runConnect() {
157
158
  catch (err) {
158
159
  console.error(`\nWarning: Failed to install Claude Code instructions — ${err.message}`);
159
160
  }
161
+ try {
162
+ (0, utils_1.installClaudeCodeMcp)();
163
+ installed.push("Claude Code MCP server (~/.claude.json)");
164
+ }
165
+ catch (err) {
166
+ console.error(`\nWarning: Failed to install Claude Code MCP server — ${err.message}`);
167
+ }
160
168
  }
161
169
  if (hasCodex) {
162
170
  try {
@@ -173,9 +181,39 @@ async function runConnect() {
173
181
  catch (err) {
174
182
  console.error(`\nWarning: Failed to install Codex instructions — ${err.message}`);
175
183
  }
184
+ try {
185
+ (0, utils_1.installCodexMcp)();
186
+ installed.push("Codex MCP server (~/.codex/config.toml)");
187
+ }
188
+ catch (err) {
189
+ console.error(`\nWarning: Failed to install Codex MCP server — ${err.message}`);
190
+ }
191
+ }
192
+ if (hasCursor) {
193
+ try {
194
+ (0, utils_1.installCursorHook)();
195
+ installed.push("Cursor sessionEnd hook (~/.cursor/hooks.json)");
196
+ }
197
+ catch (err) {
198
+ console.error(`\nWarning: Failed to install Cursor hook — ${err.message}`);
199
+ }
200
+ try {
201
+ (0, utils_1.installCursorInstructions)();
202
+ installed.push("Cursor agent instructions (~/.cursor/rules/codiedev.mdc)");
203
+ }
204
+ catch (err) {
205
+ console.error(`\nWarning: Failed to install Cursor instructions — ${err.message}`);
206
+ }
207
+ try {
208
+ (0, utils_1.installCursorMcp)();
209
+ installed.push("Cursor MCP server (~/.cursor/mcp.json)");
210
+ }
211
+ catch (err) {
212
+ console.error(`\nWarning: Failed to install Cursor MCP server — ${err.message}`);
213
+ }
176
214
  }
177
- if (!hasClaude && !hasCodex) {
178
- console.warn("\nNo Claude Code (~/.claude) or Codex (~/.codex) install detected.");
215
+ if (!hasClaude && !hasCodex && !hasCursor) {
216
+ console.warn("\nNo Claude Code (~/.claude), Codex (~/.codex), or Cursor (~/.cursor) install detected.");
179
217
  console.warn("Config saved. Install one, then re-run `npx codiedev connect` to wire up capture hooks.");
180
218
  }
181
219
  console.log(`\nConnected to ${companyName}`);
package/dist/hook.js CHANGED
@@ -87,6 +87,8 @@ async function readStdin() {
87
87
  function resolveMode(arg) {
88
88
  if (arg === "capture-codex")
89
89
  return "codex";
90
+ if (arg === "capture-cursor")
91
+ return "cursor";
90
92
  return "claude-code";
91
93
  }
92
94
  async function main() {
@@ -101,7 +103,10 @@ async function main() {
101
103
  catch {
102
104
  process.exit(0);
103
105
  }
104
- const { session_id, transcript_path, cwd } = hookInput;
106
+ const { session_id, transcript_path, cwd, workspace_roots } = hookInput;
107
+ const workspaceRoot = workspace_roots && workspace_roots.length > 0
108
+ ? workspace_roots[0]
109
+ : cwd || process.cwd();
105
110
  const config = (0, utils_1.readConfig)();
106
111
  if (!config) {
107
112
  process.exit(0);
@@ -116,13 +121,14 @@ async function main() {
116
121
  catch {
117
122
  process.exit(0);
118
123
  }
119
- // Resolve repo URL. Codex embeds it in session_meta; fall back to git for CC.
124
+ // Resolve repo URL. Codex embeds it in session_meta; CC and Cursor both
125
+ // need the git remote of the workspace.
120
126
  let remoteUrl = null;
121
127
  if (mode === "codex") {
122
128
  remoteUrl = (0, utils_1.extractCodexRepoUrl)(transcriptContent);
123
129
  }
124
130
  if (!remoteUrl) {
125
- remoteUrl = (0, utils_1.getGitRemoteUrl)(cwd || process.cwd());
131
+ remoteUrl = (0, utils_1.getGitRemoteUrl)(workspaceRoot);
126
132
  }
127
133
  if (!remoteUrl) {
128
134
  process.exit(0);
@@ -131,9 +137,14 @@ async function main() {
131
137
  if (!matchedRepo) {
132
138
  process.exit(0);
133
139
  }
140
+ // Cursor's transcript format is undocumented — skip stats parsing for now
141
+ // and let the backend handle it once we have a real session to inspect.
142
+ // The transcript itself still uploads.
134
143
  const stats = mode === "codex"
135
144
  ? (0, utils_1.parseCodexStats)(transcriptContent)
136
- : (0, utils_1.parseClaudeCodeStats)(transcriptContent);
145
+ : mode === "cursor"
146
+ ? { messageCount: 0, toolCallCount: 0 }
147
+ : (0, utils_1.parseClaudeCodeStats)(transcriptContent);
137
148
  const transcriptBase64 = Buffer.from(transcriptContent, "utf8").toString("base64");
138
149
  const payload = {
139
150
  sessionId: session_id,
package/dist/utils.d.ts CHANGED
@@ -26,14 +26,23 @@ export declare function matchRepo(remoteUrl: string, repos: Array<{
26
26
  export declare function hashToken(token: string): string;
27
27
  export declare function claudeCodeInstalled(): boolean;
28
28
  export declare function codexInstalled(): boolean;
29
+ export declare function cursorInstalled(): boolean;
29
30
  export declare function installHook(): void;
30
31
  export declare function installClaudeCodeInstructions(): void;
31
32
  export declare function installCodexInstructions(): void;
33
+ export declare function installCursorInstructions(): void;
32
34
  /**
33
35
  * Install the CodieDev MCP server into Claude Code's user-scope config.
34
36
  * Safe to call multiple times — updates the existing entry if present.
35
37
  */
36
38
  export declare function installClaudeCodeMcp(): void;
39
+ /**
40
+ * Install the CodieDev MCP server into Cursor's user-scope config.
41
+ * Cursor uses the same `mcpServers` schema as Claude Code; just a different
42
+ * file path (~/.cursor/mcp.json). Project-scoped servers can also live at
43
+ * <project>/.cursor/mcp.json — we only manage the user-scope one.
44
+ */
45
+ export declare function installCursorMcp(): void;
37
46
  /**
38
47
  * Best-effort append of the CodieDev MCP server block to ~/.codex/config.toml.
39
48
  *
@@ -42,6 +51,7 @@ export declare function installClaudeCodeMcp(): void;
42
51
  */
43
52
  export declare function installCodexMcp(): boolean;
44
53
  export declare function installCodexHook(): void;
54
+ export declare function installCursorHook(): void;
45
55
  export interface ParsedStats {
46
56
  messageCount: number;
47
57
  toolCallCount: number;
package/dist/utils.js CHANGED
@@ -41,12 +41,16 @@ exports.matchRepo = matchRepo;
41
41
  exports.hashToken = hashToken;
42
42
  exports.claudeCodeInstalled = claudeCodeInstalled;
43
43
  exports.codexInstalled = codexInstalled;
44
+ exports.cursorInstalled = cursorInstalled;
44
45
  exports.installHook = installHook;
45
46
  exports.installClaudeCodeInstructions = installClaudeCodeInstructions;
46
47
  exports.installCodexInstructions = installCodexInstructions;
48
+ exports.installCursorInstructions = installCursorInstructions;
47
49
  exports.installClaudeCodeMcp = installClaudeCodeMcp;
50
+ exports.installCursorMcp = installCursorMcp;
48
51
  exports.installCodexMcp = installCodexMcp;
49
52
  exports.installCodexHook = installCodexHook;
53
+ exports.installCursorHook = installCursorHook;
50
54
  exports.parseClaudeCodeStats = parseClaudeCodeStats;
51
55
  exports.parseCodexStats = parseCodexStats;
52
56
  exports.extractCodexRepoUrl = extractCodexRepoUrl;
@@ -133,12 +137,20 @@ const CLAUDE_SETTINGS_PATH = path.join(os.homedir(), ".claude", "settings.json")
133
137
  const CLAUDE_DIR = path.join(os.homedir(), ".claude");
134
138
  const CODEX_HOOKS_PATH = path.join(os.homedir(), ".codex", "hooks.json");
135
139
  const CODEX_DIR = path.join(os.homedir(), ".codex");
140
+ const CURSOR_DIR = path.join(os.homedir(), ".cursor");
141
+ const CURSOR_HOOKS_PATH = path.join(CURSOR_DIR, "hooks.json");
142
+ const CURSOR_MCP_PATH = path.join(CURSOR_DIR, "mcp.json");
143
+ const CURSOR_RULES_DIR = path.join(CURSOR_DIR, "rules");
144
+ const CURSOR_RULES_PATH = path.join(CURSOR_RULES_DIR, "codiedev.mdc");
136
145
  function claudeCodeInstalled() {
137
146
  return fs.existsSync(CLAUDE_DIR);
138
147
  }
139
148
  function codexInstalled() {
140
149
  return fs.existsSync(CODEX_DIR);
141
150
  }
151
+ function cursorInstalled() {
152
+ return fs.existsSync(CURSOR_DIR);
153
+ }
142
154
  function installHook() {
143
155
  let settings = {};
144
156
  try {
@@ -312,25 +324,74 @@ function installClaudeCodeInstructions() {
312
324
  function installCodexInstructions() {
313
325
  writeInstructionsBlock(CODEX_USER_INSTRUCTIONS_PATH);
314
326
  }
327
+ // Cursor uses .mdc rule files with YAML frontmatter (description, alwaysApply,
328
+ // globs). `.cursorrules` is deprecated and ignored by Agent mode as of 2026.
329
+ const CURSOR_INSTRUCTIONS_FRONTMATTER = `---
330
+ description: CodieDev team artifact layer — push, pull, search, ping, and share artifacts with teammates
331
+ alwaysApply: true
332
+ ---
333
+
334
+ `;
335
+ function installCursorInstructions() {
336
+ if (!fs.existsSync(CURSOR_RULES_DIR)) {
337
+ fs.mkdirSync(CURSOR_RULES_DIR, { recursive: true });
338
+ }
339
+ const block = CURSOR_INSTRUCTIONS_FRONTMATTER +
340
+ CODIEDEV_INSTRUCTIONS_BEGIN +
341
+ "\n" +
342
+ CODIEDEV_INSTRUCTIONS_BODY +
343
+ "\n" +
344
+ CODIEDEV_INSTRUCTIONS_END +
345
+ "\n";
346
+ let existing = "";
347
+ if (fs.existsSync(CURSOR_RULES_PATH)) {
348
+ existing = fs.readFileSync(CURSOR_RULES_PATH, "utf8");
349
+ }
350
+ // If the file already has our markers, replace just the block — preserve
351
+ // any frontmatter edits the user made.
352
+ const beginIdx = existing.indexOf(CODIEDEV_INSTRUCTIONS_BEGIN);
353
+ const endIdx = existing.indexOf(CODIEDEV_INSTRUCTIONS_END);
354
+ let next;
355
+ if (beginIdx !== -1 && endIdx !== -1 && endIdx > beginIdx) {
356
+ next =
357
+ existing.slice(0, beginIdx) +
358
+ CODIEDEV_INSTRUCTIONS_BEGIN +
359
+ "\n" +
360
+ CODIEDEV_INSTRUCTIONS_BODY +
361
+ "\n" +
362
+ CODIEDEV_INSTRUCTIONS_END +
363
+ existing.slice(endIdx + CODIEDEV_INSTRUCTIONS_END.length);
364
+ }
365
+ else {
366
+ next = block;
367
+ }
368
+ fs.writeFileSync(CURSOR_RULES_PATH, next, "utf8");
369
+ }
370
+ function readMcpConfigSafely(path) {
371
+ if (!fs.existsSync(path))
372
+ return {};
373
+ const raw = fs.readFileSync(path, "utf8");
374
+ if (!raw.trim())
375
+ return {};
376
+ try {
377
+ return JSON.parse(raw);
378
+ }
379
+ catch {
380
+ const backup = `${path}.bak.${Date.now()}`;
381
+ fs.copyFileSync(path, backup);
382
+ console.warn(`Warning: ${path} contained invalid JSON. Backed up to ${backup} before rewriting.`);
383
+ return {};
384
+ }
385
+ }
315
386
  /**
316
387
  * Install the CodieDev MCP server into Claude Code's user-scope config.
317
388
  * Safe to call multiple times — updates the existing entry if present.
318
389
  */
319
390
  function installClaudeCodeMcp() {
320
- let config = {};
321
- try {
322
- if (fs.existsSync(CLAUDE_USER_CONFIG_PATH)) {
323
- const raw = fs.readFileSync(CLAUDE_USER_CONFIG_PATH, "utf8");
324
- config = raw.trim() ? JSON.parse(raw) : {};
325
- }
326
- }
327
- catch {
328
- // Start fresh if the existing file is corrupt — safer than failing the install.
329
- config = {};
330
- }
391
+ const config = readMcpConfigSafely(CLAUDE_USER_CONFIG_PATH);
331
392
  const mcpServers = config.mcpServers ?? {};
332
- // Canonical Claude Code user-scope format: just command + args.
333
- // No `type` field (some CC versions reject it; stdio is the default).
393
+ if (mcpServers.codiedev)
394
+ return;
334
395
  mcpServers.codiedev = {
335
396
  command: "npx",
336
397
  args: ["codiedev-mcp"],
@@ -338,6 +399,27 @@ function installClaudeCodeMcp() {
338
399
  config.mcpServers = mcpServers;
339
400
  fs.writeFileSync(CLAUDE_USER_CONFIG_PATH, JSON.stringify(config, null, 2), "utf8");
340
401
  }
402
+ /**
403
+ * Install the CodieDev MCP server into Cursor's user-scope config.
404
+ * Cursor uses the same `mcpServers` schema as Claude Code; just a different
405
+ * file path (~/.cursor/mcp.json). Project-scoped servers can also live at
406
+ * <project>/.cursor/mcp.json — we only manage the user-scope one.
407
+ */
408
+ function installCursorMcp() {
409
+ if (!fs.existsSync(CURSOR_DIR)) {
410
+ fs.mkdirSync(CURSOR_DIR, { recursive: true });
411
+ }
412
+ const config = readMcpConfigSafely(CURSOR_MCP_PATH);
413
+ const mcpServers = config.mcpServers ?? {};
414
+ if (mcpServers.codiedev)
415
+ return;
416
+ mcpServers.codiedev = {
417
+ command: "npx",
418
+ args: ["codiedev-mcp"],
419
+ };
420
+ config.mcpServers = mcpServers;
421
+ fs.writeFileSync(CURSOR_MCP_PATH, JSON.stringify(config, null, 2), "utf8");
422
+ }
341
423
  /**
342
424
  * Best-effort append of the CodieDev MCP server block to ~/.codex/config.toml.
343
425
  *
@@ -414,6 +496,49 @@ function installCodexHook() {
414
496
  }
415
497
  fs.writeFileSync(CODEX_HOOKS_PATH, JSON.stringify(hooksFile, null, 2), "utf8");
416
498
  }
499
+ // Cursor's hooks.json uses { version: 1, hooks: { sessionEnd: [{ command, type, timeout }] } }.
500
+ // `sessionEnd` fires when a conversation closes (completed, aborted, error,
501
+ // window_close, user_close); the base stdin payload includes transcript_path
502
+ // and session_id which `codiedev-hook capture-cursor` reads to upload.
503
+ function installCursorHook() {
504
+ if (!fs.existsSync(CURSOR_DIR)) {
505
+ fs.mkdirSync(CURSOR_DIR, { recursive: true });
506
+ }
507
+ let hooksFile = {};
508
+ try {
509
+ if (fs.existsSync(CURSOR_HOOKS_PATH)) {
510
+ const raw = fs.readFileSync(CURSOR_HOOKS_PATH, "utf8");
511
+ hooksFile = raw.trim() ? JSON.parse(raw) : {};
512
+ }
513
+ }
514
+ catch {
515
+ hooksFile = {};
516
+ }
517
+ if (!hooksFile.version) {
518
+ hooksFile.version = 1;
519
+ }
520
+ if (!hooksFile.hooks) {
521
+ hooksFile.hooks = {};
522
+ }
523
+ const hooks = hooksFile.hooks;
524
+ if (!hooks.sessionEnd) {
525
+ hooks.sessionEnd = [];
526
+ }
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",
537
+ type: "command",
538
+ timeout: 30,
539
+ });
540
+ fs.writeFileSync(CURSOR_HOOKS_PATH, JSON.stringify(hooksFile, null, 2), "utf8");
541
+ }
417
542
  function parseClaudeCodeStats(content) {
418
543
  let messageCount = 0;
419
544
  let toolCallCount = 0;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "codiedev",
3
- "version": "0.5.6",
4
- "description": "Connect Claude Code or Codex to CodieDev for org-wide session capture and artifact collaboration",
3
+ "version": "0.6.0",
4
+ "description": "Connect Claude Code, Codex, or Cursor 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"
@@ -21,6 +21,7 @@
21
21
  "claude",
22
22
  "codex",
23
23
  "openai",
24
+ "cursor",
24
25
  "ai",
25
26
  "session-capture"
26
27
  ],