codiedev 0.5.5 → 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.
- package/dist/cli.js +8 -0
- package/dist/commands/ask.d.ts +1 -0
- package/dist/commands/ask.js +46 -0
- package/dist/commands/doctor.js +77 -1
- package/dist/connect.js +40 -2
- package/dist/hook.js +15 -4
- package/dist/mcp.js +48 -1
- package/dist/utils.d.ts +10 -0
- package/dist/utils.js +139 -13
- package/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -19,6 +19,7 @@ const connect_1 = require("./connect");
|
|
|
19
19
|
const push_1 = require("./commands/push");
|
|
20
20
|
const delete_1 = require("./commands/delete");
|
|
21
21
|
const pull_1 = require("./commands/pull");
|
|
22
|
+
const ask_1 = require("./commands/ask");
|
|
22
23
|
const ping_1 = require("./commands/ping");
|
|
23
24
|
const inbox_1 = require("./commands/inbox");
|
|
24
25
|
const note_1 = require("./commands/note");
|
|
@@ -43,6 +44,10 @@ Artifacts:
|
|
|
43
44
|
codiedev delete <key> [-y] Delete every version of an artifact you
|
|
44
45
|
authored (clean up bad/test pushes).
|
|
45
46
|
Asks for confirmation; -y to skip.
|
|
47
|
+
codiedev ask "<question>" Ask a natural-language question of the
|
|
48
|
+
team's collective memory. Returns an
|
|
49
|
+
LLM-synthesized answer with citations
|
|
50
|
+
pointing at sessions + artifacts.
|
|
46
51
|
codiedev promote <artifact-id> Promote an auto-extracted artifact
|
|
47
52
|
to an authored one
|
|
48
53
|
codiedev reverse-ticket [<pr-url>] Generate a Jira ticket draft.
|
|
@@ -374,6 +379,9 @@ async function main() {
|
|
|
374
379
|
case "pull":
|
|
375
380
|
await (0, pull_1.runPull)(rest);
|
|
376
381
|
return;
|
|
382
|
+
case "ask":
|
|
383
|
+
await (0, ask_1.runAsk)(rest);
|
|
384
|
+
return;
|
|
377
385
|
case "ping":
|
|
378
386
|
await (0, ping_1.runPing)(rest);
|
|
379
387
|
return;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runAsk(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runAsk = runAsk;
|
|
4
|
+
const shared_1 = require("./shared");
|
|
5
|
+
function parseArgs(args) {
|
|
6
|
+
// Everything after the command, joined back into a question string. Allows
|
|
7
|
+
// both quoted and unquoted forms: `codiedev ask "why pg-bouncer?"` or
|
|
8
|
+
// `codiedev ask why did we drop pg-bouncer`.
|
|
9
|
+
const question = args
|
|
10
|
+
.filter((a) => !a.startsWith("--"))
|
|
11
|
+
.join(" ")
|
|
12
|
+
.trim();
|
|
13
|
+
if (!question) {
|
|
14
|
+
console.error('Usage: codiedev ask "<question>"');
|
|
15
|
+
console.error('Example: codiedev ask "why did we drop pg-bouncer?"');
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
return { question };
|
|
19
|
+
}
|
|
20
|
+
async function runAsk(args) {
|
|
21
|
+
const { question } = parseArgs(args);
|
|
22
|
+
const config = (0, shared_1.requireConfig)();
|
|
23
|
+
try {
|
|
24
|
+
const res = await (0, shared_1.apiRequest)("POST", "/api/cli/ask", {
|
|
25
|
+
config,
|
|
26
|
+
body: { question },
|
|
27
|
+
});
|
|
28
|
+
console.log("");
|
|
29
|
+
console.log(res.answer);
|
|
30
|
+
if (res.citations.length > 0) {
|
|
31
|
+
console.log("");
|
|
32
|
+
console.log("Sources:");
|
|
33
|
+
for (const c of res.citations) {
|
|
34
|
+
const label = c.kind === "artifact"
|
|
35
|
+
? `[${c.type}] ${c.title}`
|
|
36
|
+
: `[session] ${c.title}`;
|
|
37
|
+
console.log(` • ${label}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
console.log("");
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
console.error(`Ask failed: ${err.message}`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -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.
|
|
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)
|
|
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;
|
|
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)(
|
|
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
|
-
:
|
|
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/mcp.js
CHANGED
|
@@ -384,7 +384,9 @@ const TOOLS = [
|
|
|
384
384
|
"when the user asks 'has anyone solved X?', 'find me the spec on Y', " +
|
|
385
385
|
"or 'look for prior work on Z'. Always search before pushing a new " +
|
|
386
386
|
"artifact so the user doesn't duplicate work. Returns ranked hits " +
|
|
387
|
-
"with title, type, filename key, and a snippet."
|
|
387
|
+
"with title, type, filename key, and a snippet. For open-ended " +
|
|
388
|
+
"questions where the user wants a synthesized answer (not just a list " +
|
|
389
|
+
"of hits), prefer codiedev_ask instead.",
|
|
388
390
|
inputSchema: {
|
|
389
391
|
type: "object",
|
|
390
392
|
properties: {
|
|
@@ -394,6 +396,32 @@ const TOOLS = [
|
|
|
394
396
|
required: ["query"],
|
|
395
397
|
},
|
|
396
398
|
},
|
|
399
|
+
{
|
|
400
|
+
name: "codiedev_ask",
|
|
401
|
+
description: "Ask a natural-language question of the team's collective memory and " +
|
|
402
|
+
"get a synthesized answer with citations. Searches across both " +
|
|
403
|
+
"auto-extracted artifacts (specs / decisions / bugfixes) AND raw " +
|
|
404
|
+
"session transcripts, then has an LLM compose an answer that cites " +
|
|
405
|
+
"the actual sources. Use when the user asks 'why did we…?', 'what " +
|
|
406
|
+
"does the team know about X?', 'has anyone done Y here before?', " +
|
|
407
|
+
"'who decided Z and why?', or any open-ended question about prior " +
|
|
408
|
+
"work where they want context, not just a hit list. For exact-match " +
|
|
409
|
+
"lookups (find me spec-x), prefer codiedev_pull. For browsing or " +
|
|
410
|
+
"filtering by kind/tag, prefer codiedev_get_library. For surfacing " +
|
|
411
|
+
"ranked hits without LLM synthesis, prefer codiedev_search.",
|
|
412
|
+
inputSchema: {
|
|
413
|
+
type: "object",
|
|
414
|
+
properties: {
|
|
415
|
+
question: {
|
|
416
|
+
type: "string",
|
|
417
|
+
description: "Natural-language question. Open-ended is fine — e.g. 'why " +
|
|
418
|
+
"did we drop pg-bouncer?' or 'how does the team think about " +
|
|
419
|
+
"feature flags?'.",
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
required: ["question"],
|
|
423
|
+
},
|
|
424
|
+
},
|
|
397
425
|
{
|
|
398
426
|
name: "codiedev_get_library",
|
|
399
427
|
description: "List artifacts in the team library — both docs (spec / decision / " +
|
|
@@ -571,6 +599,8 @@ async function dispatchTool(name, args, config) {
|
|
|
571
599
|
return await handleSendTo(args, config);
|
|
572
600
|
case "codiedev_react":
|
|
573
601
|
return await handleReact(args, config);
|
|
602
|
+
case "codiedev_ask":
|
|
603
|
+
return await handleAsk(args, config);
|
|
574
604
|
case "codiedev_search":
|
|
575
605
|
return await handleSearch(args, config);
|
|
576
606
|
case "codiedev_get_library":
|
|
@@ -922,6 +952,23 @@ async function handleSearch(args, config) {
|
|
|
922
952
|
}
|
|
923
953
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
924
954
|
}
|
|
955
|
+
async function handleAsk(args, config) {
|
|
956
|
+
const question = asString(args.question);
|
|
957
|
+
if (!question)
|
|
958
|
+
throw new Error("question required");
|
|
959
|
+
const res = await (0, shared_1.apiRequest)("POST", "/api/cli/ask", { config, body: { question } });
|
|
960
|
+
const lines = [res.answer];
|
|
961
|
+
if (res.citations && res.citations.length > 0) {
|
|
962
|
+
lines.push("", "Sources:");
|
|
963
|
+
for (const c of res.citations) {
|
|
964
|
+
const label = c.kind === "artifact"
|
|
965
|
+
? `[${c.type}] ${c.title}`
|
|
966
|
+
: `[session] ${c.title}`;
|
|
967
|
+
lines.push(` • ${label}`);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
971
|
+
}
|
|
925
972
|
async function handleGetLibrary(args, config) {
|
|
926
973
|
const scope = asStringOrUndefined(args.scope);
|
|
927
974
|
const type = asStringOrUndefined(args.type);
|
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 {
|
|
@@ -218,6 +230,7 @@ thought, use the \`codiedev\` CLI via Bash:**
|
|
|
218
230
|
| "share with Greg" / "give Jason access" (no notification) | MCP tool \`codiedev_share_with\` |
|
|
219
231
|
| "send this to Greg" / "ask Greg to look at this" | MCP tool \`codiedev_send_to\` |
|
|
220
232
|
| "find something about X" / "has anyone solved Y?" | MCP tool \`codiedev_search\` |
|
|
233
|
+
| "why did we…?" / "what does the team know about X?" | MCP tool \`codiedev_ask\` (synthesized answer + citations) |
|
|
221
234
|
| "show my library" / "what artifacts exist?" | MCP tool \`codiedev_get_library\` |
|
|
222
235
|
| "react 🛠 to that post" / "mark as used" | MCP tool \`codiedev_react\` |
|
|
223
236
|
|
|
@@ -311,25 +324,74 @@ function installClaudeCodeInstructions() {
|
|
|
311
324
|
function installCodexInstructions() {
|
|
312
325
|
writeInstructionsBlock(CODEX_USER_INSTRUCTIONS_PATH);
|
|
313
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
|
+
}
|
|
314
386
|
/**
|
|
315
387
|
* Install the CodieDev MCP server into Claude Code's user-scope config.
|
|
316
388
|
* Safe to call multiple times — updates the existing entry if present.
|
|
317
389
|
*/
|
|
318
390
|
function installClaudeCodeMcp() {
|
|
319
|
-
|
|
320
|
-
try {
|
|
321
|
-
if (fs.existsSync(CLAUDE_USER_CONFIG_PATH)) {
|
|
322
|
-
const raw = fs.readFileSync(CLAUDE_USER_CONFIG_PATH, "utf8");
|
|
323
|
-
config = raw.trim() ? JSON.parse(raw) : {};
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
catch {
|
|
327
|
-
// Start fresh if the existing file is corrupt — safer than failing the install.
|
|
328
|
-
config = {};
|
|
329
|
-
}
|
|
391
|
+
const config = readMcpConfigSafely(CLAUDE_USER_CONFIG_PATH);
|
|
330
392
|
const mcpServers = config.mcpServers ?? {};
|
|
331
|
-
|
|
332
|
-
|
|
393
|
+
if (mcpServers.codiedev)
|
|
394
|
+
return;
|
|
333
395
|
mcpServers.codiedev = {
|
|
334
396
|
command: "npx",
|
|
335
397
|
args: ["codiedev-mcp"],
|
|
@@ -337,6 +399,27 @@ function installClaudeCodeMcp() {
|
|
|
337
399
|
config.mcpServers = mcpServers;
|
|
338
400
|
fs.writeFileSync(CLAUDE_USER_CONFIG_PATH, JSON.stringify(config, null, 2), "utf8");
|
|
339
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
|
+
}
|
|
340
423
|
/**
|
|
341
424
|
* Best-effort append of the CodieDev MCP server block to ~/.codex/config.toml.
|
|
342
425
|
*
|
|
@@ -413,6 +496,49 @@ function installCodexHook() {
|
|
|
413
496
|
}
|
|
414
497
|
fs.writeFileSync(CODEX_HOOKS_PATH, JSON.stringify(hooksFile, null, 2), "utf8");
|
|
415
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
|
+
}
|
|
416
542
|
function parseClaudeCodeStats(content) {
|
|
417
543
|
let messageCount = 0;
|
|
418
544
|
let toolCallCount = 0;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codiedev",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Connect Claude Code or
|
|
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
|
],
|