exponential-cli 1.14.0 → 1.16.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/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // src/index.ts
4
- import { Command as Command23 } from "commander";
3
+ // src/program.ts
4
+ import { Command as Command25 } from "commander";
5
5
 
6
6
  // src/commands/auth.ts
7
7
  import { Command } from "commander";
@@ -125,7 +125,7 @@ function handleError(error, json = false) {
125
125
  // src/commands/auth.ts
126
126
  function createAuthCommand() {
127
127
  const auth = new Command("auth").description("Authentication commands");
128
- auth.command("login").description("Configure CLI with your API token").requiredOption("--token <token>", "JWT token from /tokens page").requiredOption("--api-url <url>", "API URL (e.g., https://app.exponential.so)").action(async (options) => {
128
+ auth.command("login").description("Configure CLI with your API token").requiredOption("--token <token>", "JWT token from /settings/api-keys").requiredOption("--api-url <url>", "API URL (e.g., https://www.exponential.im)").action(async (options) => {
129
129
  try {
130
130
  let apiUrl = options.apiUrl.replace(/\/+$/, "");
131
131
  if (!apiUrl.startsWith("http://localhost") && !apiUrl.startsWith("https://")) {
@@ -209,6 +209,33 @@ function createAuthCommand() {
209
209
  // src/commands/actions.ts
210
210
  import { Command as Command2 } from "commander";
211
211
 
212
+ // src/utils/resolve.ts
213
+ async function resolveWorkspaceId(client, slugOrId) {
214
+ return (await resolveWorkspace(client, slugOrId)).id;
215
+ }
216
+ async function resolveWorkspace(client, slugOrId) {
217
+ const config = getConfig();
218
+ const candidate = slugOrId ?? config.defaultWorkspaceSlug ?? config.defaultWorkspaceId;
219
+ if (!candidate) {
220
+ throw new Error(
221
+ "No workspace specified and no default workspace set. Pass --workspace <slug> or run `exponential workspaces set-default <slug>`."
222
+ );
223
+ }
224
+ const workspaces = await client.workspaces.list();
225
+ const byId = workspaces.find((w) => w.id === candidate);
226
+ if (byId) return byId;
227
+ const bySlug = workspaces.find((w) => w.slug === candidate);
228
+ if (bySlug) return bySlug;
229
+ const known = workspaces.map((w) => w.slug).join(", ");
230
+ throw new Error(
231
+ `Workspace "${candidate}" not found. Available: ${known || "(none)"}`
232
+ );
233
+ }
234
+ async function resolveProductId(client, workspaceId, slugOrId) {
235
+ const product = await client.products.resolve(workspaceId, slugOrId);
236
+ return product.id;
237
+ }
238
+
212
239
  // src/utils/output.ts
213
240
  import chalk3 from "chalk";
214
241
  function shouldUseJson(forceJson, forcePretty) {
@@ -236,10 +263,12 @@ function transformAction(action) {
236
263
  slug: action.workspace.slug,
237
264
  name: action.workspace.name
238
265
  } : null,
266
+ // `action.upsertBySource` returns assignees as bare `{ userId }` rows;
267
+ // every other procedure nests the user. Accept both.
239
268
  assignees: action.assignees?.map((a) => ({
240
- id: a.user.id,
241
- name: a.user.name,
242
- email: a.user.email
269
+ id: a.user?.id ?? a.userId ?? "",
270
+ name: a.user?.name ?? null,
271
+ email: a.user?.email ?? null
243
272
  })) ?? [],
244
273
  createdAt: action.createdAt?.toISOString() ?? (/* @__PURE__ */ new Date()).toISOString(),
245
274
  completedAt: action.completedAt?.toISOString() ?? null
@@ -271,12 +300,15 @@ function outputActionJson(action) {
271
300
  const output = transformAction(action);
272
301
  console.log(JSON.stringify(output, null, 2));
273
302
  }
303
+ function outputActionUpsertJson(result) {
304
+ console.log(JSON.stringify({ outcome: result.outcome, action: transformAction(result.action) }, null, 2));
305
+ }
274
306
  function outputActionPretty(action) {
275
307
  const statusColor = getKanbanStatusColor(action.kanbanStatus);
276
- const statusBadge = action.kanbanStatus ? chalk3[statusColor](`[${action.kanbanStatus}]`) : chalk3.gray("[NO STATUS]");
308
+ const statusBadge2 = action.kanbanStatus ? chalk3[statusColor](`[${action.kanbanStatus}]`) : chalk3.gray("[NO STATUS]");
277
309
  console.log(chalk3.gray("\u2500".repeat(50)));
278
310
  console.log(`
279
- ${statusBadge} ${chalk3.bold(action.name)}`);
311
+ ${statusBadge2} ${chalk3.bold(action.name)}`);
280
312
  console.log(chalk3.gray(` ID: ${action.id}`));
281
313
  if (action.project) {
282
314
  console.log(` ${chalk3.cyan("Project:")} ${action.project.name}`);
@@ -309,9 +341,9 @@ Actions (${actions.length} total)`));
309
341
  console.log(chalk3.gray("\u2500".repeat(50)));
310
342
  for (const action of actions) {
311
343
  const statusColor = getKanbanStatusColor(action.kanbanStatus);
312
- const statusBadge = action.kanbanStatus ? chalk3[statusColor](`[${action.kanbanStatus}]`) : chalk3.gray("[NO STATUS]");
344
+ const statusBadge2 = action.kanbanStatus ? chalk3[statusColor](`[${action.kanbanStatus}]`) : chalk3.gray("[NO STATUS]");
313
345
  console.log(`
314
- ${statusBadge} ${chalk3.bold(action.name)}`);
346
+ ${statusBadge2} ${chalk3.bold(action.name)}`);
315
347
  console.log(chalk3.gray(` ID: ${action.id}`));
316
348
  if (action.project) {
317
349
  console.log(` ${chalk3.cyan("Project:")} ${action.project.name}`);
@@ -930,9 +962,9 @@ function outputFeaturesPretty(features) {
930
962
  Features (${features.length} total)`));
931
963
  console.log(chalk3.gray("\u2500".repeat(50)));
932
964
  for (const f of features) {
933
- const statusBadge = chalk3[getFeatureStatusColor(f.status)](`[${f.status}]`);
965
+ const statusBadge2 = chalk3[getFeatureStatusColor(f.status)](`[${f.status}]`);
934
966
  const ticketCount = f._count?.tickets != null ? chalk3.gray(` \u2014 ${f._count.tickets} tickets`) : "";
935
- console.log(` ${statusBadge} ${chalk3.bold(f.name)}${ticketCount}`);
967
+ console.log(` ${statusBadge2} ${chalk3.bold(f.name)}${ticketCount}`);
936
968
  console.log(chalk3.gray(` ID: ${f.id}`));
937
969
  if (f.keyResultLinks?.length) {
938
970
  const titles = f.keyResultLinks.map((l) => l.keyResult.title).join("; ");
@@ -1147,10 +1179,10 @@ Tickets (${tickets.length} total)`));
1147
1179
  console.log(chalk3.gray("\u2500".repeat(50)));
1148
1180
  for (const t of tickets) {
1149
1181
  const id = t.shortId ?? (t.number != null ? `#${t.number}` : "?");
1150
- const statusBadge = chalk3[getTicketStatusColor(t.status)](`[${t.status}]`);
1182
+ const statusBadge2 = chalk3[getTicketStatusColor(t.status)](`[${t.status}]`);
1151
1183
  const typeBadge = chalk3.gray(`[${t.type}]`);
1152
1184
  const blocked = t.isBlocked ? chalk3.red(" BLOCKED") : "";
1153
- console.log(` ${statusBadge} ${typeBadge} ${chalk3.bold(id)} ${t.title}${blocked}`);
1185
+ console.log(` ${statusBadge2} ${typeBadge} ${chalk3.bold(id)} ${t.title}${blocked}`);
1154
1186
  console.log(chalk3.gray(` ID: ${t.id}`));
1155
1187
  }
1156
1188
  console.log();
@@ -1270,11 +1302,11 @@ function outputEpicsPretty(epics) {
1270
1302
  Epics (${epics.length} total)`));
1271
1303
  console.log(chalk3.gray("\u2500".repeat(50)));
1272
1304
  for (const e of epics) {
1273
- const statusBadge = chalk3[getEpicStatusColor(e.status)](`[${e.status}]`);
1305
+ const statusBadge2 = chalk3[getEpicStatusColor(e.status)](`[${e.status}]`);
1274
1306
  const counts = e._count ? chalk3.gray(
1275
1307
  ` \u2014 ${e._count.tickets ?? 0} tickets, ${e._count.actions ?? 0} actions`
1276
1308
  ) : "";
1277
- console.log(` ${statusBadge} ${chalk3.bold(e.name)}${counts}`);
1309
+ console.log(` ${statusBadge2} ${chalk3.bold(e.name)}${counts}`);
1278
1310
  console.log(chalk3.gray(` ID: ${e.id}`));
1279
1311
  }
1280
1312
  console.log();
@@ -1509,6 +1541,242 @@ Meetings (${meetings.length} total)`));
1509
1541
  }
1510
1542
  console.log();
1511
1543
  }
1544
+ function getDecisionStatusColor(status) {
1545
+ switch (status) {
1546
+ case "OPEN":
1547
+ return "yellow";
1548
+ case "PROPOSED":
1549
+ return "blue";
1550
+ case "ACCEPTED":
1551
+ return "green";
1552
+ case "DEPRECATED":
1553
+ return "red";
1554
+ default:
1555
+ return "gray";
1556
+ }
1557
+ }
1558
+ function transformDecision(d) {
1559
+ return {
1560
+ id: d.id,
1561
+ workspaceId: d.workspaceId,
1562
+ number: d.number,
1563
+ label: d.label,
1564
+ statement: d.statement,
1565
+ body: d.body,
1566
+ status: d.status,
1567
+ isOpenQuestion: d.status === "OPEN",
1568
+ reviewState: d.reviewState,
1569
+ source: d.source,
1570
+ decidedAt: d.decidedAt,
1571
+ ownerId: d.ownerId,
1572
+ createdById: d.createdById,
1573
+ confirmedById: d.confirmedById,
1574
+ confirmedAt: d.confirmedAt,
1575
+ transcriptionSessionId: d.transcriptionSessionId,
1576
+ occurrenceId: d.occurrenceId,
1577
+ productId: d.productId,
1578
+ projectId: d.projectId,
1579
+ goalId: d.goalId,
1580
+ keyResultId: d.keyResultId,
1581
+ supersededById: d.supersededById,
1582
+ adrDocumentId: d.adrDocumentId,
1583
+ evidence: d.evidence ?? [],
1584
+ createdAt: d.createdAt,
1585
+ updatedAt: d.updatedAt,
1586
+ owner: d.owner ?? null,
1587
+ deciders: d.deciders ?? [],
1588
+ meeting: d.transcriptionSession ?? null,
1589
+ product: d.product ?? null,
1590
+ project: d.project ?? null,
1591
+ goal: d.goal ?? null,
1592
+ keyResult: d.keyResult ?? null,
1593
+ supersededBy: d.supersededBy ?? null,
1594
+ supersedes: d.supersedes ?? [],
1595
+ links: d.links ?? [],
1596
+ canEdit: d.canEdit ?? null
1597
+ };
1598
+ }
1599
+ function transformDecisionListRow(d) {
1600
+ return {
1601
+ id: d.id,
1602
+ workspaceId: d.workspaceId,
1603
+ number: d.number,
1604
+ label: d.label,
1605
+ statement: d.statement,
1606
+ status: d.status,
1607
+ isOpenQuestion: d.status === "OPEN",
1608
+ reviewState: d.reviewState ?? null,
1609
+ source: d.source,
1610
+ decidedAt: d.decidedAt,
1611
+ updatedAt: d.updatedAt,
1612
+ transcriptionSessionId: d.transcriptionSessionId,
1613
+ occurrenceId: d.occurrenceId,
1614
+ productId: d.productId,
1615
+ projectId: d.projectId,
1616
+ supersededById: d.supersededById,
1617
+ evidenceCount: d.evidenceCount,
1618
+ linkCount: d._count?.links ?? 0,
1619
+ deciderCount: d._count?.deciders ?? 0,
1620
+ product: d.product ?? null,
1621
+ project: d.project ?? null,
1622
+ meeting: d.transcriptionSession ?? null,
1623
+ supersededBy: d.supersededBy ?? null
1624
+ };
1625
+ }
1626
+ function outputDecisionJson(decision) {
1627
+ console.log(JSON.stringify(transformDecision(decision), null, 2));
1628
+ }
1629
+ function outputDecisionPretty(decision) {
1630
+ const color = getDecisionStatusColor(decision.status);
1631
+ const kind = decision.status === "OPEN" ? chalk3.yellow(" [open question]") : "";
1632
+ console.log(chalk3.gray("\u2500".repeat(50)));
1633
+ console.log(
1634
+ `
1635
+ ${chalk3.gray(decision.label)} ${chalk3.bold(decision.statement)}${kind}`
1636
+ );
1637
+ console.log(chalk3.gray(` ID: ${decision.id}`));
1638
+ console.log(` ${chalk3.gray("Status:")} ${chalk3[color](decision.status)}`);
1639
+ console.log(` ${chalk3.gray("Source:")} ${decision.source}`);
1640
+ if (decision.reviewState && decision.reviewState !== "CONFIRMED") {
1641
+ console.log(` ${chalk3.yellow(decision.reviewState)}`);
1642
+ }
1643
+ if (decision.decidedAt) {
1644
+ console.log(
1645
+ ` ${chalk3.gray("Decided:")} ${new Date(decision.decidedAt).toLocaleString()}`
1646
+ );
1647
+ }
1648
+ if (decision.transcriptionSession) {
1649
+ console.log(
1650
+ ` ${chalk3.gray("Meeting:")} ${decision.transcriptionSession.title ?? "Untitled"} (${decision.transcriptionSession.id})`
1651
+ );
1652
+ }
1653
+ if (decision.product) {
1654
+ console.log(` ${chalk3.gray("Product:")} ${decision.product.name}`);
1655
+ }
1656
+ if (decision.project) {
1657
+ console.log(` ${chalk3.gray("Project:")} ${decision.project.name}`);
1658
+ }
1659
+ if (decision.owner) {
1660
+ console.log(` ${chalk3.gray("Owner:")} ${decision.owner.name ?? decision.owner.id}`);
1661
+ }
1662
+ if (decision.deciders && decision.deciders.length > 0) {
1663
+ const people = decision.deciders.map((d) => d.email ? `${d.name} <${d.email}>` : d.name).join(", ");
1664
+ console.log(` ${chalk3.gray("Deciders:")} ${people}`);
1665
+ }
1666
+ if (decision.supersededBy) {
1667
+ console.log(
1668
+ ` ${chalk3.gray("Superseded by:")} D-${decision.supersededBy.number} ${decision.supersededBy.statement}`
1669
+ );
1670
+ }
1671
+ if (decision.supersedes && decision.supersedes.length > 0) {
1672
+ for (const prior of decision.supersedes) {
1673
+ console.log(
1674
+ ` ${chalk3.gray("Supersedes:")} D-${prior.number} ${prior.statement}`
1675
+ );
1676
+ }
1677
+ }
1678
+ if (decision.links && decision.links.length > 0) {
1679
+ console.log(` ${chalk3.gray("Implemented by:")}`);
1680
+ for (const link of decision.links) {
1681
+ const target = link.ticket ? `${link.ticket.shortId ?? `#${link.ticket.number}`} ${link.ticket.title}` : link.feature ? `feature: ${link.feature.name}` : "(unknown)";
1682
+ console.log(chalk3.gray(` ${target} [link ${link.id}]`));
1683
+ }
1684
+ }
1685
+ if (decision.evidence && decision.evidence.length > 0) {
1686
+ console.log(`
1687
+ ${chalk3.bold("Evidence")}`);
1688
+ for (const turn of decision.evidence) {
1689
+ const who = turn.speaker ? `${turn.speaker}: ` : "";
1690
+ console.log(chalk3.gray(` [${turn.turnIndex}] ${who}${turn.text}`));
1691
+ }
1692
+ }
1693
+ if (decision.body) {
1694
+ console.log(`
1695
+ ${decision.body}`);
1696
+ }
1697
+ console.log();
1698
+ }
1699
+ function outputDecisionsJson(decisions, extra = {}) {
1700
+ console.log(
1701
+ JSON.stringify(
1702
+ {
1703
+ decisions: decisions.map(transformDecisionListRow),
1704
+ total: decisions.length,
1705
+ ...extra
1706
+ },
1707
+ null,
1708
+ 2
1709
+ )
1710
+ );
1711
+ }
1712
+ function outputDecisionsPretty(decisions) {
1713
+ if (decisions.length === 0) {
1714
+ console.log(chalk3.gray("No decisions found."));
1715
+ return;
1716
+ }
1717
+ const openCount = decisions.filter((d) => d.status === "OPEN").length;
1718
+ console.log(
1719
+ chalk3.bold(
1720
+ `
1721
+ Decisions (${decisions.length} total, ${openCount} open question${openCount === 1 ? "" : "s"})`
1722
+ )
1723
+ );
1724
+ console.log(chalk3.gray("\u2500".repeat(50)));
1725
+ for (const d of decisions) {
1726
+ const color = getDecisionStatusColor(d.status);
1727
+ const draft = d.reviewState && d.reviewState !== "CONFIRMED" ? chalk3.yellow(` [${d.reviewState.toLowerCase()}]`) : "";
1728
+ console.log(
1729
+ ` ${chalk3.gray(d.label)} ${chalk3.bold(d.statement)}${draft} ${chalk3[color](`[${d.status}]`)}`
1730
+ );
1731
+ console.log(chalk3.gray(` ID: ${d.id}`));
1732
+ const context = [
1733
+ d.product ? `Product: ${d.product.name}` : null,
1734
+ d.transcriptionSession ? `Meeting: ${d.transcriptionSession.title ?? d.transcriptionSession.id}` : null,
1735
+ d.decidedAt ? `Decided ${new Date(d.decidedAt).toLocaleDateString()}` : null,
1736
+ d.evidenceCount > 0 ? `${d.evidenceCount} evidence` : null,
1737
+ d._count?.links ? `${d._count.links} linked` : null
1738
+ ].filter(Boolean);
1739
+ if (context.length > 0) {
1740
+ console.log(chalk3.gray(` ${context.join(" \xB7 ")}`));
1741
+ }
1742
+ }
1743
+ console.log();
1744
+ }
1745
+ function outputDecisionBatchJson(results) {
1746
+ console.log(
1747
+ JSON.stringify(
1748
+ {
1749
+ results: results.map((r) => ({
1750
+ index: r.index,
1751
+ success: r.success,
1752
+ statement: r.statement ?? r.decision?.statement ?? null,
1753
+ decision: r.decision ? transformDecision(r.decision) : null,
1754
+ error: r.error ?? null
1755
+ })),
1756
+ total: results.length,
1757
+ succeeded: results.filter((r) => r.success).length,
1758
+ failed: results.filter((r) => !r.success).length
1759
+ },
1760
+ null,
1761
+ 2
1762
+ )
1763
+ );
1764
+ }
1765
+ function outputDecisionBatchPretty(results) {
1766
+ for (const r of results) {
1767
+ const statement = r.statement ?? r.decision?.statement ?? "";
1768
+ if (r.success) {
1769
+ console.log(
1770
+ ` ${chalk3.green("\u2713")} #${r.index}: ${chalk3.gray(r.decision?.label ?? "")} ${statement}`
1771
+ );
1772
+ } else {
1773
+ console.log(` ${chalk3.red("\u2717")} #${r.index}: ${statement} \u2014 ${r.error ?? "failed"}`);
1774
+ }
1775
+ }
1776
+ const ok = results.filter((r) => r.success).length;
1777
+ console.log(chalk3.bold(`
1778
+ ${ok}/${results.length} decisions logged`));
1779
+ }
1512
1780
  function outputRequirementJson(requirement) {
1513
1781
  console.log(JSON.stringify(requirement, null, 2));
1514
1782
  }
@@ -1990,6 +2258,134 @@ ${chalk3.bold(project.name)}`);
1990
2258
  }
1991
2259
  console.log();
1992
2260
  }
2261
+ function formatMinutes(totalMins) {
2262
+ if (totalMins <= 0) return "0m";
2263
+ const h = Math.floor(totalMins / 60);
2264
+ const m = totalMins % 60;
2265
+ if (h === 0) return `${m}m`;
2266
+ if (m === 0) return `${h}h`;
2267
+ return `${h}h ${m}m`;
2268
+ }
2269
+ function entryMinutes(entry) {
2270
+ const end = entry.endedAt ? new Date(entry.endedAt) : /* @__PURE__ */ new Date();
2271
+ const ms = end.getTime() - new Date(entry.startedAt).getTime();
2272
+ return ms <= 0 ? 0 : Math.round(ms / 6e4);
2273
+ }
2274
+ function clock(date) {
2275
+ const d = new Date(date);
2276
+ return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
2277
+ }
2278
+ function transformTimeEntry(entry) {
2279
+ return {
2280
+ id: entry.id,
2281
+ userId: entry.userId,
2282
+ actionId: entry.actionId,
2283
+ actionName: entry.action?.name ?? null,
2284
+ projectId: entry.action?.projectId ?? null,
2285
+ workspaceId: entry.workspaceId,
2286
+ startedAt: entry.startedAt,
2287
+ endedAt: entry.endedAt,
2288
+ minutes: entryMinutes(entry),
2289
+ status: entry.status,
2290
+ source: entry.source,
2291
+ sourceRef: entry.sourceRef,
2292
+ note: entry.note,
2293
+ createdByAgentId: entry.createdByAgentId
2294
+ };
2295
+ }
2296
+ function statusBadge(status) {
2297
+ return status === "PROPOSED" ? chalk3.yellow("[PROPOSED]") : chalk3.green("[CONFIRMED]");
2298
+ }
2299
+ function outputTimeLogJson(result) {
2300
+ console.log(
2301
+ JSON.stringify({ outcome: result.outcome, entry: transformTimeEntry(result.entry) }, null, 2)
2302
+ );
2303
+ }
2304
+ function outputTimeLogPretty(result) {
2305
+ const e = result.entry;
2306
+ const verb = { created: "Logged", updated: "Updated", left: "Left untouched (already confirmed)" }[result.outcome];
2307
+ console.log(
2308
+ `${chalk3.green("\u2713")} ${verb}: ${clock(e.startedAt)}\u2013${e.endedAt ? clock(e.endedAt) : "now"} ${chalk3.bold(formatMinutes(entryMinutes(e)))} ${statusBadge(e.status)} ${e.action?.name ?? e.actionId}`
2309
+ );
2310
+ if (e.note) console.log(chalk3.gray(` ${e.note}`));
2311
+ console.log(chalk3.gray(` ID: ${e.id}${e.sourceRef ? ` ref: ${e.sourceRef}` : ""} owner: ${e.userId}`));
2312
+ }
2313
+ function outputTimeEntriesJson(entries, date) {
2314
+ const rows = entries.map(transformTimeEntry);
2315
+ console.log(
2316
+ JSON.stringify(
2317
+ {
2318
+ date,
2319
+ entries: rows,
2320
+ total: rows.length,
2321
+ totalMinutes: rows.reduce((acc, r) => acc + r.minutes, 0),
2322
+ proposed: rows.filter((r) => r.status === "PROPOSED").length
2323
+ },
2324
+ null,
2325
+ 2
2326
+ )
2327
+ );
2328
+ }
2329
+ function outputTimeEntriesPretty(entries, date) {
2330
+ if (entries.length === 0) {
2331
+ console.log(chalk3.gray(`No time entries on ${date}.`));
2332
+ return;
2333
+ }
2334
+ console.log(chalk3.bold(`
2335
+ Time on ${date}
2336
+ `));
2337
+ let total = 0;
2338
+ for (const e of entries) {
2339
+ const mins = entryMinutes(e);
2340
+ total += mins;
2341
+ const range = `${clock(e.startedAt)}\u2013${e.endedAt ? clock(e.endedAt) : "now"}`;
2342
+ console.log(
2343
+ ` ${range} ${chalk3.bold(formatMinutes(mins).padStart(6))} ${statusBadge(e.status)} ${e.action?.name ?? e.actionId}${e.note ? chalk3.gray(` \xB7 ${e.note}`) : ""}`
2344
+ );
2345
+ console.log(chalk3.gray(` ${e.id} ${e.source}${e.sourceRef ? ` ${e.sourceRef}` : ""}`));
2346
+ }
2347
+ const proposed = entries.filter((e) => e.status === "PROPOSED").length;
2348
+ console.log(
2349
+ chalk3.bold(`
2350
+ ${formatMinutes(total)} across ${entries.length} entries`) + (proposed > 0 ? chalk3.yellow(`, ${proposed} proposed`) : "")
2351
+ );
2352
+ }
2353
+ function outputTimeBatchJson(results) {
2354
+ console.log(
2355
+ JSON.stringify(
2356
+ {
2357
+ results: results.map((r) => ({
2358
+ index: r.index,
2359
+ success: r.success,
2360
+ outcome: r.outcome ?? null,
2361
+ sourceRef: r.sourceRef ?? r.entry?.sourceRef ?? null,
2362
+ entry: r.entry ? transformTimeEntry(r.entry) : null,
2363
+ error: r.error ?? null
2364
+ })),
2365
+ total: results.length,
2366
+ succeeded: results.filter((r) => r.success).length,
2367
+ failed: results.filter((r) => !r.success).length
2368
+ },
2369
+ null,
2370
+ 2
2371
+ )
2372
+ );
2373
+ }
2374
+ function outputTimeBatchPretty(results) {
2375
+ for (const r of results) {
2376
+ const ref = r.sourceRef ?? r.entry?.sourceRef ?? "";
2377
+ if (r.success && r.entry) {
2378
+ console.log(
2379
+ ` ${chalk3.green("\u2713")} #${r.index}: ${chalk3.gray(r.outcome ?? "")} ${clock(r.entry.startedAt)}\u2013${r.entry.endedAt ? clock(r.entry.endedAt) : "now"} ${r.entry.action?.name ?? r.entry.actionId}${ref ? chalk3.gray(` ${ref}`) : ""}`
2380
+ );
2381
+ } else {
2382
+ console.log(` ${chalk3.red("\u2717")} #${r.index}: ${ref} \u2014 ${r.error ?? "failed"}`);
2383
+ }
2384
+ }
2385
+ const ok = results.filter((r) => r.success).length;
2386
+ console.log(chalk3.bold(`
2387
+ ${ok}/${results.length} entries logged`));
2388
+ }
1993
2389
 
1994
2390
  // src/commands/actions.ts
1995
2391
  var VALID_PRIORITIES = [
@@ -2242,6 +2638,35 @@ function createActionsCommand() {
2242
2638
  handleError(error, useJson);
2243
2639
  }
2244
2640
  });
2641
+ actions.command("upsert").description(
2642
+ "One Action per external source: find by (--workspace, --source-type, --source-id), refresh its title and links, else create it. The Daily worklog uses --source-type claude-session --source-id <conversation id>."
2643
+ ).requiredOption("--source-type <type>", "e.g. claude-session").requiredOption("--source-id <id>", "The source's own id; the same pair always maps to the same Action").requiredOption("-t, --title <title>", "Action title (refreshed on every call)").option("-w, --workspace <slug|id>", "Workspace (defaults to your default workspace)").option("-d, --description <text>", "Description").option("--project <id>", "Link to a project CUID in the same workspace").option("--ticket <id>", "Link to a ticket CUID whose product is in the same workspace").action(
2644
+ async (options, cmd) => {
2645
+ const globalOpts = cmd.optsWithGlobals();
2646
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
2647
+ try {
2648
+ const client = getClient();
2649
+ const workspaceId = await resolveWorkspaceId(client, options.workspace);
2650
+ const result = await client.actions.upsertBySource({
2651
+ sourceType: options.sourceType,
2652
+ sourceId: options.sourceId,
2653
+ name: options.title,
2654
+ workspaceId,
2655
+ description: options.description,
2656
+ projectId: options.project,
2657
+ ticketId: options.ticket
2658
+ });
2659
+ if (useJson) {
2660
+ outputActionUpsertJson(result);
2661
+ } else {
2662
+ console.log(result.outcome === "created" ? "Created action:" : "Updated action:");
2663
+ outputActionPretty(result.action);
2664
+ }
2665
+ } catch (error) {
2666
+ handleError(error);
2667
+ }
2668
+ }
2669
+ );
2245
2670
  const comment = new Command2("comment").description("Manage comments on an action");
2246
2671
  comment.command("list").description("List comments on an action").requiredOption("--id <id>", "Action ID").action(async (options, cmd) => {
2247
2672
  const globalOpts = cmd.optsWithGlobals();
@@ -2398,113 +2823,749 @@ function createContactsCommand() {
2398
2823
  handleError(error, useJson);
2399
2824
  }
2400
2825
  });
2401
- contacts.command("update").description("Update an existing contact").requiredOption("--id <id>", "Contact ID").option("--first-name <name>", "First name").option("--last-name <name>", "Last name").option("--email <email>", 'Email (use "null" to clear)').option("--phone <phone>", 'Phone (use "null" to clear)').option("--linkedin <url>", 'LinkedIn URL (use "null" to clear)').option("--telegram <handle>", 'Telegram handle (use "null" to clear)').option("--twitter <handle>", 'Twitter/X handle (use "null" to clear)').option("--github <handle>", 'GitHub username (use "null" to clear)').option("--bluesky <handle>", 'Bluesky handle (use "null" to clear)').option("--about <text>", "About / bio").option("--profile-type <type>", "Profile type").option("--skills <skills>", "Skills (comma-separated)").option("--tags <tags>", "Tags (comma-separated)").option("--organization <id>", 'Organization ID (use "null" to clear)').option("--organization-name <name>", "Organization name (find-or-create; ignored if --organization is set)").action(async (options, cmd) => {
2826
+ contacts.command("update").description("Update an existing contact").requiredOption("--id <id>", "Contact ID").option("--first-name <name>", "First name").option("--last-name <name>", "Last name").option("--email <email>", 'Email (use "null" to clear)').option("--phone <phone>", 'Phone (use "null" to clear)').option("--linkedin <url>", 'LinkedIn URL (use "null" to clear)').option("--telegram <handle>", 'Telegram handle (use "null" to clear)').option("--twitter <handle>", 'Twitter/X handle (use "null" to clear)').option("--github <handle>", 'GitHub username (use "null" to clear)').option("--bluesky <handle>", 'Bluesky handle (use "null" to clear)').option("--about <text>", "About / bio").option("--profile-type <type>", "Profile type").option("--skills <skills>", "Skills (comma-separated)").option("--tags <tags>", "Tags (comma-separated)").option("--organization <id>", 'Organization ID (use "null" to clear)').option("--organization-name <name>", "Organization name (find-or-create; ignored if --organization is set)").action(async (options, cmd) => {
2827
+ const globalOpts = cmd.optsWithGlobals();
2828
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
2829
+ const nullable = (v) => v === "null" ? null : v;
2830
+ try {
2831
+ const client = getClient();
2832
+ const contact = await client.contacts.update({
2833
+ id: options.id,
2834
+ firstName: options.firstName,
2835
+ lastName: options.lastName,
2836
+ email: nullable(options.email),
2837
+ phone: nullable(options.phone),
2838
+ linkedIn: nullable(options.linkedin),
2839
+ telegram: nullable(options.telegram),
2840
+ twitter: nullable(options.twitter),
2841
+ github: nullable(options.github),
2842
+ bluesky: nullable(options.bluesky),
2843
+ about: options.about,
2844
+ profileType: options.profileType,
2845
+ skills: options.skills?.split(",").map((s) => s.trim()),
2846
+ tags: options.tags?.split(",").map((t) => t.trim()),
2847
+ organizationId: nullable(options.organization),
2848
+ organizationName: options.organizationName
2849
+ });
2850
+ if (useJson) {
2851
+ outputContactJson(contact);
2852
+ } else {
2853
+ console.log("\n\u2713 Contact updated successfully");
2854
+ outputContactPretty(contact);
2855
+ }
2856
+ } catch (error) {
2857
+ handleError(error, useJson);
2858
+ }
2859
+ });
2860
+ contacts.command("delete").description("Delete a contact").argument("<id>", "Contact ID").action(async (id, _options, cmd) => {
2861
+ const globalOpts = cmd.optsWithGlobals();
2862
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
2863
+ try {
2864
+ const client = getClient();
2865
+ await client.contacts.delete(id);
2866
+ if (useJson) {
2867
+ console.log(JSON.stringify({ deleted: true, id }, null, 2));
2868
+ } else {
2869
+ console.log(`
2870
+ \u2713 Contact ${id} deleted`);
2871
+ }
2872
+ } catch (error) {
2873
+ handleError(error, useJson);
2874
+ }
2875
+ });
2876
+ contacts.command("add-interaction").description("Add an interaction to a contact").requiredOption("--contact <id>", "Contact ID").requiredOption("--type <type>", `Interaction type (${VALID_INTERACTION_TYPES.join(", ")})`).requiredOption("--direction <dir>", `Direction (${VALID_INTERACTION_DIRECTIONS.join(", ")})`).option("--subject <text>", "Subject line").option("--notes <text>", "Notes").action(async (options, cmd) => {
2877
+ const globalOpts = cmd.optsWithGlobals();
2878
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
2879
+ try {
2880
+ if (!VALID_INTERACTION_TYPES.includes(options.type)) {
2881
+ throw new Error(`Invalid interaction type "${options.type}". Valid: ${VALID_INTERACTION_TYPES.join(", ")}`);
2882
+ }
2883
+ if (!VALID_INTERACTION_DIRECTIONS.includes(options.direction)) {
2884
+ throw new Error(`Invalid direction "${options.direction}". Valid: ${VALID_INTERACTION_DIRECTIONS.join(", ")}`);
2885
+ }
2886
+ const client = getClient();
2887
+ const interaction = await client.contacts.addInteraction({
2888
+ contactId: options.contact,
2889
+ type: options.type,
2890
+ direction: options.direction,
2891
+ subject: options.subject,
2892
+ notes: options.notes
2893
+ });
2894
+ if (useJson) {
2895
+ outputInteractionJson(interaction);
2896
+ } else {
2897
+ console.log("\n\u2713 Interaction added");
2898
+ outputInteractionPretty(interaction);
2899
+ }
2900
+ } catch (error) {
2901
+ handleError(error, useJson);
2902
+ }
2903
+ });
2904
+ contacts.command("enrich").description("Queue a web-search enrichment job for an existing contact (runs regardless of the workspace auto-enrich setting)").argument("<id>", "Contact ID").action(async (id, _options, cmd) => {
2905
+ const globalOpts = cmd.optsWithGlobals();
2906
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
2907
+ try {
2908
+ const client = getClient();
2909
+ const result = await client.contacts.enrich(id);
2910
+ if (useJson) {
2911
+ console.log(JSON.stringify(result, null, 2));
2912
+ } else if (result.enqueued) {
2913
+ console.log(`
2914
+ \u2713 Enrichment queued for contact ${id} (job ${result.enrichmentId})`);
2915
+ } else if (result.reason === "already-in-flight") {
2916
+ console.log(`
2917
+ \u2022 Enrichment already in progress for contact ${id} (job ${result.enrichmentId})`);
2918
+ } else {
2919
+ console.log(`
2920
+ \u2022 Not queued for contact ${id}${result.reason ? ` (${result.reason})` : ""}`);
2921
+ }
2922
+ } catch (error) {
2923
+ handleError(error, useJson);
2924
+ }
2925
+ });
2926
+ return contacts;
2927
+ }
2928
+
2929
+ // src/commands/decisions.ts
2930
+ import { Command as Command4 } from "commander";
2931
+ import chalk4 from "chalk";
2932
+
2933
+ // src/utils/input.ts
2934
+ import { readFileSync } from "fs";
2935
+ var stdinConsumed = false;
2936
+ function readText(inline, file) {
2937
+ if (inline !== void 0) return inline;
2938
+ if (file === void 0) return void 0;
2939
+ if (file === "-") {
2940
+ if (stdinConsumed) {
2941
+ throw new Error('stdin ("-") can only back one option per invocation.');
2942
+ }
2943
+ stdinConsumed = true;
2944
+ return readFileSync(0, "utf-8");
2945
+ }
2946
+ return readFileSync(file, "utf-8");
2947
+ }
2948
+ function parseDate(value) {
2949
+ if (value === void 0 || value === "") return void 0;
2950
+ const date = /^\d{4}-\d{2}-\d{2}$/.test(value) ? /* @__PURE__ */ new Date(`${value}T00:00:00`) : new Date(value);
2951
+ if (Number.isNaN(date.getTime())) {
2952
+ throw new Error(
2953
+ `Invalid date: "${value}". Use ISO format, e.g. 2026-08-25 or 2026-08-25T14:00.`
2954
+ );
2955
+ }
2956
+ return date;
2957
+ }
2958
+
2959
+ // src/commands/decisions.ts
2960
+ var STATUSES = [
2961
+ "OPEN",
2962
+ "PROPOSED",
2963
+ "ACCEPTED",
2964
+ "SUPERSEDED",
2965
+ "DEPRECATED"
2966
+ ];
2967
+ var SOURCES = ["MEETING", "MANUAL", "AGENT"];
2968
+ var OPEN_QUESTION_NOTE = `An OPEN QUESTION is a decision with --status OPEN \u2014 there is no separate model, and that is exactly what a meeting's "Open questions" panel shows.`;
2969
+ function parseStatus(value) {
2970
+ const upper = value.toUpperCase();
2971
+ if (!STATUSES.includes(upper)) {
2972
+ throw new Error(
2973
+ `Invalid status "${value}". Use one of: ${STATUSES.join(", ")}. ${OPEN_QUESTION_NOTE}`
2974
+ );
2975
+ }
2976
+ return upper;
2977
+ }
2978
+ function parseSource(value) {
2979
+ const upper = value.toUpperCase();
2980
+ if (!SOURCES.includes(upper)) {
2981
+ throw new Error(
2982
+ `Invalid source "${value}". Use one of: ${SOURCES.join(", ")}.`
2983
+ );
2984
+ }
2985
+ return upper;
2986
+ }
2987
+ function parseDecider(spec) {
2988
+ const angled = /^\s*(.*?)\s*<\s*([^>]+?)\s*>\s*$/.exec(spec);
2989
+ if (angled) {
2990
+ const name = angled[1]?.trim() ?? "";
2991
+ const email = angled[2].trim();
2992
+ return { name: name === "" ? email : name, email };
2993
+ }
2994
+ const bare = spec.trim();
2995
+ if (bare === "") {
2996
+ throw new Error('Empty --decider. Use "Name <email>", a name, or an email.');
2997
+ }
2998
+ if (bare.includes("@") && !bare.includes(" ")) {
2999
+ return { name: bare, email: bare };
3000
+ }
3001
+ return { name: bare };
3002
+ }
3003
+ function parseEvidence(raw) {
3004
+ let parsed;
3005
+ try {
3006
+ parsed = JSON.parse(raw);
3007
+ } catch (error) {
3008
+ throw new Error(
3009
+ `Evidence must be JSON: ${error instanceof Error ? error.message : String(error)}`
3010
+ );
3011
+ }
3012
+ if (!Array.isArray(parsed)) {
3013
+ throw new Error(
3014
+ "Evidence must be a JSON array of {turnIndex, speaker?, startTime?, text} objects."
3015
+ );
3016
+ }
3017
+ return parsed.map((turn, i) => {
3018
+ if (typeof turn !== "object" || turn === null) {
3019
+ throw new Error(`Evidence[${i}] is not an object.`);
3020
+ }
3021
+ const t = turn;
3022
+ if (typeof t.turnIndex !== "number" || typeof t.text !== "string") {
3023
+ throw new Error(
3024
+ `Evidence[${i}] needs a numeric "turnIndex" and a string "text".`
3025
+ );
3026
+ }
3027
+ return {
3028
+ turnIndex: t.turnIndex,
3029
+ speaker: typeof t.speaker === "string" ? t.speaker : null,
3030
+ startTime: typeof t.startTime === "number" ? t.startTime : null,
3031
+ text: t.text
3032
+ };
3033
+ });
3034
+ }
3035
+ async function resolveProductFilter(client, workspaceId, value) {
3036
+ if (value === void 0) return void 0;
3037
+ if (value === "workspace") return "workspace";
3038
+ return await resolveProductId(client, workspaceId, value);
3039
+ }
3040
+ function parsePositiveInt(v, flag) {
3041
+ if (v === void 0) return void 0;
3042
+ const n = Number.parseInt(v, 10);
3043
+ if (Number.isNaN(n) || n < 1) {
3044
+ throw new Error(`${flag} must be a positive integer, got "${v}".`);
3045
+ }
3046
+ return n;
3047
+ }
3048
+ function parseNullableString(v) {
3049
+ if (v === void 0) return void 0;
3050
+ return v === "null" ? null : v;
3051
+ }
3052
+ function parseNullableInt(v) {
3053
+ if (v === void 0) return void 0;
3054
+ if (v === "null") return null;
3055
+ const n = Number.parseInt(v, 10);
3056
+ if (Number.isNaN(n)) throw new Error(`Expected a number, got "${v}".`);
3057
+ return n;
3058
+ }
3059
+ var BATCH_ITEM_KEYS = /* @__PURE__ */ new Set([
3060
+ "statement",
3061
+ "body",
3062
+ "status",
3063
+ "source",
3064
+ "meeting",
3065
+ "transcriptionSessionId",
3066
+ "productId",
3067
+ "projectId",
3068
+ "goalId",
3069
+ "keyResultId",
3070
+ "occurrenceId",
3071
+ "decidedAt",
3072
+ "ownerId",
3073
+ "deciders",
3074
+ "evidence"
3075
+ ]);
3076
+ function parseBatchFile(raw) {
3077
+ let parsed;
3078
+ try {
3079
+ parsed = JSON.parse(raw);
3080
+ } catch (error) {
3081
+ throw new Error(
3082
+ `--from-file must be JSON: ${error instanceof Error ? error.message : String(error)}`
3083
+ );
3084
+ }
3085
+ const items = Array.isArray(parsed) ? parsed : typeof parsed === "object" && parsed !== null && Array.isArray(parsed.decisions) ? parsed.decisions : null;
3086
+ if (!items) {
3087
+ throw new Error(
3088
+ '--from-file must contain a JSON array of decisions, or {"decisions": [...]}.'
3089
+ );
3090
+ }
3091
+ if (items.length === 0) {
3092
+ throw new Error("--from-file contained no decisions.");
3093
+ }
3094
+ return items.map((item, i) => {
3095
+ if (typeof item !== "object" || item === null) {
3096
+ throw new Error(`Decision[${i}] is not an object.`);
3097
+ }
3098
+ const entry = item;
3099
+ if (typeof entry.statement !== "string" || entry.statement.trim() === "") {
3100
+ throw new Error(`Decision[${i}] needs a non-empty "statement".`);
3101
+ }
3102
+ const unknown = Object.keys(entry).filter(
3103
+ (key) => !BATCH_ITEM_KEYS.has(key)
3104
+ );
3105
+ if (unknown.length > 0) {
3106
+ throw new Error(
3107
+ `Decision[${i}] has unknown field(s): ${unknown.join(", ")}. Accepted: ${[...BATCH_ITEM_KEYS].join(", ")}.`
3108
+ );
3109
+ }
3110
+ if (entry.goalId !== void 0 && typeof entry.goalId !== "number") {
3111
+ throw new Error(`Decision[${i}].goalId must be a number, not a string.`);
3112
+ }
3113
+ return entry;
3114
+ });
3115
+ }
3116
+ function batchDeciders(value, index) {
3117
+ if (value === void 0 || value === null) return void 0;
3118
+ if (!Array.isArray(value)) {
3119
+ throw new Error(`Decision[${index}].deciders must be an array.`);
3120
+ }
3121
+ return value.map((d) => {
3122
+ if (typeof d === "string") return parseDecider(d);
3123
+ if (typeof d === "object" && d !== null && typeof d.name === "string") {
3124
+ const obj = d;
3125
+ return {
3126
+ name: obj.name,
3127
+ email: typeof obj.email === "string" ? obj.email : null,
3128
+ userId: typeof obj.userId === "string" ? obj.userId : null
3129
+ };
3130
+ }
3131
+ throw new Error(
3132
+ `Decision[${index}].deciders entries must be "Name <email>" strings or {name, email?} objects.`
3133
+ );
3134
+ });
3135
+ }
3136
+ function createDecisionsCommand() {
3137
+ const decisions = new Command4("decisions").description(
3138
+ [
3139
+ "Log and manage decisions and open questions (the workspace Decision Log).",
3140
+ "",
3141
+ OPEN_QUESTION_NOTE,
3142
+ "",
3143
+ "Statuses: OPEN (an open question) \xB7 PROPOSED \xB7 ACCEPTED \xB7 SUPERSEDED \xB7 DEPRECATED.",
3144
+ 'SUPERSEDED and DEPRECATED are reached with "decisions status", never at creation.'
3145
+ ].join("\n")
3146
+ );
3147
+ decisions.command("list").description(
3148
+ `List decisions, newest decided first. Pass --status OPEN for open questions only. ${OPEN_QUESTION_NOTE}`
3149
+ ).option("--workspace <slug|id>", "Workspace (defaults to your default workspace)").option(
3150
+ "--meeting <id>",
3151
+ "One meeting's decisions and open questions (includes drafts if you can edit it)"
3152
+ ).option(
3153
+ "--status <status>",
3154
+ "Filter by status (repeatable): OPEN, PROPOSED, ACCEPTED, SUPERSEDED, DEPRECATED",
3155
+ (value, previous) => [...previous, parseStatus(value)],
3156
+ []
3157
+ ).option(
3158
+ "--source <source>",
3159
+ "Filter by source (repeatable): MEETING, MANUAL, AGENT",
3160
+ (value, previous) => [...previous, parseSource(value)],
3161
+ []
3162
+ ).option("--product <slug|id>", 'Filter by product, or "workspace" for decisions with no product').option("--include-workspace-wide", "With --product: also include decisions that have no product").option("--project <id>", "Filter by project CUID").option("--search <text>", "Free-text over statement and body").option("--number <n>", "One decision by its workspace sequence number (D-0003 is 3)").option("--limit <n>", "Cap the rows returned (1-500)").option("--adr <id>", "Decisions formalised as this ADR document").action(
3163
+ async (options, cmd) => {
3164
+ const globalOpts = cmd.optsWithGlobals();
3165
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
3166
+ try {
3167
+ const client = getClient();
3168
+ if (options.meeting) {
3169
+ const result = await client.decisions.listForMeeting(options.meeting);
3170
+ let rows2 = result.decisions;
3171
+ if (options.status.length > 0) {
3172
+ rows2 = rows2.filter((d) => options.status.includes(d.status));
3173
+ }
3174
+ if (options.source.length > 0) {
3175
+ rows2 = rows2.filter((d) => options.source.includes(d.source));
3176
+ }
3177
+ if (useJson) {
3178
+ outputDecisionsJson(rows2, {
3179
+ meetingId: options.meeting,
3180
+ workspaceId: result.workspaceId,
3181
+ canLogDecision: result.canLogDecision
3182
+ });
3183
+ } else {
3184
+ outputDecisionsPretty(rows2);
3185
+ if (!result.canLogDecision) {
3186
+ console.log(
3187
+ chalk4.yellow(
3188
+ "You cannot log decisions on this meeting (no edit access, or it has no workspace)."
3189
+ )
3190
+ );
3191
+ }
3192
+ }
3193
+ return;
3194
+ }
3195
+ const workspaceId = await resolveWorkspaceId(client, options.workspace);
3196
+ const rows = options.adr ? await client.decisions.listForAdr(workspaceId, options.adr) : await client.decisions.list({
3197
+ workspaceId,
3198
+ statuses: options.status.length > 0 ? options.status : void 0,
3199
+ sources: options.source.length > 0 ? options.source : void 0,
3200
+ productId: await resolveProductFilter(client, workspaceId, options.product),
3201
+ includeWorkspaceWide: options.includeWorkspaceWide,
3202
+ projectId: options.project,
3203
+ search: options.search,
3204
+ number: parsePositiveInt(options.number, "--number"),
3205
+ limit: parsePositiveInt(options.limit, "--limit")
3206
+ });
3207
+ if (useJson) outputDecisionsJson(rows, { workspaceId });
3208
+ else outputDecisionsPretty(rows);
3209
+ } catch (error) {
3210
+ handleError(error, useJson);
3211
+ }
3212
+ }
3213
+ );
3214
+ decisions.command("get <id>").description("Show one decision in full: body, evidence, deciders, chain and links").option("--workspace <slug|id>", "Workspace (defaults to your default workspace)").action(async (id, options, cmd) => {
3215
+ const globalOpts = cmd.optsWithGlobals();
3216
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
3217
+ try {
3218
+ const client = getClient();
3219
+ const workspaceId = await resolveWorkspaceId(client, options.workspace);
3220
+ const decision = await client.decisions.get(workspaceId, id);
3221
+ if (useJson) outputDecisionJson(decision);
3222
+ else outputDecisionPretty(decision);
3223
+ } catch (error) {
3224
+ handleError(error, useJson);
3225
+ }
3226
+ });
3227
+ decisions.command("create").description(
3228
+ [
3229
+ "Log a decision, or an open question with --status OPEN.",
3230
+ OPEN_QUESTION_NOTE,
3231
+ "Bodies are Markdown: use --body-file <path> (or - for stdin) rather than a shell argument.",
3232
+ "Bulk: --from-file <path|-> takes a JSON array of decisions; the other flags become its defaults."
3233
+ ].join(" ")
3234
+ ).option("-s, --statement <text>", "The decision, or the open question (max 500 chars)").option("-b, --body <markdown>", "Markdown detail: context, alternatives, consequences").option("--body-file <path>", 'Read the body from a file ("-" = stdin)').option("--status <status>", "OPEN (open question), PROPOSED (default) or ACCEPTED").option("--source <source>", "MEETING, MANUAL or AGENT").option("--meeting <id>", "Meeting (transcriptionSessionId) this came out of \u2014 needs edit access to it").option("--workspace <slug|id>", "Workspace (defaults to your default workspace)").option("--product <slug|id>", "Link to a product").option("--project <id>", "Link to a project CUID").option("--goal <id>", "Link to a goal (integer id)").option("--key-result <id>", "Link to a key result CUID").option("--occurrence <id>", "Link to a ceremony occurrence CUID").option("--decided-at <iso>", "When it was decided (e.g. 2026-09-10T14:00)").option("--owner <userId>", "Owner user ID").option(
3235
+ '--decider <"Name <email>">',
3236
+ "Someone who made the decision (repeatable); external people need no account",
3237
+ (value, previous) => [...previous, parseDecider(value)],
3238
+ []
3239
+ ).option(
3240
+ "--evidence-file <path>",
3241
+ 'JSON array of {turnIndex, text} transcript quotes ("-" = stdin). Needs --meeting: the server checks every quote against that transcript and silently drops the ones that do not match.'
3242
+ ).option("--from-file <path>", 'Bulk create from a JSON array of decisions ("-" = stdin)').action(
3243
+ async (options, cmd) => {
3244
+ const globalOpts = cmd.optsWithGlobals();
3245
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
3246
+ try {
3247
+ if (!options.statement && !options.fromFile) {
3248
+ throw new Error(
3249
+ "Pass --statement <text>, or --from-file <path|-> for a bulk create."
3250
+ );
3251
+ }
3252
+ if (options.statement && options.fromFile) {
3253
+ throw new Error(
3254
+ "--statement and --from-file are alternatives: the file carries the statements."
3255
+ );
3256
+ }
3257
+ const client = getClient();
3258
+ const workspaceId = await resolveWorkspaceId(client, options.workspace);
3259
+ const productId = options.product ? await resolveProductId(client, workspaceId, options.product) : void 0;
3260
+ const status = options.status ? parseStatus(options.status) : void 0;
3261
+ const source = options.source ? parseSource(options.source) : void 0;
3262
+ const decidedAt = parseDate(options.decidedAt);
3263
+ const goalId = options.goal ? Number.parseInt(options.goal, 10) : void 0;
3264
+ if (goalId !== void 0 && Number.isNaN(goalId)) {
3265
+ throw new Error(`--goal must be an integer id, got "${options.goal}".`);
3266
+ }
3267
+ const defaults = {
3268
+ workspaceId,
3269
+ status,
3270
+ source,
3271
+ transcriptionSessionId: options.meeting,
3272
+ productId,
3273
+ projectId: options.project,
3274
+ goalId,
3275
+ keyResultId: options.keyResult,
3276
+ occurrenceId: options.occurrence,
3277
+ decidedAt,
3278
+ ownerId: options.owner,
3279
+ deciders: options.decider.length > 0 ? options.decider : void 0
3280
+ };
3281
+ if (options.fromFile) {
3282
+ const perEntryOnly = [
3283
+ options.body !== void 0 ? "--body" : null,
3284
+ options.bodyFile !== void 0 ? "--body-file" : null,
3285
+ options.evidenceFile !== void 0 ? "--evidence-file" : null
3286
+ ].filter((flag) => flag !== null);
3287
+ if (perEntryOnly.length > 0) {
3288
+ throw new Error(
3289
+ `${perEntryOnly.join(", ")} ${perEntryOnly.length === 1 ? "is" : "are"} per-decision content \u2014 put "body" and "evidence" on each entry in --from-file instead.`
3290
+ );
3291
+ }
3292
+ const items = parseBatchFile(readText(void 0, options.fromFile));
3293
+ const results = [];
3294
+ for (const [index, item] of items.entries()) {
3295
+ const statement = String(item.statement);
3296
+ try {
3297
+ const input = {
3298
+ ...defaults,
3299
+ statement,
3300
+ body: typeof item.body === "string" ? item.body : void 0,
3301
+ status: item.status !== void 0 ? parseStatus(String(item.status)) : defaults.status,
3302
+ source: item.source !== void 0 ? parseSource(String(item.source)) : defaults.source,
3303
+ transcriptionSessionId: typeof item.transcriptionSessionId === "string" ? item.transcriptionSessionId : typeof item.meeting === "string" ? item.meeting : defaults.transcriptionSessionId,
3304
+ productId: typeof item.productId === "string" ? item.productId : defaults.productId,
3305
+ projectId: typeof item.projectId === "string" ? item.projectId : defaults.projectId,
3306
+ goalId: typeof item.goalId === "number" ? item.goalId : defaults.goalId,
3307
+ keyResultId: typeof item.keyResultId === "string" ? item.keyResultId : defaults.keyResultId,
3308
+ occurrenceId: typeof item.occurrenceId === "string" ? item.occurrenceId : defaults.occurrenceId,
3309
+ decidedAt: typeof item.decidedAt === "string" ? parseDate(item.decidedAt) : defaults.decidedAt,
3310
+ ownerId: typeof item.ownerId === "string" ? item.ownerId : defaults.ownerId,
3311
+ deciders: batchDeciders(item.deciders, index) ?? defaults.deciders,
3312
+ evidence: item.evidence === void 0 ? void 0 : parseEvidence(JSON.stringify(item.evidence))
3313
+ };
3314
+ if (input.evidence && !input.transcriptionSessionId) {
3315
+ throw new Error(
3316
+ 'Evidence is quotes from a meeting transcript, so this entry needs a "meeting" (or the --meeting flag).'
3317
+ );
3318
+ }
3319
+ const decision2 = await client.decisions.create(input);
3320
+ results.push({ index, success: true, decision: decision2, statement });
3321
+ } catch (error) {
3322
+ results.push({
3323
+ index,
3324
+ success: false,
3325
+ statement,
3326
+ error: error instanceof Error ? error.message : String(error)
3327
+ });
3328
+ }
3329
+ }
3330
+ if (results.some((r) => !r.success)) process.exitCode = 1;
3331
+ if (useJson) outputDecisionBatchJson(results);
3332
+ else outputDecisionBatchPretty(results);
3333
+ return;
3334
+ }
3335
+ const evidenceRaw = readText(void 0, options.evidenceFile);
3336
+ if (evidenceRaw !== void 0 && !options.meeting) {
3337
+ throw new Error(
3338
+ "Evidence is quotes from a meeting transcript, so --evidence-file needs --meeting <id>."
3339
+ );
3340
+ }
3341
+ const decision = await client.decisions.create({
3342
+ ...defaults,
3343
+ statement: options.statement,
3344
+ body: readText(options.body, options.bodyFile),
3345
+ deciders: options.decider.length > 0 ? options.decider : void 0,
3346
+ evidence: evidenceRaw === void 0 ? void 0 : parseEvidence(evidenceRaw)
3347
+ });
3348
+ if (useJson) outputDecisionJson(decision);
3349
+ else {
3350
+ console.log(
3351
+ `
3352
+ \u2713 ${decision.status === "OPEN" ? "Open question" : "Decision"} logged`
3353
+ );
3354
+ outputDecisionPretty(decision);
3355
+ }
3356
+ } catch (error) {
3357
+ handleError(error, useJson);
3358
+ }
3359
+ }
3360
+ );
3361
+ decisions.command("update").description(
3362
+ `Update a decision's content and scope; only the fields you pass are written. Status changes go through "decisions status".`
3363
+ ).requiredOption("--id <id>", "Decision CUID").option("--workspace <slug|id>", "Workspace (defaults to your default workspace)").option("-s, --statement <text>", "New statement").option("-b, --body <markdown>", 'New Markdown body (or "null" to clear)').option("--body-file <path>", 'Read the new body from a file ("-" = stdin)').option("--decided-at <iso>", 'When it was decided (or "null" to clear)').option("--owner <userId>", 'Owner user ID (or "null" to clear)').option("--product <slug|id>", 'Product (or "null" to detach)').option("--project <id>", 'Project CUID (or "null" to detach)').option("--goal <id>", 'Goal integer id (or "null" to detach)').option("--key-result <id>", 'Key result CUID (or "null" to detach)').option("--adr <id>", 'ADR document CUID (or "null" to detach)').action(
3364
+ async (options, cmd) => {
3365
+ const globalOpts = cmd.optsWithGlobals();
3366
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
3367
+ try {
3368
+ const body = options.body === "null" ? null : readText(options.body, options.bodyFile);
3369
+ const patch = {
3370
+ statement: options.statement,
3371
+ body,
3372
+ decidedAt: options.decidedAt === "null" ? null : parseDate(options.decidedAt),
3373
+ ownerId: parseNullableString(options.owner),
3374
+ projectId: parseNullableString(options.project),
3375
+ goalId: parseNullableInt(options.goal),
3376
+ keyResultId: parseNullableString(options.keyResult),
3377
+ adrDocumentId: parseNullableString(options.adr)
3378
+ };
3379
+ if (options.product === void 0 && Object.values(patch).every((v) => v === void 0)) {
3380
+ throw new Error(
3381
+ "Nothing to update. Pass at least one of --statement, --body, --body-file, --decided-at, --owner, --product, --project, --goal, --key-result, --adr."
3382
+ );
3383
+ }
3384
+ const client = getClient();
3385
+ const workspaceId = await resolveWorkspaceId(client, options.workspace);
3386
+ const productId = options.product === void 0 ? void 0 : options.product === "null" ? null : await resolveProductId(client, workspaceId, options.product);
3387
+ const decision = await client.decisions.update({
3388
+ workspaceId,
3389
+ decisionId: options.id,
3390
+ ...patch,
3391
+ productId
3392
+ });
3393
+ if (useJson) outputDecisionJson(decision);
3394
+ else {
3395
+ console.log("\n\u2713 Decision updated");
3396
+ outputDecisionPretty(decision);
3397
+ }
3398
+ } catch (error) {
3399
+ handleError(error, useJson);
3400
+ }
3401
+ }
3402
+ );
3403
+ decisions.command("status").description(
3404
+ "Move a decision through its lifecycle \u2014 including answering an open question by moving it from OPEN to ACCEPTED."
3405
+ ).requiredOption("--id <id>", "Decision CUID").requiredOption(
3406
+ "--status <status>",
3407
+ "OPEN, PROPOSED, ACCEPTED, SUPERSEDED or DEPRECATED"
3408
+ ).option("--superseded-by <id>", "The decision that replaced this one (with --status SUPERSEDED)").option("--workspace <slug|id>", "Workspace (defaults to your default workspace)").action(
3409
+ async (options, cmd) => {
3410
+ const globalOpts = cmd.optsWithGlobals();
3411
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
3412
+ try {
3413
+ const status = parseStatus(options.status);
3414
+ if (status === "SUPERSEDED" && !options.supersededBy) {
3415
+ throw new Error(
3416
+ "--status SUPERSEDED needs --superseded-by <id>: the decision that replaced this one."
3417
+ );
3418
+ }
3419
+ const client = getClient();
3420
+ const workspaceId = await resolveWorkspaceId(client, options.workspace);
3421
+ const decision = await client.decisions.setStatus({
3422
+ workspaceId,
3423
+ decisionId: options.id,
3424
+ status,
3425
+ supersededById: options.supersededBy
3426
+ });
3427
+ if (useJson) outputDecisionJson(decision);
3428
+ else {
3429
+ console.log(`
3430
+ \u2713 Decision is now ${decision.status}`);
3431
+ outputDecisionPretty(decision);
3432
+ }
3433
+ } catch (error) {
3434
+ handleError(error, useJson);
3435
+ }
3436
+ }
3437
+ );
3438
+ decisions.command("link").description("Record what implements a decision: a ticket or a feature. Idempotent.").requiredOption("--id <id>", "Decision CUID").option("--ticket <id>", "Ticket CUID").option("--feature <id>", "Feature CUID").option("--workspace <slug|id>", "Workspace (defaults to your default workspace)").action(
3439
+ async (options, cmd) => {
3440
+ const globalOpts = cmd.optsWithGlobals();
3441
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
3442
+ try {
3443
+ if (!options.ticket && !options.feature) {
3444
+ throw new Error("Pass --ticket <id> or --feature <id>.");
3445
+ }
3446
+ if (options.ticket && options.feature) {
3447
+ throw new Error("Pass one of --ticket or --feature, not both.");
3448
+ }
3449
+ const client = getClient();
3450
+ const workspaceId = await resolveWorkspaceId(client, options.workspace);
3451
+ const link = options.ticket ? await client.decisions.linkTicket(workspaceId, options.id, options.ticket) : await client.decisions.linkFeature(workspaceId, options.id, options.feature);
3452
+ if (useJson) console.log(JSON.stringify(link, null, 2));
3453
+ else {
3454
+ console.log(`\u2713 Linked ${chalk4.gray(`(link id ${link.id})`)}`);
3455
+ console.log(
3456
+ chalk4.gray(" Remove it with: exponential decisions unlink --link " + link.id)
3457
+ );
3458
+ }
3459
+ } catch (error) {
3460
+ handleError(error, useJson);
3461
+ }
3462
+ }
3463
+ );
3464
+ decisions.command("unlink").description(
3465
+ 'Remove one "implemented by" link. Takes the LINK id (shown by "decisions get"), not the ticket or feature id.'
3466
+ ).requiredOption("--link <id>", "DecisionLink CUID").option("--workspace <slug|id>", "Workspace (defaults to your default workspace)").action(async (options, cmd) => {
3467
+ const globalOpts = cmd.optsWithGlobals();
3468
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
3469
+ try {
3470
+ const client = getClient();
3471
+ const workspaceId = await resolveWorkspaceId(client, options.workspace);
3472
+ await client.decisions.unlink(workspaceId, options.link);
3473
+ if (useJson) console.log(JSON.stringify({ deleted: true, linkId: options.link }, null, 2));
3474
+ else console.log("\u2713 Link removed");
3475
+ } catch (error) {
3476
+ handleError(error, useJson);
3477
+ }
3478
+ });
3479
+ const drafts = new Command4("draft").description(
3480
+ "Review decisions an agent proposed but nobody has confirmed yet (visible only on their source meeting)"
3481
+ );
3482
+ drafts.command("extract").description(
3483
+ "Propose draft decisions from a meeting's notes and transcript, for someone to confirm. Idempotent: existing drafts are returned rather than regenerated, and a meeting that already has confirmed decisions is left alone."
3484
+ ).requiredOption("--meeting <id>", "Meeting CUID (transcriptionSessionId)").action(async (options, cmd) => {
2402
3485
  const globalOpts = cmd.optsWithGlobals();
2403
3486
  const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
2404
- const nullable = (v) => v === "null" ? null : v;
2405
3487
  try {
2406
3488
  const client = getClient();
2407
- const contact = await client.contacts.update({
2408
- id: options.id,
2409
- firstName: options.firstName,
2410
- lastName: options.lastName,
2411
- email: nullable(options.email),
2412
- phone: nullable(options.phone),
2413
- linkedIn: nullable(options.linkedin),
2414
- telegram: nullable(options.telegram),
2415
- twitter: nullable(options.twitter),
2416
- github: nullable(options.github),
2417
- bluesky: nullable(options.bluesky),
2418
- about: options.about,
2419
- profileType: options.profileType,
2420
- skills: options.skills?.split(",").map((s) => s.trim()),
2421
- tags: options.tags?.split(",").map((t) => t.trim()),
2422
- organizationId: nullable(options.organization),
2423
- organizationName: options.organizationName
2424
- });
2425
- if (useJson) {
2426
- outputContactJson(contact);
2427
- } else {
2428
- console.log("\n\u2713 Contact updated successfully");
2429
- outputContactPretty(contact);
3489
+ const result = await client.decisions.extractDrafts(options.meeting);
3490
+ if (useJson) console.log(JSON.stringify(result, null, 2));
3491
+ else {
3492
+ if (result.alreadyPublished) {
3493
+ console.log(
3494
+ chalk4.gray("This meeting already has confirmed decisions \u2014 nothing was extracted.")
3495
+ );
3496
+ } else if (result.alreadyDrafted) {
3497
+ console.log(chalk4.gray("Returning the drafts this meeting already had."));
3498
+ }
3499
+ console.log(
3500
+ `\u2713 ${result.draftCount} draft${result.draftCount === 1 ? "" : "s"} awaiting review (${result.draftsCreated} new)`
3501
+ );
3502
+ if (result.discardedWithoutEvidence > 0) {
3503
+ console.log(
3504
+ chalk4.gray(
3505
+ ` ${result.discardedWithoutEvidence} candidate(s) dropped \u2014 no transcript turn supported them.`
3506
+ )
3507
+ );
3508
+ }
3509
+ for (const message of result.errors) console.log(chalk4.yellow(` ${message}`));
2430
3510
  }
2431
3511
  } catch (error) {
2432
3512
  handleError(error, useJson);
2433
3513
  }
2434
3514
  });
2435
- contacts.command("delete").description("Delete a contact").argument("<id>", "Contact ID").action(async (id, _options, cmd) => {
3515
+ drafts.command("confirm").description("Publish a draft decision into the log").requiredOption("--id <id>", "Decision CUID").option("--workspace <slug|id>", "Workspace (defaults to your default workspace)").action(async (options, cmd) => {
2436
3516
  const globalOpts = cmd.optsWithGlobals();
2437
3517
  const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
2438
3518
  try {
2439
3519
  const client = getClient();
2440
- await client.contacts.delete(id);
2441
- if (useJson) {
2442
- console.log(JSON.stringify({ deleted: true, id }, null, 2));
2443
- } else {
2444
- console.log(`
2445
- \u2713 Contact ${id} deleted`);
3520
+ const workspaceId = await resolveWorkspaceId(client, options.workspace);
3521
+ const decision = await client.decisions.confirmDraft(workspaceId, options.id);
3522
+ if (useJson) outputDecisionJson(decision);
3523
+ else {
3524
+ console.log("\n\u2713 Draft confirmed");
3525
+ outputDecisionPretty(decision);
2446
3526
  }
2447
3527
  } catch (error) {
2448
3528
  handleError(error, useJson);
2449
3529
  }
2450
3530
  });
2451
- contacts.command("add-interaction").description("Add an interaction to a contact").requiredOption("--contact <id>", "Contact ID").requiredOption("--type <type>", `Interaction type (${VALID_INTERACTION_TYPES.join(", ")})`).requiredOption("--direction <dir>", `Direction (${VALID_INTERACTION_DIRECTIONS.join(", ")})`).option("--subject <text>", "Subject line").option("--notes <text>", "Notes").action(async (options, cmd) => {
3531
+ drafts.command("reject").description(
3532
+ 'Reject a draft. A confirmed decision is never rejected \u2014 deprecate or supersede it with "decisions status".'
3533
+ ).requiredOption("--id <id>", "Decision CUID").option("--workspace <slug|id>", "Workspace (defaults to your default workspace)").action(async (options, cmd) => {
2452
3534
  const globalOpts = cmd.optsWithGlobals();
2453
3535
  const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
2454
3536
  try {
2455
- if (!VALID_INTERACTION_TYPES.includes(options.type)) {
2456
- throw new Error(`Invalid interaction type "${options.type}". Valid: ${VALID_INTERACTION_TYPES.join(", ")}`);
2457
- }
2458
- if (!VALID_INTERACTION_DIRECTIONS.includes(options.direction)) {
2459
- throw new Error(`Invalid direction "${options.direction}". Valid: ${VALID_INTERACTION_DIRECTIONS.join(", ")}`);
2460
- }
2461
3537
  const client = getClient();
2462
- const interaction = await client.contacts.addInteraction({
2463
- contactId: options.contact,
2464
- type: options.type,
2465
- direction: options.direction,
2466
- subject: options.subject,
2467
- notes: options.notes
2468
- });
2469
- if (useJson) {
2470
- outputInteractionJson(interaction);
2471
- } else {
2472
- console.log("\n\u2713 Interaction added");
2473
- outputInteractionPretty(interaction);
2474
- }
3538
+ const workspaceId = await resolveWorkspaceId(client, options.workspace);
3539
+ const result = await client.decisions.rejectDraft(workspaceId, options.id);
3540
+ if (useJson) console.log(JSON.stringify(result, null, 2));
3541
+ else console.log("\u2713 Draft rejected");
2475
3542
  } catch (error) {
2476
3543
  handleError(error, useJson);
2477
3544
  }
2478
3545
  });
2479
- contacts.command("enrich").description("Queue a web-search enrichment job for an existing contact (runs regardless of the workspace auto-enrich setting)").argument("<id>", "Contact ID").action(async (id, _options, cmd) => {
3546
+ drafts.command("delete").description(
3547
+ "Permanently delete a draft or rejected decision. Confirmed decisions are never deleted."
3548
+ ).requiredOption("--id <id>", "Decision CUID").option("--workspace <slug|id>", "Workspace (defaults to your default workspace)").action(async (options, cmd) => {
2480
3549
  const globalOpts = cmd.optsWithGlobals();
2481
3550
  const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
2482
3551
  try {
2483
3552
  const client = getClient();
2484
- const result = await client.contacts.enrich(id);
2485
- if (useJson) {
2486
- console.log(JSON.stringify(result, null, 2));
2487
- } else if (result.enqueued) {
2488
- console.log(`
2489
- \u2713 Enrichment queued for contact ${id} (job ${result.enrichmentId})`);
2490
- } else if (result.reason === "already-in-flight") {
2491
- console.log(`
2492
- \u2022 Enrichment already in progress for contact ${id} (job ${result.enrichmentId})`);
2493
- } else {
2494
- console.log(`
2495
- \u2022 Not queued for contact ${id}${result.reason ? ` (${result.reason})` : ""}`);
2496
- }
3553
+ const workspaceId = await resolveWorkspaceId(client, options.workspace);
3554
+ const result = await client.decisions.deleteDraft(workspaceId, options.id);
3555
+ if (useJson) console.log(JSON.stringify({ deleted: true, ...result }, null, 2));
3556
+ else console.log("\u2713 Draft deleted");
2497
3557
  } catch (error) {
2498
3558
  handleError(error, useJson);
2499
3559
  }
2500
3560
  });
2501
- return contacts;
3561
+ decisions.addCommand(drafts);
3562
+ return decisions;
2502
3563
  }
2503
3564
 
2504
3565
  // src/commands/deals.ts
2505
- import { Command as Command4 } from "commander";
3566
+ import { Command as Command5 } from "commander";
2506
3567
  function createDealsCommand() {
2507
- const deals = new Command4("deals").description("Manage pipeline deals");
3568
+ const deals = new Command5("deals").description("Manage pipeline deals");
2508
3569
  deals.command("pipeline").description("Get pipeline overview with stages").requiredOption("--workspace <id>", "Workspace ID").action(async (options, cmd) => {
2509
3570
  const globalOpts = cmd.optsWithGlobals();
2510
3571
  const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
@@ -2689,36 +3750,7 @@ function createDealsCommand() {
2689
3750
  }
2690
3751
 
2691
3752
  // src/commands/epics.ts
2692
- import { Command as Command5 } from "commander";
2693
-
2694
- // src/utils/resolve.ts
2695
- async function resolveWorkspaceId(client, slugOrId) {
2696
- return (await resolveWorkspace(client, slugOrId)).id;
2697
- }
2698
- async function resolveWorkspace(client, slugOrId) {
2699
- const config = getConfig();
2700
- const candidate = slugOrId ?? config.defaultWorkspaceSlug ?? config.defaultWorkspaceId;
2701
- if (!candidate) {
2702
- throw new Error(
2703
- "No workspace specified and no default workspace set. Pass --workspace <slug> or run `exponential workspaces set-default <slug>`."
2704
- );
2705
- }
2706
- const workspaces = await client.workspaces.list();
2707
- const byId = workspaces.find((w) => w.id === candidate);
2708
- if (byId) return byId;
2709
- const bySlug = workspaces.find((w) => w.slug === candidate);
2710
- if (bySlug) return bySlug;
2711
- const known = workspaces.map((w) => w.slug).join(", ");
2712
- throw new Error(
2713
- `Workspace "${candidate}" not found. Available: ${known || "(none)"}`
2714
- );
2715
- }
2716
- async function resolveProductId(client, workspaceId, slugOrId) {
2717
- const product = await client.products.resolve(workspaceId, slugOrId);
2718
- return product.id;
2719
- }
2720
-
2721
- // src/commands/epics.ts
3753
+ import { Command as Command6 } from "commander";
2722
3754
  var EPIC_STATUSES = ["OPEN", "IN_PROGRESS", "DONE", "CANCELLED"];
2723
3755
  var EPIC_PRIORITIES = ["HIGH", "MEDIUM", "LOW", "NONE"];
2724
3756
  function validateEpicStatus(value) {
@@ -2737,7 +3769,7 @@ function validateEpicPriority(value) {
2737
3769
  }
2738
3770
  return value;
2739
3771
  }
2740
- function parseDate(value) {
3772
+ function parseDate2(value) {
2741
3773
  if (!value) return void 0;
2742
3774
  const d = new Date(value);
2743
3775
  if (isNaN(d.getTime())) {
@@ -2746,7 +3778,7 @@ function parseDate(value) {
2746
3778
  return d;
2747
3779
  }
2748
3780
  function createEpicsCommand() {
2749
- const epics = new Command5("epics").description(
3781
+ const epics = new Command6("epics").description(
2750
3782
  "Manage epics. Epics are workspace-scoped and group actions and tickets across products."
2751
3783
  );
2752
3784
  epics.command("list").description("List epics in a workspace").option("--workspace <slug|id>", "Workspace slug or CUID").option("--status <status>", `Filter by status: ${EPIC_STATUSES.join(", ")}`).action(
@@ -2783,8 +3815,8 @@ function createEpicsCommand() {
2783
3815
  const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
2784
3816
  try {
2785
3817
  const priority = validateEpicPriority(options.priority);
2786
- const startDate = parseDate(options.start);
2787
- const targetDate = parseDate(options.target);
3818
+ const startDate = parseDate2(options.start);
3819
+ const targetDate = parseDate2(options.target);
2788
3820
  const client = getClient();
2789
3821
  const workspaceId = await resolveWorkspaceId(client, options.workspace);
2790
3822
  const epic = await client.epics.create({
@@ -2846,10 +3878,10 @@ function createEpicsCommand() {
2846
3878
  }
2847
3879
 
2848
3880
  // src/commands/features.ts
2849
- import { Command as Command11 } from "commander";
3881
+ import { Command as Command12 } from "commander";
2850
3882
 
2851
3883
  // src/commands/stories.ts
2852
- import { Command as Command6 } from "commander";
3884
+ import { Command as Command7 } from "commander";
2853
3885
  async function readStdin() {
2854
3886
  const chunks = [];
2855
3887
  for await (const chunk of process.stdin) {
@@ -2858,7 +3890,7 @@ async function readStdin() {
2858
3890
  return Buffer.concat(chunks).toString("utf-8");
2859
3891
  }
2860
3892
  function createStoriesCommand() {
2861
- const stories = new Command6("stories").description(
3893
+ const stories = new Command7("stories").description(
2862
3894
  "Manage a feature's native, structured user stories (As a / I want / So that)."
2863
3895
  );
2864
3896
  stories.command("add").description(
@@ -2956,7 +3988,7 @@ function createStoriesCommand() {
2956
3988
  }
2957
3989
 
2958
3990
  // src/commands/scopes.ts
2959
- import { Command as Command7 } from "commander";
3991
+ import { Command as Command8 } from "commander";
2960
3992
  var SCOPE_STATUSES = [
2961
3993
  "PLANNED",
2962
3994
  "IN_PROGRESS",
@@ -2973,7 +4005,7 @@ function validateScopeStatus(value) {
2973
4005
  return value;
2974
4006
  }
2975
4007
  function createScopesCommand() {
2976
- const scopes = new Command7("scopes").description(
4008
+ const scopes = new Command8("scopes").description(
2977
4009
  `Manage a feature's scopes: shippable increments ("V1", "V2"), each with its own lifecycle.`
2978
4010
  );
2979
4011
  scopes.command("list").description("List a feature's scopes").requiredOption("--feature <id>", "Feature CUID").action(
@@ -3052,7 +4084,7 @@ function createScopesCommand() {
3052
4084
  }
3053
4085
 
3054
4086
  // src/commands/requirements.ts
3055
- import { Command as Command8 } from "commander";
4087
+ import { Command as Command9 } from "commander";
3056
4088
  var REQUIREMENT_KINDS = [
3057
4089
  "FUNCTIONAL",
3058
4090
  "NON_FUNCTIONAL",
@@ -3075,7 +4107,7 @@ async function readStdin2() {
3075
4107
  return Buffer.concat(chunks).toString("utf-8");
3076
4108
  }
3077
4109
  function createRequirementsCommand() {
3078
- const requirements = new Command8("requirements").description(
4110
+ const requirements = new Command9("requirements").description(
3079
4111
  `Manage a feature's requirements: atomic, testable EARS-style "shall" statements, checkable met/unmet.`
3080
4112
  );
3081
4113
  requirements.command("add").description(
@@ -3206,25 +4238,25 @@ function createRequirementsCommand() {
3206
4238
  }
3207
4239
 
3208
4240
  // src/commands/areas.ts
3209
- import { Command as Command9 } from "commander";
3210
- import chalk4 from "chalk";
4241
+ import { Command as Command10 } from "commander";
4242
+ import chalk5 from "chalk";
3211
4243
  function outputAreasPretty(areas) {
3212
4244
  if (areas.length === 0) {
3213
- console.log(chalk4.gray("No areas found."));
4245
+ console.log(chalk5.gray("No areas found."));
3214
4246
  return;
3215
4247
  }
3216
- console.log(chalk4.bold(`
4248
+ console.log(chalk5.bold(`
3217
4249
  Areas (${areas.length} total)`));
3218
- console.log(chalk4.gray("\u2500".repeat(50)));
4250
+ console.log(chalk5.gray("\u2500".repeat(50)));
3219
4251
  for (const a of areas) {
3220
- const count = a._count?.features != null ? chalk4.gray(` (${a._count.features} features)`) : "";
3221
- console.log(` ${chalk4.bold(a.name)}${count}`);
3222
- console.log(chalk4.gray(` ID: ${a.id}`));
4252
+ const count = a._count?.features != null ? chalk5.gray(` (${a._count.features} features)`) : "";
4253
+ console.log(` ${chalk5.bold(a.name)}${count}`);
4254
+ console.log(chalk5.gray(` ID: ${a.id}`));
3223
4255
  }
3224
4256
  console.log();
3225
4257
  }
3226
4258
  function createAreasCommand() {
3227
- const areas = new Command9("areas").description(
4259
+ const areas = new Command10("areas").description(
3228
4260
  "Manage a product's areas: the buckets features are filed under (a feature has exactly one or none)."
3229
4261
  );
3230
4262
  areas.command("list").description("List a product's areas").requiredOption("--product <slug|id>", "Product slug or CUID").option("--workspace <slug|id>", "Workspace (required when --product is a slug)").action(
@@ -3268,7 +4300,7 @@ function createAreasCommand() {
3268
4300
  }
3269
4301
 
3270
4302
  // src/commands/featureComments.ts
3271
- import { Command as Command10 } from "commander";
4303
+ import { Command as Command11 } from "commander";
3272
4304
 
3273
4305
  // src/utils/mentions.ts
3274
4306
  function candidateLabels(member) {
@@ -3341,7 +4373,7 @@ function collectMention(value, previous) {
3341
4373
 
3342
4374
  // src/commands/featureComments.ts
3343
4375
  function createFeatureCommentsCommand() {
3344
- const comment = new Command10("comment").description(
4376
+ const comment = new Command11("comment").description(
3345
4377
  "Read and post comments on a feature. Mention teammates with --mention."
3346
4378
  );
3347
4379
  comment.command("list").description("List comments on a feature").requiredOption("--feature <id>", "Feature ID").action(async (options, cmd) => {
@@ -3542,7 +4574,7 @@ function validateFeatureStatus(value) {
3542
4574
  return value;
3543
4575
  }
3544
4576
  function createFeaturesCommand() {
3545
- const features = new Command11("features").description(
4577
+ const features = new Command12("features").description(
3546
4578
  "Manage features. A feature lives under a product and groups user stories, scopes, and tickets."
3547
4579
  );
3548
4580
  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(
@@ -3799,8 +4831,8 @@ Aborted: ${reason}. Feature "${feature.name}" was NOT deleted.`);
3799
4831
  }
3800
4832
 
3801
4833
  // src/commands/labels.ts
3802
- import { Command as Command12 } from "commander";
3803
- import chalk5 from "chalk";
4834
+ import { Command as Command13 } from "commander";
4835
+ import chalk6 from "chalk";
3804
4836
  var TRIAGE_LABELS = [
3805
4837
  { slug: "needs-triage", name: "needs-triage", color: "avatar-orange" },
3806
4838
  { slug: "needs-info", name: "needs-info", color: "avatar-yellow" },
@@ -3814,22 +4846,22 @@ function looksLikeCuid(value) {
3814
4846
  }
3815
4847
  function outputTagsPretty(tags) {
3816
4848
  if (tags.length === 0) {
3817
- console.log(chalk5.dim("No labels found."));
4849
+ console.log(chalk6.dim("No labels found."));
3818
4850
  return;
3819
4851
  }
3820
4852
  console.log();
3821
4853
  for (const tag of tags) {
3822
- const scope = tag.workspaceId === null ? chalk5.cyan("global") : chalk5.magenta("workspace");
3823
- const category = tag.category === null ? chalk5.dim("(label)") : chalk5.dim(`(${tag.category})`);
4854
+ const scope = tag.workspaceId === null ? chalk6.cyan("global") : chalk6.magenta("workspace");
4855
+ const category = tag.category === null ? chalk6.dim("(label)") : chalk6.dim(`(${tag.category})`);
3824
4856
  console.log(
3825
- ` ${chalk5.bold(tag.slug)} ${tag.name} ${chalk5.gray(tag.color)} ${scope} ${category}`
4857
+ ` ${chalk6.bold(tag.slug)} ${tag.name} ${chalk6.gray(tag.color)} ${scope} ${category}`
3826
4858
  );
3827
4859
  }
3828
4860
  console.log();
3829
4861
  }
3830
4862
  function outputTagPretty(tag, prefix) {
3831
4863
  if (prefix) console.log(prefix);
3832
- console.log(` ${chalk5.bold(tag.slug)} ${tag.name} ${chalk5.gray(tag.color)}`);
4864
+ console.log(` ${chalk6.bold(tag.slug)} ${tag.name} ${chalk6.gray(tag.color)}`);
3833
4865
  }
3834
4866
  async function resolveLabelIds(client, workspaceId, values) {
3835
4867
  if (values.length === 0) return [];
@@ -3853,7 +4885,7 @@ async function resolveLabelIds(client, workspaceId, values) {
3853
4885
  return [...byId, ...resolvedFromSlugs];
3854
4886
  }
3855
4887
  function createLabelsCommand() {
3856
- const labels = new Command12("labels").description(
4888
+ const labels = new Command13("labels").description(
3857
4889
  "Manage labels (orthogonal tags applied to actions, tickets, features, and epics)."
3858
4890
  );
3859
4891
  labels.command("list").description("List labels visible in a workspace").option("--workspace <slug|id>", "Workspace slug or CUID").option(
@@ -3901,7 +4933,7 @@ function createLabelsCommand() {
3901
4933
  if (useJson) {
3902
4934
  console.log(JSON.stringify(tag, null, 2));
3903
4935
  } else {
3904
- outputTagPretty(tag, chalk5.green("\n\u2713 Label created"));
4936
+ outputTagPretty(tag, chalk6.green("\n\u2713 Label created"));
3905
4937
  }
3906
4938
  } catch (error) {
3907
4939
  handleError(error, useJson);
@@ -3939,10 +4971,10 @@ function createLabelsCommand() {
3939
4971
  console.log(JSON.stringify(results, null, 2));
3940
4972
  } else {
3941
4973
  console.log();
3942
- console.log(chalk5.bold("Triage labels:"));
4974
+ console.log(chalk6.bold("Triage labels:"));
3943
4975
  for (const r of results) {
3944
- const tag = chalk5.bold(r.slug);
3945
- const status = r.status === "created" ? chalk5.green("created") : chalk5.dim("existed");
4976
+ const tag = chalk6.bold(r.slug);
4977
+ const status = r.status === "created" ? chalk6.green("created") : chalk6.dim("existed");
3946
4978
  console.log(` ${tag} ${status}`);
3947
4979
  }
3948
4980
  console.log();
@@ -3956,38 +4988,10 @@ function createLabelsCommand() {
3956
4988
  }
3957
4989
 
3958
4990
  // src/commands/meetings.ts
3959
- import { Command as Command13 } from "commander";
3960
- import chalk6 from "chalk";
3961
-
3962
- // src/utils/input.ts
3963
- import { readFileSync } from "fs";
3964
- var stdinConsumed = false;
3965
- function readText(inline, file) {
3966
- if (inline !== void 0) return inline;
3967
- if (file === void 0) return void 0;
3968
- if (file === "-") {
3969
- if (stdinConsumed) {
3970
- throw new Error('stdin ("-") can only back one option per invocation.');
3971
- }
3972
- stdinConsumed = true;
3973
- return readFileSync(0, "utf-8");
3974
- }
3975
- return readFileSync(file, "utf-8");
3976
- }
3977
- function parseDate2(value) {
3978
- if (value === void 0 || value === "") return void 0;
3979
- const date = /^\d{4}-\d{2}-\d{2}$/.test(value) ? /* @__PURE__ */ new Date(`${value}T00:00:00`) : new Date(value);
3980
- if (Number.isNaN(date.getTime())) {
3981
- throw new Error(
3982
- `Invalid date: "${value}". Use ISO format, e.g. 2026-08-25 or 2026-08-25T14:00.`
3983
- );
3984
- }
3985
- return date;
3986
- }
3987
-
3988
- // src/commands/meetings.ts
4991
+ import { Command as Command14 } from "commander";
4992
+ import chalk7 from "chalk";
3989
4993
  function createMeetingsCommand() {
3990
- const meetings = new Command13("meetings").description(
4994
+ const meetings = new Command14("meetings").description(
3991
4995
  "Manage meetings and their notes (recorded, imported, or manually created)"
3992
4996
  );
3993
4997
  meetings.command("list").description(
@@ -4048,7 +5052,7 @@ function createMeetingsCommand() {
4048
5052
  transcription,
4049
5053
  notes: readText(options.notes, options.notesFile),
4050
5054
  description: options.description,
4051
- meetingDate: parseDate2(options.date),
5055
+ meetingDate: parseDate(options.date),
4052
5056
  projectId: options.project,
4053
5057
  workspaceId
4054
5058
  });
@@ -4082,7 +5086,7 @@ function createMeetingsCommand() {
4082
5086
  notes: notes2,
4083
5087
  summary: options.summary,
4084
5088
  description: options.description,
4085
- meetingDate: parseDate2(options.date)
5089
+ meetingDate: parseDate(options.date)
4086
5090
  });
4087
5091
  if (useJson) outputMeetingJson(meeting);
4088
5092
  else {
@@ -4148,7 +5152,7 @@ function createMeetingsCommand() {
4148
5152
  console.log(`\u2713 Deleted ${count} of ${ids.length} meeting${ids.length === 1 ? "" : "s"}`);
4149
5153
  if (skipped > 0) {
4150
5154
  console.log(
4151
- chalk6.yellow(
5155
+ chalk7.yellow(
4152
5156
  `${skipped} id(s) were skipped \u2014 not found, or not owned by you.`
4153
5157
  )
4154
5158
  );
@@ -4159,7 +5163,7 @@ function createMeetingsCommand() {
4159
5163
  }
4160
5164
  }
4161
5165
  );
4162
- const notes = new Command13("notes").description(
5166
+ const notes = new Command14("notes").description(
4163
5167
  "Read and write a meeting's notes"
4164
5168
  );
4165
5169
  notes.command("get <id>").description(
@@ -4175,7 +5179,7 @@ function createMeetingsCommand() {
4175
5179
  process.stdout.write(body.endsWith("\n") ? body : `${body}
4176
5180
  `);
4177
5181
  } else if (process.stdout.isTTY) {
4178
- console.log(chalk6.gray("No notes yet."));
5182
+ console.log(chalk7.gray("No notes yet."));
4179
5183
  }
4180
5184
  } catch (error) {
4181
5185
  handleError(error, shouldUseJson(globalOpts.json, globalOpts.pretty));
@@ -4224,7 +5228,7 @@ function createMeetingsCommand() {
4224
5228
  }
4225
5229
 
4226
5230
  // src/commands/organizations.ts
4227
- import { Command as Command14 } from "commander";
5231
+ import { Command as Command15 } from "commander";
4228
5232
  var VALID_SIZES = [
4229
5233
  "1-10",
4230
5234
  "11-50",
@@ -4234,7 +5238,7 @@ var VALID_SIZES = [
4234
5238
  "1000+"
4235
5239
  ];
4236
5240
  function createOrganizationsCommand() {
4237
- const organizations = new Command14("organizations").alias("orgs").description("Manage CRM organizations");
5241
+ const organizations = new Command15("organizations").alias("orgs").description("Manage CRM organizations");
4238
5242
  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) => {
4239
5243
  const globalOpts = cmd.optsWithGlobals();
4240
5244
  const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
@@ -4302,12 +5306,12 @@ function createOrganizationsCommand() {
4302
5306
  }
4303
5307
 
4304
5308
  // src/commands/pages.ts
4305
- import { Command as Command16 } from "commander";
5309
+ import { Command as Command17 } from "commander";
4306
5310
 
4307
5311
  // src/commands/pageComments.ts
4308
- import { Command as Command15 } from "commander";
5312
+ import { Command as Command16 } from "commander";
4309
5313
  function createPageCommentsCommand() {
4310
- const comment = new Command15("comment").description(
5314
+ const comment = new Command16("comment").description(
4311
5315
  "Read and post comments on a page. Mention teammates with --mention."
4312
5316
  );
4313
5317
  comment.command("list").description("List comments on a page").requiredOption("--page <id>", "Page ID").action(async (options, cmd) => {
@@ -4402,7 +5406,7 @@ function createPageCommentsCommand() {
4402
5406
 
4403
5407
  // src/commands/pages.ts
4404
5408
  function createPagesCommand() {
4405
- const pages = new Command16("pages").description(
5409
+ const pages = new Command17("pages").description(
4406
5410
  'Manage Knowledge pages (PRDs, research, technical specs). Link one to a feature with "features link-page".'
4407
5411
  );
4408
5412
  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(
@@ -4485,9 +5489,9 @@ function createPagesCommand() {
4485
5489
  }
4486
5490
 
4487
5491
  // src/commands/products.ts
4488
- import { Command as Command17 } from "commander";
5492
+ import { Command as Command18 } from "commander";
4489
5493
  function createProductsCommand() {
4490
- const products = new Command17("products").description(
5494
+ const products = new Command18("products").description(
4491
5495
  "Manage products. A workspace contains products; a product holds features and tickets."
4492
5496
  );
4493
5497
  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) => {
@@ -4550,7 +5554,7 @@ function createProductsCommand() {
4550
5554
  }
4551
5555
 
4552
5556
  // src/commands/projects.ts
4553
- import { Command as Command18 } from "commander";
5557
+ import { Command as Command19 } from "commander";
4554
5558
  var PROJECT_STATUSES = [
4555
5559
  "ACTIVE",
4556
5560
  "ON_HOLD",
@@ -4577,7 +5581,7 @@ function validateProjectPriority(value) {
4577
5581
  return value;
4578
5582
  }
4579
5583
  function createProjectsCommand() {
4580
- const projects = new Command18("projects").description("Manage projects");
5584
+ const projects = new Command19("projects").description("Manage projects");
4581
5585
  projects.command("list").description("List all projects").option("--workspace <slug|id>", "Workspace slug or CUID").option("--include-actions", "Include actions in output").action(async (options, cmd) => {
4582
5586
  const globalOpts = cmd.optsWithGlobals();
4583
5587
  const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
@@ -4696,8 +5700,8 @@ Re-run with --force to delete it anyway.`
4696
5700
  }
4697
5701
 
4698
5702
  // src/commands/search.ts
4699
- import { Command as Command19 } from "commander";
4700
- import chalk7 from "chalk";
5703
+ import { Command as Command20 } from "commander";
5704
+ import chalk8 from "chalk";
4701
5705
  var TYPE_LABELS = {
4702
5706
  workspace: "Workspaces",
4703
5707
  project: "Projects",
@@ -4715,7 +5719,7 @@ var TYPE_LABELS = {
4715
5719
  product: "Products"
4716
5720
  };
4717
5721
  function createSearchCommand() {
4718
- const search = new Command19("search").description(
5722
+ const search = new Command20("search").description(
4719
5723
  "Global search across projects, actions, goals, workspaces and more \u2014 same coverage as the app's Cmd+K palette"
4720
5724
  ).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) => {
4721
5725
  const globalOpts = cmd.optsWithGlobals();
@@ -4729,10 +5733,10 @@ function createSearchCommand() {
4729
5733
  (w) => w.slug === options.workspace || w.id === options.workspace
4730
5734
  );
4731
5735
  if (!match) {
4732
- console.error(chalk7.red(`Workspace "${options.workspace}" not found.`));
4733
- console.log(chalk7.gray("Available workspaces:"));
5736
+ console.error(chalk8.red(`Workspace "${options.workspace}" not found.`));
5737
+ console.log(chalk8.gray("Available workspaces:"));
4734
5738
  workspaces.forEach((w) => {
4735
- console.log(chalk7.gray(` - ${w.slug} (${w.name})`));
5739
+ console.log(chalk8.gray(` - ${w.slug} (${w.name})`));
4736
5740
  });
4737
5741
  process.exit(1);
4738
5742
  }
@@ -4745,7 +5749,7 @@ function createSearchCommand() {
4745
5749
  return;
4746
5750
  }
4747
5751
  if (result.results.length === 0) {
4748
- console.log(chalk7.gray(`No matches for "${result.query}".`));
5752
+ console.log(chalk8.gray(`No matches for "${result.query}".`));
4749
5753
  return;
4750
5754
  }
4751
5755
  const byType = /* @__PURE__ */ new Map();
@@ -4755,14 +5759,14 @@ function createSearchCommand() {
4755
5759
  byType.set(r.type, bucket);
4756
5760
  }
4757
5761
  for (const [type, rows] of byType) {
4758
- console.log(chalk7.bold(`
5762
+ console.log(chalk8.bold(`
4759
5763
  ${TYPE_LABELS[type] ?? type}`));
4760
5764
  for (const row of rows) {
4761
- const parts = [chalk7.white(row.title)];
4762
- if (row.subtitle) parts.push(chalk7.gray(row.subtitle));
4763
- if (row.workspace) parts.push(chalk7.cyan(`@${row.workspace.slug}`));
4764
- console.log(` ${parts.join(chalk7.gray(" \xB7 "))}`);
4765
- console.log(chalk7.gray(` ${row.id}${row.url ? ` ${row.url}` : ""}`));
5765
+ const parts = [chalk8.white(row.title)];
5766
+ if (row.subtitle) parts.push(chalk8.gray(row.subtitle));
5767
+ if (row.workspace) parts.push(chalk8.cyan(`@${row.workspace.slug}`));
5768
+ console.log(` ${parts.join(chalk8.gray(" \xB7 "))}`);
5769
+ console.log(chalk8.gray(` ${row.id}${row.url ? ` ${row.url}` : ""}`));
4766
5770
  }
4767
5771
  }
4768
5772
  console.log();
@@ -4774,7 +5778,7 @@ ${TYPE_LABELS[type] ?? type}`));
4774
5778
  }
4775
5779
 
4776
5780
  // src/commands/tickets.ts
4777
- import { Command as Command20 } from "commander";
5781
+ import { Command as Command21 } from "commander";
4778
5782
  function collectRepeatable(value, previous) {
4779
5783
  return [...previous, value];
4780
5784
  }
@@ -4817,7 +5821,7 @@ function validateTicketType(value) {
4817
5821
  return value;
4818
5822
  }
4819
5823
  function createTicketsCommand() {
4820
- const tickets = new Command20("tickets").description(
5824
+ const tickets = new Command21("tickets").description(
4821
5825
  "Manage product backlog tickets. Tickets live under a product, link to features/epics, depend on each other, and can be implemented via actions."
4822
5826
  );
4823
5827
  tickets.command("list").description(
@@ -4998,7 +6002,7 @@ function createTicketsCommand() {
4998
6002
  const globalOpts = cmd.optsWithGlobals();
4999
6003
  const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
5000
6004
  try {
5001
- const parseNullableInt = (v) => {
6005
+ const parseNullableInt2 = (v) => {
5002
6006
  if (v === void 0) return void 0;
5003
6007
  if (v === "null") return null;
5004
6008
  const n = parseInt(v, 10);
@@ -5012,7 +6016,7 @@ function createTicketsCommand() {
5012
6016
  if (isNaN(n)) throw new Error(`Invalid number: ${v}`);
5013
6017
  return n;
5014
6018
  };
5015
- const parseNullableString = (v) => {
6019
+ const parseNullableString2 = (v) => {
5016
6020
  if (v === void 0) return void 0;
5017
6021
  if (v === "null") return null;
5018
6022
  return v;
@@ -5026,14 +6030,14 @@ function createTicketsCommand() {
5026
6030
  body: options.body,
5027
6031
  type,
5028
6032
  status,
5029
- priority: parseNullableInt(options.priority),
6033
+ priority: parseNullableInt2(options.priority),
5030
6034
  points: parseNullableFloat(options.points),
5031
- featureId: parseNullableString(options.feature),
5032
- epicId: parseNullableString(options.epic),
5033
- cycleId: parseNullableString(options.cycle),
5034
- assigneeId: parseNullableString(options.assignee),
5035
- branchName: parseNullableString(options.branch),
5036
- prUrl: parseNullableString(options.pr)
6035
+ featureId: parseNullableString2(options.feature),
6036
+ epicId: parseNullableString2(options.epic),
6037
+ cycleId: parseNullableString2(options.cycle),
6038
+ assigneeId: parseNullableString2(options.assignee),
6039
+ branchName: parseNullableString2(options.branch),
6040
+ prUrl: parseNullableString2(options.pr)
5037
6041
  });
5038
6042
  if (options.addLabel.length > 0 || options.removeLabel.length > 0) {
5039
6043
  const current = await client.labels.listForEntity({
@@ -5143,7 +6147,7 @@ function createTicketsCommand() {
5143
6147
  handleError(error, useJson);
5144
6148
  }
5145
6149
  });
5146
- const comment = new Command20("comment").description("Manage comments on a ticket");
6150
+ const comment = new Command21("comment").description("Manage comments on a ticket");
5147
6151
  comment.command("list").description("List comments on a ticket").requiredOption("--id <id>", "Ticket CUID").action(async (options, cmd) => {
5148
6152
  const globalOpts = cmd.optsWithGlobals();
5149
6153
  const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
@@ -5234,11 +6238,187 @@ function createTicketsCommand() {
5234
6238
  return tickets;
5235
6239
  }
5236
6240
 
6241
+ // src/commands/time.ts
6242
+ import { Command as Command22 } from "commander";
6243
+ var SOURCES2 = ["manual", "claude-desktop", "agent-run"];
6244
+ var STATUSES2 = ["PROPOSED", "CONFIRMED"];
6245
+ var OWNERSHIP_NOTE = "Run with an external-agent key, the entry belongs to the agent's OWNER and is always PROPOSED; a personal token logs your own time, CONFIRMED by default.";
6246
+ function parseSource2(value) {
6247
+ const lower = value.toLowerCase();
6248
+ if (!SOURCES2.includes(lower)) {
6249
+ throw new Error(`Invalid source "${value}". Use one of: ${SOURCES2.join(", ")}.`);
6250
+ }
6251
+ return lower;
6252
+ }
6253
+ function parseStatus2(value) {
6254
+ const upper = value.toUpperCase();
6255
+ if (!STATUSES2.includes(upper)) {
6256
+ throw new Error(`Invalid status "${value}". Use one of: ${STATUSES2.join(", ")}.`);
6257
+ }
6258
+ return upper;
6259
+ }
6260
+ var BATCH_ITEM_KEYS2 = /* @__PURE__ */ new Set([
6261
+ "actionId",
6262
+ "action",
6263
+ "startedAt",
6264
+ "from",
6265
+ "endedAt",
6266
+ "to",
6267
+ "source",
6268
+ "status",
6269
+ "sourceRef",
6270
+ "ref",
6271
+ "note"
6272
+ ]);
6273
+ function parseBatchFile2(raw) {
6274
+ let parsed;
6275
+ try {
6276
+ parsed = JSON.parse(raw);
6277
+ } catch (error) {
6278
+ throw new Error(
6279
+ `--from-file must be JSON: ${error instanceof Error ? error.message : String(error)}`
6280
+ );
6281
+ }
6282
+ const items = Array.isArray(parsed) ? parsed : typeof parsed === "object" && parsed !== null && Array.isArray(parsed.entries) ? parsed.entries : null;
6283
+ if (!items) {
6284
+ throw new Error('--from-file must contain a JSON array of entries, or {"entries": [...]}.');
6285
+ }
6286
+ if (items.length === 0) {
6287
+ throw new Error("--from-file contained no entries.");
6288
+ }
6289
+ return items.map((item, i) => {
6290
+ if (typeof item !== "object" || item === null) {
6291
+ throw new Error(`Entry[${i}] is not an object.`);
6292
+ }
6293
+ const entry = item;
6294
+ const unknown = Object.keys(entry).filter((key) => !BATCH_ITEM_KEYS2.has(key));
6295
+ if (unknown.length > 0) {
6296
+ throw new Error(
6297
+ `Entry[${i}] has unknown field(s): ${unknown.join(", ")}. Accepted: ${[...BATCH_ITEM_KEYS2].join(", ")}.`
6298
+ );
6299
+ }
6300
+ return entry;
6301
+ });
6302
+ }
6303
+ function optionalString(value, label, index) {
6304
+ if (value === void 0 || value === null) return void 0;
6305
+ if (typeof value !== "string") throw new Error(`Entry[${index}].${label} must be a string.`);
6306
+ return value;
6307
+ }
6308
+ function batchItemToInput(item, index, defaults) {
6309
+ const actionId = optionalString(item.actionId ?? item.action, "actionId", index) ?? defaults.actionId;
6310
+ const from = optionalString(item.startedAt ?? item.from, "startedAt", index);
6311
+ const to = optionalString(item.endedAt ?? item.to, "endedAt", index);
6312
+ const startedAt = from !== void 0 ? parseDate(from) : defaults.startedAt;
6313
+ const endedAt = to !== void 0 ? parseDate(to) : defaults.endedAt;
6314
+ if (!actionId) throw new Error(`Entry[${index}] needs an "actionId" (or the --action flag).`);
6315
+ if (!startedAt || !endedAt) {
6316
+ throw new Error(`Entry[${index}] needs "startedAt" and "endedAt" (or --from and --to).`);
6317
+ }
6318
+ const source = optionalString(item.source, "source", index);
6319
+ const status = optionalString(item.status, "status", index);
6320
+ return {
6321
+ actionId,
6322
+ startedAt,
6323
+ endedAt,
6324
+ source: source !== void 0 ? parseSource2(source) : defaults.source,
6325
+ status: status !== void 0 ? parseStatus2(status) : defaults.status,
6326
+ sourceRef: optionalString(item.sourceRef ?? item.ref, "sourceRef", index) ?? defaults.sourceRef,
6327
+ note: optionalString(item.note, "note", index) ?? defaults.note
6328
+ };
6329
+ }
6330
+ function createTimeCommand() {
6331
+ const time = new Command22("time").description(
6332
+ `Time entries \u2014 log the day's work and list it. ${OWNERSHIP_NOTE}`
6333
+ );
6334
+ time.command("log").description(
6335
+ [
6336
+ "Log one completed entry with explicit bounds; the running Timer is never touched.",
6337
+ "With --ref the write is idempotent per owner: a re-run updates a PROPOSED entry and leaves a CONFIRMED one alone.",
6338
+ "Bulk: --from-file <path|-> takes a JSON array of entries; the other flags become its defaults.",
6339
+ OWNERSHIP_NOTE
6340
+ ].join(" ")
6341
+ ).option("-a, --action <id>", "Action CUID the time was spent on").option("--from <iso>", "Start, e.g. 2026-09-11T09:22").option("--to <iso>", "End, e.g. 2026-09-11T10:30").option("--source <source>", `${SOURCES2.join(", ")} (default: manual)`).option("--status <status>", "PROPOSED or CONFIRMED (ignored under an agent key: always PROPOSED)").option("--ref <sourceRef>", "Idempotency key, e.g. claude-session:<sessionId>#<segment>").option("--note <text>", "One line shown in the day view and list row").option("--from-file <path>", 'Bulk log from a JSON array of entries ("-" = stdin)').action(
6342
+ async (options, cmd) => {
6343
+ const globalOpts = cmd.optsWithGlobals();
6344
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
6345
+ try {
6346
+ const defaults = {
6347
+ actionId: options.action,
6348
+ startedAt: parseDate(options.from),
6349
+ endedAt: parseDate(options.to),
6350
+ source: options.source ? parseSource2(options.source) : void 0,
6351
+ status: options.status ? parseStatus2(options.status) : void 0,
6352
+ sourceRef: options.ref,
6353
+ note: options.note
6354
+ };
6355
+ const client = getClient();
6356
+ if (options.fromFile) {
6357
+ if (options.ref !== void 0) {
6358
+ throw new Error(
6359
+ '--ref is per-entry: a shared sourceRef would make every entry overwrite the last. Put "sourceRef" on each entry in --from-file instead.'
6360
+ );
6361
+ }
6362
+ const items = parseBatchFile2(readText(void 0, options.fromFile));
6363
+ const inputs = items.map((item, i) => {
6364
+ try {
6365
+ return batchItemToInput(item, i, defaults);
6366
+ } catch (error) {
6367
+ return error instanceof Error ? error : new Error(String(error));
6368
+ }
6369
+ });
6370
+ const results = [];
6371
+ for (const [index, input] of inputs.entries()) {
6372
+ if (input instanceof Error) {
6373
+ results.push({ index, success: false, error: input.message });
6374
+ continue;
6375
+ }
6376
+ const [result2] = await client.time.logBatch([input]);
6377
+ results.push({ ...result2, index });
6378
+ }
6379
+ if (results.some((r) => !r.success)) process.exitCode = 1;
6380
+ if (useJson) outputTimeBatchJson(results);
6381
+ else outputTimeBatchPretty(results);
6382
+ return;
6383
+ }
6384
+ if (!defaults.actionId) throw new Error("Pass --action <id>, or --from-file <path|-> for a batch.");
6385
+ if (!defaults.startedAt || !defaults.endedAt) throw new Error("Pass both --from <iso> and --to <iso>.");
6386
+ if (defaults.endedAt.getTime() <= defaults.startedAt.getTime()) {
6387
+ throw new Error("--to must be after --from.");
6388
+ }
6389
+ const result = await client.time.log(defaults);
6390
+ if (useJson) outputTimeLogJson(result);
6391
+ else outputTimeLogPretty(result);
6392
+ } catch (error) {
6393
+ handleError(error);
6394
+ }
6395
+ }
6396
+ );
6397
+ time.command("list").description("One day's entries for the signed-in user, oldest first, each with its PROPOSED/CONFIRMED status").requiredOption("-d, --date <YYYY-MM-DD>", "The day (local time)").option("-w, --workspace <slug|id>", "Only entries in this workspace (default: all)").option("--proposed", "Only PROPOSED entries").action(async (options, cmd) => {
6398
+ const globalOpts = cmd.optsWithGlobals();
6399
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
6400
+ try {
6401
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(options.date)) {
6402
+ throw new Error(`--date must be YYYY-MM-DD, got "${options.date}".`);
6403
+ }
6404
+ const client = getClient();
6405
+ const workspaceId = options.workspace ? await resolveWorkspaceId(client, options.workspace) : void 0;
6406
+ let entries = await client.time.list(options.date, workspaceId);
6407
+ if (options.proposed) entries = entries.filter((e) => e.status === "PROPOSED");
6408
+ if (useJson) outputTimeEntriesJson(entries, options.date);
6409
+ else outputTimeEntriesPretty(entries, options.date);
6410
+ } catch (error) {
6411
+ handleError(error);
6412
+ }
6413
+ });
6414
+ return time;
6415
+ }
6416
+
5237
6417
  // src/commands/workspaces.ts
5238
- import { Command as Command21 } from "commander";
5239
- import chalk8 from "chalk";
6418
+ import { Command as Command23 } from "commander";
6419
+ import chalk9 from "chalk";
5240
6420
  function createWorkspacesCommand() {
5241
- const workspaces = new Command21("workspaces").description("Manage workspaces");
6421
+ const workspaces = new Command23("workspaces").description("Manage workspaces");
5242
6422
  workspaces.command("list").description("List all workspaces").action(async (_options, cmd) => {
5243
6423
  const globalOpts = cmd.optsWithGlobals();
5244
6424
  const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
@@ -5251,7 +6431,7 @@ function createWorkspacesCommand() {
5251
6431
  const config = getConfig();
5252
6432
  outputWorkspacesPretty(workspaces2);
5253
6433
  if (config.defaultWorkspaceSlug) {
5254
- console.log(chalk8.gray(`Default workspace: ${config.defaultWorkspaceSlug}`));
6434
+ console.log(chalk9.gray(`Default workspace: ${config.defaultWorkspaceSlug}`));
5255
6435
  }
5256
6436
  }
5257
6437
  } catch (error) {
@@ -5264,10 +6444,10 @@ function createWorkspacesCommand() {
5264
6444
  const workspaces2 = await client.workspaces.list();
5265
6445
  const workspace = workspaces2.find((w) => w.slug === slug);
5266
6446
  if (!workspace) {
5267
- console.error(chalk8.red(`Workspace with slug "${slug}" not found.`));
5268
- console.log(chalk8.gray("Available workspaces:"));
6447
+ console.error(chalk9.red(`Workspace with slug "${slug}" not found.`));
6448
+ console.log(chalk9.gray("Available workspaces:"));
5269
6449
  workspaces2.forEach((w) => {
5270
- console.log(chalk8.gray(` - ${w.slug} (${w.name})`));
6450
+ console.log(chalk9.gray(` - ${w.slug} (${w.name})`));
5271
6451
  });
5272
6452
  process.exit(1);
5273
6453
  }
@@ -5275,7 +6455,7 @@ function createWorkspacesCommand() {
5275
6455
  defaultWorkspaceId: workspace.id,
5276
6456
  defaultWorkspaceSlug: workspace.slug
5277
6457
  });
5278
- console.log(chalk8.green(`Default workspace set to: ${workspace.name} (${workspace.slug})`));
6458
+ console.log(chalk9.green(`Default workspace set to: ${workspace.name} (${workspace.slug})`));
5279
6459
  } catch (error) {
5280
6460
  handleError(error);
5281
6461
  }
@@ -5303,20 +6483,20 @@ function createWorkspacesCommand() {
5303
6483
  return;
5304
6484
  }
5305
6485
  if (members.length === 0) {
5306
- console.log(chalk8.gray("No members found."));
6486
+ console.log(chalk9.gray("No members found."));
5307
6487
  return;
5308
6488
  }
5309
- console.log(chalk8.bold(`
6489
+ console.log(chalk9.bold(`
5310
6490
  Members (${members.length})
5311
6491
  `));
5312
6492
  for (const m of members) {
5313
6493
  const label = m.name ?? m.email ?? m.id;
5314
- const via = m.source === "team" ? chalk8.gray(
6494
+ const via = m.source === "team" ? chalk9.gray(
5315
6495
  ` via team${m.teams.length ? `: ${m.teams.map((t) => t.name).join(", ")}` : ""}` + (m.teamRole ? ` [team ${m.teamRole}]` : "")
5316
6496
  ) : "";
5317
- console.log(`${chalk8.bold(label)} ${chalk8.gray(`(${m.role})`)}${via}`);
5318
- if (m.email) console.log(chalk8.gray(` ${m.email}`));
5319
- console.log(chalk8.gray(` mention: ${m.mentionSyntax}`));
6497
+ console.log(`${chalk9.bold(label)} ${chalk9.gray(`(${m.role})`)}${via}`);
6498
+ if (m.email) console.log(chalk9.gray(` ${m.email}`));
6499
+ console.log(chalk9.gray(` mention: ${m.mentionSyntax}`));
5320
6500
  console.log();
5321
6501
  }
5322
6502
  } catch (error) {
@@ -5328,7 +6508,7 @@ Members (${members.length})
5328
6508
  }
5329
6509
 
5330
6510
  // src/commands/goals.ts
5331
- import { Command as Command22 } from "commander";
6511
+ import { Command as Command24 } from "commander";
5332
6512
  var GOAL_STATUSES = [
5333
6513
  "planned",
5334
6514
  "active",
@@ -5440,7 +6620,7 @@ function validateKeyResultUnit(value) {
5440
6620
  return value;
5441
6621
  }
5442
6622
  function createKeyResultsCommand() {
5443
- const kr = new Command22("kr").description(
6623
+ const kr = new Command24("kr").description(
5444
6624
  "Key results on an objective. Ids are CUIDs; --goal takes the objective's integer id."
5445
6625
  );
5446
6626
  kr.command("list").description("List key results").option("--goal <id>", "Objective ID (a number)").option("-w, --workspace <slug|id>", "Workspace (workspace-wide: every member's KRs)").option("--period <period>", "Period, e.g. Q3-2026").option("--status <status>", `Filter by status: ${KEY_RESULT_STATUSES.join(", ")}`).option("--mine", "Narrow a workspace list to key results you own").action(
@@ -5643,7 +6823,7 @@ function createKeyResultsCommand() {
5643
6823
  return kr;
5644
6824
  }
5645
6825
  function createGoalCommentsCommand() {
5646
- const comment = new Command22("comment").description(
6826
+ const comment = new Command24("comment").description(
5647
6827
  "Read and post comments on a goal. Mention teammates with --mention."
5648
6828
  );
5649
6829
  comment.command("list").description("List comments on a goal").requiredOption("--goal <id>", "Goal ID (a number)").action(async (options, cmd) => {
@@ -5738,7 +6918,7 @@ function createGoalCommentsCommand() {
5738
6918
  return comment;
5739
6919
  }
5740
6920
  function createGoalsCommand() {
5741
- const goals = new Command22("goals").description(
6921
+ const goals = new Command24("goals").description(
5742
6922
  "Manage goals (objectives) and their key results. Objectives have integer ids; key results are CUIDs."
5743
6923
  );
5744
6924
  goals.command("list").description(
@@ -6004,7 +7184,7 @@ Re-run with --with-key-results to go ahead, or move them with \`goals kr update
6004
7184
  return goals;
6005
7185
  }
6006
7186
  function createOkrsCommand() {
6007
- const okrs = new Command22("okrs").description(
7187
+ const okrs = new Command24("okrs").description(
6008
7188
  "OKRs \u2014 objectives with their key results nested. Manage the parts with `goals` and `goals kr`."
6009
7189
  );
6010
7190
  okrs.command("list").description(
@@ -6081,39 +7261,54 @@ function createOkrsCommand() {
6081
7261
  return okrs;
6082
7262
  }
6083
7263
 
6084
- // src/index.ts
7264
+ // src/program.ts
6085
7265
  import { readFileSync as readFileSync2 } from "fs";
6086
7266
  import { dirname, join } from "path";
6087
7267
  import { fileURLToPath } from "url";
6088
7268
  var PKG_VERSION = JSON.parse(
6089
7269
  readFileSync2(join(dirname(fileURLToPath(import.meta.url)), "..", "package.json"), "utf-8")
6090
7270
  ).version;
6091
- var program = new Command23();
6092
- program.name("exponential").description(
6093
- [
6094
- "CLI to interact with Exponential productivity app.",
6095
- "",
6096
- "Work-tracking hierarchy:",
6097
- " workspace -> product -> feature -> ticket -> action",
6098
- " epics are workspace-scoped and can group tickets and actions across products."
6099
- ].join("\n")
6100
- ).version(PKG_VERSION).option("--json", "Output as JSON (default when piped)").option("--pretty", "Force pretty-printed output");
6101
- program.addCommand(createAuthCommand());
6102
- program.addCommand(createActionsCommand());
6103
- program.addCommand(createContactsCommand());
6104
- program.addCommand(createDealsCommand());
6105
- program.addCommand(createEpicsCommand());
6106
- program.addCommand(createFeaturesCommand());
6107
- program.addCommand(createLabelsCommand());
6108
- program.addCommand(createMeetingsCommand());
6109
- program.addCommand(createOrganizationsCommand());
6110
- program.addCommand(createPagesCommand());
6111
- program.addCommand(createProductsCommand());
6112
- program.addCommand(createProjectsCommand());
6113
- program.addCommand(createSearchCommand());
6114
- program.addCommand(createTicketsCommand());
6115
- program.addCommand(createWorkspacesCommand());
6116
- program.addCommand(createGoalsCommand());
6117
- program.addCommand(createOkrsCommand());
6118
- program.parse();
7271
+ var CLI_VERSION_FLAGS = "-V, --cli-version";
7272
+ function isBareVersionRequest(args) {
7273
+ return args.includes("--version") && args.every((arg) => arg.startsWith("-"));
7274
+ }
7275
+ function buildProgram() {
7276
+ const program = new Command25();
7277
+ program.name("exponential").description(
7278
+ [
7279
+ "CLI to interact with Exponential productivity app.",
7280
+ "",
7281
+ "Work-tracking hierarchy:",
7282
+ " workspace -> product -> feature -> ticket -> action",
7283
+ " epics are workspace-scoped and can group tickets and actions across products."
7284
+ ].join("\n")
7285
+ ).version(PKG_VERSION, CLI_VERSION_FLAGS, "Output the CLI version").option("--json", "Output as JSON (default when piped)").option("--pretty", "Force pretty-printed output");
7286
+ program.addCommand(createAuthCommand());
7287
+ program.addCommand(createActionsCommand());
7288
+ program.addCommand(createContactsCommand());
7289
+ program.addCommand(createDecisionsCommand());
7290
+ program.addCommand(createDealsCommand());
7291
+ program.addCommand(createEpicsCommand());
7292
+ program.addCommand(createFeaturesCommand());
7293
+ program.addCommand(createLabelsCommand());
7294
+ program.addCommand(createMeetingsCommand());
7295
+ program.addCommand(createOrganizationsCommand());
7296
+ program.addCommand(createPagesCommand());
7297
+ program.addCommand(createProductsCommand());
7298
+ program.addCommand(createProjectsCommand());
7299
+ program.addCommand(createSearchCommand());
7300
+ program.addCommand(createTicketsCommand());
7301
+ program.addCommand(createTimeCommand());
7302
+ program.addCommand(createWorkspacesCommand());
7303
+ program.addCommand(createGoalsCommand());
7304
+ program.addCommand(createOkrsCommand());
7305
+ return program;
7306
+ }
7307
+
7308
+ // src/index.ts
7309
+ if (isBareVersionRequest(process.argv.slice(2))) {
7310
+ console.log(PKG_VERSION);
7311
+ } else {
7312
+ buildProgram().parse();
7313
+ }
6119
7314
  //# sourceMappingURL=index.js.map