codiedev 0.7.10 → 0.7.11
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 +47 -6
- package/dist/commands/ask.js +12 -0
- package/dist/commands/delete.js +12 -0
- package/dist/commands/doctor.js +6 -31
- package/dist/commands/inbox.js +30 -0
- package/dist/commands/library.d.ts +2 -0
- package/dist/commands/library.js +105 -0
- package/dist/commands/note.js +12 -0
- package/dist/commands/ping.js +12 -0
- package/dist/commands/post.d.ts +2 -0
- package/dist/commands/post.js +96 -0
- package/dist/commands/promote.js +12 -0
- package/dist/commands/pull.js +12 -0
- package/dist/commands/push.js +12 -0
- package/dist/commands/react.d.ts +2 -0
- package/dist/commands/react.js +40 -0
- package/dist/commands/reverseTicket.js +30 -0
- package/dist/commands/search.d.ts +2 -0
- package/dist/commands/search.js +72 -0
- package/dist/commands/send.d.ts +2 -0
- package/dist/commands/send.js +67 -0
- package/dist/commands/share.d.ts +2 -0
- package/dist/commands/share.js +87 -0
- package/dist/commands/shared.d.ts +19 -1
- package/dist/commands/shared.js +46 -1
- package/dist/connect.js +49 -38
- package/dist/detection.d.ts +18 -0
- package/dist/detection.js +28 -0
- package/dist/hook.js +0 -0
- package/dist/mcp.js +7 -7
- package/dist/utils.d.ts +22 -0
- package/dist/utils.js +122 -32
- package/package.json +1 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// `codiedev react <postId> <emoji>` — toggle a reaction on a feed post.
|
|
3
|
+
// Calling twice with the same emoji removes it.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.runReact = runReact;
|
|
6
|
+
const shared_1 = require("./shared");
|
|
7
|
+
async function runReact(args, configOverride) {
|
|
8
|
+
const positionals = args.filter((a) => !a.startsWith("--"));
|
|
9
|
+
const [postId, emoji] = positionals;
|
|
10
|
+
if (!postId || !emoji) {
|
|
11
|
+
console.error("Usage: codiedev react <postId> <emoji>");
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
const config = configOverride ?? (0, shared_1.requireConfig)();
|
|
15
|
+
const start = Date.now();
|
|
16
|
+
try {
|
|
17
|
+
const res = await (0, shared_1.apiRequest)("POST", "/api/cli/react", {
|
|
18
|
+
config,
|
|
19
|
+
body: { postId, emoji },
|
|
20
|
+
});
|
|
21
|
+
(0, shared_1.recordAgentEvent)(config, {
|
|
22
|
+
tool: "codiedev_react",
|
|
23
|
+
ok: true,
|
|
24
|
+
latencyMs: Date.now() - start,
|
|
25
|
+
});
|
|
26
|
+
console.log(res.action === "removed"
|
|
27
|
+
? `Removed ${res.emoji ?? emoji} from post ${postId}.`
|
|
28
|
+
: `Reacted to post ${postId} with ${res.emoji ?? emoji}.`);
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
(0, shared_1.recordAgentEvent)(config, {
|
|
32
|
+
tool: "codiedev_react",
|
|
33
|
+
ok: false,
|
|
34
|
+
latencyMs: Date.now() - start,
|
|
35
|
+
error: err.message,
|
|
36
|
+
});
|
|
37
|
+
console.error(`React failed: ${err.message}`);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -183,6 +183,7 @@ async function runReverseTicket(args) {
|
|
|
183
183
|
console.log("");
|
|
184
184
|
console.log("Generating ticket…");
|
|
185
185
|
console.log("");
|
|
186
|
+
const start = Date.now();
|
|
186
187
|
try {
|
|
187
188
|
const res = await (0, shared_1.apiRequest)("POST", "/api/cli/reverseTicket", {
|
|
188
189
|
config,
|
|
@@ -191,6 +192,11 @@ async function runReverseTicket(args) {
|
|
|
191
192
|
forcedArtifactKey: forcedKey,
|
|
192
193
|
},
|
|
193
194
|
});
|
|
195
|
+
(0, shared_1.recordAgentEvent)(config, {
|
|
196
|
+
tool: "codiedev_reverse_ticket",
|
|
197
|
+
ok: true,
|
|
198
|
+
latencyMs: Date.now() - start,
|
|
199
|
+
});
|
|
194
200
|
if (res.match) {
|
|
195
201
|
console.log(`✓ Matched artifact: ${res.match.key} (score=${res.match.score.toFixed(2)})`);
|
|
196
202
|
console.log(` signals · body=${res.match.signals.bodyMention} · files=${res.match.signals.fileOverlap.toFixed(2)} · transcript=${res.match.signals.transcriptEdit}`);
|
|
@@ -212,6 +218,12 @@ async function runReverseTicket(args) {
|
|
|
212
218
|
console.log(res.ticket);
|
|
213
219
|
}
|
|
214
220
|
catch (err) {
|
|
221
|
+
(0, shared_1.recordAgentEvent)(config, {
|
|
222
|
+
tool: "codiedev_reverse_ticket",
|
|
223
|
+
ok: false,
|
|
224
|
+
latencyMs: Date.now() - start,
|
|
225
|
+
error: err.message,
|
|
226
|
+
});
|
|
215
227
|
console.error(`Reverse-ticket failed: ${err.message}`);
|
|
216
228
|
process.exit(1);
|
|
217
229
|
}
|
|
@@ -367,6 +379,7 @@ async function runBranchMode(opts) {
|
|
|
367
379
|
console.log("Generating ticket draft…");
|
|
368
380
|
console.log("");
|
|
369
381
|
const config = (0, shared_1.requireConfig)();
|
|
382
|
+
const start = Date.now();
|
|
370
383
|
try {
|
|
371
384
|
const res = await (0, shared_1.apiRequest)("POST", "/api/cli/reverseTicketFromBranch", {
|
|
372
385
|
config,
|
|
@@ -387,9 +400,20 @@ async function runBranchMode(opts) {
|
|
|
387
400
|
},
|
|
388
401
|
});
|
|
389
402
|
if (!res.ok || !res.artifactId) {
|
|
403
|
+
(0, shared_1.recordAgentEvent)(config, {
|
|
404
|
+
tool: "codiedev_reverse_ticket",
|
|
405
|
+
ok: false,
|
|
406
|
+
latencyMs: Date.now() - start,
|
|
407
|
+
error: "no result",
|
|
408
|
+
});
|
|
390
409
|
console.error("Reverse-ticket generation returned no result. The writer may have skipped due to missing context.");
|
|
391
410
|
process.exit(1);
|
|
392
411
|
}
|
|
412
|
+
(0, shared_1.recordAgentEvent)(config, {
|
|
413
|
+
tool: "codiedev_reverse_ticket",
|
|
414
|
+
ok: true,
|
|
415
|
+
latencyMs: Date.now() - start,
|
|
416
|
+
});
|
|
393
417
|
const portalHost = (process.env.CODIEDEV_PORTAL_URL || "https://codiedev.com").replace(/\/$/, "");
|
|
394
418
|
const portalLink = res.portalUrl.startsWith("http")
|
|
395
419
|
? res.portalUrl
|
|
@@ -403,6 +427,12 @@ async function runBranchMode(opts) {
|
|
|
403
427
|
console.log("Each call creates a fresh draft — clean up duplicates from the portal if needed.");
|
|
404
428
|
}
|
|
405
429
|
catch (err) {
|
|
430
|
+
(0, shared_1.recordAgentEvent)(config, {
|
|
431
|
+
tool: "codiedev_reverse_ticket",
|
|
432
|
+
ok: false,
|
|
433
|
+
latencyMs: Date.now() - start,
|
|
434
|
+
error: err.message,
|
|
435
|
+
});
|
|
406
436
|
console.error(`Reverse-ticket failed: ${err.message}`);
|
|
407
437
|
process.exit(1);
|
|
408
438
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// `codiedev search "<query>"` — semantic + keyword search across the org's
|
|
3
|
+
// captured sessions, artifacts, and skills. Hits are filtered by companyId
|
|
4
|
+
// server-side, so a single search surfaces results from every teammate's
|
|
5
|
+
// prior sessions in the same workspace.
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.runSearch = runSearch;
|
|
8
|
+
const shared_1 = require("./shared");
|
|
9
|
+
function parseArgs(args) {
|
|
10
|
+
let query;
|
|
11
|
+
let limit;
|
|
12
|
+
for (let i = 0; i < args.length; i++) {
|
|
13
|
+
const a = args[i];
|
|
14
|
+
if (a === "--limit" && i + 1 < args.length) {
|
|
15
|
+
const n = Number(args[++i]);
|
|
16
|
+
if (Number.isInteger(n) && n > 0)
|
|
17
|
+
limit = n;
|
|
18
|
+
}
|
|
19
|
+
else if (a.startsWith("--limit=")) {
|
|
20
|
+
const n = Number(a.slice("--limit=".length));
|
|
21
|
+
if (Number.isInteger(n) && n > 0)
|
|
22
|
+
limit = n;
|
|
23
|
+
}
|
|
24
|
+
else if (!a.startsWith("--") && query === undefined) {
|
|
25
|
+
query = a;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (!query || !query.trim()) {
|
|
29
|
+
console.error('Usage: codiedev search "<query>" [--limit N]');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
return { query: query.trim(), limit };
|
|
33
|
+
}
|
|
34
|
+
async function runSearch(args, configOverride) {
|
|
35
|
+
const { query, limit } = parseArgs(args);
|
|
36
|
+
const config = configOverride ?? (0, shared_1.requireConfig)();
|
|
37
|
+
const start = Date.now();
|
|
38
|
+
try {
|
|
39
|
+
const res = await (0, shared_1.apiRequest)("GET", "/api/cli/search", {
|
|
40
|
+
config,
|
|
41
|
+
query: { q: query, limit },
|
|
42
|
+
});
|
|
43
|
+
(0, shared_1.recordAgentEvent)(config, {
|
|
44
|
+
tool: "codiedev_search",
|
|
45
|
+
ok: true,
|
|
46
|
+
latencyMs: Date.now() - start,
|
|
47
|
+
});
|
|
48
|
+
if (!res.hits || res.hits.length === 0) {
|
|
49
|
+
console.log(`No matches for "${query}".`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
console.log(`Found ${res.hits.length} match${res.hits.length === 1 ? "" : "es"} for "${query}":\n`);
|
|
53
|
+
for (const h of res.hits) {
|
|
54
|
+
const keyOrId = h.key ?? h._id;
|
|
55
|
+
console.log(` ${h.title} (${h.type}, score ${h.score.toFixed(2)})`);
|
|
56
|
+
console.log(` ${keyOrId}`);
|
|
57
|
+
if (h.snippet)
|
|
58
|
+
console.log(` ${h.snippet.slice(0, 240)}`);
|
|
59
|
+
console.log();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
(0, shared_1.recordAgentEvent)(config, {
|
|
64
|
+
tool: "codiedev_search",
|
|
65
|
+
ok: false,
|
|
66
|
+
latencyMs: Date.now() - start,
|
|
67
|
+
error: err.message,
|
|
68
|
+
});
|
|
69
|
+
console.error(`Search failed: ${err.message}`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// `codiedev send <filename> <recipient> [message]` — share an artifact AND
|
|
3
|
+
// send a short message that lands in the teammate's inbox. The active-
|
|
4
|
+
// handoff version of `codiedev share`. Use this when the user is actively
|
|
5
|
+
// looping someone in.
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.runSend = runSend;
|
|
8
|
+
const shared_1 = require("./shared");
|
|
9
|
+
function parseArgs(args) {
|
|
10
|
+
const positionals = args.filter((a) => !a.startsWith("--"));
|
|
11
|
+
const [filename, to, ...rest] = positionals;
|
|
12
|
+
if (!filename || !to) {
|
|
13
|
+
console.error('Usage: codiedev send <filename> <recipient> ["<message>"]');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
// Recombine remaining tokens — supports both quoted and unquoted messages.
|
|
17
|
+
const message = rest.length > 0 ? (0, shared_1.stripQuotes)(rest.join(" ")) : undefined;
|
|
18
|
+
return { filename, to, message };
|
|
19
|
+
}
|
|
20
|
+
async function runSend(args, configOverride) {
|
|
21
|
+
const parsed = parseArgs(args);
|
|
22
|
+
const config = configOverride ?? (0, shared_1.requireConfig)();
|
|
23
|
+
const start = Date.now();
|
|
24
|
+
try {
|
|
25
|
+
const body = {
|
|
26
|
+
filename: parsed.filename,
|
|
27
|
+
to: parsed.to,
|
|
28
|
+
};
|
|
29
|
+
if (parsed.message)
|
|
30
|
+
body.message = parsed.message;
|
|
31
|
+
const res = await (0, shared_1.apiRequest)("POST", "/api/cli/send-to", {
|
|
32
|
+
config,
|
|
33
|
+
body,
|
|
34
|
+
});
|
|
35
|
+
(0, shared_1.recordAgentEvent)(config, {
|
|
36
|
+
tool: "codiedev_send_to",
|
|
37
|
+
ok: true,
|
|
38
|
+
latencyMs: Date.now() - start,
|
|
39
|
+
});
|
|
40
|
+
const who = res.recipient ? `${res.recipient.name} (${res.recipient.email})` : parsed.to;
|
|
41
|
+
if (parsed.message) {
|
|
42
|
+
console.log(`Sent ${parsed.filename} to ${who}.`);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
console.log(`Shared ${parsed.filename} with ${who} (no message).`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
(0, shared_1.recordAgentEvent)(config, {
|
|
50
|
+
tool: "codiedev_send_to",
|
|
51
|
+
ok: false,
|
|
52
|
+
latencyMs: Date.now() - start,
|
|
53
|
+
error: err.message,
|
|
54
|
+
});
|
|
55
|
+
const body = err.body;
|
|
56
|
+
if (body && Array.isArray(body.candidates) && body.candidates.length > 0) {
|
|
57
|
+
console.error(`Recipient "${parsed.to}" is ambiguous. Candidates:`);
|
|
58
|
+
for (const c of body.candidates) {
|
|
59
|
+
console.error(` - ${c.name} (${c.email})`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
console.error(`Send failed: ${err.message}`);
|
|
64
|
+
}
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// `codiedev share <filename> <recipient> [--role read|edit]` — silently
|
|
3
|
+
// grant a teammate access to an artifact (no notification). Use
|
|
4
|
+
// `codiedev send` instead when the user wants to actively loop someone in.
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runShare = runShare;
|
|
7
|
+
const shared_1 = require("./shared");
|
|
8
|
+
const VALID_ROLES = new Set(["read", "edit"]);
|
|
9
|
+
function parseArgs(args) {
|
|
10
|
+
let filename;
|
|
11
|
+
let to;
|
|
12
|
+
let role;
|
|
13
|
+
for (let i = 0; i < args.length; i++) {
|
|
14
|
+
const a = args[i];
|
|
15
|
+
if (a === "--role" && i + 1 < args.length) {
|
|
16
|
+
role = args[++i];
|
|
17
|
+
}
|
|
18
|
+
else if (a.startsWith("--role=")) {
|
|
19
|
+
role = a.slice("--role=".length);
|
|
20
|
+
}
|
|
21
|
+
else if (!a.startsWith("--")) {
|
|
22
|
+
if (filename === undefined)
|
|
23
|
+
filename = a;
|
|
24
|
+
else if (to === undefined)
|
|
25
|
+
to = a;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (!filename || !to) {
|
|
29
|
+
console.error("Usage: codiedev share <filename> <recipient> [--role read|edit]");
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
if (role && !VALID_ROLES.has(role)) {
|
|
33
|
+
console.error(`Invalid --role. Valid: ${[...VALID_ROLES].join(", ")}`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
return { filename, to, role: (role ?? "read") };
|
|
37
|
+
}
|
|
38
|
+
async function runShare(args, configOverride) {
|
|
39
|
+
const parsed = parseArgs(args);
|
|
40
|
+
const config = configOverride ?? (0, shared_1.requireConfig)();
|
|
41
|
+
const start = Date.now();
|
|
42
|
+
try {
|
|
43
|
+
const res = await (0, shared_1.apiRequest)("POST", "/api/cli/share-with", {
|
|
44
|
+
config,
|
|
45
|
+
body: {
|
|
46
|
+
filename: parsed.filename,
|
|
47
|
+
to: parsed.to,
|
|
48
|
+
role: parsed.role,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
(0, shared_1.recordAgentEvent)(config, {
|
|
52
|
+
tool: "codiedev_share_with",
|
|
53
|
+
ok: true,
|
|
54
|
+
latencyMs: Date.now() - start,
|
|
55
|
+
});
|
|
56
|
+
const who = res.recipient ? `${res.recipient.name} (${res.recipient.email})` : parsed.to;
|
|
57
|
+
if (res.noop) {
|
|
58
|
+
console.log(`Already shared with ${who}.`);
|
|
59
|
+
}
|
|
60
|
+
else if (res.updated) {
|
|
61
|
+
console.log(`Updated access for ${who} to ${parsed.role}.`);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
console.log(`Shared ${parsed.filename} with ${who} (${parsed.role}).`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
(0, shared_1.recordAgentEvent)(config, {
|
|
69
|
+
tool: "codiedev_share_with",
|
|
70
|
+
ok: false,
|
|
71
|
+
latencyMs: Date.now() - start,
|
|
72
|
+
error: err.message,
|
|
73
|
+
});
|
|
74
|
+
// Ambiguous recipients return 409 with candidates in the body.
|
|
75
|
+
const body = err.body;
|
|
76
|
+
if (body && Array.isArray(body.candidates) && body.candidates.length > 0) {
|
|
77
|
+
console.error(`Recipient "${parsed.to}" is ambiguous. Candidates:`);
|
|
78
|
+
for (const c of body.candidates) {
|
|
79
|
+
console.error(` - ${c.name} (${c.email})`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
console.error(`Share failed: ${err.message}`);
|
|
84
|
+
}
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -13,12 +13,30 @@ export declare function apiRequest<T = unknown>(method: "GET" | "POST", path: st
|
|
|
13
13
|
body?: Record<string, unknown>;
|
|
14
14
|
query?: Record<string, string | number | undefined>;
|
|
15
15
|
}): Promise<T>;
|
|
16
|
+
/**
|
|
17
|
+
* Fire-and-forget per-invocation telemetry from CLI subcommands. Mirrors
|
|
18
|
+
* the MCP server's emitTelemetry but stamps `source: "cli"` so adoption
|
|
19
|
+
* dashboards can distinguish between the two surfaces. Never blocks the
|
|
20
|
+
* user's command, never throws — telemetry must not change behavior.
|
|
21
|
+
*/
|
|
22
|
+
export declare function recordAgentEvent(config: CodiedevConfig, event: {
|
|
23
|
+
tool: string;
|
|
24
|
+
ok: boolean;
|
|
25
|
+
latencyMs: number;
|
|
26
|
+
error?: string;
|
|
27
|
+
}): void;
|
|
28
|
+
/**
|
|
29
|
+
* Wraps a CLI subcommand body so success and failure both record telemetry
|
|
30
|
+
* and the original error propagates after the event is queued. Use this in
|
|
31
|
+
* each `runXxx` command instead of inline timing + try/catch boilerplate.
|
|
32
|
+
*/
|
|
33
|
+
export declare function withTelemetry<T>(config: CodiedevConfig, tool: string, fn: () => Promise<T>): Promise<T>;
|
|
16
34
|
/**
|
|
17
35
|
* Relative-time formatter for inbox / listing output.
|
|
18
36
|
*/
|
|
19
37
|
export declare function timeAgo(ts: number): string;
|
|
20
38
|
/**
|
|
21
39
|
* Strip surrounding quotes from a CLI argument if the user wrapped it.
|
|
22
|
-
* `codiedev ping
|
|
40
|
+
* `codiedev ping <name> "hey"` on some shells passes the quotes through.
|
|
23
41
|
*/
|
|
24
42
|
export declare function stripQuotes(s: string): string;
|
package/dist/commands/shared.js
CHANGED
|
@@ -35,6 +35,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.requireConfig = requireConfig;
|
|
37
37
|
exports.apiRequest = apiRequest;
|
|
38
|
+
exports.recordAgentEvent = recordAgentEvent;
|
|
39
|
+
exports.withTelemetry = withTelemetry;
|
|
38
40
|
exports.timeAgo = timeAgo;
|
|
39
41
|
exports.stripQuotes = stripQuotes;
|
|
40
42
|
const https = __importStar(require("https"));
|
|
@@ -116,6 +118,49 @@ function apiRequest(method, path, options) {
|
|
|
116
118
|
req.end();
|
|
117
119
|
});
|
|
118
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* Fire-and-forget per-invocation telemetry from CLI subcommands. Mirrors
|
|
123
|
+
* the MCP server's emitTelemetry but stamps `source: "cli"` so adoption
|
|
124
|
+
* dashboards can distinguish between the two surfaces. Never blocks the
|
|
125
|
+
* user's command, never throws — telemetry must not change behavior.
|
|
126
|
+
*/
|
|
127
|
+
function recordAgentEvent(config, event) {
|
|
128
|
+
apiRequest("POST", "/api/telemetry/mcp-event", {
|
|
129
|
+
config,
|
|
130
|
+
body: {
|
|
131
|
+
tool: event.tool,
|
|
132
|
+
ok: event.ok,
|
|
133
|
+
latencyMs: event.latencyMs,
|
|
134
|
+
error: event.error,
|
|
135
|
+
mcpVersion: version_1.CLI_VERSION,
|
|
136
|
+
source: "cli",
|
|
137
|
+
},
|
|
138
|
+
}).catch(() => {
|
|
139
|
+
// Swallow — telemetry must never affect user-facing flow.
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Wraps a CLI subcommand body so success and failure both record telemetry
|
|
144
|
+
* and the original error propagates after the event is queued. Use this in
|
|
145
|
+
* each `runXxx` command instead of inline timing + try/catch boilerplate.
|
|
146
|
+
*/
|
|
147
|
+
async function withTelemetry(config, tool, fn) {
|
|
148
|
+
const start = Date.now();
|
|
149
|
+
try {
|
|
150
|
+
const result = await fn();
|
|
151
|
+
recordAgentEvent(config, { tool, ok: true, latencyMs: Date.now() - start });
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
recordAgentEvent(config, {
|
|
156
|
+
tool,
|
|
157
|
+
ok: false,
|
|
158
|
+
latencyMs: Date.now() - start,
|
|
159
|
+
error: err.message?.slice(0, 240),
|
|
160
|
+
});
|
|
161
|
+
throw err;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
119
164
|
/**
|
|
120
165
|
* Relative-time formatter for inbox / listing output.
|
|
121
166
|
*/
|
|
@@ -134,7 +179,7 @@ function timeAgo(ts) {
|
|
|
134
179
|
}
|
|
135
180
|
/**
|
|
136
181
|
* Strip surrounding quotes from a CLI argument if the user wrapped it.
|
|
137
|
-
* `codiedev ping
|
|
182
|
+
* `codiedev ping <name> "hey"` on some shells passes the quotes through.
|
|
138
183
|
*/
|
|
139
184
|
function stripQuotes(s) {
|
|
140
185
|
if ((s.startsWith('"') && s.endsWith('"')) ||
|
package/dist/connect.js
CHANGED
|
@@ -111,7 +111,7 @@ function promptHidden(question) {
|
|
|
111
111
|
stdin.on("data", onData);
|
|
112
112
|
});
|
|
113
113
|
}
|
|
114
|
-
function postJson(url, body) {
|
|
114
|
+
function postJson(url, body, extraHeaders = {}) {
|
|
115
115
|
return new Promise((resolve, reject) => {
|
|
116
116
|
const parsed = new URL(url);
|
|
117
117
|
const data = JSON.stringify(body);
|
|
@@ -124,6 +124,7 @@ function postJson(url, body) {
|
|
|
124
124
|
"Content-Type": "application/json",
|
|
125
125
|
"Content-Length": Buffer.byteLength(data),
|
|
126
126
|
"X-Codiedev-Cli-Version": version_1.CLI_VERSION,
|
|
127
|
+
...extraHeaders,
|
|
127
128
|
},
|
|
128
129
|
};
|
|
129
130
|
const lib = parsed.protocol === "https:" ? https : http;
|
|
@@ -213,16 +214,44 @@ async function runConnect(args = []) {
|
|
|
213
214
|
console.error(`\nError: Failed to save config — ${err.message}`);
|
|
214
215
|
process.exit(1);
|
|
215
216
|
}
|
|
217
|
+
// Migration: strip MCP server entries written by pre-CLI-only versions
|
|
218
|
+
// of `codiedev connect`. These pointed at `npx codiedev-mcp`, a binary
|
|
219
|
+
// that was removed in 0.3.4 and that 0.7.11+ no longer ships. Without
|
|
220
|
+
// cleanup, customers upgrading from any prior version see persistent
|
|
221
|
+
// "MCP server failed to start" errors in their agent UIs even though
|
|
222
|
+
// the CLI works fine. Each helper is idempotent and a no-op when the
|
|
223
|
+
// relevant config file doesn't exist.
|
|
224
|
+
//
|
|
225
|
+
// Each call is wrapped because the user's upgrade path matters more
|
|
226
|
+
// than the cleanup. A locked / wrong-owner config file (rare but real)
|
|
227
|
+
// shouldn't block the install loop or strand them on an old version.
|
|
228
|
+
for (const [label, fn] of [
|
|
229
|
+
["Claude Code", utils_1.cleanupClaudeCodeMcp],
|
|
230
|
+
["Codex", utils_1.cleanupCodexMcp],
|
|
231
|
+
["Cursor", utils_1.cleanupCursorMcp],
|
|
232
|
+
["VS Code", utils_1.cleanupVSCodeMcp],
|
|
233
|
+
]) {
|
|
234
|
+
try {
|
|
235
|
+
fn();
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
console.warn(`\nWarning: Couldn't clean up legacy ${label} MCP entry — ${err.message}`);
|
|
239
|
+
console.warn("Continuing with install. The stale entry won't break the CLI but the agent may show a broken-MCP indicator until you remove it manually.");
|
|
240
|
+
}
|
|
241
|
+
}
|
|
216
242
|
const hasClaude = (0, utils_1.claudeCodeInstalled)();
|
|
217
243
|
const hasCodex = (0, utils_1.codexInstalled)();
|
|
218
244
|
const hasCursor = (0, utils_1.cursorInstalled)();
|
|
219
|
-
const hasVSCodeCopilot = (0, utils_1.vscodeCopilotInstalled)();
|
|
220
245
|
const installed = [];
|
|
221
|
-
|
|
246
|
+
// Machine-readable target keys reported back to the backend via
|
|
247
|
+
// /api/cli/markInstalled so the portal's "Connected" state reflects
|
|
248
|
+
// what's actually wired up on this machine, not just "token validated."
|
|
249
|
+
const installedTargets = [];
|
|
222
250
|
if (hasClaude) {
|
|
223
251
|
try {
|
|
224
252
|
(0, utils_1.installHook)();
|
|
225
253
|
installed.push("Claude Code SessionEnd hook (~/.claude/settings.json)");
|
|
254
|
+
installedTargets.push("claudeCode-hook");
|
|
226
255
|
}
|
|
227
256
|
catch (err) {
|
|
228
257
|
console.error(`\nWarning: Failed to install Claude Code hook — ${err.message}`);
|
|
@@ -230,22 +259,17 @@ async function runConnect(args = []) {
|
|
|
230
259
|
try {
|
|
231
260
|
(0, utils_1.installClaudeCodeInstructions)();
|
|
232
261
|
installed.push("Claude Code agent instructions (~/.claude/CLAUDE.md)");
|
|
262
|
+
installedTargets.push("claudeCode-instructions");
|
|
233
263
|
}
|
|
234
264
|
catch (err) {
|
|
235
265
|
console.error(`\nWarning: Failed to install Claude Code instructions — ${err.message}`);
|
|
236
266
|
}
|
|
237
|
-
try {
|
|
238
|
-
(0, utils_1.installClaudeCodeMcp)();
|
|
239
|
-
installed.push("Claude Code MCP server (~/.claude.json)");
|
|
240
|
-
}
|
|
241
|
-
catch (err) {
|
|
242
|
-
console.error(`\nWarning: Failed to install Claude Code MCP server — ${err.message}`);
|
|
243
|
-
}
|
|
244
267
|
}
|
|
245
268
|
if (hasCodex) {
|
|
246
269
|
try {
|
|
247
270
|
(0, utils_1.installCodexHook)();
|
|
248
271
|
installed.push("Codex Stop hook (~/.codex/hooks.json)");
|
|
272
|
+
installedTargets.push("codex-hook");
|
|
249
273
|
}
|
|
250
274
|
catch (err) {
|
|
251
275
|
console.error(`\nWarning: Failed to install Codex hook — ${err.message}`);
|
|
@@ -253,22 +277,17 @@ async function runConnect(args = []) {
|
|
|
253
277
|
try {
|
|
254
278
|
(0, utils_1.installCodexInstructions)();
|
|
255
279
|
installed.push("Codex agent instructions (~/.codex/AGENTS.md)");
|
|
280
|
+
installedTargets.push("codex-instructions");
|
|
256
281
|
}
|
|
257
282
|
catch (err) {
|
|
258
283
|
console.error(`\nWarning: Failed to install Codex instructions — ${err.message}`);
|
|
259
284
|
}
|
|
260
|
-
try {
|
|
261
|
-
(0, utils_1.installCodexMcp)();
|
|
262
|
-
installed.push("Codex MCP server (~/.codex/config.toml)");
|
|
263
|
-
}
|
|
264
|
-
catch (err) {
|
|
265
|
-
console.error(`\nWarning: Failed to install Codex MCP server — ${err.message}`);
|
|
266
|
-
}
|
|
267
285
|
}
|
|
268
286
|
if (hasCursor) {
|
|
269
287
|
try {
|
|
270
288
|
(0, utils_1.installCursorHook)();
|
|
271
289
|
installed.push("Cursor sessionEnd hook (~/.cursor/hooks.json)");
|
|
290
|
+
installedTargets.push("cursor-hook");
|
|
272
291
|
}
|
|
273
292
|
catch (err) {
|
|
274
293
|
console.error(`\nWarning: Failed to install Cursor hook — ${err.message}`);
|
|
@@ -276,30 +295,29 @@ async function runConnect(args = []) {
|
|
|
276
295
|
try {
|
|
277
296
|
(0, utils_1.installCursorInstructions)();
|
|
278
297
|
installed.push("Cursor agent instructions (~/.cursor/rules/codiedev.mdc)");
|
|
298
|
+
installedTargets.push("cursor-instructions");
|
|
279
299
|
}
|
|
280
300
|
catch (err) {
|
|
281
301
|
console.error(`\nWarning: Failed to install Cursor instructions — ${err.message}`);
|
|
282
302
|
}
|
|
283
|
-
try {
|
|
284
|
-
(0, utils_1.installCursorMcp)();
|
|
285
|
-
installed.push("Cursor MCP server (~/.cursor/mcp.json)");
|
|
286
|
-
}
|
|
287
|
-
catch (err) {
|
|
288
|
-
console.error(`\nWarning: Failed to install Cursor MCP server — ${err.message}`);
|
|
289
|
-
}
|
|
290
303
|
}
|
|
291
|
-
|
|
304
|
+
// Tell the backend which targets we wired up so the portal's onboarding
|
|
305
|
+
// card flips to "Connected" only when something actually installed —
|
|
306
|
+
// not on bare token validation. Best-effort: a network failure here
|
|
307
|
+
// doesn't undo the local install, and `codiedev doctor` still works.
|
|
308
|
+
if (installedTargets.length > 0) {
|
|
292
309
|
try {
|
|
293
|
-
(
|
|
294
|
-
|
|
295
|
-
|
|
310
|
+
await postJson(`${BACKEND_URL}/api/cli/markInstalled`, {
|
|
311
|
+
installedTargets,
|
|
312
|
+
}, { Authorization: `Bearer ${token}` });
|
|
296
313
|
}
|
|
297
314
|
catch (err) {
|
|
298
|
-
console.error(`\nWarning: Failed to install
|
|
315
|
+
console.error(`\nWarning: Failed to report install to backend — ${err.message}`);
|
|
316
|
+
console.error("Local install succeeded; portal may not reflect it until you re-run.");
|
|
299
317
|
}
|
|
300
318
|
}
|
|
301
|
-
if (!hasClaude && !hasCodex && !hasCursor
|
|
302
|
-
console.warn("\nNo Claude Code, Codex,
|
|
319
|
+
if (!hasClaude && !hasCodex && !hasCursor) {
|
|
320
|
+
console.warn("\nNo Claude Code, Codex, or Cursor install detected.");
|
|
303
321
|
console.warn("If you just installed Claude Code, launch it once (so ~/.claude is created),");
|
|
304
322
|
console.warn("then re-run `npx codiedev connect` to wire up the capture hook.");
|
|
305
323
|
}
|
|
@@ -312,13 +330,6 @@ async function runConnect(args = []) {
|
|
|
312
330
|
}
|
|
313
331
|
console.log("Sessions will be captured automatically.");
|
|
314
332
|
}
|
|
315
|
-
if (vscodeMcpInstalled) {
|
|
316
|
-
console.log();
|
|
317
|
-
console.log("VS Code: open Copilot Chat — it will prompt you to trust the codiedev MCP server.");
|
|
318
|
-
console.log("Click Allow once, then ask Copilot: \"create a reverse ticket from my current changes\".");
|
|
319
|
-
console.log("Note: Copilot doesn't expose chat transcripts to MCP, so on-demand tools (reverse_ticket,");
|
|
320
|
-
console.log("push, pull, ask, ping) work fully — auto-captured decisions need Claude Code or Codex.");
|
|
321
|
-
}
|
|
322
333
|
console.log();
|
|
323
334
|
console.log("Run `codiedev doctor` to verify everything's wired up.");
|
|
324
335
|
console.log();
|
package/dist/detection.d.ts
CHANGED
|
@@ -10,3 +10,21 @@ export interface ClaudeCodeProbe {
|
|
|
10
10
|
* obviously committed to the tool.
|
|
11
11
|
*/
|
|
12
12
|
export declare function detectClaudeCode(probe: ClaudeCodeProbe): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* True iff `cmd` looks like a hook command codiedev itself wrote. Used
|
|
15
|
+
* both by the install filter (so `connect` replaces the existing entry
|
|
16
|
+
* instead of stacking another one) and by `codiedev doctor` (so it can
|
|
17
|
+
* grade whether the hook is wired up).
|
|
18
|
+
*
|
|
19
|
+
* Must match every form codiedev can write:
|
|
20
|
+
* - Legacy `npx codiedev-hook <subcommand>`
|
|
21
|
+
* - Absolute path from npm-registry install:
|
|
22
|
+
* `<node> /.../node_modules/codiedev/dist/hook.js <subcommand>`
|
|
23
|
+
* - Absolute path from `npm link` dev install (the monorepo folder
|
|
24
|
+
* is named `codiedev-cli`, not `codiedev`):
|
|
25
|
+
* `<node> /.../codiedev-cli/dist/hook.js <subcommand>`
|
|
26
|
+
*
|
|
27
|
+
* The `codiedev[^/\\]*` allows the trailing `-cli` (or any future suffix)
|
|
28
|
+
* without matching unrelated paths like `notcodiedev/dist/hook.js`.
|
|
29
|
+
*/
|
|
30
|
+
export declare function isCodiedevHookCommand(cmd: string | undefined): boolean;
|