codiedev 0.10.0-beta.15 → 0.10.0-beta.17

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.
@@ -0,0 +1,761 @@
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.WEBHOOKS_HELP = exports.ISSUE_HELP = exports.PROJECT_HELP = exports.PROJECTS_OFF = void 0;
37
+ exports.parseWorkArgs = parseWorkArgs;
38
+ exports.listFlag = listFlag;
39
+ exports.projectTree = projectTree;
40
+ exports.formatDuration = formatDuration;
41
+ exports.formatIssueContext = formatIssueContext;
42
+ exports.runProject = runProject;
43
+ exports.runIssue = runIssue;
44
+ exports.runWebhooks = runWebhooks;
45
+ const child_process_1 = require("child_process");
46
+ const fs = __importStar(require("fs"));
47
+ const shared_1 = require("./shared");
48
+ const utils_1 = require("../utils");
49
+ exports.PROJECTS_OFF = /Projects isn't on for your company/;
50
+ const BOOLEAN_FLAGS = new Set(["json", "all", "remove", "help", "top"]);
51
+ function parseWorkArgs(args) {
52
+ const pos = [];
53
+ const flags = {};
54
+ for (let i = 0; i < args.length; i++) {
55
+ const a = args[i];
56
+ if (a.startsWith("--")) {
57
+ const eq = a.indexOf("=");
58
+ if (eq > 0) {
59
+ flags[a.slice(2, eq)] = a.slice(eq + 1);
60
+ }
61
+ else {
62
+ const next = args[i + 1];
63
+ if (next !== undefined && !next.startsWith("--") && !BOOLEAN_FLAGS.has(a.slice(2))) {
64
+ flags[a.slice(2)] = next;
65
+ i++;
66
+ }
67
+ else {
68
+ flags[a.slice(2)] = true;
69
+ }
70
+ }
71
+ }
72
+ else {
73
+ pos.push((0, shared_1.stripQuotes)(a));
74
+ }
75
+ }
76
+ return { pos, flags };
77
+ }
78
+ const flagStr = (flags, k) => typeof flags[k] === "string" ? flags[k] : undefined;
79
+ function listFlag(flags, k) {
80
+ const raw = flagStr(flags, k);
81
+ if (!raw)
82
+ return undefined;
83
+ const keys = raw.split(/[\s,]+/).map((x) => x.trim()).filter(Boolean);
84
+ return keys.length ? keys : undefined;
85
+ }
86
+ function readBody(flags, name = "body") {
87
+ const file = flagStr(flags, `${name}-file`);
88
+ if (file)
89
+ return fs.readFileSync(file === "-" ? 0 : file, "utf8");
90
+ return flagStr(flags, name);
91
+ }
92
+ const PRIORITY_LABEL = { 0: "", 1: "urgent", 2: "high", 3: "medium", 4: "low" };
93
+ const STATE_LABEL = {
94
+ backlog: "Backlog",
95
+ todo: "Todo",
96
+ in_progress: "In Progress",
97
+ in_review: "In Review",
98
+ done: "Done",
99
+ canceled: "Canceled",
100
+ };
101
+ const STATE_ORDER = ["in_progress", "in_review", "todo", "backlog", "done", "canceled"];
102
+ function projectTree(projects) {
103
+ const byKey = new Map(projects.map((p) => [p.key, p]));
104
+ const kids = new Map();
105
+ const roots = [];
106
+ for (const p of projects) {
107
+ const parent = p.parentKey && p.parentKey !== p.key ? byKey.get(p.parentKey) : undefined;
108
+ if (parent)
109
+ kids.set(parent.key, [...(kids.get(parent.key) ?? []), p]);
110
+ else
111
+ roots.push(p);
112
+ }
113
+ const out = [];
114
+ const walk = (p, depth, seen) => {
115
+ const row = { p, depth, done: p.done, total: p.total, inProgress: p.counts.in_progress ?? 0 };
116
+ out.push(row);
117
+ for (const c of kids.get(p.key) ?? []) {
118
+ if (seen.has(c.key))
119
+ continue;
120
+ const sub = walk(c, depth + 1, new Set([...seen, c.key]));
121
+ row.done += sub.done;
122
+ row.total += sub.total;
123
+ row.inProgress += sub.inProgress;
124
+ }
125
+ return row;
126
+ };
127
+ for (const r of roots)
128
+ walk(r, 0, new Set([r.key]));
129
+ return out;
130
+ }
131
+ function formatDuration(ms) {
132
+ const min = Math.max(1, Math.round(ms / 60_000));
133
+ if (min < 60)
134
+ return `${min}m`;
135
+ const h = Math.floor(min / 60);
136
+ const m = min % 60;
137
+ return m ? `${h}h ${m}m` : `${h}h`;
138
+ }
139
+ function firstName(p) {
140
+ return p?.name.split(/\s+/)[0] ?? "someone";
141
+ }
142
+ function prLine(pr) {
143
+ const diff = pr.additions !== null || pr.deletions !== null
144
+ ? ` +${pr.additions ?? 0}/-${pr.deletions ?? 0}${pr.changedFiles ? ` in ${pr.changedFiles} file${pr.changedFiles === 1 ? "" : "s"}` : ""}`
145
+ : "";
146
+ return `PR ${pr.state}${diff}: ${pr.url}`;
147
+ }
148
+ function formatIssueContext(i, opts = {}) {
149
+ const out = [];
150
+ if (i.requester)
151
+ out.push(`Requested by ${i.requester.name} (they get a reply when it's done)`);
152
+ for (const pr of i.pullRequests ?? [])
153
+ out.push(prLine(pr));
154
+ if (i.body)
155
+ out.push("", i.body);
156
+ if (i.artifacts?.length) {
157
+ out.push("", "Attached docs (pull before you start):");
158
+ for (const a of i.artifacts)
159
+ out.push(` codiedev pull ${a.key} ${a.title}`);
160
+ }
161
+ const sessions = i.sessions ?? [];
162
+ if (sessions.length) {
163
+ const yw = i.yourWork;
164
+ const yours = yw && yw.sessions
165
+ ? ` · yours: ${yw.sessions}${yw.activeMs ? `, ${formatDuration(yw.activeMs)} active` : ""}${yw.costUsd ? `, $${yw.costUsd.toFixed(2)}` : ""}`
166
+ : "";
167
+ out.push("", `Sessions (${sessions.length}${yours})`);
168
+ for (const s of sessions.slice(0, opts.forStart ? 3 : 5)) {
169
+ const who = s.mine ? "you: " : s.person ? `${firstName(s.person)}: ` : "";
170
+ const head = s.headline ?? (s.captured ? "(no summary)" : "(not captured yet)");
171
+ out.push(` ${who}${head}`);
172
+ }
173
+ const last = sessions.find((s) => s.mine && s.summary);
174
+ if (opts.forStart && last?.summary) {
175
+ out.push("", "Where your last session left off:", ` ${last.summary.trim().replace(/\n+/g, "\n ")}`);
176
+ }
177
+ }
178
+ const comments = i.comments.filter((c) => c.kind === "comment");
179
+ if (comments.length) {
180
+ out.push("", opts.forStart ? "Latest comments" : "Comments");
181
+ for (const c of opts.forStart ? comments.slice(-5) : comments)
182
+ out.push(` ${c.author?.name ?? "someone"}: ${c.body}`);
183
+ }
184
+ return out;
185
+ }
186
+ function issueLine(i) {
187
+ const bits = [
188
+ i.identifier.padEnd(8),
189
+ i.title,
190
+ PRIORITY_LABEL[i.priority] ? `(${PRIORITY_LABEL[i.priority]})` : "",
191
+ i.pullRequestUrl
192
+ ? `${(i.pullRequestCount ?? 1) > 1 ? `${i.pullRequestCount} PRs` : "PR"} ${i.pullRequestState ?? ""}`.trim()
193
+ : "",
194
+ i.assignee ? `@${i.assignee.name.split(/\s+/)[0]}` : "",
195
+ ];
196
+ return " " + bits.filter(Boolean).join(" ");
197
+ }
198
+ async function call(config, op, body) {
199
+ const start = Date.now();
200
+ try {
201
+ const res = await (0, shared_1.apiRequest)("POST", "/api/cli/work", { config, body: { op, ...body } });
202
+ (0, shared_1.recordAgentEvent)(config, { tool: `codiedev_${op.replace(".", "_")}`, ok: true, latencyMs: Date.now() - start });
203
+ (0, utils_1.setProjectsFeature)(true);
204
+ return res;
205
+ }
206
+ catch (err) {
207
+ if (exports.PROJECTS_OFF.test(err.message))
208
+ (0, utils_1.setProjectsFeature)(false);
209
+ (0, shared_1.recordAgentEvent)(config, {
210
+ tool: `codiedev_${op.replace(".", "_")}`,
211
+ ok: false,
212
+ latencyMs: Date.now() - start,
213
+ error: err.message,
214
+ });
215
+ throw err;
216
+ }
217
+ }
218
+ function fail(msg) {
219
+ console.error(msg);
220
+ process.exit(1);
221
+ }
222
+ function git(args) {
223
+ try {
224
+ return (0, child_process_1.execFileSync)("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim() || undefined;
225
+ }
226
+ catch {
227
+ return undefined;
228
+ }
229
+ }
230
+ function currentSessionId(flags) {
231
+ return (flagStr(flags, "session") ??
232
+ process.env.CLAUDE_CODE_SESSION_ID ??
233
+ process.env.CLAUDE_SESSION_ID ??
234
+ process.env.CODEX_SESSION_ID ??
235
+ undefined);
236
+ }
237
+ exports.PROJECT_HELP = `
238
+ codiedev project — Break Room projects (folders of tasks)
239
+
240
+ codiedev project create "<name>" [--key WEB] [--description "…"] [--in <key>]
241
+ codiedev project move <key> --in <key> | --top
242
+ nest a project inside another
243
+ (up to 6 levels); --top un-nests
244
+ codiedev project list [--archived] projects you own or were invited to
245
+ codiedev project edit <key> [--name "…"] [--description "…"]
246
+ codiedev project archive <key> hide it (restore: project restore <key>)
247
+ codiedev project invite <key> <teammate> add them; they get a ping + link
248
+ codiedev project show <key|name> [--json]
249
+ codiedev project next <key|name> open tasks, most urgent first
250
+
251
+ Tasks: see 'codiedev task help'.
252
+ `.trim();
253
+ exports.ISSUE_HELP = `
254
+ codiedev task — work inside a project (alias: codiedev issue)
255
+
256
+ codiedev task add <project> "<title>" [--body "…" | --body-file f.md]
257
+ [--priority urgent|high|medium|low]
258
+ [--state backlog|todo] [--with spec-a,spec-b]
259
+ [--assignee <teammate|me>] hand it off in one step
260
+ codiedev task add <project> --from-ping <ping-id> ["<title>"]
261
+ a teammate's ping becomes the
262
+ task; they get one reply in
263
+ that thread when it's done
264
+ codiedev task mine [--json] your open tasks, all projects
265
+ codiedev task search "<words>" [--json] tasks across your projects
266
+ codiedev task show <ID> [--json] e.g. WEB-3: body, docs, PR,
267
+ sessions, comments
268
+ codiedev task start <ID> In Progress + link this
269
+ git branch: its PRs attach
270
+ and close the task on merge
271
+ (a branch or PR title with
272
+ the ID, e.g. web-3-fix, links
273
+ even without start)
274
+ codiedev task state <ID> <state> todo, in_progress, in_review,
275
+ done, canceled, backlog
276
+ codiedev task done <ID>
277
+ codiedev task edit <ID> [--title "…"] [--body "…" | --append "…"]
278
+ [--priority high] [--assignee <teammate|me|none>]
279
+ codiedev task assign <ID> <teammate|me|none> they get a ping + project access
280
+ codiedev task attach <ID> <key> [<key>…] [--remove]
281
+ library docs/skills as context
282
+ codiedev task comment <ID> "<text>"
283
+ codiedev task ask <ID> <teammate> "<question>" comment + ping them
284
+ `.trim();
285
+ async function runProject(args) {
286
+ const [sub, ...rest] = args;
287
+ const { pos, flags } = parseWorkArgs(rest);
288
+ if (!sub || sub === "help" || sub === "--help") {
289
+ console.log(exports.PROJECT_HELP);
290
+ return;
291
+ }
292
+ const config = (0, shared_1.requireConfig)();
293
+ try {
294
+ switch (sub) {
295
+ case "create":
296
+ case "new": {
297
+ const name = pos.join(" ").trim();
298
+ if (!name)
299
+ fail('Usage: codiedev project create "<name>" [--key WEB] [--in <parent key>]');
300
+ const res = await call(config, "project.create", {
301
+ name,
302
+ key: flagStr(flags, "key"),
303
+ description: readBody(flags, "description"),
304
+ parent: flagStr(flags, "in") ?? flagStr(flags, "parent"),
305
+ });
306
+ console.log(`✓ Created project ${res.name} (${res.key})${res.parentKey ? ` inside ${res.parentKey}` : ""}`);
307
+ console.log(` Add tasks: codiedev task add ${res.key} "<title>"`);
308
+ return;
309
+ }
310
+ case "list":
311
+ case "ls": {
312
+ const archived = !!flags.archived;
313
+ const res = await call(config, archived ? "project.archived" : "project.list", {});
314
+ if (flags.json)
315
+ return void console.log(JSON.stringify(res.projects, null, 2));
316
+ if (res.projects.length === 0) {
317
+ if (archived)
318
+ return void console.log("No archived projects.");
319
+ console.log('No projects yet. Create one: codiedev project create "<name>"');
320
+ return;
321
+ }
322
+ for (const r of projectTree(res.projects)) {
323
+ const indent = " ".repeat(r.depth);
324
+ console.log(`${indent}${r.p.key.padEnd(6)} ${r.p.name} ${r.done}/${r.total} done, ${r.inProgress} in progress`);
325
+ }
326
+ return;
327
+ }
328
+ case "show":
329
+ case "view": {
330
+ const key = pos.join(" ").trim();
331
+ if (!key)
332
+ fail("Usage: codiedev project show <key|name>");
333
+ const res = await call(config, "project.show", { project: key });
334
+ const p = res.project;
335
+ if (flags.json)
336
+ return void console.log(JSON.stringify(p, null, 2));
337
+ if (p.ancestors?.length)
338
+ console.log(p.ancestors.map((a) => `${a.name} (${a.key})`).join(" › ") + " ›");
339
+ console.log(`${p.name} (${p.key}) ${p.done}/${p.total} done`);
340
+ if (p.description)
341
+ console.log(p.description);
342
+ if (p.members.length) {
343
+ console.log(`Members: ${[p.owner?.name, ...p.members.map((m) => m.name)].filter(Boolean).join(", ")}`);
344
+ }
345
+ if (p.children?.length) {
346
+ console.log(`\nSub-projects (${p.children.length})`);
347
+ for (const c of p.children)
348
+ console.log(` ${c.key.padEnd(6)} ${c.name} ${c.done}/${c.total} done`);
349
+ }
350
+ for (const state of STATE_ORDER) {
351
+ const rows = p.issues.filter((i) => i.state === state);
352
+ if (rows.length === 0)
353
+ continue;
354
+ if ((state === "done" || state === "canceled") && !flags.all) {
355
+ console.log(`\n${STATE_LABEL[state]} (${rows.length}) — --all to list`);
356
+ continue;
357
+ }
358
+ console.log(`\n${STATE_LABEL[state]} (${rows.length})`);
359
+ for (const i of rows)
360
+ console.log(issueLine(i));
361
+ }
362
+ if (p.issues.length === 0)
363
+ console.log(`\nNo tasks yet: codiedev task add ${p.key} "<title>"`);
364
+ return;
365
+ }
366
+ case "edit":
367
+ case "rename": {
368
+ const key = pos[0];
369
+ const name = flagStr(flags, "name") ?? (sub === "rename" ? pos.slice(1).join(" ").trim() || undefined : undefined);
370
+ const description = readBody(flags, "description");
371
+ if (!key || (name === undefined && description === undefined)) {
372
+ fail('Usage: codiedev project edit <key> [--name "…"] [--description "…"]');
373
+ }
374
+ const res = await call(config, "project.edit", { project: key, name, description });
375
+ console.log(`✓ Updated ${res.key} (${res.name})`);
376
+ return;
377
+ }
378
+ case "archive":
379
+ case "restore":
380
+ case "unarchive": {
381
+ const key = pos.join(" ").trim();
382
+ if (!key)
383
+ fail(`Usage: codiedev project ${sub} <key>`);
384
+ const archived = sub === "archive";
385
+ const res = await call(config, "project.edit", { project: key, archived });
386
+ console.log(archived
387
+ ? `✓ Archived ${res.key}. It's hidden from your lists; bring it back with: codiedev project restore ${res.key}`
388
+ : `✓ Restored ${res.key} (${res.name})`);
389
+ return;
390
+ }
391
+ case "invite":
392
+ case "share": {
393
+ const [key, ...who] = pos;
394
+ const handle = who.join(" ").trim();
395
+ if (!key || !handle)
396
+ fail("Usage: codiedev project invite <key> <teammate name or email>");
397
+ const res = await call(config, "project.invite", { project: key, handle });
398
+ console.log(res.alreadyMember
399
+ ? `${res.person.name} is already on ${res.project.name}.`
400
+ : `✓ Added ${res.person.name} <${res.person.email}> to ${res.project.name}. They got a ping with the link.`);
401
+ return;
402
+ }
403
+ case "move":
404
+ case "nest": {
405
+ const key = pos[0];
406
+ const into = flagStr(flags, "in") ?? flagStr(flags, "parent") ?? pos[1];
407
+ if (!key || (!into && !flags.top))
408
+ fail("Usage: codiedev project move <key> --in <parent key> | --top");
409
+ const res = await call(config, "project.edit", {
410
+ project: key,
411
+ parent: flags.top ? null : into,
412
+ });
413
+ console.log(res.parentKey ? `✓ ${res.key} is now inside ${res.parentKey}` : `✓ ${res.key} is now top level`);
414
+ return;
415
+ }
416
+ case "next": {
417
+ const key = pos.join(" ").trim();
418
+ if (!key)
419
+ fail("Usage: codiedev project next <key|name>");
420
+ await printNext(config, key, flags);
421
+ return;
422
+ }
423
+ default:
424
+ fail(`Unknown project command "${sub}".\n\n${exports.PROJECT_HELP}`);
425
+ }
426
+ }
427
+ catch (err) {
428
+ fail(`project ${sub} failed: ${err.message}`);
429
+ }
430
+ }
431
+ async function printNext(config, project, flags) {
432
+ const res = await call(config, "issue.next", {
433
+ project,
434
+ limit: flagStr(flags, "limit") ? Number(flagStr(flags, "limit")) : undefined,
435
+ });
436
+ if (flags.json)
437
+ return void console.log(JSON.stringify(res, null, 2));
438
+ if (res.issues.length === 0) {
439
+ console.log(`Nothing open in ${res.project.name}.`);
440
+ return;
441
+ }
442
+ console.log(`Open in ${res.project.name}, most urgent first:`);
443
+ for (const i of res.issues)
444
+ console.log(issueLine(i));
445
+ console.log(`\nStart one: codiedev task start ${res.issues[0].identifier}`);
446
+ }
447
+ async function runIssue(args) {
448
+ const [sub, ...rest] = args;
449
+ const { pos, flags } = parseWorkArgs(rest);
450
+ if (!sub || sub === "help" || sub === "--help") {
451
+ console.log(exports.ISSUE_HELP);
452
+ return;
453
+ }
454
+ const config = (0, shared_1.requireConfig)();
455
+ try {
456
+ switch (sub) {
457
+ case "add":
458
+ case "create":
459
+ case "new": {
460
+ const [project, ...titleParts] = pos;
461
+ const title = titleParts.join(" ").trim();
462
+ const fromPingId = flagStr(flags, "from-ping");
463
+ if (!project || (!title && !fromPingId)) {
464
+ fail('Usage: codiedev task add <project> "<title>" [--body …] [--priority high] [--assignee <teammate>] [--with <key>]\n codiedev task add <project> --from-ping <ping-id>');
465
+ }
466
+ const res = await call(config, "issue.add", {
467
+ project,
468
+ title: title || undefined,
469
+ body: readBody(flags),
470
+ priority: flagStr(flags, "priority"),
471
+ state: flagStr(flags, "state"),
472
+ artifactKeys: listFlag(flags, "with"),
473
+ fromPingId,
474
+ assignee: flagStr(flags, "assignee"),
475
+ });
476
+ if (res.existing) {
477
+ console.log(`Already tracked: ${res.identifier} ${res.title}`);
478
+ return;
479
+ }
480
+ console.log(`✓ ${res.identifier} ${res.title}`);
481
+ if (res.requester)
482
+ console.log(` From ${res.requester.name}'s ping: they get a reply in that thread when it's done.`);
483
+ if (res.artifacts?.length)
484
+ console.log(` Attached: ${res.artifacts.map((a) => a.key).join(", ")}`);
485
+ if (res.assignee)
486
+ console.log(` Assigned to ${res.assignee}; they got a ping with the link.`);
487
+ return;
488
+ }
489
+ case "show":
490
+ case "view": {
491
+ const id = pos[0];
492
+ if (!id)
493
+ fail("Usage: codiedev task show <ID>");
494
+ const res = await call(config, "issue.show", { identifier: id });
495
+ const i = res.issue;
496
+ if (flags.json)
497
+ return void console.log(JSON.stringify(i, null, 2));
498
+ console.log(`${i.identifier} ${i.title}`);
499
+ console.log([
500
+ STATE_LABEL[i.state],
501
+ PRIORITY_LABEL[i.priority] ? `priority ${PRIORITY_LABEL[i.priority]}` : "",
502
+ i.assignee ? `@${i.assignee.name}` : "",
503
+ i.project ? `in ${i.project.name}` : "",
504
+ ]
505
+ .filter(Boolean)
506
+ .join(" · "));
507
+ if (i.branch)
508
+ console.log(`branch ${i.branch}`);
509
+ if (i.pullRequestUrl && !i.pullRequests?.length)
510
+ console.log(`PR ${i.pullRequestState}: ${i.pullRequestUrl}`);
511
+ for (const line of formatIssueContext(i))
512
+ console.log(line);
513
+ return;
514
+ }
515
+ case "start": {
516
+ const id = pos[0];
517
+ if (!id)
518
+ fail("Usage: codiedev task start <ID>");
519
+ const repoUrl = git(["remote", "get-url", "origin"]);
520
+ const branch = git(["rev-parse", "--abbrev-ref", "HEAD"]);
521
+ const res = await call(config, "issue.start", { identifier: id, repoUrl, branch, sessionId: currentSessionId(flags) });
522
+ if (flags.json)
523
+ return void console.log(JSON.stringify(res, null, 2));
524
+ console.log(`✓ ${res.identifier} → ${STATE_LABEL[res.state]}`);
525
+ if (res.branch && res.repoLinked) {
526
+ console.log(` Linked to branch ${res.branch}. Its PR attaches automatically and merging closes ${res.identifier}.`);
527
+ }
528
+ else if (res.branch) {
529
+ console.log(` Branch ${res.branch} saved, but this repo isn't connected to Codiedev, so close it with: codiedev task done ${res.identifier}`);
530
+ }
531
+ else if (!repoUrl) {
532
+ console.log(` Not in a git repo: run this from the repo, on a feature branch, to auto-link its PR.`);
533
+ }
534
+ else {
535
+ const slug = res.identifier.toLowerCase();
536
+ console.log(` On the default branch: create a feature branch with ${res.identifier} in its name (e.g. git checkout -b ${slug}-short-desc). Its PR links on its own and merging closes ${res.identifier}.`);
537
+ }
538
+ if (res.pullRequestUrl && !res.context?.pullRequests?.length)
539
+ console.log(` PR: ${res.pullRequestUrl}`);
540
+ if (res.context) {
541
+ console.log(`\n${res.context.identifier} ${res.context.title}`);
542
+ for (const line of formatIssueContext(res.context, { forStart: true }))
543
+ console.log(line);
544
+ }
545
+ return;
546
+ }
547
+ case "attach":
548
+ case "detach": {
549
+ const [id, ...keys] = pos;
550
+ if (!id || keys.length === 0)
551
+ fail("Usage: codiedev task attach <ID> <library-key> [<key>…] [--remove]");
552
+ const remove = sub === "detach" || !!flags.remove;
553
+ const res = await call(config, "issue.attach", { identifier: id, keys, remove });
554
+ console.log(`✓ ${res.identifier} ${remove ? "detached" : "attached"} ${keys.join(", ")}`);
555
+ if (res.artifacts.length)
556
+ console.log(` Now: ${res.artifacts.map((a) => a.key).join(", ")}`);
557
+ return;
558
+ }
559
+ case "mine":
560
+ case "my": {
561
+ const res = await call(config, "issue.mine", { limit: flagStr(flags, "limit") ? Number(flagStr(flags, "limit")) : undefined });
562
+ if (flags.json)
563
+ return void console.log(JSON.stringify(res.issues, null, 2));
564
+ if (res.issues.length === 0) {
565
+ console.log("Nothing assigned to you is open. Find work: codiedev project next <key>");
566
+ return;
567
+ }
568
+ for (const state of ["in_progress", "in_review", "todo"]) {
569
+ const rows = res.issues.filter((r) => r.state === state);
570
+ if (rows.length === 0)
571
+ continue;
572
+ console.log(`\n${STATE_LABEL[state]} (${rows.length})`);
573
+ for (const r of rows)
574
+ console.log(`${issueLine(r)} [${r.project.key}]`);
575
+ }
576
+ return;
577
+ }
578
+ case "state":
579
+ case "move":
580
+ case "status": {
581
+ const [id, ...stateParts] = pos;
582
+ const state = stateParts.join(" ").trim();
583
+ if (!id || !state)
584
+ fail("Usage: codiedev task state <ID> <todo|in_progress|in_review|done|canceled|backlog>");
585
+ const res = await call(config, "issue.update", { identifier: id, state });
586
+ console.log(`✓ ${res.identifier} → ${STATE_LABEL[res.state]}`);
587
+ return;
588
+ }
589
+ case "done":
590
+ case "close": {
591
+ const id = pos[0];
592
+ if (!id)
593
+ fail("Usage: codiedev task done <ID>");
594
+ const res = await call(config, "issue.update", { identifier: id, state: "done" });
595
+ console.log(`✓ ${res.identifier} → Done`);
596
+ return;
597
+ }
598
+ case "edit":
599
+ case "update": {
600
+ const id = pos[0];
601
+ if (!id)
602
+ fail('Usage: codiedev task edit <ID> [--title …] [--body … | --append …] [--priority high]');
603
+ const res = await call(config, "issue.update", {
604
+ identifier: id,
605
+ title: flagStr(flags, "title"),
606
+ body: readBody(flags),
607
+ appendBody: readBody(flags, "append"),
608
+ priority: flagStr(flags, "priority"),
609
+ state: flagStr(flags, "state"),
610
+ assignee: flagStr(flags, "assignee"),
611
+ });
612
+ console.log(`✓ Updated ${res.identifier}`);
613
+ return;
614
+ }
615
+ case "assign": {
616
+ const [id, ...who] = pos;
617
+ const assignee = who.join(" ").trim();
618
+ if (!id || !assignee)
619
+ fail("Usage: codiedev task assign <ID> <teammate|me|none>");
620
+ const res = await call(config, "issue.update", {
621
+ identifier: id,
622
+ assignee,
623
+ });
624
+ console.log(res.assignee ? `✓ ${res.identifier} → ${res.assignee}` : `✓ ${res.identifier} unassigned`);
625
+ return;
626
+ }
627
+ case "search":
628
+ case "find": {
629
+ const q = pos.join(" ").trim();
630
+ if (!q)
631
+ fail('Usage: codiedev task search "<words>"');
632
+ const res = await call(config, "issue.search", { q });
633
+ if (flags.json)
634
+ return void console.log(JSON.stringify(res.issues, null, 2));
635
+ if (res.issues.length === 0)
636
+ return void console.log(`No tasks match "${q}".`);
637
+ for (const r of res.issues)
638
+ console.log(`${issueLine(r)} ${STATE_LABEL[r.state]}`);
639
+ return;
640
+ }
641
+ case "comment": {
642
+ const [id, ...text] = pos;
643
+ const body = text.join(" ").trim() || readBody(flags);
644
+ if (!id || !body)
645
+ fail('Usage: codiedev task comment <ID> "<text>"');
646
+ await call(config, "issue.comment", { identifier: id, body });
647
+ console.log(`✓ Commented on ${id.toUpperCase()}`);
648
+ return;
649
+ }
650
+ case "ask":
651
+ case "feedback": {
652
+ const [id, to, ...text] = pos;
653
+ const question = text.join(" ").trim();
654
+ if (!id || !to || !question)
655
+ fail('Usage: codiedev task ask <ID> <teammate> "<question>"');
656
+ const res = await call(config, "issue.ask", { identifier: id, handle: to, question });
657
+ console.log(`✓ Asked ${res.person.name} about ${res.identifier} (comment added, ping sent).`);
658
+ if (res.sharedProject)
659
+ console.log(` They weren't on the project, so it's now shared with them.`);
660
+ return;
661
+ }
662
+ case "next": {
663
+ const project = pos.join(" ").trim();
664
+ if (!project)
665
+ fail("Usage: codiedev task next <project>");
666
+ await printNext(config, project, flags);
667
+ return;
668
+ }
669
+ default:
670
+ fail(`Unknown task command "${sub}".\n\n${exports.ISSUE_HELP}`);
671
+ }
672
+ }
673
+ catch (err) {
674
+ fail(`task ${sub} failed: ${err.message}`);
675
+ }
676
+ }
677
+ exports.WEBHOOKS_HELP = `
678
+ codiedev webhooks — get told when something happens, at your own URL
679
+
680
+ codiedev webhooks add <https-url> [--events task.done,ping.received]
681
+ [--project WEB] prints the signing secret once
682
+ codiedev webhooks list
683
+ codiedev webhooks test <id> sends a webhook.test event
684
+ codiedev webhooks rm <id>
685
+
686
+ Events: task.created, task.updated, task.done, task.commented,
687
+ task.pr_linked, ping.received (default: all). You only get events
688
+ for projects you can see and pings sent to you.
689
+ Each POST is JSON {id, event, at, data}, signed:
690
+ X-Codiedev-Signature: sha256=HMAC_SHA256(secret, "<X-Codiedev-Timestamp>.<raw body>")
691
+ `.trim();
692
+ async function runWebhooks(args) {
693
+ const [sub, ...rest] = args;
694
+ const { pos, flags } = parseWorkArgs(rest);
695
+ if (!sub || sub === "help" || sub === "--help") {
696
+ console.log(exports.WEBHOOKS_HELP);
697
+ return;
698
+ }
699
+ const config = (0, shared_1.requireConfig)();
700
+ try {
701
+ switch (sub) {
702
+ case "add":
703
+ case "create": {
704
+ const url = pos[0];
705
+ if (!url)
706
+ fail("Usage: codiedev webhooks add <https-url> [--events task.done,ping.received] [--project WEB]");
707
+ const res = await call(config, "webhook.add", {
708
+ url,
709
+ events: listFlag(flags, "events"),
710
+ project: flagStr(flags, "project"),
711
+ });
712
+ const w = res.webhook;
713
+ if (flags.json)
714
+ return void console.log(JSON.stringify(w, null, 2));
715
+ console.log(`✓ Webhook ${w.id}`);
716
+ console.log(` ${w.url}`);
717
+ console.log(` Events: ${w.events.join(", ") === "*" ? "all" : w.events.join(", ")}${w.project ? ` (project ${w.project})` : ""}`);
718
+ console.log(` Signing secret (shown once): ${w.secret}`);
719
+ console.log(` Try it: codiedev webhooks test ${w.id}`);
720
+ return;
721
+ }
722
+ case "list":
723
+ case "ls": {
724
+ const res = await call(config, "webhook.list", {});
725
+ if (flags.json)
726
+ return void console.log(JSON.stringify(res.webhooks, null, 2));
727
+ if (res.webhooks.length === 0)
728
+ return void console.log("No webhooks. Add one: codiedev webhooks add <https-url>");
729
+ for (const w of res.webhooks) {
730
+ const events = w.events.includes("*") ? "all events" : w.events.join(", ");
731
+ console.log(`${w.id} ${w.url}`);
732
+ console.log(` ${events}${w.project ? ` · ${w.project}` : ""}${w.lastStatus ? ` · last: ${w.lastStatus}` : ""}`);
733
+ }
734
+ return;
735
+ }
736
+ case "test": {
737
+ const id = pos[0];
738
+ if (!id)
739
+ fail("Usage: codiedev webhooks test <id>");
740
+ const res = await call(config, "webhook.test", { id });
741
+ console.log(`✓ Sent webhook.test to ${res.url}. Check it with: codiedev webhooks list`);
742
+ return;
743
+ }
744
+ case "rm":
745
+ case "remove":
746
+ case "delete": {
747
+ const id = pos[0];
748
+ if (!id)
749
+ fail("Usage: codiedev webhooks rm <id>");
750
+ await call(config, "webhook.remove", { id });
751
+ console.log(`✓ Removed webhook ${id}`);
752
+ return;
753
+ }
754
+ default:
755
+ fail(`Unknown webhooks command "${sub}".\n\n${exports.WEBHOOKS_HELP}`);
756
+ }
757
+ }
758
+ catch (err) {
759
+ fail(`webhooks ${sub} failed: ${err.message}`);
760
+ }
761
+ }