codiedev 0.7.10 → 0.8.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 CHANGED
@@ -25,7 +25,15 @@ const inbox_1 = require("./commands/inbox");
25
25
  const note_1 = require("./commands/note");
26
26
  const promote_1 = require("./commands/promote");
27
27
  const reverseTicket_1 = require("./commands/reverseTicket");
28
+ const createTicket_1 = require("./commands/createTicket");
28
29
  const doctor_1 = require("./commands/doctor");
30
+ const search_1 = require("./commands/search");
31
+ const library_1 = require("./commands/library");
32
+ const post_1 = require("./commands/post");
33
+ const share_1 = require("./commands/share");
34
+ const send_1 = require("./commands/send");
35
+ const react_1 = require("./commands/react");
36
+ const version_1 = require("./version");
29
37
  const HELP = `
30
38
  CodieDev CLI
31
39
 
@@ -56,13 +64,38 @@ Artifacts:
56
64
  Draft lands in /portal/agentic-ticketing.
57
65
  codiedev reverse-ticket --base <ref> Override the base ref in branch mode
58
66
  (defaults to origin/main).
67
+ codiedev create-ticket [<prompt>] Forward-write a scoped ticket plan.
68
+ Claude investigates the codebase, asks
69
+ "single repo or multiple? where?", then
70
+ emits a plan with N+1 split. Lands in
71
+ /portal/research/<id>; push to Jira from there.
72
+ Beta — opt-in via betaCreateTicket.
73
+ codiedev create-ticket --submit <path|->
74
+ Internal: Claude calls this itself with
75
+ a plan JSON to submit. Use - for stdin.
59
76
 
60
77
  Messaging:
61
78
  codiedev ping <user> "<msg>" [--with <key>]
62
79
  Send a message to a teammate
80
+ codiedev send <key> <user> ["<msg>"] Share an artifact + ping in one step
81
+ codiedev share <key> <user> [--role read|edit]
82
+ Silently grant access (no notification)
63
83
  codiedev inbox [--unread] [--limit N] Show messages
64
84
  codiedev read <ping-id> Mark read + show full message
65
85
 
86
+ Discovery:
87
+ codiedev search "<query>" Semantic search across the team's
88
+ captured sessions and artifacts
89
+ codiedev library [--scope mine|shared|all] [--kind doc|skill]
90
+ [--type T] [--tag X] [--limit N]
91
+ List artifacts in your workspace
92
+
93
+ Feed:
94
+ codiedev post --title "<t>" --body "<b>" [--intent share|request_review|
95
+ request_expertise|link_share|rfc] [--filename <key>]
96
+ [--mention <name>]... Publish to the team feed
97
+ codiedev react <postId> <emoji> Toggle a reaction on a feed post
98
+
66
99
  Capture:
67
100
  codiedev note "<text>" Capture a passing thought
68
101
 
@@ -90,7 +123,7 @@ Examples:
90
123
  codiedev push spec-cart-clear.md # the typical case
91
124
  codiedev push portal-design.md # with name:/description: → skill
92
125
  codiedev pull spec-cart-clear.md
93
- codiedev ping maya "thoughts?" --with spec-cart-clear.md
126
+ codiedev ping <teammate> "thoughts?" --with spec-cart-clear.md
94
127
  codiedev inbox --unread
95
128
  codiedev note "idempotency key is worth a follow-up"
96
129
 
@@ -162,8 +195,8 @@ codiedev ping — send a teammate a message, optionally attached to an artifact
162
195
  Teammates are notified by email and can reply from their own agent.
163
196
 
164
197
  Examples:
165
- codiedev ping maya "thoughts on batch vs parallel?" --with spec-cart-clear.md
166
- codiedev ping nic@signalandcode.co "can you take this one?"
198
+ codiedev ping <teammate> "thoughts on batch vs parallel?" --with spec-cart-clear.md
199
+ codiedev ping <teammate>@example.com "can you take this one?"
167
200
  `.trim(),
