exponential-cli 1.8.0 → 1.10.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/README.md +81 -1
- package/dist/index.js +922 -55
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command22 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/auth.ts
|
|
7
7
|
import { Command } from "commander";
|
|
@@ -329,6 +329,91 @@ ${statusBadge} ${chalk3.bold(action.name)}`);
|
|
|
329
329
|
}
|
|
330
330
|
console.log();
|
|
331
331
|
}
|
|
332
|
+
function outputTodaysActionsJson(t, filters = {}) {
|
|
333
|
+
console.log(JSON.stringify({
|
|
334
|
+
overdue: { count: t.overdue.count, actions: t.overdue.actions },
|
|
335
|
+
today: { count: t.today.count, actions: t.today.actions },
|
|
336
|
+
inbox: { count: t.inbox.count, actions: t.inbox.actions },
|
|
337
|
+
total: t.overdue.count + t.today.count + t.inbox.count,
|
|
338
|
+
filters
|
|
339
|
+
}, null, 2));
|
|
340
|
+
}
|
|
341
|
+
function printTodayGroup(label, group, color) {
|
|
342
|
+
if (group.count === 0) return;
|
|
343
|
+
const truncated = group.count > group.actions.length ? chalk3.gray(` (showing ${group.actions.length})`) : "";
|
|
344
|
+
console.log(`
|
|
345
|
+
${chalk3[color].bold(`${label} (${group.count})`)}${truncated}`);
|
|
346
|
+
for (const a of group.actions) {
|
|
347
|
+
const when = a.scheduledStart ?? a.dueDate;
|
|
348
|
+
const stamp = when ? chalk3.gray(formatDate(new Date(when))) : "";
|
|
349
|
+
const project = a.projectName ? chalk3.cyan(` [${a.projectName}]`) : "";
|
|
350
|
+
console.log(` ${a.name}${project} ${stamp}`);
|
|
351
|
+
console.log(chalk3.gray(` ID: ${a.id}`));
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
function outputTodaysActionsPretty(t) {
|
|
355
|
+
const total = t.overdue.count + t.today.count + t.inbox.count;
|
|
356
|
+
if (total === 0) {
|
|
357
|
+
console.log(chalk3.gray("Nothing on your plate."));
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
console.log(chalk3.bold("\nOn your plate"));
|
|
361
|
+
console.log(chalk3.gray("\u2500".repeat(50)));
|
|
362
|
+
printTodayGroup("Overdue", t.overdue, "red");
|
|
363
|
+
printTodayGroup("Today", t.today, "green");
|
|
364
|
+
printTodayGroup("Inbox", t.inbox, "gray");
|
|
365
|
+
if (t.overdue.count > 0) {
|
|
366
|
+
console.log(chalk3.gray(`
|
|
367
|
+
Run "exponential actions overdue" to see why the overdue pile is that size.`));
|
|
368
|
+
}
|
|
369
|
+
console.log();
|
|
370
|
+
}
|
|
371
|
+
function outputOverdueTriageJson(t) {
|
|
372
|
+
console.log(JSON.stringify(t, null, 2));
|
|
373
|
+
}
|
|
374
|
+
function outputOverdueTriagePretty(t) {
|
|
375
|
+
if (t.totalOverdue === 0) {
|
|
376
|
+
console.log(chalk3.green("Nothing overdue."));
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
console.log(chalk3.bold(`
|
|
380
|
+
Overdue: ${t.totalOverdue}`));
|
|
381
|
+
console.log(chalk3.gray("\u2500".repeat(50)));
|
|
382
|
+
if (t.cohorts.length > 0) {
|
|
383
|
+
console.log(
|
|
384
|
+
`
|
|
385
|
+
${chalk3.yellow.bold(`${t.cohortCount} of these were bulk-created`)} ${chalk3.gray("\u2014 stamped with one identical timestamp, so almost certainly never individually due.")}`
|
|
386
|
+
);
|
|
387
|
+
for (const c of t.cohorts) {
|
|
388
|
+
console.log(
|
|
389
|
+
`
|
|
390
|
+
${chalk3.bold(`${c.count} actions`)} stamped ${chalk3.gray(c.stampedAt.toISOString())} ${chalk3.red(`(${c.daysOverdue}d overdue)`)}`
|
|
391
|
+
);
|
|
392
|
+
if (c.projectNames.length > 0) {
|
|
393
|
+
console.log(` ${chalk3.cyan("Projects:")} ${c.projectNames.join(", ")}`);
|
|
394
|
+
}
|
|
395
|
+
for (const a of c.actions.slice(0, 4)) {
|
|
396
|
+
console.log(chalk3.gray(` \xB7 ${a.name.substring(0, 62)}`));
|
|
397
|
+
}
|
|
398
|
+
if (c.actions.length > 4) {
|
|
399
|
+
console.log(chalk3.gray(` \xB7 \u2026 and ${c.actions.length - 4} more`));
|
|
400
|
+
}
|
|
401
|
+
console.log(
|
|
402
|
+
chalk3.gray(` Amnesty: `) + `exponential actions defer --ids ${c.actionIds.slice(0, 2).join(",")}${c.actionIds.length > 2 ? ",\u2026" : ""}`
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
if (t.loose.length > 0) {
|
|
407
|
+
console.log(`
|
|
408
|
+
${chalk3.bold(`${t.loose.length} individually dated`)} ${chalk3.gray("\u2014 real debt, oldest first.")}`);
|
|
409
|
+
for (const a of t.loose) {
|
|
410
|
+
const project = a.projectName ? chalk3.cyan(` [${a.projectName}]`) : "";
|
|
411
|
+
console.log(` ${chalk3.red(`${String(a.daysOverdue).padStart(3)}d`)} ${a.name.substring(0, 62)}${project}`);
|
|
412
|
+
console.log(chalk3.gray(` ID: ${a.id}`));
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
console.log();
|
|
416
|
+
}
|
|
332
417
|
function outputProjectsJson(projects) {
|
|
333
418
|
const output = {
|
|
334
419
|
projects: projects.map(transformProject),
|
|
@@ -667,30 +752,59 @@ function outputStagesJson(stages) {
|
|
|
667
752
|
total: stages.length
|
|
668
753
|
}, null, 2));
|
|
669
754
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
const
|
|
755
|
+
var COMMENT_PARENT_KEYS = ["actionId", "ticketId", "goalId", "featureId", "pageId"];
|
|
756
|
+
function normalizeComment(comment) {
|
|
757
|
+
const raw = comment;
|
|
758
|
+
const parentKey = COMMENT_PARENT_KEYS.find((k) => k in raw) ?? "parentId";
|
|
759
|
+
const author = raw.author ?? raw.createdBy ?? null;
|
|
673
760
|
return {
|
|
674
761
|
id: comment.id,
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
author
|
|
679
|
-
|
|
680
|
-
|
|
762
|
+
parentKey,
|
|
763
|
+
parentId: raw[parentKey],
|
|
764
|
+
authorId: raw.authorId ?? raw.createdById,
|
|
765
|
+
author,
|
|
766
|
+
content: raw.content ?? raw.body ?? "",
|
|
767
|
+
createdAt: new Date(comment.createdAt),
|
|
768
|
+
updatedAt: new Date(comment.updatedAt),
|
|
769
|
+
parentCommentId: raw.parentId ?? null,
|
|
770
|
+
threadId: raw.threadId ?? null,
|
|
771
|
+
resolvedAt: raw.resolvedAt ? new Date(raw.resolvedAt) : null
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
function transformComment(comment) {
|
|
775
|
+
const n = normalizeComment(comment);
|
|
776
|
+
const out = {
|
|
777
|
+
id: n.id,
|
|
778
|
+
[n.parentKey]: n.parentId,
|
|
779
|
+
authorId: n.authorId,
|
|
780
|
+
content: n.content,
|
|
781
|
+
author: n.author,
|
|
782
|
+
createdAt: n.createdAt.toISOString(),
|
|
783
|
+
updatedAt: n.updatedAt.toISOString()
|
|
681
784
|
};
|
|
785
|
+
if (n.threadId !== null) out.threadId = n.threadId;
|
|
786
|
+
if (n.parentCommentId !== null && n.parentKey !== "parentId") {
|
|
787
|
+
out.parentCommentId = n.parentCommentId;
|
|
788
|
+
}
|
|
789
|
+
if (n.resolvedAt) out.resolvedAt = n.resolvedAt.toISOString();
|
|
790
|
+
return out;
|
|
682
791
|
}
|
|
683
792
|
function outputCommentJson(comment) {
|
|
684
793
|
console.log(JSON.stringify(transformComment(comment), null, 2));
|
|
685
794
|
}
|
|
686
795
|
function outputCommentPretty(comment) {
|
|
687
|
-
const
|
|
688
|
-
const
|
|
796
|
+
const n = normalizeComment(comment);
|
|
797
|
+
const author = n.author?.name ?? n.authorId;
|
|
798
|
+
const when = formatDate(n.createdAt);
|
|
799
|
+
const badges = [
|
|
800
|
+
n.parentCommentId && n.parentKey !== "parentId" ? chalk3.gray("(reply)") : "",
|
|
801
|
+
n.resolvedAt ? chalk3.green("(resolved)") : ""
|
|
802
|
+
].filter(Boolean).join(" ");
|
|
689
803
|
console.log(chalk3.gray("\u2500".repeat(50)));
|
|
690
|
-
console.log(`${chalk3.bold(author)} ${chalk3.gray(`\u2014 ${when}`)}`);
|
|
691
|
-
console.log(chalk3.gray(` ID: ${
|
|
804
|
+
console.log(`${chalk3.bold(author)} ${chalk3.gray(`\u2014 ${when}`)}${badges ? ` ${badges}` : ""}`);
|
|
805
|
+
console.log(chalk3.gray(` ID: ${n.id}`));
|
|
692
806
|
console.log();
|
|
693
|
-
console.log(
|
|
807
|
+
console.log(n.content);
|
|
694
808
|
console.log();
|
|
695
809
|
}
|
|
696
810
|
function outputCommentsJson(comments) {
|
|
@@ -1312,6 +1426,22 @@ var VALID_PRIORITIES = [
|
|
|
1312
1426
|
"Watch",
|
|
1313
1427
|
"Someday Maybe"
|
|
1314
1428
|
];
|
|
1429
|
+
function parseNullableDate(raw, flag) {
|
|
1430
|
+
if (raw === void 0) return void 0;
|
|
1431
|
+
if (raw === "null") return null;
|
|
1432
|
+
const parsed = new Date(raw);
|
|
1433
|
+
if (isNaN(parsed.getTime())) {
|
|
1434
|
+
throw new Error(`Invalid --${flag} value "${raw}". Use YYYY-MM-DD, an ISO datetime, or "null".`);
|
|
1435
|
+
}
|
|
1436
|
+
return parsed;
|
|
1437
|
+
}
|
|
1438
|
+
function parseIds(raw) {
|
|
1439
|
+
const ids = raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1440
|
+
if (ids.length === 0) {
|
|
1441
|
+
throw new Error("No action IDs supplied. Pass --ids id1,id2");
|
|
1442
|
+
}
|
|
1443
|
+
return ids;
|
|
1444
|
+
}
|
|
1315
1445
|
function createActionsCommand() {
|
|
1316
1446
|
const actions = new Command2("actions").description("Manage actions/tasks");
|
|
1317
1447
|
actions.command("list").description("List all actions").option("--project <id>", "Filter by project ID").option("--status <status>", "Filter by kanban status (BACKLOG, TODO, IN_PROGRESS, IN_REVIEW, DONE)").option("--assignee <id>", "Filter by assignee ID").action(async (options, cmd) => {
|
|
@@ -1337,16 +1467,76 @@ function createActionsCommand() {
|
|
|
1337
1467
|
handleError(error, useJson);
|
|
1338
1468
|
}
|
|
1339
1469
|
});
|
|
1340
|
-
actions.command("today").description("
|
|
1470
|
+
actions.command("today").description("What's on your plate: overdue, today, and inbox").option("--workspace <id>", "Filter by workspace ID").option("--due-only", "Only actions whose dueDate is today (the pre-1.8 shape \u2014 excludes overdue)").action(async (options, cmd) => {
|
|
1341
1471
|
const globalOpts = cmd.optsWithGlobals();
|
|
1342
1472
|
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1343
1473
|
try {
|
|
1344
1474
|
const client = getClient();
|
|
1345
|
-
|
|
1475
|
+
if (options.dueOnly) {
|
|
1476
|
+
const actions2 = await client.actions.getToday(options.workspace);
|
|
1477
|
+
if (useJson) {
|
|
1478
|
+
outputActionsJson(actions2, { workspaceId: options.workspace });
|
|
1479
|
+
} else {
|
|
1480
|
+
outputActionsPretty(actions2);
|
|
1481
|
+
}
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
const todays = await client.actions.getTodaysActions(options.workspace);
|
|
1346
1485
|
if (useJson) {
|
|
1347
|
-
|
|
1486
|
+
outputTodaysActionsJson(todays, { workspaceId: options.workspace });
|
|
1348
1487
|
} else {
|
|
1349
|
-
|
|
1488
|
+
outputTodaysActionsPretty(todays);
|
|
1489
|
+
}
|
|
1490
|
+
} catch (error) {
|
|
1491
|
+
handleError(error, useJson);
|
|
1492
|
+
}
|
|
1493
|
+
});
|
|
1494
|
+
actions.command("overdue").description("Why the overdue pile is that size: bulk-created cohorts vs real debt").option("--workspace <id>", "Filter by workspace ID").action(async (options, cmd) => {
|
|
1495
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1496
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1497
|
+
try {
|
|
1498
|
+
const client = getClient();
|
|
1499
|
+
const triage = await client.actions.getOverdueTriage(options.workspace);
|
|
1500
|
+
if (useJson) {
|
|
1501
|
+
outputOverdueTriageJson(triage);
|
|
1502
|
+
} else {
|
|
1503
|
+
outputOverdueTriagePretty(triage);
|
|
1504
|
+
}
|
|
1505
|
+
} catch (error) {
|
|
1506
|
+
handleError(error, useJson);
|
|
1507
|
+
}
|
|
1508
|
+
});
|
|
1509
|
+
actions.command("defer").description("Amnesty: clear dates so actions fall back to their project backlog untimed").requiredOption("--ids <ids>", "Comma-separated action IDs").action(async (options, cmd) => {
|
|
1510
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1511
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1512
|
+
try {
|
|
1513
|
+
const ids = parseIds(options.ids);
|
|
1514
|
+
const client = getClient();
|
|
1515
|
+
const result = await client.actions.bulkDefer(ids);
|
|
1516
|
+
if (useJson) {
|
|
1517
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1518
|
+
} else {
|
|
1519
|
+
console.log(`\u2713 ${result.message}`);
|
|
1520
|
+
}
|
|
1521
|
+
} catch (error) {
|
|
1522
|
+
handleError(error, useJson);
|
|
1523
|
+
}
|
|
1524
|
+
});
|
|
1525
|
+
actions.command("reschedule").description('Move actions to a new do-date (use "actions defer" if they were never really due)').requiredOption("--ids <ids>", "Comma-separated action IDs").requiredOption("--to <date>", 'New do-date (YYYY-MM-DD, or "today")').action(async (options, cmd) => {
|
|
1526
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1527
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1528
|
+
try {
|
|
1529
|
+
const ids = parseIds(options.ids);
|
|
1530
|
+
const when = options.to === "today" ? /* @__PURE__ */ new Date() : new Date(options.to);
|
|
1531
|
+
if (isNaN(when.getTime())) {
|
|
1532
|
+
throw new Error('Invalid date format. Use YYYY-MM-DD or "today"');
|
|
1533
|
+
}
|
|
1534
|
+
const client = getClient();
|
|
1535
|
+
const result = await client.actions.bulkReschedule(ids, when);
|
|
1536
|
+
if (useJson) {
|
|
1537
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1538
|
+
} else {
|
|
1539
|
+
console.log(`\u2713 Rescheduled ${result.count} action${result.count === 1 ? "" : "s"}`);
|
|
1350
1540
|
}
|
|
1351
1541
|
} catch (error) {
|
|
1352
1542
|
handleError(error, useJson);
|
|
@@ -1395,7 +1585,7 @@ function createActionsCommand() {
|
|
|
1395
1585
|
handleError(error, useJson);
|
|
1396
1586
|
}
|
|
1397
1587
|
});
|
|
1398
|
-
actions.command("update").description("Update an existing action").requiredOption("--id <id>", "Action ID to update").option("-n, --name <name>", "New action name").option("-d, --description <text>", "New description").option("-p, --project <id>", "Move to project ID").option("--priority <priority>", "Priority (Quick, Scheduled, 1st Priority, etc.)").option("--status <status>", "Status (ACTIVE, COMPLETED, CANCELLED)").option("--kanban <status>", "Kanban status (BACKLOG, TODO, IN_PROGRESS, IN_REVIEW, DONE)").option("--due <date>", 'Due date (YYYY-MM-DD or "null" to clear)').action(async (options, cmd) => {
|
|
1588
|
+
actions.command("update").description("Update an existing action").requiredOption("--id <id>", "Action ID to update").option("-n, --name <name>", "New action name").option("-d, --description <text>", "New description").option("-p, --project <id>", "Move to project ID").option("--priority <priority>", "Priority (Quick, Scheduled, 1st Priority, etc.)").option("--status <status>", "Status (ACTIVE, COMPLETED, CANCELLED)").option("--kanban <status>", "Kanban status (BACKLOG, TODO, IN_PROGRESS, IN_REVIEW, DONE)").option("--due <date>", 'Due date (YYYY-MM-DD or "null" to clear)').option("--scheduled-start <datetime>", 'Do-date: when you plan to work on it (YYYY-MM-DD, ISO datetime, or "null" to clear). This is what /today partitions on.').option("--scheduled-end <datetime>", 'End of the time block (YYYY-MM-DD, ISO datetime, or "null" to clear)').action(async (options, cmd) => {
|
|
1399
1589
|
const globalOpts = cmd.optsWithGlobals();
|
|
1400
1590
|
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1401
1591
|
try {
|
|
@@ -1411,6 +1601,8 @@ function createActionsCommand() {
|
|
|
1411
1601
|
throw new Error("Invalid due date format. Use YYYY-MM-DD");
|
|
1412
1602
|
}
|
|
1413
1603
|
}
|
|
1604
|
+
const scheduledStart = parseNullableDate(options.scheduledStart, "scheduled-start");
|
|
1605
|
+
const scheduledEnd = parseNullableDate(options.scheduledEnd, "scheduled-end");
|
|
1414
1606
|
const client = getClient();
|
|
1415
1607
|
const action = await client.actions.update({
|
|
1416
1608
|
id: options.id,
|
|
@@ -1420,7 +1612,9 @@ function createActionsCommand() {
|
|
|
1420
1612
|
priority: options.priority,
|
|
1421
1613
|
status: options.status,
|
|
1422
1614
|
kanbanStatus: options.kanban,
|
|
1423
|
-
dueDate
|
|
1615
|
+
dueDate,
|
|
1616
|
+
scheduledStart,
|
|
1617
|
+
scheduledEnd
|
|
1424
1618
|
});
|
|
1425
1619
|
if (useJson) {
|
|
1426
1620
|
outputActionJson(action);
|
|
@@ -2034,7 +2228,7 @@ function createEpicsCommand() {
|
|
|
2034
2228
|
const globalOpts = cmd.optsWithGlobals();
|
|
2035
2229
|
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2036
2230
|
try {
|
|
2037
|
-
const
|
|
2231
|
+
const parseNullableDate2 = (v) => {
|
|
2038
2232
|
if (v === void 0) return void 0;
|
|
2039
2233
|
if (v === "null") return null;
|
|
2040
2234
|
const d = new Date(v);
|
|
@@ -2053,8 +2247,8 @@ function createEpicsCommand() {
|
|
|
2053
2247
|
description,
|
|
2054
2248
|
status,
|
|
2055
2249
|
priority,
|
|
2056
|
-
startDate:
|
|
2057
|
-
targetDate:
|
|
2250
|
+
startDate: parseNullableDate2(options.start),
|
|
2251
|
+
targetDate: parseNullableDate2(options.target)
|
|
2058
2252
|
});
|
|
2059
2253
|
if (useJson) outputEpicJson(epic);
|
|
2060
2254
|
else {
|
|
@@ -2070,7 +2264,7 @@ function createEpicsCommand() {
|
|
|
2070
2264
|
}
|
|
2071
2265
|
|
|
2072
2266
|
// src/commands/features.ts
|
|
2073
|
-
import { Command as
|
|
2267
|
+
import { Command as Command11 } from "commander";
|
|
2074
2268
|
|
|
2075
2269
|
// src/commands/stories.ts
|
|
2076
2270
|
import { Command as Command6 } from "commander";
|
|
@@ -2491,6 +2685,240 @@ function createAreasCommand() {
|
|
|
2491
2685
|
return areas;
|
|
2492
2686
|
}
|
|
2493
2687
|
|
|
2688
|
+
// src/commands/featureComments.ts
|
|
2689
|
+
import { Command as Command10 } from "commander";
|
|
2690
|
+
|
|
2691
|
+
// src/utils/mentions.ts
|
|
2692
|
+
function candidateLabels(member) {
|
|
2693
|
+
const labels = [member.id];
|
|
2694
|
+
if (member.name) {
|
|
2695
|
+
labels.push(member.name);
|
|
2696
|
+
const first = member.name.trim().split(/\s+/)[0];
|
|
2697
|
+
if (first) labels.push(first);
|
|
2698
|
+
}
|
|
2699
|
+
if (member.email) {
|
|
2700
|
+
labels.push(member.email);
|
|
2701
|
+
const local = member.email.split("@")[0];
|
|
2702
|
+
if (local) labels.push(local);
|
|
2703
|
+
}
|
|
2704
|
+
return labels;
|
|
2705
|
+
}
|
|
2706
|
+
function describe(member) {
|
|
2707
|
+
return member.name ?? member.email ?? member.id;
|
|
2708
|
+
}
|
|
2709
|
+
function resolveMember(members, token) {
|
|
2710
|
+
const needle = token.trim().toLowerCase().replace(/^@/, "");
|
|
2711
|
+
const exact = members.filter(
|
|
2712
|
+
(m) => m.id.toLowerCase() === needle || m.name?.toLowerCase() === needle || m.email?.toLowerCase() === needle
|
|
2713
|
+
);
|
|
2714
|
+
if (exact.length === 1) return exact[0];
|
|
2715
|
+
if (exact.length > 1) {
|
|
2716
|
+
throw new Error(
|
|
2717
|
+
`"${token}" matches ${exact.length} members. Use a user id instead: ${exact.map((m) => `${describe(m)} (${m.id})`).join(", ")}`
|
|
2718
|
+
);
|
|
2719
|
+
}
|
|
2720
|
+
const partial = members.filter(
|
|
2721
|
+
(m) => candidateLabels(m).some((l) => l.toLowerCase() === needle)
|
|
2722
|
+
);
|
|
2723
|
+
if (partial.length === 1) return partial[0];
|
|
2724
|
+
if (partial.length > 1) {
|
|
2725
|
+
throw new Error(
|
|
2726
|
+
`"${token}" is ambiguous \u2014 matches ${partial.map((m) => `${describe(m)} (${m.id})`).join(", ")}. Use a full name, email, or user id.`
|
|
2727
|
+
);
|
|
2728
|
+
}
|
|
2729
|
+
const known = members.map(describe).join(", ");
|
|
2730
|
+
throw new Error(
|
|
2731
|
+
`No workspace member matches "${token}". Members: ${known || "(none)"}. Run \`exponential workspaces members\` to see the roster.`
|
|
2732
|
+
);
|
|
2733
|
+
}
|
|
2734
|
+
function escapeRegExp(value) {
|
|
2735
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2736
|
+
}
|
|
2737
|
+
async function applyMentions(client, workspaceId, body, mentions) {
|
|
2738
|
+
if (!mentions || mentions.length === 0) return body;
|
|
2739
|
+
const members = await client.workspaces.listMembers(workspaceId);
|
|
2740
|
+
let result = body;
|
|
2741
|
+
const prepend = [];
|
|
2742
|
+
for (const token of mentions) {
|
|
2743
|
+
const member = resolveMember(members, token);
|
|
2744
|
+
const handle = token.trim().replace(/^@/, "");
|
|
2745
|
+
const inline = new RegExp(`@${escapeRegExp(handle)}(?![\\w-])`, "gi");
|
|
2746
|
+
const substituted = result.replace(inline, member.mentionSyntax);
|
|
2747
|
+
if (substituted !== result) {
|
|
2748
|
+
result = substituted;
|
|
2749
|
+
} else if (!result.includes(member.mentionSyntax)) {
|
|
2750
|
+
prepend.push(member.mentionSyntax);
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
return prepend.length > 0 ? `${prepend.join(" ")} ${result}` : result;
|
|
2754
|
+
}
|
|
2755
|
+
var MENTION_OPTION_DESCRIPTION = "Mention a workspace member by name, email, or id (repeatable). Writing @name in the message substitutes in place; otherwise the mention is prepended.";
|
|
2756
|
+
function collectMention(value, previous) {
|
|
2757
|
+
return [...previous, value];
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
// src/commands/featureComments.ts
|
|
2761
|
+
function createFeatureCommentsCommand() {
|
|
2762
|
+
const comment = new Command10("comment").description(
|
|
2763
|
+
"Read and post comments on a feature. Mention teammates with --mention."
|
|
2764
|
+
);
|
|
2765
|
+
comment.command("list").description("List comments on a feature").requiredOption("--feature <id>", "Feature ID").action(async (options, cmd) => {
|
|
2766
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2767
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2768
|
+
try {
|
|
2769
|
+
const client = getClient();
|
|
2770
|
+
const comments = await client.featureComments.list(options.feature);
|
|
2771
|
+
if (useJson) outputCommentsJson(comments);
|
|
2772
|
+
else outputCommentsPretty(comments);
|
|
2773
|
+
} catch (error) {
|
|
2774
|
+
handleError(error, useJson);
|
|
2775
|
+
}
|
|
2776
|
+
});
|
|
2777
|
+
comment.command("add").description("Add a comment to a feature").requiredOption("--feature <id>", "Feature ID").requiredOption("-m, --message <text>", "Comment body (markdown supported)").option("--mention <name>", MENTION_OPTION_DESCRIPTION, collectMention, []).option(
|
|
2778
|
+
"--workspace <slug|id>",
|
|
2779
|
+
"Workspace used to resolve --mention (defaults to your default workspace)"
|
|
2780
|
+
).option("--scope <id>", "Attach to a scope's activity feed instead of the feature").action(
|
|
2781
|
+
async (options, cmd) => {
|
|
2782
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2783
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2784
|
+
try {
|
|
2785
|
+
const client = getClient();
|
|
2786
|
+
const body = options.mention.length > 0 ? await applyMentions(
|
|
2787
|
+
client,
|
|
2788
|
+
await resolveWorkspaceId(client, options.workspace),
|
|
2789
|
+
options.message,
|
|
2790
|
+
options.mention
|
|
2791
|
+
) : options.message;
|
|
2792
|
+
const created = await client.featureComments.create({
|
|
2793
|
+
featureId: options.feature,
|
|
2794
|
+
scopeId: options.scope,
|
|
2795
|
+
body
|
|
2796
|
+
});
|
|
2797
|
+
if (useJson) {
|
|
2798
|
+
outputCommentJson(created);
|
|
2799
|
+
} else {
|
|
2800
|
+
console.log("\n\u2713 Comment added");
|
|
2801
|
+
outputCommentPretty(created);
|
|
2802
|
+
}
|
|
2803
|
+
} catch (error) {
|
|
2804
|
+
handleError(error, useJson);
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
);
|
|
2808
|
+
comment.command("reply").description("Reply to a comment on a feature").requiredOption("--comment-id <id>", "Comment ID to reply to").requiredOption("-m, --message <text>", "Reply body (markdown supported)").option("--mention <name>", MENTION_OPTION_DESCRIPTION, collectMention, []).option(
|
|
2809
|
+
"--workspace <slug|id>",
|
|
2810
|
+
"Workspace used to resolve --mention (defaults to your default workspace)"
|
|
2811
|
+
).action(
|
|
2812
|
+
async (options, cmd) => {
|
|
2813
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2814
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2815
|
+
try {
|
|
2816
|
+
const client = getClient();
|
|
2817
|
+
const body = options.mention.length > 0 ? await applyMentions(
|
|
2818
|
+
client,
|
|
2819
|
+
await resolveWorkspaceId(client, options.workspace),
|
|
2820
|
+
options.message,
|
|
2821
|
+
options.mention
|
|
2822
|
+
) : options.message;
|
|
2823
|
+
const created = await client.featureComments.reply({
|
|
2824
|
+
parentId: options.commentId,
|
|
2825
|
+
body
|
|
2826
|
+
});
|
|
2827
|
+
if (useJson) {
|
|
2828
|
+
outputCommentJson(created);
|
|
2829
|
+
} else {
|
|
2830
|
+
console.log("\n\u2713 Reply added");
|
|
2831
|
+
outputCommentPretty(created);
|
|
2832
|
+
}
|
|
2833
|
+
} catch (error) {
|
|
2834
|
+
handleError(error, useJson);
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
);
|
|
2838
|
+
comment.command("update").description("Update one of your own comments").requiredOption("--comment-id <id>", "Comment ID").requiredOption("-m, --message <text>", "New comment body").option("--mention <name>", MENTION_OPTION_DESCRIPTION, collectMention, []).option(
|
|
2839
|
+
"--workspace <slug|id>",
|
|
2840
|
+
"Workspace used to resolve --mention (defaults to your default workspace)"
|
|
2841
|
+
).action(
|
|
2842
|
+
async (options, cmd) => {
|
|
2843
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2844
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2845
|
+
try {
|
|
2846
|
+
const client = getClient();
|
|
2847
|
+
const body = options.mention.length > 0 ? await applyMentions(
|
|
2848
|
+
client,
|
|
2849
|
+
await resolveWorkspaceId(client, options.workspace),
|
|
2850
|
+
options.message,
|
|
2851
|
+
options.mention
|
|
2852
|
+
) : options.message;
|
|
2853
|
+
const updated = await client.featureComments.update({
|
|
2854
|
+
commentId: options.commentId,
|
|
2855
|
+
body
|
|
2856
|
+
});
|
|
2857
|
+
if (useJson) {
|
|
2858
|
+
outputCommentJson(updated);
|
|
2859
|
+
} else {
|
|
2860
|
+
console.log("\n\u2713 Comment updated");
|
|
2861
|
+
outputCommentPretty(updated);
|
|
2862
|
+
}
|
|
2863
|
+
} catch (error) {
|
|
2864
|
+
handleError(error, useJson);
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
);
|
|
2868
|
+
comment.command("rm").description("Delete one of your own comments").requiredOption("--comment-id <id>", "Comment ID").action(async (options, cmd) => {
|
|
2869
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2870
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2871
|
+
try {
|
|
2872
|
+
const client = getClient();
|
|
2873
|
+
await client.featureComments.delete(options.commentId);
|
|
2874
|
+
if (useJson) {
|
|
2875
|
+
console.log(JSON.stringify({ deleted: true, id: options.commentId }, null, 2));
|
|
2876
|
+
} else {
|
|
2877
|
+
console.log("\u2713 Comment deleted");
|
|
2878
|
+
}
|
|
2879
|
+
} catch (error) {
|
|
2880
|
+
handleError(error, useJson);
|
|
2881
|
+
}
|
|
2882
|
+
});
|
|
2883
|
+
comment.command("resolve").description("Resolve an anchored comment thread").requiredOption("--feature <id>", "Feature ID").requiredOption("--thread <id>", "Thread ID (anchored comments only)").action(async (options, cmd) => {
|
|
2884
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2885
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2886
|
+
try {
|
|
2887
|
+
const client = getClient();
|
|
2888
|
+
await client.featureComments.resolve({
|
|
2889
|
+
featureId: options.feature,
|
|
2890
|
+
threadId: options.thread
|
|
2891
|
+
});
|
|
2892
|
+
if (useJson) {
|
|
2893
|
+
console.log(JSON.stringify({ resolved: true, threadId: options.thread }, null, 2));
|
|
2894
|
+
} else {
|
|
2895
|
+
console.log("\u2713 Thread resolved");
|
|
2896
|
+
}
|
|
2897
|
+
} catch (error) {
|
|
2898
|
+
handleError(error, useJson);
|
|
2899
|
+
}
|
|
2900
|
+
});
|
|
2901
|
+
comment.command("unresolve").description("Reopen a resolved comment thread").requiredOption("--feature <id>", "Feature ID").requiredOption("--thread <id>", "Thread ID (anchored comments only)").action(async (options, cmd) => {
|
|
2902
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2903
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2904
|
+
try {
|
|
2905
|
+
const client = getClient();
|
|
2906
|
+
await client.featureComments.unresolve({
|
|
2907
|
+
featureId: options.feature,
|
|
2908
|
+
threadId: options.thread
|
|
2909
|
+
});
|
|
2910
|
+
if (useJson) {
|
|
2911
|
+
console.log(JSON.stringify({ resolved: false, threadId: options.thread }, null, 2));
|
|
2912
|
+
} else {
|
|
2913
|
+
console.log("\u2713 Thread reopened");
|
|
2914
|
+
}
|
|
2915
|
+
} catch (error) {
|
|
2916
|
+
handleError(error, useJson);
|
|
2917
|
+
}
|
|
2918
|
+
});
|
|
2919
|
+
return comment;
|
|
2920
|
+
}
|
|
2921
|
+
|
|
2494
2922
|
// src/commands/features.ts
|
|
2495
2923
|
var FEATURE_STATUSES = [
|
|
2496
2924
|
"IDEA",
|
|
@@ -2500,6 +2928,17 @@ var FEATURE_STATUSES = [
|
|
|
2500
2928
|
"DEPRECATED",
|
|
2501
2929
|
"ARCHIVED"
|
|
2502
2930
|
];
|
|
2931
|
+
function summarizeTicket(ticket) {
|
|
2932
|
+
return {
|
|
2933
|
+
id: ticket.id,
|
|
2934
|
+
shortId: ticket.shortId ?? null,
|
|
2935
|
+
number: ticket.number ?? null,
|
|
2936
|
+
title: ticket.title
|
|
2937
|
+
};
|
|
2938
|
+
}
|
|
2939
|
+
function ticketLabel(ticket) {
|
|
2940
|
+
return ticket.shortId ?? (ticket.number != null ? `#${ticket.number}` : ticket.id);
|
|
2941
|
+
}
|
|
2503
2942
|
function validateFeatureStatus(value) {
|
|
2504
2943
|
if (!value) return void 0;
|
|
2505
2944
|
if (!FEATURE_STATUSES.includes(value)) {
|
|
@@ -2510,7 +2949,7 @@ function validateFeatureStatus(value) {
|
|
|
2510
2949
|
return value;
|
|
2511
2950
|
}
|
|
2512
2951
|
function createFeaturesCommand() {
|
|
2513
|
-
const features = new
|
|
2952
|
+
const features = new Command11("features").description(
|
|
2514
2953
|
"Manage features. A feature lives under a product and groups user stories, scopes, and tickets."
|
|
2515
2954
|
);
|
|
2516
2955
|
features.command("list").description("List features in a product").requiredOption("--product <slug|id>", "Product slug or CUID").option("--workspace <slug|id>", "Workspace (required when --product is a slug)").option("--status <status>", `Filter by status: ${FEATURE_STATUSES.join(", ")}`).action(
|
|
@@ -2606,6 +3045,119 @@ function createFeaturesCommand() {
|
|
|
2606
3045
|
}
|
|
2607
3046
|
}
|
|
2608
3047
|
);
|
|
3048
|
+
features.command("delete <id>").description(
|
|
3049
|
+
"Delete a feature. Refuses if it still has tickets \u2014 deleting a feature does not delete its tickets, it unlinks them (Ticket.featureId is SetNull), silently orphaning them in the product backlog."
|
|
3050
|
+
).option(
|
|
3051
|
+
"--with-tickets",
|
|
3052
|
+
"Delete the feature's tickets first, then the feature (destructive)"
|
|
3053
|
+
).option(
|
|
3054
|
+
"--orphan-tickets",
|
|
3055
|
+
"Delete the feature anyway, leaving its tickets in the backlog unlinked"
|
|
3056
|
+
).action(
|
|
3057
|
+
async (id, options, cmd) => {
|
|
3058
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
3059
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
3060
|
+
try {
|
|
3061
|
+
if (options.withTickets && options.orphanTickets) {
|
|
3062
|
+
throw new Error(
|
|
3063
|
+
"--with-tickets and --orphan-tickets are mutually exclusive"
|
|
3064
|
+
);
|
|
3065
|
+
}
|
|
3066
|
+
const client = getClient();
|
|
3067
|
+
const feature = await client.features.get(id);
|
|
3068
|
+
const tickets = feature._count?.tickets === 0 ? [] : await client.tickets.list({
|
|
3069
|
+
productId: feature.productId,
|
|
3070
|
+
featureId: feature.id
|
|
3071
|
+
});
|
|
3072
|
+
if (tickets.length > 0 && !options.withTickets && !options.orphanTickets) {
|
|
3073
|
+
const reason = `has ${tickets.length} ticket${tickets.length === 1 ? "" : "s"}`;
|
|
3074
|
+
if (useJson) {
|
|
3075
|
+
console.log(
|
|
3076
|
+
JSON.stringify(
|
|
3077
|
+
{
|
|
3078
|
+
deleted: false,
|
|
3079
|
+
id: feature.id,
|
|
3080
|
+
reason,
|
|
3081
|
+
tickets: tickets.map(summarizeTicket)
|
|
3082
|
+
},
|
|
3083
|
+
null,
|
|
3084
|
+
2
|
|
3085
|
+
)
|
|
3086
|
+
);
|
|
3087
|
+
} else {
|
|
3088
|
+
console.error(
|
|
3089
|
+
`
|
|
3090
|
+
Refusing to delete "${feature.name}": it ${reason}.
|
|
3091
|
+
Deleting the feature would leave them unlinked in the product backlog.
|
|
3092
|
+
--with-tickets delete those tickets first, then the feature
|
|
3093
|
+
--orphan-tickets delete the feature anyway and unlink them`
|
|
3094
|
+
);
|
|
3095
|
+
for (const t of tickets) {
|
|
3096
|
+
console.error(` - ${ticketLabel(t)} ${t.title}`);
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
process.exitCode = 1;
|
|
3100
|
+
return;
|
|
3101
|
+
}
|
|
3102
|
+
const ticketsDeleted = [];
|
|
3103
|
+
const ticketsFailed = [];
|
|
3104
|
+
if (options.withTickets) {
|
|
3105
|
+
for (const ticket of tickets) {
|
|
3106
|
+
try {
|
|
3107
|
+
await client.tickets.delete(ticket.id);
|
|
3108
|
+
ticketsDeleted.push(summarizeTicket(ticket));
|
|
3109
|
+
} catch (error) {
|
|
3110
|
+
ticketsFailed.push({
|
|
3111
|
+
...summarizeTicket(ticket),
|
|
3112
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3113
|
+
});
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
if (ticketsFailed.length > 0) {
|
|
3118
|
+
const reason = `failed to delete ${ticketsFailed.length} of ${tickets.length} tickets`;
|
|
3119
|
+
if (useJson) {
|
|
3120
|
+
console.log(
|
|
3121
|
+
JSON.stringify(
|
|
3122
|
+
{ deleted: false, id: feature.id, reason, ticketsDeleted, ticketsFailed },
|
|
3123
|
+
null,
|
|
3124
|
+
2
|
|
3125
|
+
)
|
|
3126
|
+
);
|
|
3127
|
+
} else {
|
|
3128
|
+
console.error(`
|
|
3129
|
+
Aborted: ${reason}. Feature "${feature.name}" was NOT deleted.`);
|
|
3130
|
+
for (const t of ticketsFailed) {
|
|
3131
|
+
console.error(` - ${t.shortId ?? t.id}: ${t.error}`);
|
|
3132
|
+
}
|
|
3133
|
+
}
|
|
3134
|
+
process.exitCode = 1;
|
|
3135
|
+
return;
|
|
3136
|
+
}
|
|
3137
|
+
await client.features.delete(feature.id);
|
|
3138
|
+
const ticketsOrphaned = options.orphanTickets ? tickets.map(summarizeTicket) : [];
|
|
3139
|
+
if (useJson) {
|
|
3140
|
+
console.log(
|
|
3141
|
+
JSON.stringify(
|
|
3142
|
+
{ deleted: true, id: feature.id, ticketsDeleted, ticketsOrphaned },
|
|
3143
|
+
null,
|
|
3144
|
+
2
|
|
3145
|
+
)
|
|
3146
|
+
);
|
|
3147
|
+
} else {
|
|
3148
|
+
console.log("\n\u2713 Feature deleted");
|
|
3149
|
+
if (ticketsDeleted.length > 0) {
|
|
3150
|
+
console.log(` ${ticketsDeleted.length} ticket(s) deleted with it`);
|
|
3151
|
+
}
|
|
3152
|
+
if (ticketsOrphaned.length > 0) {
|
|
3153
|
+
console.log(` ${ticketsOrphaned.length} ticket(s) left unlinked in the backlog`);
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3156
|
+
} catch (error) {
|
|
3157
|
+
handleError(error, useJson);
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
);
|
|
2609
3161
|
features.command("link-page").description("Link a Knowledge page (PRD, spec, research) to a feature").requiredOption("--feature <id>", "Feature CUID").requiredOption("--page <id>", "Page CUID").option("--scope <id>", "Feature scope CUID to pin the page to").action(
|
|
2610
3162
|
async (options, cmd) => {
|
|
2611
3163
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -2645,11 +3197,12 @@ function createFeaturesCommand() {
|
|
|
2645
3197
|
features.addCommand(createScopesCommand());
|
|
2646
3198
|
features.addCommand(createRequirementsCommand());
|
|
2647
3199
|
features.addCommand(createAreasCommand());
|
|
3200
|
+
features.addCommand(createFeatureCommentsCommand());
|
|
2648
3201
|
return features;
|
|
2649
3202
|
}
|
|
2650
3203
|
|
|
2651
3204
|
// src/commands/labels.ts
|
|
2652
|
-
import { Command as
|
|
3205
|
+
import { Command as Command12 } from "commander";
|
|
2653
3206
|
import chalk5 from "chalk";
|
|
2654
3207
|
var TRIAGE_LABELS = [
|
|
2655
3208
|
{ slug: "needs-triage", name: "needs-triage", color: "avatar-orange" },
|
|
@@ -2703,7 +3256,7 @@ async function resolveLabelIds(client, workspaceId, values) {
|
|
|
2703
3256
|
return [...byId, ...resolvedFromSlugs];
|
|
2704
3257
|
}
|
|
2705
3258
|
function createLabelsCommand() {
|
|
2706
|
-
const labels = new
|
|
3259
|
+
const labels = new Command12("labels").description(
|
|
2707
3260
|
"Manage labels (orthogonal tags applied to actions, tickets, features, and epics)."
|
|
2708
3261
|
);
|
|
2709
3262
|
labels.command("list").description("List labels visible in a workspace").option("--workspace <slug|id>", "Workspace slug or CUID").option(
|
|
@@ -2806,7 +3359,7 @@ function createLabelsCommand() {
|
|
|
2806
3359
|
}
|
|
2807
3360
|
|
|
2808
3361
|
// src/commands/organizations.ts
|
|
2809
|
-
import { Command as
|
|
3362
|
+
import { Command as Command13 } from "commander";
|
|
2810
3363
|
var VALID_SIZES = [
|
|
2811
3364
|
"1-10",
|
|
2812
3365
|
"11-50",
|
|
@@ -2816,7 +3369,7 @@ var VALID_SIZES = [
|
|
|
2816
3369
|
"1000+"
|
|
2817
3370
|
];
|
|
2818
3371
|
function createOrganizationsCommand() {
|
|
2819
|
-
const organizations = new
|
|
3372
|
+
const organizations = new Command13("organizations").alias("orgs").description("Manage CRM organizations");
|
|
2820
3373
|
organizations.command("list").description("List organizations").requiredOption("--workspace <id>", "Workspace ID").option("--search <query>", "Search by name or description").option("--industry <industry>", "Filter by industry").option("--limit <n>", "Max results to return", parseInt).option("--cursor <cursor>", "Pagination cursor").action(async (options, cmd) => {
|
|
2821
3374
|
const globalOpts = cmd.optsWithGlobals();
|
|
2822
3375
|
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
@@ -2885,7 +3438,105 @@ function createOrganizationsCommand() {
|
|
|
2885
3438
|
|
|
2886
3439
|
// src/commands/pages.ts
|
|
2887
3440
|
import { readFileSync } from "fs";
|
|
2888
|
-
import { Command as
|
|
3441
|
+
import { Command as Command15 } from "commander";
|
|
3442
|
+
|
|
3443
|
+
// src/commands/pageComments.ts
|
|
3444
|
+
import { Command as Command14 } from "commander";
|
|
3445
|
+
function createPageCommentsCommand() {
|
|
3446
|
+
const comment = new Command14("comment").description(
|
|
3447
|
+
"Read and post comments on a page. Mention teammates with --mention."
|
|
3448
|
+
);
|
|
3449
|
+
comment.command("list").description("List comments on a page").requiredOption("--page <id>", "Page ID").action(async (options, cmd) => {
|
|
3450
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
3451
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
3452
|
+
try {
|
|
3453
|
+
const client = getClient();
|
|
3454
|
+
const comments = await client.pageComments.list(options.page);
|
|
3455
|
+
if (useJson) outputCommentsJson(comments);
|
|
3456
|
+
else outputCommentsPretty(comments);
|
|
3457
|
+
} catch (error) {
|
|
3458
|
+
handleError(error, useJson);
|
|
3459
|
+
}
|
|
3460
|
+
});
|
|
3461
|
+
comment.command("add").description("Add a comment to a page").requiredOption("--page <id>", "Page ID").requiredOption("-m, --message <text>", "Comment body (markdown supported)").option("--mention <name>", MENTION_OPTION_DESCRIPTION, collectMention, []).option(
|
|
3462
|
+
"--workspace <slug|id>",
|
|
3463
|
+
"Workspace used to resolve --mention (defaults to your default workspace)"
|
|
3464
|
+
).action(
|
|
3465
|
+
async (options, cmd) => {
|
|
3466
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
3467
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
3468
|
+
try {
|
|
3469
|
+
const client = getClient();
|
|
3470
|
+
const body = options.mention.length > 0 ? await applyMentions(
|
|
3471
|
+
client,
|
|
3472
|
+
await resolveWorkspaceId(client, options.workspace),
|
|
3473
|
+
options.message,
|
|
3474
|
+
options.mention
|
|
3475
|
+
) : options.message;
|
|
3476
|
+
const created = await client.pageComments.create({
|
|
3477
|
+
pageId: options.page,
|
|
3478
|
+
body
|
|
3479
|
+
});
|
|
3480
|
+
if (useJson) {
|
|
3481
|
+
outputCommentJson(created);
|
|
3482
|
+
} else {
|
|
3483
|
+
console.log("\n\u2713 Comment added");
|
|
3484
|
+
outputCommentPretty(created);
|
|
3485
|
+
}
|
|
3486
|
+
} catch (error) {
|
|
3487
|
+
handleError(error, useJson);
|
|
3488
|
+
}
|
|
3489
|
+
}
|
|
3490
|
+
);
|
|
3491
|
+
comment.command("update").description("Update one of your own comments").requiredOption("--comment-id <id>", "Comment ID").requiredOption("-m, --message <text>", "New comment body").option("--mention <name>", MENTION_OPTION_DESCRIPTION, collectMention, []).option(
|
|
3492
|
+
"--workspace <slug|id>",
|
|
3493
|
+
"Workspace used to resolve --mention (defaults to your default workspace)"
|
|
3494
|
+
).action(
|
|
3495
|
+
async (options, cmd) => {
|
|
3496
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
3497
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
3498
|
+
try {
|
|
3499
|
+
const client = getClient();
|
|
3500
|
+
const body = options.mention.length > 0 ? await applyMentions(
|
|
3501
|
+
client,
|
|
3502
|
+
await resolveWorkspaceId(client, options.workspace),
|
|
3503
|
+
options.message,
|
|
3504
|
+
options.mention
|
|
3505
|
+
) : options.message;
|
|
3506
|
+
const updated = await client.pageComments.update({
|
|
3507
|
+
commentId: options.commentId,
|
|
3508
|
+
body
|
|
3509
|
+
});
|
|
3510
|
+
if (useJson) {
|
|
3511
|
+
outputCommentJson(updated);
|
|
3512
|
+
} else {
|
|
3513
|
+
console.log("\n\u2713 Comment updated");
|
|
3514
|
+
outputCommentPretty(updated);
|
|
3515
|
+
}
|
|
3516
|
+
} catch (error) {
|
|
3517
|
+
handleError(error, useJson);
|
|
3518
|
+
}
|
|
3519
|
+
}
|
|
3520
|
+
);
|
|
3521
|
+
comment.command("rm").description("Delete one of your own comments").requiredOption("--comment-id <id>", "Comment ID").action(async (options, cmd) => {
|
|
3522
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
3523
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
3524
|
+
try {
|
|
3525
|
+
const client = getClient();
|
|
3526
|
+
await client.pageComments.delete(options.commentId);
|
|
3527
|
+
if (useJson) {
|
|
3528
|
+
console.log(JSON.stringify({ deleted: true, id: options.commentId }, null, 2));
|
|
3529
|
+
} else {
|
|
3530
|
+
console.log("\u2713 Comment deleted");
|
|
3531
|
+
}
|
|
3532
|
+
} catch (error) {
|
|
3533
|
+
handleError(error, useJson);
|
|
3534
|
+
}
|
|
3535
|
+
});
|
|
3536
|
+
return comment;
|
|
3537
|
+
}
|
|
3538
|
+
|
|
3539
|
+
// src/commands/pages.ts
|
|
2889
3540
|
function readBody(body, bodyFile) {
|
|
2890
3541
|
if (body !== void 0) return body;
|
|
2891
3542
|
if (bodyFile === void 0) return void 0;
|
|
@@ -2893,7 +3544,7 @@ function readBody(body, bodyFile) {
|
|
|
2893
3544
|
return readFileSync(bodyFile, "utf-8");
|
|
2894
3545
|
}
|
|
2895
3546
|
function createPagesCommand() {
|
|
2896
|
-
const pages = new
|
|
3547
|
+
const pages = new Command15("pages").description(
|
|
2897
3548
|
'Manage Knowledge pages (PRDs, research, technical specs). Link one to a feature with "features link-page".'
|
|
2898
3549
|
);
|
|
2899
3550
|
pages.command("list").description("List pages in a workspace").option("--workspace <slug|id>", "Workspace (defaults to the configured default)").option("--search <text>", "Filter by title/content").action(
|
|
@@ -2971,13 +3622,14 @@ function createPagesCommand() {
|
|
|
2971
3622
|
}
|
|
2972
3623
|
}
|
|
2973
3624
|
);
|
|
3625
|
+
pages.addCommand(createPageCommentsCommand());
|
|
2974
3626
|
return pages;
|
|
2975
3627
|
}
|
|
2976
3628
|
|
|
2977
3629
|
// src/commands/products.ts
|
|
2978
|
-
import { Command as
|
|
3630
|
+
import { Command as Command16 } from "commander";
|
|
2979
3631
|
function createProductsCommand() {
|
|
2980
|
-
const products = new
|
|
3632
|
+
const products = new Command16("products").description(
|
|
2981
3633
|
"Manage products. A workspace contains products; a product holds features and tickets."
|
|
2982
3634
|
);
|
|
2983
3635
|
products.command("list").description("List products in a workspace").option("--workspace <slug|id>", "Workspace slug or CUID (defaults to configured workspace)").action(async (options, cmd) => {
|
|
@@ -3040,9 +3692,9 @@ function createProductsCommand() {
|
|
|
3040
3692
|
}
|
|
3041
3693
|
|
|
3042
3694
|
// src/commands/projects.ts
|
|
3043
|
-
import { Command as
|
|
3695
|
+
import { Command as Command17 } from "commander";
|
|
3044
3696
|
function createProjectsCommand() {
|
|
3045
|
-
const projects = new
|
|
3697
|
+
const projects = new Command17("projects").description("Manage projects");
|
|
3046
3698
|
projects.command("list").description("List all projects").option("--workspace <id>", "Filter by workspace ID").option("--include-actions", "Include actions in output").action(async (options, cmd) => {
|
|
3047
3699
|
const globalOpts = cmd.optsWithGlobals();
|
|
3048
3700
|
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
@@ -3065,7 +3717,7 @@ function createProjectsCommand() {
|
|
|
3065
3717
|
}
|
|
3066
3718
|
|
|
3067
3719
|
// src/commands/search.ts
|
|
3068
|
-
import { Command as
|
|
3720
|
+
import { Command as Command18 } from "commander";
|
|
3069
3721
|
import chalk6 from "chalk";
|
|
3070
3722
|
var TYPE_LABELS = {
|
|
3071
3723
|
workspace: "Workspaces",
|
|
@@ -3084,7 +3736,7 @@ var TYPE_LABELS = {
|
|
|
3084
3736
|
product: "Products"
|
|
3085
3737
|
};
|
|
3086
3738
|
function createSearchCommand() {
|
|
3087
|
-
const search = new
|
|
3739
|
+
const search = new Command18("search").description(
|
|
3088
3740
|
"Global search across projects, actions, goals, workspaces and more \u2014 same coverage as the app's Cmd+K palette"
|
|
3089
3741
|
).argument("<query>", "Text to search for").option("-w, --workspace <slug>", "Restrict results to one workspace (slug or ID)").option("-l, --limit <n>", "Max results per entity type (1-25, default 10)").action(async (query, options, cmd) => {
|
|
3090
3742
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -3143,7 +3795,7 @@ ${TYPE_LABELS[type] ?? type}`));
|
|
|
3143
3795
|
}
|
|
3144
3796
|
|
|
3145
3797
|
// src/commands/tickets.ts
|
|
3146
|
-
import { Command as
|
|
3798
|
+
import { Command as Command19 } from "commander";
|
|
3147
3799
|
function collectRepeatable(value, previous) {
|
|
3148
3800
|
return [...previous, value];
|
|
3149
3801
|
}
|
|
@@ -3186,7 +3838,7 @@ function validateTicketType(value) {
|
|
|
3186
3838
|
return value;
|
|
3187
3839
|
}
|
|
3188
3840
|
function createTicketsCommand() {
|
|
3189
|
-
const tickets = new
|
|
3841
|
+
const tickets = new Command19("tickets").description(
|
|
3190
3842
|
"Manage product backlog tickets. Tickets live under a product, link to features/epics, depend on each other, and can be implemented via actions."
|
|
3191
3843
|
);
|
|
3192
3844
|
tickets.command("list").description(
|
|
@@ -3500,25 +4152,78 @@ function createTicketsCommand() {
|
|
|
3500
4152
|
handleError(error, useJson);
|
|
3501
4153
|
}
|
|
3502
4154
|
});
|
|
3503
|
-
const comment = new
|
|
3504
|
-
comment.command("
|
|
4155
|
+
const comment = new Command19("comment").description("Manage comments on a ticket");
|
|
4156
|
+
comment.command("list").description("List comments on a ticket").requiredOption("--id <id>", "Ticket CUID").action(async (options, cmd) => {
|
|
3505
4157
|
const globalOpts = cmd.optsWithGlobals();
|
|
3506
4158
|
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
3507
4159
|
try {
|
|
3508
4160
|
const client = getClient();
|
|
3509
|
-
const
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
if (useJson) outputCommentJson(created);
|
|
3514
|
-
else {
|
|
3515
|
-
console.log("\n\u2713 Comment added");
|
|
3516
|
-
outputCommentPretty(created);
|
|
3517
|
-
}
|
|
4161
|
+
const ticket = await client.tickets.get(options.id);
|
|
4162
|
+
const comments = ticket.comments ?? [];
|
|
4163
|
+
if (useJson) outputCommentsJson(comments);
|
|
4164
|
+
else outputCommentsPretty(comments);
|
|
3518
4165
|
} catch (error) {
|
|
3519
4166
|
handleError(error, useJson);
|
|
3520
4167
|
}
|
|
3521
4168
|
});
|
|
4169
|
+
comment.command("add").description("Add a comment to a ticket").requiredOption("--id <id>", "Ticket CUID").requiredOption("-m, --message <text>", "Comment content (markdown supported)").option("--mention <name>", MENTION_OPTION_DESCRIPTION, collectMention, []).option(
|
|
4170
|
+
"--workspace <slug|id>",
|
|
4171
|
+
"Workspace used to resolve --mention (defaults to your default workspace)"
|
|
4172
|
+
).action(
|
|
4173
|
+
async (options, cmd) => {
|
|
4174
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
4175
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
4176
|
+
try {
|
|
4177
|
+
const client = getClient();
|
|
4178
|
+
const content = options.mention.length > 0 ? await applyMentions(
|
|
4179
|
+
client,
|
|
4180
|
+
await resolveWorkspaceId(client, options.workspace),
|
|
4181
|
+
options.message,
|
|
4182
|
+
options.mention
|
|
4183
|
+
) : options.message;
|
|
4184
|
+
const created = await client.tickets.addComment({
|
|
4185
|
+
ticketId: options.id,
|
|
4186
|
+
content
|
|
4187
|
+
});
|
|
4188
|
+
if (useJson) outputCommentJson(created);
|
|
4189
|
+
else {
|
|
4190
|
+
console.log("\n\u2713 Comment added");
|
|
4191
|
+
outputCommentPretty(created);
|
|
4192
|
+
}
|
|
4193
|
+
} catch (error) {
|
|
4194
|
+
handleError(error, useJson);
|
|
4195
|
+
}
|
|
4196
|
+
}
|
|
4197
|
+
);
|
|
4198
|
+
comment.command("update").description("Update one of your own ticket comments").requiredOption("--comment-id <id>", "Comment ID").requiredOption("-m, --message <text>", "New comment content").option("--mention <name>", MENTION_OPTION_DESCRIPTION, collectMention, []).option(
|
|
4199
|
+
"--workspace <slug|id>",
|
|
4200
|
+
"Workspace used to resolve --mention (defaults to your default workspace)"
|
|
4201
|
+
).action(
|
|
4202
|
+
async (options, cmd) => {
|
|
4203
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
4204
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
4205
|
+
try {
|
|
4206
|
+
const client = getClient();
|
|
4207
|
+
const content = options.mention.length > 0 ? await applyMentions(
|
|
4208
|
+
client,
|
|
4209
|
+
await resolveWorkspaceId(client, options.workspace),
|
|
4210
|
+
options.message,
|
|
4211
|
+
options.mention
|
|
4212
|
+
) : options.message;
|
|
4213
|
+
const updated = await client.tickets.updateComment({
|
|
4214
|
+
id: options.commentId,
|
|
4215
|
+
content
|
|
4216
|
+
});
|
|
4217
|
+
if (useJson) outputCommentJson(updated);
|
|
4218
|
+
else {
|
|
4219
|
+
console.log("\n\u2713 Comment updated");
|
|
4220
|
+
outputCommentPretty(updated);
|
|
4221
|
+
}
|
|
4222
|
+
} catch (error) {
|
|
4223
|
+
handleError(error, useJson);
|
|
4224
|
+
}
|
|
4225
|
+
}
|
|
4226
|
+
);
|
|
3522
4227
|
comment.command("delete").description("Delete one of your own ticket comments").requiredOption("--comment-id <id>", "Comment ID").action(async (options, cmd) => {
|
|
3523
4228
|
const globalOpts = cmd.optsWithGlobals();
|
|
3524
4229
|
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
@@ -3539,10 +4244,10 @@ function createTicketsCommand() {
|
|
|
3539
4244
|
}
|
|
3540
4245
|
|
|
3541
4246
|
// src/commands/workspaces.ts
|
|
3542
|
-
import { Command as
|
|
4247
|
+
import { Command as Command20 } from "commander";
|
|
3543
4248
|
import chalk7 from "chalk";
|
|
3544
4249
|
function createWorkspacesCommand() {
|
|
3545
|
-
const workspaces = new
|
|
4250
|
+
const workspaces = new Command20("workspaces").description("Manage workspaces");
|
|
3546
4251
|
workspaces.command("list").description("List all workspaces").action(async (_options, cmd) => {
|
|
3547
4252
|
const globalOpts = cmd.optsWithGlobals();
|
|
3548
4253
|
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
@@ -3584,11 +4289,172 @@ function createWorkspacesCommand() {
|
|
|
3584
4289
|
handleError(error);
|
|
3585
4290
|
}
|
|
3586
4291
|
});
|
|
4292
|
+
workspaces.command("members").description(
|
|
4293
|
+
"List workspace members. Each row carries the exact @[Name](id) markup to paste into a comment to mention that person."
|
|
4294
|
+
).option("--workspace <slug|id>", "Workspace (defaults to your default workspace)").option("--search <text>", "Filter by name or email").action(
|
|
4295
|
+
async (options, cmd) => {
|
|
4296
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
4297
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
4298
|
+
try {
|
|
4299
|
+
const client = getClient();
|
|
4300
|
+
const workspaceId = await resolveWorkspaceId(client, options.workspace);
|
|
4301
|
+
let members = await client.workspaces.listMembers(workspaceId);
|
|
4302
|
+
if (options.search) {
|
|
4303
|
+
const needle = options.search.toLowerCase();
|
|
4304
|
+
members = members.filter(
|
|
4305
|
+
(m) => m.name?.toLowerCase().includes(needle) || m.email?.toLowerCase().includes(needle)
|
|
4306
|
+
);
|
|
4307
|
+
}
|
|
4308
|
+
if (useJson) {
|
|
4309
|
+
console.log(
|
|
4310
|
+
JSON.stringify({ members, total: members.length }, null, 2)
|
|
4311
|
+
);
|
|
4312
|
+
return;
|
|
4313
|
+
}
|
|
4314
|
+
if (members.length === 0) {
|
|
4315
|
+
console.log(chalk7.gray("No members found."));
|
|
4316
|
+
return;
|
|
4317
|
+
}
|
|
4318
|
+
console.log(chalk7.bold(`
|
|
4319
|
+
Members (${members.length})
|
|
4320
|
+
`));
|
|
4321
|
+
for (const m of members) {
|
|
4322
|
+
const label = m.name ?? m.email ?? m.id;
|
|
4323
|
+
const via = m.source === "team" ? chalk7.gray(
|
|
4324
|
+
` via team${m.teams.length ? `: ${m.teams.map((t) => t.name).join(", ")}` : ""}` + (m.teamRole ? ` [team ${m.teamRole}]` : "")
|
|
4325
|
+
) : "";
|
|
4326
|
+
console.log(`${chalk7.bold(label)} ${chalk7.gray(`(${m.role})`)}${via}`);
|
|
4327
|
+
if (m.email) console.log(chalk7.gray(` ${m.email}`));
|
|
4328
|
+
console.log(chalk7.gray(` mention: ${m.mentionSyntax}`));
|
|
4329
|
+
console.log();
|
|
4330
|
+
}
|
|
4331
|
+
} catch (error) {
|
|
4332
|
+
handleError(error, useJson);
|
|
4333
|
+
}
|
|
4334
|
+
}
|
|
4335
|
+
);
|
|
3587
4336
|
return workspaces;
|
|
3588
4337
|
}
|
|
3589
4338
|
|
|
4339
|
+
// src/commands/goals.ts
|
|
4340
|
+
import { Command as Command21 } from "commander";
|
|
4341
|
+
function parseGoalId(value) {
|
|
4342
|
+
const id = Number(value);
|
|
4343
|
+
if (!Number.isInteger(id)) {
|
|
4344
|
+
throw new Error(
|
|
4345
|
+
`Goal id must be a whole number, got "${value}". Find one with \`exponential search "<goal title>"\`.`
|
|
4346
|
+
);
|
|
4347
|
+
}
|
|
4348
|
+
return id;
|
|
4349
|
+
}
|
|
4350
|
+
function createGoalsCommand() {
|
|
4351
|
+
const goals = new Command21("goals").description(
|
|
4352
|
+
'Work with goals. Find goal ids with `exponential search "<title>"`.'
|
|
4353
|
+
);
|
|
4354
|
+
const comment = new Command21("comment").description(
|
|
4355
|
+
"Read and post comments on a goal. Mention teammates with --mention."
|
|
4356
|
+
);
|
|
4357
|
+
comment.command("list").description("List comments on a goal").requiredOption("--goal <id>", "Goal ID (a number)").action(async (options, cmd) => {
|
|
4358
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
4359
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
4360
|
+
try {
|
|
4361
|
+
const client = getClient();
|
|
4362
|
+
const comments = await client.goalComments.list(parseGoalId(options.goal));
|
|
4363
|
+
if (useJson) outputCommentsJson(comments);
|
|
4364
|
+
else outputCommentsPretty(comments);
|
|
4365
|
+
} catch (error) {
|
|
4366
|
+
handleError(error, useJson);
|
|
4367
|
+
}
|
|
4368
|
+
});
|
|
4369
|
+
comment.command("add").description("Add a comment to a goal").requiredOption("--goal <id>", "Goal ID (a number)").requiredOption("-m, --message <text>", "Comment content (markdown supported)").option("--mention <name>", MENTION_OPTION_DESCRIPTION, collectMention, []).option(
|
|
4370
|
+
"--workspace <slug|id>",
|
|
4371
|
+
"Workspace used to resolve --mention (defaults to your default workspace)"
|
|
4372
|
+
).option("--parent-update <id>", "Thread under a specific goal update").action(
|
|
4373
|
+
async (options, cmd) => {
|
|
4374
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
4375
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
4376
|
+
try {
|
|
4377
|
+
const client = getClient();
|
|
4378
|
+
const goalId = parseGoalId(options.goal);
|
|
4379
|
+
const content = options.mention.length > 0 ? await applyMentions(
|
|
4380
|
+
client,
|
|
4381
|
+
await resolveWorkspaceId(client, options.workspace),
|
|
4382
|
+
options.message,
|
|
4383
|
+
options.mention
|
|
4384
|
+
) : options.message;
|
|
4385
|
+
const created = await client.goalComments.add({
|
|
4386
|
+
goalId,
|
|
4387
|
+
content,
|
|
4388
|
+
parentUpdateId: options.parentUpdate
|
|
4389
|
+
});
|
|
4390
|
+
if (useJson) {
|
|
4391
|
+
outputCommentJson(created);
|
|
4392
|
+
} else {
|
|
4393
|
+
console.log("\n\u2713 Comment added");
|
|
4394
|
+
outputCommentPretty(created);
|
|
4395
|
+
}
|
|
4396
|
+
} catch (error) {
|
|
4397
|
+
handleError(error, useJson);
|
|
4398
|
+
}
|
|
4399
|
+
}
|
|
4400
|
+
);
|
|
4401
|
+
comment.command("update").description("Update one of your own comments").requiredOption("--comment-id <id>", "Comment ID").requiredOption("-m, --message <text>", "New comment content").option("--mention <name>", MENTION_OPTION_DESCRIPTION, collectMention, []).option(
|
|
4402
|
+
"--workspace <slug|id>",
|
|
4403
|
+
"Workspace used to resolve --mention (defaults to your default workspace)"
|
|
4404
|
+
).action(
|
|
4405
|
+
async (options, cmd) => {
|
|
4406
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
4407
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
4408
|
+
try {
|
|
4409
|
+
const client = getClient();
|
|
4410
|
+
const content = options.mention.length > 0 ? await applyMentions(
|
|
4411
|
+
client,
|
|
4412
|
+
await resolveWorkspaceId(client, options.workspace),
|
|
4413
|
+
options.message,
|
|
4414
|
+
options.mention
|
|
4415
|
+
) : options.message;
|
|
4416
|
+
const updated = await client.goalComments.update({
|
|
4417
|
+
commentId: options.commentId,
|
|
4418
|
+
content
|
|
4419
|
+
});
|
|
4420
|
+
if (useJson) {
|
|
4421
|
+
outputCommentJson(updated);
|
|
4422
|
+
} else {
|
|
4423
|
+
console.log("\n\u2713 Comment updated");
|
|
4424
|
+
outputCommentPretty(updated);
|
|
4425
|
+
}
|
|
4426
|
+
} catch (error) {
|
|
4427
|
+
handleError(error, useJson);
|
|
4428
|
+
}
|
|
4429
|
+
}
|
|
4430
|
+
);
|
|
4431
|
+
comment.command("rm").description("Delete one of your own comments").requiredOption("--comment-id <id>", "Comment ID").action(async (options, cmd) => {
|
|
4432
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
4433
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
4434
|
+
try {
|
|
4435
|
+
const client = getClient();
|
|
4436
|
+
await client.goalComments.delete(options.commentId);
|
|
4437
|
+
if (useJson) {
|
|
4438
|
+
console.log(JSON.stringify({ deleted: true, id: options.commentId }, null, 2));
|
|
4439
|
+
} else {
|
|
4440
|
+
console.log("\u2713 Comment deleted");
|
|
4441
|
+
}
|
|
4442
|
+
} catch (error) {
|
|
4443
|
+
handleError(error, useJson);
|
|
4444
|
+
}
|
|
4445
|
+
});
|
|
4446
|
+
goals.addCommand(comment);
|
|
4447
|
+
return goals;
|
|
4448
|
+
}
|
|
4449
|
+
|
|
3590
4450
|
// src/index.ts
|
|
3591
|
-
|
|
4451
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
4452
|
+
import { dirname, join } from "path";
|
|
4453
|
+
import { fileURLToPath } from "url";
|
|
4454
|
+
var PKG_VERSION = JSON.parse(
|
|
4455
|
+
readFileSync2(join(dirname(fileURLToPath(import.meta.url)), "..", "package.json"), "utf-8")
|
|
4456
|
+
).version;
|
|
4457
|
+
var program = new Command22();
|
|
3592
4458
|
program.name("exponential").description(
|
|
3593
4459
|
[
|
|
3594
4460
|
"CLI to interact with Exponential productivity app.",
|
|
@@ -3597,7 +4463,7 @@ program.name("exponential").description(
|
|
|
3597
4463
|
" workspace -> product -> feature -> ticket -> action",
|
|
3598
4464
|
" epics are workspace-scoped and can group tickets and actions across products."
|
|
3599
4465
|
].join("\n")
|
|
3600
|
-
).version(
|
|
4466
|
+
).version(PKG_VERSION).option("--json", "Output as JSON (default when piped)").option("--pretty", "Force pretty-printed output");
|
|
3601
4467
|
program.addCommand(createAuthCommand());
|
|
3602
4468
|
program.addCommand(createActionsCommand());
|
|
3603
4469
|
program.addCommand(createContactsCommand());
|
|
@@ -3612,5 +4478,6 @@ program.addCommand(createProjectsCommand());
|
|
|
3612
4478
|
program.addCommand(createSearchCommand());
|
|
3613
4479
|
program.addCommand(createTicketsCommand());
|
|
3614
4480
|
program.addCommand(createWorkspacesCommand());
|
|
4481
|
+
program.addCommand(createGoalsCommand());
|
|
3615
4482
|
program.parse();
|
|
3616
4483
|
//# sourceMappingURL=index.js.map
|