168
201
  inbox: `
169
202
  codiedev inbox — show messages from teammates
@@ -218,6 +251,27 @@ codiedev promote — promote an auto-extracted artifact to authored
218
251
 
219
252
  Find artifact-ids in the portal under Knowledge → Memory, or via
220
253
  the portal search.
254
+ `.trim(),
255
+ "create-ticket": `
256
+ codiedev create-ticket — forward-write a scoped ticket plan
257
+
258
+ Default path:
259
+ codiedev create-ticket "I want to add SSO with Google"
260
+
261
+ Bare mode (no --submit) prints skill instructions for Claude to follow.
262
+ Claude asks where your repos live, investigates them locally, and emits a
263
+ plan as JSON. The CLI then re-invokes itself with --submit to upload it.
264
+
265
+ The plan lands at /portal/research/<id> as a ready-state research session.
266
+ Single ticket renders as one ticket; multi-ticket renders as a folder/group.
267
+ Push to Jira from the portal.
268
+
269
+ Beta. Opt-in by setting userProfiles.betaCreateTicket = true on your row.
270
+
271
+ Examples:
272
+ codiedev create-ticket
273
+ codiedev create-ticket "I want to add Google SSO"
274
+ codiedev create-ticket --submit /tmp/plan.json # internal, called by Claude
221
275
  `.trim(),
222
276
  };
223
277
  const DOCS = `
@@ -242,7 +296,7 @@ specs, reviews, decisions, proposals, and captured thoughts — so:
242
296
 
243
297
  2. Your teammate gets pinged (either directly by you, or because
244
298
  they asked to be kept in the loop on that key):
245
- codiedev ping maya "thoughts?" --with spec-cart-clear.md
299
+ codiedev ping <teammate> "thoughts?" --with spec-cart-clear.md
246
300
 
247
301
  3. They check their inbox:
248
302
  codiedev inbox
@@ -305,9 +359,9 @@ If you use Claude Code or Codex, the agent already knows the commands —
305
359
  'codiedev connect' installs instructions into your user-level config.
306
360
  Just say things like:
307
361
 
308
- "push this spec and ping Nic"
362
+ "push this spec and ping a teammate"
309
363
  "any messages?"
310
- "pull Maya's latest spec"
364
+ "pull a teammate's latest spec"
311
365
 
312
366
  The agent will run the right command via Bash.
313
367
 
@@ -357,7 +411,7 @@ async function main() {
357
411
  }
358
412
  }
359
413
  if (command === "version" || command === "--version" || command === "-v") {
360
- console.log("codiedev cli");
414
+ console.log(`codiedev v${version_1.CLI_VERSION}`);
361
415
  return;
362
416
  }
363
417
  try {
@@ -408,6 +462,29 @@ async function main() {
408
462
  case "reverseTicket":
409
463
  await (0, reverseTicket_1.runReverseTicket)(rest);
410
464
  return;
465
+ case "create-ticket":
466
+ case "createTicket":
467
+ await (0, createTicket_1.runCreateTicket)(rest);
468
+ return;
469
+ case "search":
470
+ await (0, search_1.runSearch)(rest);
471
+ return;
472
+ case "library":
473
+ case "ls":
474
+ await (0, library_1.runLibrary)(rest);
475
+ return;
476
+ case "post":
477
+ await (0, post_1.runPost)(rest);
478
+ return;
479
+ case "share":
480
+ await (0, share_1.runShare)(rest);
481
+ return;
482
+ case "send":
483
+ await (0, send_1.runSend)(rest);
484
+ return;
485
+ case "react":
486
+ await (0, react_1.runReact)(rest);
487
+ return;
411
488
  default:
412
489
  console.error(`Unknown command: ${command}`);
413
490
  console.error(HELP);
@@ -20,11 +20,17 @@ function parseArgs(args) {
20
20
  async function runAsk(args) {
21
21
  const { question } = parseArgs(args);
22
22
  const config = (0, shared_1.requireConfig)();
23
+ const start = Date.now();
23
24
  try {
24
25
  const res = await (0, shared_1.apiRequest)("POST", "/api/cli/ask", {
25
26
  config,
26
27
  body: { question },
27
28
  });
29
+ (0, shared_1.recordAgentEvent)(config, {
30
+ tool: "codiedev_ask",
31
+ ok: true,
32
+ latencyMs: Date.now() - start,
33
+ });
28
34
  console.log("");
29
35
  console.log(res.answer);
30
36
  if (res.citations.length > 0) {
@@ -40,6 +46,12 @@ async function runAsk(args) {
40
46
  console.log("");
41
47
  }
42
48
  catch (err) {
49
+ (0, shared_1.recordAgentEvent)(config, {
50
+ tool: "codiedev_ask",
51
+ ok: false,
52
+ latencyMs: Date.now() - start,
53
+ error: err.message,
54
+ });
43
55
  console.error(`Ask failed: ${err.message}`);
44
56
  process.exit(1);
45
57
  }
@@ -0,0 +1,27 @@
1
+ import type { CodiedevConfig } from "../utils";
2
+ export declare function substitutePrompt(protocol: string, prompt: string | undefined): string;
3
+ interface ConnectedRepo {
4
+ repoId: string;
5
+ fullName: string;
6
+ }
7
+ export declare function validatePlanHasRepo(parsedPlan: any): string | null;
8
+ export declare function resolveRepoIds(fullNames: string[], repos: ConnectedRepo[]): string[];
9
+ export interface CreateTicketArgs {
10
+ mode: "bare" | "submit";
11
+ prompt?: string;
12
+ submitSource?: "stdin" | string;
13
+ }
14
+ export declare function parseCreateTicketArgs(args: string[]): CreateTicketArgs;
15
+ export declare function runCreateTicket(args: string[]): Promise<void>;
16
+ /**
17
+ * Bare-mode entrypoint. Fetches the protocol body from the backend so we can
18
+ * iterate on it without shipping a new CLI, substitutes the user's prompt,
19
+ * and writes the result to stdout for the agent to follow. Hard-errors on
20
+ * fetch failure rather than falling back to a stale local copy — the whole
21
+ * point of moving the protocol server-side is that the freshest version is
22
+ * the only version.
23
+ */
24
+ export declare function runBare(config: CodiedevConfig, prompt: string | undefined): Promise<void>;
25
+ export declare function runSubmit(config: CodiedevConfig, rawJson: string): Promise<void>;
26
+ export declare function readSubmitInput(source: "stdin" | string): string;
27
+ export {};
@@ -0,0 +1,198 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.substitutePrompt = substitutePrompt;
37
+ exports.validatePlanHasRepo = validatePlanHasRepo;
38
+ exports.resolveRepoIds = resolveRepoIds;
39
+ exports.parseCreateTicketArgs = parseCreateTicketArgs;
40
+ exports.runCreateTicket = runCreateTicket;
41
+ exports.runBare = runBare;
42
+ exports.runSubmit = runSubmit;
43
+ exports.readSubmitInput = readSubmitInput;
44
+ const fs = __importStar(require("node:fs"));
45
+ const shared_1 = require("./shared");
46
+ const PROMPT_PLACEHOLDER = "{{USER_PROMPT}}";
47
+ const MISSING_PROMPT_FALLBACK = "(not yet provided — ask the user a single combined question that covers both the intent AND repo locations: \"What are you working on, and is this in a single repo or multiple? If multiple, give me the paths.\" Skip Step 1 since you've folded it into this question. Then proceed to Step 2.)";
48
+ function substitutePrompt(protocol, prompt) {
49
+ return protocol.replace(PROMPT_PLACEHOLDER, prompt && prompt.length > 0 ? prompt : MISSING_PROMPT_FALLBACK);
50
+ }
51
+ function validatePlanHasRepo(parsedPlan) {
52
+ const fullNames = Array.isArray(parsedPlan?.repoFullNames) ? parsedPlan.repoFullNames : [];
53
+ const explicitIds = Array.isArray(parsedPlan?.repoIds) ? parsedPlan.repoIds : [];
54
+ if (fullNames.length === 0 && explicitIds.length === 0) {
55
+ return "Plan must include at least one repo (repoFullNames: ['owner/name'] or repoIds: [...])";
56
+ }
57
+ return null;
58
+ }
59
+ function resolveRepoIds(fullNames, repos) {
60
+ const lookup = new Map();
61
+ for (const r of repos) {
62
+ lookup.set(r.fullName.toLowerCase(), r.repoId);
63
+ }
64
+ const ids = [];
65
+ for (const fn of fullNames) {
66
+ const id = lookup.get(fn.toLowerCase());
67
+ if (!id) {
68
+ throw new Error(`Repo "${fn}" is not connected to your CodieDev account. Run \`codiedev connect\` to refresh, or push the missing repo through Settings.`);
69
+ }
70
+ ids.push(id);
71
+ }
72
+ return ids;
73
+ }
74
+ function parseCreateTicketArgs(args) {
75
+ let mode = "bare";
76
+ let prompt;
77
+ let submitSource;
78
+ for (let i = 0; i < args.length; i++) {
79
+ const a = args[i];
80
+ if (a === "--submit" && i + 1 < args.length) {
81
+ mode = "submit";
82
+ const next = args[++i];
83
+ submitSource = next === "-" ? "stdin" : next;
84
+ }
85
+ else if (a.startsWith("--submit=")) {
86
+ mode = "submit";
87
+ const v = a.slice("--submit=".length);
88
+ submitSource = v === "-" ? "stdin" : v;
89
+ }
90
+ else if (!a.startsWith("--") && !prompt && mode === "bare") {
91
+ prompt = a;
92
+ }
93
+ }
94
+ return { mode, prompt, submitSource };
95
+ }
96
+ async function runCreateTicket(args) {
97
+ const parsed = parseCreateTicketArgs(args);
98
+ if (parsed.mode === "bare") {
99
+ const config = (0, shared_1.requireConfig)();
100
+ await runBare(config, parsed.prompt);
101
+ return;
102
+ }
103
+ // submit mode
104
+ if (!parsed.submitSource) {
105
+ console.error("--submit requires a path or '-' for stdin");
106
+ process.exit(1);
107
+ }
108
+ const config = (0, shared_1.requireConfig)();
109
+ let raw;
110
+ try {
111
+ raw = readSubmitInput(parsed.submitSource);
112
+ }
113
+ catch (err) {
114
+ console.error(`Failed to read plan: ${err.message}`);
115
+ process.exit(1);
116
+ }
117
+ // Translate fullNames → IDs before submitting
118
+ let parsedPlan;
119
+ try {
120
+ parsedPlan = JSON.parse(raw);
121
+ }
122
+ catch (err) {
123
+ console.error(`Plan JSON did not parse: ${err.message}`);
124
+ process.exit(1);
125
+ }
126
+ const validationError = validatePlanHasRepo(parsedPlan);
127
+ if (validationError) {
128
+ console.error(validationError);
129
+ process.exit(1);
130
+ }
131
+ const fullNames = Array.isArray(parsedPlan.repoFullNames) ? parsedPlan.repoFullNames : [];
132
+ const explicitIds = Array.isArray(parsedPlan.repoIds) ? parsedPlan.repoIds : [];
133
+ if (fullNames.length > 0 && explicitIds.length === 0) {
134
+ parsedPlan.repoIds = resolveRepoIds(fullNames, config.repos);
135
+ }
136
+ for (const t of parsedPlan.plan?.tickets ?? []) {
137
+ if (t.targetRepoFullName && !t.targetRepoId) {
138
+ const ids = resolveRepoIds([t.targetRepoFullName], config.repos);
139
+ t.targetRepoId = ids[0];
140
+ }
141
+ }
142
+ await runSubmit(config, JSON.stringify(parsedPlan));
143
+ }
144
+ /**
145
+ * Bare-mode entrypoint. Fetches the protocol body from the backend so we can
146
+ * iterate on it without shipping a new CLI, substitutes the user's prompt,
147
+ * and writes the result to stdout for the agent to follow. Hard-errors on
148
+ * fetch failure rather than falling back to a stale local copy — the whole
149
+ * point of moving the protocol server-side is that the freshest version is
150
+ * the only version.
151
+ */
152
+ async function runBare(config, prompt) {
153
+ let res;
154
+ try {
155
+ res = await (0, shared_1.apiRequest)("GET", "/api/cli/createTicketProtocol", { config });
156
+ }
157
+ catch (err) {
158
+ const status = err.status;
159
+ if (status === 403) {
160
+ console.error("create-ticket is in beta and not enabled for your account. Ask your CodieDev admin to flip betaCreateTicket on your profile.");
161
+ }
162
+ else {
163
+ console.error(`Failed to fetch create-ticket protocol: ${err.message}. Check your network connection and re-run.`);
164
+ }
165
+ process.exit(1);
166
+ }
167
+ process.stdout.write(substitutePrompt(res.protocol, prompt));
168
+ }
169
+ async function runSubmit(config, rawJson) {
170
+ let payload;
171
+ try {
172
+ payload = JSON.parse(rawJson);
173
+ }
174
+ catch (err) {
175
+ throw new Error(`Plan JSON did not parse — emit valid JSON matching the schema printed by \`codiedev create-ticket\`. Original error: ${err.message}`);
176
+ }
177
+ if (!payload.plan || !payload.prompt || !payload.repoIds) {
178
+ throw new Error("Plan JSON missing one of: plan, prompt, repoIds");
179
+ }
180
+ const res = await (0, shared_1.withTelemetry)(config, "codiedev_create_ticket", () => (0, shared_1.apiRequest)("POST", "/api/cli/createPlan", {
181
+ config,
182
+ body: payload,
183
+ }));
184
+ const portalHost = (process.env.CODIEDEV_PORTAL_URL || "https://codiedev.com").replace(/\/$/, "");
185
+ const portalLink = res.portalUrl.startsWith("http")
186
+ ? res.portalUrl
187
+ : `${portalHost}${res.portalUrl}`;
188
+ console.log(`✓ Created ${res.ticketCount} ticket(s).`);
189
+ console.log(` portal: ${portalLink}`);
190
+ console.log("");
191
+ console.log("Open in your portal to review and push to Jira.");
192
+ }
193
+ function readSubmitInput(source) {
194
+ if (source === "stdin") {
195
+ return fs.readFileSync(0, "utf8");
196
+ }
197
+ return fs.readFileSync(source, "utf8");
198
+ }
@@ -75,11 +75,17 @@ async function runDelete(args) {
75
75
  return;
76
76
  }
77
77
  }
78
+ const start = Date.now();
78
79
  try {
79
80
  const res = await (0, shared_1.apiRequest)("POST", "/api/cli/delete", {
80
81
  config,
81
82
  body: { key },
82
83
  });
84
+ (0, shared_1.recordAgentEvent)(config, {
85
+ tool: "codiedev_delete",
86
+ ok: true,
87
+ latencyMs: Date.now() - start,
88
+ });
83
89
  if (res.deleted === 0) {
84
90
  console.log(`No artifact found with key '${key}'. Nothing to delete.`);
85
91
  return;
@@ -90,6 +96,12 @@ async function runDelete(args) {
90
96
  console.log(`✓ Deleted ${res.deleted} version${res.deleted === 1 ? "" : "s"}${cascaded} of '${res.key}'.`);
91
97
  }
92
98
  catch (err) {
99
+ (0, shared_1.recordAgentEvent)(config, {
100
+ tool: "codiedev_delete",
101
+ ok: false,
102
+ latencyMs: Date.now() - start,
103
+ error: err.message,
104
+ });
93
105
  console.error(`Delete failed: ${err.message}`);
94
106
  process.exit(1);
95
107
  }
@@ -40,6 +40,7 @@ const os = __importStar(require("os"));
40
40
  const child_process_1 = require("child_process");
41
41
  const shared_1 = require("./shared");
42
42
  const utils_1 = require("../utils");
43
+ const detection_1 = require("../detection");
43
44
  const version_1 = require("../version");
44
45
  const upgradeNudge_1 = require("../upgradeNudge");
45
46
  const CLAUDE_SETTINGS_PATH = path.join(os.homedir(), ".claude", "settings.json");
@@ -48,7 +49,6 @@ const CODEX_HOOKS_PATH = path.join(os.homedir(), ".codex", "hooks.json");
48
49
  const CODEX_INSTRUCTIONS_PATH = path.join(os.homedir(), ".codex", "AGENTS.md");
49
50
  const CURSOR_HOOKS_PATH = path.join(os.homedir(), ".cursor", "hooks.json");
50
51
  const CURSOR_INSTRUCTIONS_PATH = path.join(os.homedir(), ".cursor", "rules", "codiedev.mdc");
51
- const CURSOR_MCP_PATH = path.join(os.homedir(), ".cursor", "mcp.json");
52
52
  function symbol(status) {
53
53
  if (status === "pass")
54
54
  return "✓";
@@ -62,27 +62,9 @@ function codexInstalled() {
62
62
  function cursorInstalled() {
63
63
  return fs.existsSync(path.join(os.homedir(), ".cursor"));
64
64
  }
65
- function hasCursorMcpEntry() {
66
- try {
67
- if (!fs.existsSync(CURSOR_MCP_PATH))
68
- return false;
69
- const raw = fs.readFileSync(CURSOR_MCP_PATH, "utf8");
70
- const parsed = JSON.parse(raw);
71
- const servers = parsed.mcpServers;
72
- return !!servers && "codiedev" in servers;
73
- }
74
- catch {
75
- return false;
76
- }
77
- }
78
- // Match both legacy `npx codiedev-hook ...` and absolute-path forms
79
- // `<node> <.../codiedev/dist/hook.js> ...` — installer switched to absolute
80
- // paths in 0.6.1 to work in GUI-launched contexts where shell PATH is missing.
81
- function isCodiedevHookCommand(cmd) {
82
- if (!cmd)
83
- return false;
84
- return cmd.includes("codiedev-hook") || /codiedev[\\/]dist[\\/]hook/.test(cmd);
85
- }
65
+ // `isCodiedevHookCommand` lives in ../detection alongside detectClaudeCode
66
+ // so utils.ts and doctor.ts share one definition (they used to drift —
67
+ // each had its own copy).
86
68
  // Cursor's hooks.json schema is { hooks: { sessionEnd: [{ command, ... }] } }
87
69
  // — flat array of objects with `command`, not the Claude/Codex nested
88
70
  // `{ hooks: [{ command }] }` wrapper.
@@ -95,7 +77,7 @@ function hasCursorHook() {
95
77
  const arr = parsed.hooks?.sessionEnd;
96
78
  if (!Array.isArray(arr))
97
79
  return false;
98
- return arr.some((h) => isCodiedevHookCommand(h.command));
80
+ return arr.some((h) => (0, detection_1.isCodiedevHookCommand)(h.command));
99
81
  }
100
82
  catch {
101
83
  return false;
@@ -114,7 +96,7 @@ function hasCodiedevHook(settingsPath, hookKey) {
114
96
  const inner = h.hooks;
115
97
  if (!Array.isArray(inner))
116
98
  return false;
117
- return inner.some((x) => isCodiedevHookCommand(x.command));
99
+ return inner.some((x) => (0, detection_1.isCodiedevHookCommand)(x.command));
118
100
  });
119
101
  }
120
102
  catch {
@@ -307,13 +289,6 @@ async function runDoctor(_args) {
307
289
  ? "~/.cursor/rules/codiedev.mdc"
308
290
  : "missing — re-run `codiedev connect`",
309
291
  });
310
- checks.push({
311
- name: "Cursor MCP server",
312
- status: hasCursorMcpEntry() ? "pass" : "fail",
313
- detail: hasCursorMcpEntry()
314
- ? "~/.cursor/mcp.json"
315
- : "missing — re-run `codiedev connect`",
316
- });
317
292
  }
318
293
  else {
319
294
  checks.push({
@@ -26,6 +26,7 @@ function parseInboxArgs(args) {
26
26
  async function runInbox(args) {
27
27
  const { unreadOnly, limit } = parseInboxArgs(args);
28
28
  const config = (0, shared_1.requireConfig)();
29
+ const start = Date.now();
29
30
  try {
30
31
  const res = await (0, shared_1.apiRequest)("GET", "/api/cli/inbox", {
31
32
  config,
@@ -34,6 +35,11 @@ async function runInbox(args) {
34
35
  limit,
35
36
  },
36
37
  });
38
+ (0, shared_1.recordAgentEvent)(config, {
39
+ tool: "codiedev_inbox",
40
+ ok: true,
41
+ latencyMs: Date.now() - start,
42
+ });
37
43
  const pings = res.pings;
38
44
  const unreadCount = pings.filter((p) => !p.readAt).length;
39
45
  if (pings.length === 0) {
@@ -56,6 +62,12 @@ async function runInbox(args) {
56
62
  console.log(`Mark read: codiedev read <ping-id>`);
57
63
  }
58
64
  catch (err) {
65
+ (0, shared_1.recordAgentEvent)(config, {
66
+ tool: "codiedev_inbox",
67
+ ok: false,
68
+ latencyMs: Date.now() - start,
69
+ error: err.message,
70
+ });
59
71
  console.error(`Inbox failed: ${err.message}`);
60
72
  process.exit(1);
61
73
  }
@@ -67,6 +79,7 @@ async function runRead(args) {
67
79
  process.exit(1);
68
80
  }
69
81
  const config = (0, shared_1.requireConfig)();
82
+ const start = Date.now();
70
83
  try {
71
84
  // First, fetch inbox so we can show the full body (markRead endpoint
72
85
  // doesn't return content).
@@ -76,6 +89,12 @@ async function runRead(args) {
76
89
  });
77
90
  const ping = listRes.pings.find((p) => p.pingId === pingId);
78
91
  if (!ping) {
92
+ (0, shared_1.recordAgentEvent)(config, {
93
+ tool: "codiedev_read",
94
+ ok: false,
95
+ latencyMs: Date.now() - start,
96
+ error: "ping not found",
97
+ });
79
98
  console.error(`Ping not found: ${pingId}`);
80
99
  process.exit(1);
81
100
  }
@@ -83,6 +102,11 @@ async function runRead(args) {
83
102
  config,
84
103
  body: { pingId },
85
104
  });
105
+ (0, shared_1.recordAgentEvent)(config, {
106
+ tool: "codiedev_read",
107
+ ok: true,
108
+ latencyMs: Date.now() - start,
109
+ });
86
110
  const from = ping.from?.name ?? "unknown";
87
111
  const when = (0, shared_1.timeAgo)(ping.createdAt);
88
112
  const where = ping.subjectKey ? ` · on ${ping.subjectKey}` : "";
@@ -100,6 +124,12 @@ async function runRead(args) {
100
124
  }
101
125
  }
102
126
  catch (err) {
127
+ (0, shared_1.recordAgentEvent)(config, {
128
+ tool: "codiedev_read",
129
+ ok: false,
130
+ latencyMs: Date.now() - start,
131
+ error: err.message,
132
+ });
103
133
  console.error(`Read failed: ${err.message}`);
104
134
  process.exit(1);
105
135
  }
@@ -0,0 +1,2 @@
1
+ import type { CodiedevConfig } from "../utils";
2
+ export declare function runLibrary(args: string[], configOverride?: CodiedevConfig): Promise<void>;