exponential-cli 1.10.0 → 1.11.1

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
@@ -1411,6 +1411,343 @@ Scopes (${scopes.length} total)`));
1411
1411
  }
1412
1412
  console.log();
1413
1413
  }
1414
+ function getGoalStatusColor(status) {
1415
+ switch (status) {
1416
+ case "planned":
1417
+ return "blue";
1418
+ case "active":
1419
+ return "yellow";
1420
+ case "completed":
1421
+ return "green";
1422
+ case "archived":
1423
+ return "gray";
1424
+ case "on-hold":
1425
+ return "red";
1426
+ default:
1427
+ return "gray";
1428
+ }
1429
+ }
1430
+ function getKeyResultStatusColor(status) {
1431
+ switch (status) {
1432
+ case "not-started":
1433
+ return "gray";
1434
+ case "on-track":
1435
+ return "green";
1436
+ case "at-risk":
1437
+ return "yellow";
1438
+ case "off-track":
1439
+ return "red";
1440
+ case "achieved":
1441
+ return "green";
1442
+ default:
1443
+ return "gray";
1444
+ }
1445
+ }
1446
+ function keyResultProgress(kr) {
1447
+ const range = kr.targetValue - kr.startValue;
1448
+ if (range <= 0) return 0;
1449
+ return Math.max(
1450
+ 0,
1451
+ Math.min(100, Math.round((kr.currentValue - kr.startValue) / range * 100))
1452
+ );
1453
+ }
1454
+ function transformGoal(goal) {
1455
+ return {
1456
+ id: goal.id,
1457
+ title: goal.title,
1458
+ description: goal.description,
1459
+ status: goal.status,
1460
+ period: goal.period,
1461
+ health: goal.healthOverride ?? goal.health,
1462
+ // Effective progress: manual override, else the key-result mean, else null
1463
+ // for "no signal". Undefined (rather than null) means the server predates
1464
+ // the field — report null rather than inventing a 0.
1465
+ progress: goal.resolvedProgress ?? null,
1466
+ isProgressManual: goal.isProgressManual ?? false,
1467
+ dueDate: goal.dueDate ? new Date(goal.dueDate).toISOString() : null,
1468
+ workspaceId: goal.workspaceId,
1469
+ workspace: goal.workspace ? { id: goal.workspace.id, slug: goal.workspace.slug, name: goal.workspace.name } : null,
1470
+ parentGoalId: goal.parentGoalId,
1471
+ driUserId: goal.driUserId,
1472
+ lifeDomain: goal.lifeDomain ? { id: goal.lifeDomain.id, title: goal.lifeDomain.title } : null,
1473
+ // THE JOIN: actions carry a projectId and goals carry projects, so inverting
1474
+ // this map is what ties a day's work back to an objective. Never elided.
1475
+ projects: goal.projects?.map((p) => ({ id: p.id, name: p.name, slug: p.slug ?? null })) ?? [],
1476
+ childGoals: goal.childGoals?.map((c) => ({ id: c.id, title: c.title, status: c.status })) ?? [],
1477
+ keyResultCount: goal._count?.keyResults ?? goal.keyResults?.length ?? null,
1478
+ createdAt: goal.createdAt ? new Date(goal.createdAt).toISOString() : null,
1479
+ updatedAt: goal.updatedAt ? new Date(goal.updatedAt).toISOString() : null
1480
+ };
1481
+ }
1482
+ function outputGoalJson(goal) {
1483
+ console.log(JSON.stringify(transformGoal(goal), null, 2));
1484
+ }
1485
+ function outputGoalPretty(goal) {
1486
+ console.log(chalk3.gray("\u2500".repeat(50)));
1487
+ const badge = chalk3[getGoalStatusColor(goal.status)](`[${goal.status}]`);
1488
+ console.log(`
1489
+ ${badge} ${chalk3.bold(goal.title)} ${chalk3.gray(`#${goal.id}`)}`);
1490
+ if (goal.period) console.log(` ${chalk3.gray("Period:")} ${goal.period}`);
1491
+ const health = goal.healthOverride ?? goal.health;
1492
+ if (health) console.log(` ${chalk3.gray("Health:")} ${health}`);
1493
+ if (goal.description) {
1494
+ console.log(` ${chalk3.gray("Description:")} ${goal.description.substring(0, 200)}${goal.description.length > 200 ? "..." : ""}`);
1495
+ }
1496
+ if (goal.parentGoal) {
1497
+ console.log(` ${chalk3.gray("Parent:")} ${goal.parentGoal.title} ${chalk3.gray(`#${goal.parentGoal.id}`)}`);
1498
+ }
1499
+ if (goal.projects?.length) {
1500
+ console.log(` ${chalk3.cyan("Projects:")} ${goal.projects.map((p) => p.name).join(", ")}`);
1501
+ }
1502
+ if (goal.childGoals?.length) {
1503
+ console.log(` ${chalk3.cyan("Sub-goals:")} ${goal.childGoals.length}`);
1504
+ }
1505
+ console.log();
1506
+ }
1507
+ function outputGoalsJson(goals) {
1508
+ console.log(JSON.stringify({
1509
+ goals: goals.map(transformGoal),
1510
+ total: goals.length
1511
+ }, null, 2));
1512
+ }
1513
+ function outputGoalsPretty(goals) {
1514
+ if (goals.length === 0) {
1515
+ console.log(chalk3.gray("No goals found."));
1516
+ return;
1517
+ }
1518
+ console.log(chalk3.bold(`
1519
+ Goals (${goals.length} total)`));
1520
+ console.log(chalk3.gray("\u2500".repeat(50)));
1521
+ for (const g of goals) {
1522
+ const badge = chalk3[getGoalStatusColor(g.status)](`[${g.status}]`);
1523
+ const period = g.period ? chalk3.gray(` ${g.period}`) : "";
1524
+ const krs = g._count?.keyResults ? chalk3.gray(` \u2014 ${g._count.keyResults} KRs`) : "";
1525
+ console.log(` ${badge}${period} ${chalk3.bold(g.title)}${krs}`);
1526
+ console.log(chalk3.gray(` ID: ${g.id}`));
1527
+ }
1528
+ console.log();
1529
+ }
1530
+ function transformGoalTree(node) {
1531
+ return {
1532
+ ...transformGoal(node),
1533
+ keyResults: node.keyResults?.map((kr) => ({
1534
+ id: kr.id,
1535
+ status: kr.status,
1536
+ currentValue: kr.currentValue,
1537
+ targetValue: kr.targetValue
1538
+ })) ?? [],
1539
+ children: node.childGoals?.map(transformGoalTree) ?? []
1540
+ };
1541
+ }
1542
+ function outputGoalTreeJson(nodes) {
1543
+ console.log(JSON.stringify({
1544
+ goals: nodes.map(transformGoalTree),
1545
+ total: nodes.length
1546
+ }, null, 2));
1547
+ }
1548
+ function outputGoalTreePretty(nodes) {
1549
+ if (nodes.length === 0) {
1550
+ console.log(chalk3.gray("No goals found."));
1551
+ return;
1552
+ }
1553
+ console.log(chalk3.bold("\nGoal tree"));
1554
+ console.log(chalk3.gray("\u2500".repeat(50)));
1555
+ const walk = (node, depth) => {
1556
+ const pad = " ".repeat(depth + 1);
1557
+ const badge = chalk3[getGoalStatusColor(node.status)](`[${node.status}]`);
1558
+ const period = node.period ? chalk3.gray(` ${node.period}`) : "";
1559
+ const krs = node.keyResults?.length ? chalk3.gray(` \u2014 ${node.keyResults.length} KRs`) : "";
1560
+ console.log(`${pad}${depth > 0 ? "\u2514 " : ""}${badge}${period} ${chalk3.bold(node.title)} ${chalk3.gray(`#${node.id}`)}${krs}`);
1561
+ for (const child of node.childGoals ?? []) {
1562
+ walk(child, depth + 1);
1563
+ }
1564
+ };
1565
+ for (const node of nodes) walk(node, 0);
1566
+ console.log();
1567
+ }
1568
+ function outputGoalPeriodsJson(periods) {
1569
+ console.log(JSON.stringify({ periods, total: periods.length }, null, 2));
1570
+ }
1571
+ function outputGoalPeriodsPretty(periods) {
1572
+ console.log(chalk3.bold(`
1573
+ Periods (${periods.length} total)`));
1574
+ console.log(chalk3.gray("\u2500".repeat(50)));
1575
+ for (const p of periods) {
1576
+ console.log(` ${chalk3.bold(p.value)} ${chalk3.gray(p.label)}`);
1577
+ }
1578
+ console.log();
1579
+ }
1580
+ function outputGoalStatsJson(stats) {
1581
+ console.log(JSON.stringify({
1582
+ ...stats,
1583
+ periodEndDate: stats.periodEndDate ? new Date(stats.periodEndDate).toISOString() : null
1584
+ }, null, 2));
1585
+ }
1586
+ function outputGoalStatsPretty(stats) {
1587
+ console.log(chalk3.bold("\nOKR stats"));
1588
+ console.log(chalk3.gray("\u2500".repeat(50)));
1589
+ console.log(` ${chalk3.gray("Objectives:")} ${stats.totalObjectives}`);
1590
+ console.log(` ${chalk3.gray("Key results:")} ${stats.totalKeyResults} (${stats.completedKeyResults} achieved)`);
1591
+ console.log(
1592
+ ` ${chalk3.gray("Breakdown:")} ${chalk3.green(`${stats.statusBreakdown.onTrack} on-track`)}, ${chalk3.yellow(`${stats.statusBreakdown.atRisk} at-risk`)}, ${chalk3.red(`${stats.statusBreakdown.offTrack} off-track`)}, ${chalk3.green(`${stats.statusBreakdown.achieved} achieved`)}`
1593
+ );
1594
+ console.log(` ${chalk3.gray("Avg progress:")} ${stats.averageProgress}%`);
1595
+ if (stats.averageConfidence != null) {
1596
+ console.log(` ${chalk3.gray("Avg confidence:")} ${stats.averageConfidence}%`);
1597
+ }
1598
+ if (stats.periodEndDate) {
1599
+ console.log(` ${chalk3.gray("Period ends:")} ${new Date(stats.periodEndDate).toISOString().slice(0, 10)}`);
1600
+ }
1601
+ console.log();
1602
+ }
1603
+ function lastCheckInAt(kr) {
1604
+ if (!kr.checkIns?.length) return null;
1605
+ const latest = kr.checkIns.reduce(
1606
+ (newest, c) => new Date(c.createdAt) > new Date(newest.createdAt) ? c : newest
1607
+ );
1608
+ return new Date(latest.createdAt).toISOString();
1609
+ }
1610
+ function transformKeyResult(kr) {
1611
+ return {
1612
+ id: kr.id,
1613
+ goalId: kr.goalId,
1614
+ goal: kr.goal ? { id: kr.goal.id, title: kr.goal.title } : null,
1615
+ title: kr.title,
1616
+ description: kr.description,
1617
+ status: kr.statusOverride ?? kr.status,
1618
+ statusOverride: kr.statusOverride ?? null,
1619
+ startValue: kr.startValue,
1620
+ currentValue: kr.currentValue,
1621
+ targetValue: kr.targetValue,
1622
+ progress: keyResultProgress(kr),
1623
+ unit: kr.unit,
1624
+ unitLabel: kr.unitLabel ?? null,
1625
+ period: kr.period,
1626
+ periodEnd: kr.periodEnd ? new Date(kr.periodEnd).toISOString() : null,
1627
+ lastCheckInAt: lastCheckInAt(kr),
1628
+ confidence: kr.confidence ?? null,
1629
+ driUserId: kr.driUserId,
1630
+ workspaceId: kr.workspaceId,
1631
+ projects: kr.projects?.map((l) => ({ id: l.projectId, name: l.project?.name ?? null })) ?? [],
1632
+ features: kr.features?.map((l) => ({ id: l.featureId, name: l.feature?.name ?? null })) ?? [],
1633
+ createdAt: kr.createdAt ? new Date(kr.createdAt).toISOString() : null,
1634
+ updatedAt: kr.updatedAt ? new Date(kr.updatedAt).toISOString() : null
1635
+ };
1636
+ }
1637
+ function outputKeyResultJson(kr) {
1638
+ console.log(JSON.stringify(transformKeyResult(kr), null, 2));
1639
+ }
1640
+ function outputKeyResultPretty(kr) {
1641
+ const status = kr.statusOverride ?? kr.status;
1642
+ const badge = chalk3[getKeyResultStatusColor(status)](`[${status}]`);
1643
+ console.log(` ${badge} ${chalk3.bold(kr.title)}`);
1644
+ console.log(
1645
+ chalk3.gray(
1646
+ ` ${kr.currentValue}/${kr.targetValue} ${kr.unit} (${keyResultProgress(kr)}%)${kr.period ? ` \xB7 ${kr.period}` : ""} \xB7 objective #${kr.goalId}`
1647
+ )
1648
+ );
1649
+ console.log(chalk3.gray(` ID: ${kr.id}`));
1650
+ if (kr.projects?.length) {
1651
+ console.log(chalk3.gray(` Projects: ${kr.projects.map((l) => l.project?.name ?? l.projectId).join(", ")}`));
1652
+ }
1653
+ if (kr.features?.length) {
1654
+ console.log(chalk3.gray(` Features: ${kr.features.map((l) => l.feature?.name ?? l.featureId).join(", ")}`));
1655
+ }
1656
+ }
1657
+ function outputKeyResultsJson(keyResults) {
1658
+ console.log(JSON.stringify({
1659
+ keyResults: keyResults.map(transformKeyResult),
1660
+ total: keyResults.length
1661
+ }, null, 2));
1662
+ }
1663
+ function outputKeyResultsPretty(keyResults) {
1664
+ if (keyResults.length === 0) {
1665
+ console.log(chalk3.gray("No key results found."));
1666
+ return;
1667
+ }
1668
+ console.log(chalk3.bold(`
1669
+ Key results (${keyResults.length} total)`));
1670
+ console.log(chalk3.gray("\u2500".repeat(50)));
1671
+ for (const kr of keyResults) {
1672
+ outputKeyResultPretty(kr);
1673
+ }
1674
+ console.log();
1675
+ }
1676
+ function outputCheckInJson(checkIn) {
1677
+ console.log(JSON.stringify({
1678
+ id: checkIn.id,
1679
+ keyResultId: checkIn.keyResultId,
1680
+ previousValue: checkIn.previousValue,
1681
+ newValue: checkIn.newValue,
1682
+ notes: checkIn.notes,
1683
+ createdAt: checkIn.createdAt ? new Date(checkIn.createdAt).toISOString() : null
1684
+ }, null, 2));
1685
+ }
1686
+ function outputCheckInPretty(checkIn) {
1687
+ console.log(` ${chalk3.gray("Value:")} ${checkIn.previousValue} \u2192 ${chalk3.bold(String(checkIn.newValue))}`);
1688
+ if (checkIn.notes) console.log(` ${chalk3.gray("Note:")} ${checkIn.notes}`);
1689
+ console.log(chalk3.gray(` Check-in ID: ${checkIn.id}`));
1690
+ console.log();
1691
+ }
1692
+ function transformObjective(objective) {
1693
+ return {
1694
+ id: objective.id,
1695
+ title: objective.title,
1696
+ description: objective.description,
1697
+ status: objective.status,
1698
+ period: objective.period,
1699
+ health: objective.health,
1700
+ progress: objective.progress,
1701
+ statusCounts: objective.statusCounts,
1702
+ workspaceId: objective.workspaceId,
1703
+ workspace: objective.workspace ? {
1704
+ id: objective.workspace.id,
1705
+ slug: objective.workspace.slug,
1706
+ name: objective.workspace.name
1707
+ } : null,
1708
+ parentGoalId: objective.parentGoalId,
1709
+ keyResults: objective.keyResults.map(transformKeyResult)
1710
+ };
1711
+ }
1712
+ function outputObjectivesJson(objectives) {
1713
+ const totalKeyResults = objectives.reduce((n, o) => n + o.keyResults.length, 0);
1714
+ console.log(JSON.stringify({
1715
+ objectives: objectives.map(transformObjective),
1716
+ total: objectives.length,
1717
+ totalKeyResults
1718
+ }, null, 2));
1719
+ }
1720
+ function outputObjectivesPretty(objectives) {
1721
+ if (objectives.length === 0) {
1722
+ console.log(chalk3.gray("No OKRs found."));
1723
+ return;
1724
+ }
1725
+ const totalKeyResults = objectives.reduce((n, o) => n + o.keyResults.length, 0);
1726
+ console.log(
1727
+ chalk3.bold(`
1728
+ OKRs (${objectives.length} objectives, ${totalKeyResults} key results)`)
1729
+ );
1730
+ console.log(chalk3.gray("\u2500".repeat(50)));
1731
+ for (const o of objectives) {
1732
+ const badge = chalk3[getGoalStatusColor(o.status)](`[${o.status}]`);
1733
+ const period = o.period ? chalk3.gray(` ${o.period}`) : "";
1734
+ console.log(
1735
+ `
1736
+ ${badge}${period} ${chalk3.bold(o.title)} ${chalk3.gray(`#${o.id}`)} ${chalk3.gray(`${o.progress}%`)}`
1737
+ );
1738
+ if (o.keyResults.length === 0) {
1739
+ console.log(chalk3.gray(" (no key results)"));
1740
+ continue;
1741
+ }
1742
+ for (const kr of o.keyResults) {
1743
+ const krStatus = kr.statusOverride ?? kr.status;
1744
+ const krBadge = chalk3[getKeyResultStatusColor(krStatus)](`[${krStatus}]`);
1745
+ console.log(` ${krBadge} ${kr.title} ${chalk3.gray(`${kr.currentValue}/${kr.targetValue} ${kr.unit}`)}`);
1746
+ console.log(chalk3.gray(` ID: ${kr.id}`));
1747
+ }
1748
+ }
1749
+ console.log();
1750
+ }
1414
1751
 
1415
1752
  // src/commands/actions.ts
1416
1753
  var VALID_PRIORITIES = [
@@ -2114,6 +2451,9 @@ import { Command as Command5 } from "commander";
2114
2451
 
2115
2452
  // src/utils/resolve.ts
2116
2453
  async function resolveWorkspaceId(client, slugOrId) {
2454
+ return (await resolveWorkspace(client, slugOrId)).id;
2455
+ }
2456
+ async function resolveWorkspace(client, slugOrId) {
2117
2457
  const config = getConfig();
2118
2458
  const candidate = slugOrId ?? config.defaultWorkspaceSlug ?? config.defaultWorkspaceId;
2119
2459
  if (!candidate) {
@@ -2123,9 +2463,9 @@ async function resolveWorkspaceId(client, slugOrId) {
2123
2463
  }
2124
2464
  const workspaces = await client.workspaces.list();
2125
2465
  const byId = workspaces.find((w) => w.id === candidate);
2126
- if (byId) return byId.id;
2466
+ if (byId) return byId;
2127
2467
  const bySlug = workspaces.find((w) => w.slug === candidate);
2128
- if (bySlug) return bySlug.id;
2468
+ if (bySlug) return bySlug;
2129
2469
  const known = workspaces.map((w) => w.slug).join(", ");
2130
2470
  throw new Error(
2131
2471
  `Workspace "${candidate}" not found. Available: ${known || "(none)"}`
@@ -3871,18 +4211,8 @@ function createTicketsCommand() {
3871
4211
  );
3872
4212
  }
3873
4213
  const client = getClient();
3874
- let workspaceId;
3875
- let productId;
3876
- if (options.product) {
3877
- workspaceId = await resolveWorkspaceId(client, options.workspace);
3878
- productId = await resolveProductId(
3879
- client,
3880
- workspaceId,
3881
- options.product
3882
- );
3883
- }
3884
- const list = await client.tickets.list({
3885
- productId,
4214
+ const workspaceId = await resolveWorkspaceId(client, options.workspace);
4215
+ const filters = {
3886
4216
  status,
3887
4217
  type,
3888
4218
  featureId: options.feature,
@@ -3890,7 +4220,27 @@ function createTicketsCommand() {
3890
4220
  assigneeId: options.assignee,
3891
4221
  prUrl: options.pr,
3892
4222
  branchName: options.branch
3893
- });
4223
+ };
4224
+ let list;
4225
+ if (options.product) {
4226
+ const productId = await resolveProductId(
4227
+ client,
4228
+ workspaceId,
4229
+ options.product
4230
+ );
4231
+ list = await client.tickets.list({ productId, ...filters });
4232
+ } else {
4233
+ const products = await client.products.list(workspaceId);
4234
+ const settled = await Promise.allSettled(
4235
+ products.map((p) => client.tickets.list({ productId: p.id, ...filters }))
4236
+ );
4237
+ list = settled.flatMap((r) => r.status === "fulfilled" ? r.value : []);
4238
+ settled.forEach((r, i) => {
4239
+ if (r.status !== "rejected") return;
4240
+ const reason = r.reason instanceof Error ? r.reason.message : String(r.reason);
4241
+ console.error(`Warning: skipped product ${products[i].slug}: ${reason}`);
4242
+ });
4243
+ }
3894
4244
  let filtered = list;
3895
4245
  if (options.label.length > 0) {
3896
4246
  const labelIds = new Set(
@@ -4338,19 +4688,313 @@ Members (${members.length})
4338
4688
 
4339
4689
  // src/commands/goals.ts
4340
4690
  import { Command as Command21 } from "commander";
4691
+ var GOAL_STATUSES = [
4692
+ "planned",
4693
+ "active",
4694
+ "completed",
4695
+ "archived",
4696
+ "on-hold"
4697
+ ];
4698
+ var GOAL_WRITABLE_STATUSES = [
4699
+ "planned",
4700
+ "active",
4701
+ "completed",
4702
+ "archived"
4703
+ ];
4704
+ var KEY_RESULT_STATUSES = [
4705
+ "not-started",
4706
+ "on-track",
4707
+ "at-risk",
4708
+ "off-track",
4709
+ "achieved"
4710
+ ];
4711
+ var KEY_RESULT_UNITS = [
4712
+ "percent",
4713
+ "count",
4714
+ "currency",
4715
+ "hours",
4716
+ "custom"
4717
+ ];
4718
+ function currentPeriod() {
4719
+ const now = /* @__PURE__ */ new Date();
4720
+ return `Q${Math.floor(now.getMonth() / 3) + 1}-${now.getFullYear()}`;
4721
+ }
4722
+ async function targetWorkspaces(client, options) {
4723
+ if (options.allWorkspaces) {
4724
+ if (options.workspace) {
4725
+ throw new Error("--workspace and --all-workspaces are mutually exclusive.");
4726
+ }
4727
+ return await client.workspaces.list();
4728
+ }
4729
+ return [await resolveWorkspace(client, options.workspace)];
4730
+ }
4731
+ async function fanOutByWorkspace(workspaces, read) {
4732
+ const settled = await Promise.allSettled(workspaces.map(read));
4733
+ const items = [];
4734
+ const skipped = [];
4735
+ settled.forEach((result, i) => {
4736
+ if (result.status === "fulfilled") {
4737
+ items.push(...result.value);
4738
+ return;
4739
+ }
4740
+ const reason = result.reason instanceof Error ? result.reason.message : String(result.reason);
4741
+ skipped.push(`${workspaces[i].slug}: ${reason}`);
4742
+ });
4743
+ if (skipped.length === workspaces.length) {
4744
+ const [first] = settled.filter((r) => r.status === "rejected");
4745
+ throw first.reason;
4746
+ }
4747
+ for (const line of skipped) {
4748
+ console.error(`Warning: skipped workspace ${line}`);
4749
+ }
4750
+ return items;
4751
+ }
4341
4752
  function parseGoalId(value) {
4342
4753
  const id = Number(value);
4343
4754
  if (!Number.isInteger(id)) {
4344
4755
  throw new Error(
4345
- `Goal id must be a whole number, got "${value}". Find one with \`exponential search "<goal title>"\`.`
4756
+ `Goal id must be a whole number, got "${value}". List them with \`exponential goals list\`.`
4346
4757
  );
4347
4758
  }
4348
4759
  return id;
4349
4760
  }
4350
- function createGoalsCommand() {
4351
- const goals = new Command21("goals").description(
4352
- 'Work with goals. Find goal ids with `exponential search "<title>"`.'
4761
+ function parseNumber(value, label) {
4762
+ const n = Number(value);
4763
+ if (!Number.isFinite(n)) {
4764
+ throw new Error(`${label} must be a number, got "${value}".`);
4765
+ }
4766
+ return n;
4767
+ }
4768
+ function validateGoalStatus(value) {
4769
+ if (!value) return void 0;
4770
+ if (!GOAL_STATUSES.includes(value)) {
4771
+ throw new Error(`Invalid status "${value}". Valid: ${GOAL_STATUSES.join(", ")}`);
4772
+ }
4773
+ return value;
4774
+ }
4775
+ function validateWritableGoalStatus(value) {
4776
+ if (!value) return void 0;
4777
+ if (!GOAL_WRITABLE_STATUSES.includes(value)) {
4778
+ const extra = value === "on-hold" ? " Use `goals set-status` for on-hold." : "";
4779
+ throw new Error(
4780
+ `Invalid status "${value}". Valid: ${GOAL_WRITABLE_STATUSES.join(", ")}.${extra}`
4781
+ );
4782
+ }
4783
+ return value;
4784
+ }
4785
+ function validateKeyResultStatus(value) {
4786
+ if (!value) return void 0;
4787
+ if (!KEY_RESULT_STATUSES.includes(value)) {
4788
+ throw new Error(
4789
+ `Invalid status "${value}". Valid: ${KEY_RESULT_STATUSES.join(", ")}`
4790
+ );
4791
+ }
4792
+ return value;
4793
+ }
4794
+ function validateKeyResultUnit(value) {
4795
+ if (!value) return void 0;
4796
+ if (!KEY_RESULT_UNITS.includes(value)) {
4797
+ throw new Error(`Invalid unit "${value}". Valid: ${KEY_RESULT_UNITS.join(", ")}`);
4798
+ }
4799
+ return value;
4800
+ }
4801
+ function createKeyResultsCommand() {
4802
+ const kr = new Command21("kr").description(
4803
+ "Key results on an objective. Ids are CUIDs; --goal takes the objective's integer id."
4804
+ );
4805
+ 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(
4806
+ async (options, cmd) => {
4807
+ const globalOpts = cmd.optsWithGlobals();
4808
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
4809
+ try {
4810
+ const status = validateKeyResultStatus(options.status);
4811
+ const client = getClient();
4812
+ const workspaceId = options.workspace ? await resolveWorkspaceId(client, options.workspace) : void 0;
4813
+ const list = await client.goals.keyResults.list({
4814
+ workspaceId,
4815
+ goalId: options.goal ? parseGoalId(options.goal) : void 0,
4816
+ period: options.period,
4817
+ status,
4818
+ onlyMine: options.mine
4819
+ });
4820
+ if (useJson) outputKeyResultsJson(list);
4821
+ else outputKeyResultsPretty(list);
4822
+ } catch (error) {
4823
+ handleError(error, useJson);
4824
+ }
4825
+ }
4826
+ );
4827
+ kr.command("get <id>").description("Get a key result by CUID").action(async (id, _options, cmd) => {
4828
+ const globalOpts = cmd.optsWithGlobals();
4829
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
4830
+ try {
4831
+ const keyResult = await getClient().goals.keyResults.get(id);
4832
+ if (useJson) outputKeyResultJson(keyResult);
4833
+ else outputKeyResultPretty(keyResult);
4834
+ } catch (error) {
4835
+ handleError(error, useJson);
4836
+ }
4837
+ });
4838
+ kr.command("create").description("Create a key result on an objective").requiredOption("--goal <id>", "Objective ID (a number)").requiredOption("-t, --title <text>", "Key result title").requiredOption("--target <n>", "Target value").option("--start <n>", "Starting value (default 0)").option("--current <n>", "Current value (defaults to the start value)").option("--unit <unit>", `Unit: ${KEY_RESULT_UNITS.join(", ")}`).option("--period <period>", "Period, e.g. Q3-2026 (defaults to the objective's)").option("-d, --description <text>", "Description").option("--dri <userId>", "Directly responsible individual (user ID)").action(
4839
+ async (options, cmd) => {
4840
+ const globalOpts = cmd.optsWithGlobals();
4841
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
4842
+ try {
4843
+ const unit = validateKeyResultUnit(options.unit);
4844
+ const client = getClient();
4845
+ const goalId = parseGoalId(options.goal);
4846
+ const startValue = options.start ? parseNumber(options.start, "--start") : void 0;
4847
+ let period = options.period;
4848
+ let workspaceId;
4849
+ if (!period) {
4850
+ const goal = await client.goals.get(goalId);
4851
+ if (!goal.period) {
4852
+ throw new Error(
4853
+ `Objective #${goalId} has no period, so --period is required. See \`exponential goals periods\`.`
4854
+ );
4855
+ }
4856
+ period = goal.period;
4857
+ workspaceId = goal.workspaceId ?? void 0;
4858
+ }
4859
+ const keyResult = await client.goals.keyResults.create({
4860
+ goalId,
4861
+ workspaceId,
4862
+ title: options.title,
4863
+ targetValue: parseNumber(options.target, "--target"),
4864
+ startValue,
4865
+ currentValue: options.current ? parseNumber(options.current, "--current") : startValue,
4866
+ unit,
4867
+ period,
4868
+ description: options.description,
4869
+ driUserId: options.dri
4870
+ });
4871
+ if (useJson) outputKeyResultJson(keyResult);
4872
+ else {
4873
+ console.log("\n\u2713 Key result created");
4874
+ outputKeyResultPretty(keyResult);
4875
+ }
4876
+ } catch (error) {
4877
+ handleError(error, useJson);
4878
+ }
4879
+ }
4353
4880
  );
4881
+ kr.command("update").description("Update a key result (only the flags you pass are written)").requiredOption("--id <cuid>", "Key result CUID").option("-t, --title <text>", "New title").option("-d, --description <text>", "New description").option("--current <n>", "Current value (prefer `kr checkin` \u2014 it records history)").option("--target <n>", "Target value").option("--start <n>", "Starting value").option("--unit <unit>", `Unit: ${KEY_RESULT_UNITS.join(", ")}`).option("--status <status>", `Status: ${KEY_RESULT_STATUSES.join(", ")}`).option("--confidence <n>", "Confidence 0-100").option("--dri <userId>", "Directly responsible individual (user ID)").option("--goal <id>", "Move the key result to a different objective").action(
4882
+ async (options, cmd) => {
4883
+ const globalOpts = cmd.optsWithGlobals();
4884
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
4885
+ try {
4886
+ const status = validateKeyResultStatus(options.status);
4887
+ const unit = validateKeyResultUnit(options.unit);
4888
+ const keyResult = await getClient().goals.keyResults.update({
4889
+ id: options.id,
4890
+ title: options.title,
4891
+ description: options.description,
4892
+ currentValue: options.current ? parseNumber(options.current, "--current") : void 0,
4893
+ targetValue: options.target ? parseNumber(options.target, "--target") : void 0,
4894
+ startValue: options.start ? parseNumber(options.start, "--start") : void 0,
4895
+ unit,
4896
+ status,
4897
+ confidence: options.confidence ? parseNumber(options.confidence, "--confidence") : void 0,
4898
+ driUserId: options.dri,
4899
+ goalId: options.goal ? parseGoalId(options.goal) : void 0
4900
+ });
4901
+ if (useJson) outputKeyResultJson(keyResult);
4902
+ else {
4903
+ console.log("\n\u2713 Key result updated");
4904
+ outputKeyResultPretty(keyResult);
4905
+ }
4906
+ } catch (error) {
4907
+ handleError(error, useJson);
4908
+ }
4909
+ }
4910
+ );
4911
+ kr.command("checkin").description(
4912
+ "Record a progress check-in. Moves the value AND re-derives the status from where it lands between start and target."
4913
+ ).requiredOption("--id <cuid>", "Key result CUID").requiredOption("--value <n>", "New current value").option("--note <text>", "Check-in note").action(
4914
+ async (options, cmd) => {
4915
+ const globalOpts = cmd.optsWithGlobals();
4916
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
4917
+ try {
4918
+ const checkIn = await getClient().goals.keyResults.checkIn({
4919
+ id: options.id,
4920
+ value: parseNumber(options.value, "--value"),
4921
+ note: options.note
4922
+ });
4923
+ if (useJson) outputCheckInJson(checkIn);
4924
+ else {
4925
+ console.log("\n\u2713 Check-in recorded");
4926
+ outputCheckInPretty(checkIn);
4927
+ }
4928
+ } catch (error) {
4929
+ handleError(error, useJson);
4930
+ }
4931
+ }
4932
+ );
4933
+ kr.command("delete").description("Delete a key result").requiredOption("--id <cuid>", "Key result CUID").action(async (options, cmd) => {
4934
+ const globalOpts = cmd.optsWithGlobals();
4935
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
4936
+ try {
4937
+ await getClient().goals.keyResults.delete(options.id);
4938
+ if (useJson) {
4939
+ console.log(JSON.stringify({ deleted: true, id: options.id }, null, 2));
4940
+ } else {
4941
+ console.log("\u2713 Key result deleted");
4942
+ }
4943
+ } catch (error) {
4944
+ handleError(error, useJson);
4945
+ }
4946
+ });
4947
+ kr.command("link").description("Link executing work (a project or a feature) to a key result").requiredOption("--id <cuid>", "Key result CUID").option("--project <cuid>", "Project CUID").option("--feature <cuid>", "Feature CUID").action(
4948
+ async (options, cmd) => {
4949
+ const globalOpts = cmd.optsWithGlobals();
4950
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
4951
+ try {
4952
+ if (!options.project === !options.feature) {
4953
+ throw new Error("Pass exactly one of --project or --feature.");
4954
+ }
4955
+ const client = getClient();
4956
+ const result = options.project ? await client.goals.keyResults.linkProject({
4957
+ keyResultId: options.id,
4958
+ projectId: options.project
4959
+ }) : await client.goals.keyResults.linkFeature({
4960
+ keyResultId: options.id,
4961
+ featureId: options.feature
4962
+ });
4963
+ if (useJson) console.log(JSON.stringify(result, null, 2));
4964
+ else console.log(`
4965
+ \u2713 ${options.project ? "Project" : "Feature"} linked`);
4966
+ } catch (error) {
4967
+ handleError(error, useJson);
4968
+ }
4969
+ }
4970
+ );
4971
+ kr.command("unlink").description("Unlink a project or feature from a key result").requiredOption("--id <cuid>", "Key result CUID").option("--project <cuid>", "Project CUID").option("--feature <cuid>", "Feature CUID").action(
4972
+ async (options, cmd) => {
4973
+ const globalOpts = cmd.optsWithGlobals();
4974
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
4975
+ try {
4976
+ if (!options.project === !options.feature) {
4977
+ throw new Error("Pass exactly one of --project or --feature.");
4978
+ }
4979
+ const client = getClient();
4980
+ const result = options.project ? await client.goals.keyResults.unlinkProject({
4981
+ keyResultId: options.id,
4982
+ projectId: options.project
4983
+ }) : await client.goals.keyResults.unlinkFeature({
4984
+ keyResultId: options.id,
4985
+ featureId: options.feature
4986
+ });
4987
+ if (useJson) console.log(JSON.stringify(result, null, 2));
4988
+ else console.log(`
4989
+ \u2713 ${options.project ? "Project" : "Feature"} unlinked`);
4990
+ } catch (error) {
4991
+ handleError(error, useJson);
4992
+ }
4993
+ }
4994
+ );
4995
+ return kr;
4996
+ }
4997
+ function createGoalCommentsCommand() {
4354
4998
  const comment = new Command21("comment").description(
4355
4999
  "Read and post comments on a goal. Mention teammates with --mention."
4356
5000
  );
@@ -4443,9 +5087,302 @@ function createGoalsCommand() {
4443
5087
  handleError(error, useJson);
4444
5088
  }
4445
5089
  });
4446
- goals.addCommand(comment);
5090
+ return comment;
5091
+ }
5092
+ function createGoalsCommand() {
5093
+ const goals = new Command21("goals").description(
5094
+ "Manage goals (objectives) and their key results. Objectives have integer ids; key results are CUIDs."
5095
+ );
5096
+ goals.command("list").description(
5097
+ "List objectives, each with the projects linked to it \u2014 the join that ties an action (which carries a projectId) back to a goal."
5098
+ ).option("-w, --workspace <slug|id>", "Workspace slug or ID (defaults to your default workspace)").option("--all-workspaces", "List across every workspace you belong to").option("--period <period>", "Filter by period, e.g. Q3-2026").option("--status <status>", `Filter by status: ${GOAL_STATUSES.join(", ")}`).option("--tree", "Render the parent/child cascade instead of a flat list").action(
5099
+ async (options, cmd) => {
5100
+ const globalOpts = cmd.optsWithGlobals();
5101
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
5102
+ try {
5103
+ const status = validateGoalStatus(options.status);
5104
+ const client = getClient();
5105
+ const workspaces = await targetWorkspaces(client, options);
5106
+ if (options.tree) {
5107
+ const trees = await fanOutByWorkspace(
5108
+ workspaces,
5109
+ (w) => client.goals.tree({ workspaceId: w.id, status })
5110
+ );
5111
+ const filtered = options.period ? trees.filter((g) => g.period === options.period) : trees;
5112
+ if (useJson) outputGoalTreeJson(filtered);
5113
+ else outputGoalTreePretty(filtered);
5114
+ return;
5115
+ }
5116
+ const list = await fanOutByWorkspace(workspaces, async (workspace) => {
5117
+ const goals2 = await client.goals.list({
5118
+ workspaceId: workspace.id,
5119
+ period: options.period,
5120
+ status
5121
+ });
5122
+ return goals2.map((g) => ({ ...g, workspace: g.workspace ?? workspace }));
5123
+ });
5124
+ if (useJson) outputGoalsJson(list);
5125
+ else outputGoalsPretty(list);
5126
+ } catch (error) {
5127
+ handleError(error, useJson);
5128
+ }
5129
+ }
5130
+ );
5131
+ goals.command("get <id>").description("Get an objective by its integer ID").action(async (id, _options, cmd) => {
5132
+ const globalOpts = cmd.optsWithGlobals();
5133
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
5134
+ try {
5135
+ const goal = await getClient().goals.get(parseGoalId(id));
5136
+ if (useJson) outputGoalJson(goal);
5137
+ else outputGoalPretty(goal);
5138
+ } catch (error) {
5139
+ handleError(error, useJson);
5140
+ }
5141
+ });
5142
+ goals.command("create").description("Create an objective").requiredOption("-t, --title <text>", "Objective title").option("-w, --workspace <slug|id>", "Workspace slug or ID (defaults to your default workspace)").option("--period <period>", "Period, e.g. Q3-2026 or Annual-2026").option("--status <status>", `Status: ${GOAL_WRITABLE_STATUSES.join(", ")}`).option("-d, --description <text>", "Description (markdown supported)").option("--why <text>", "Why this goal matters").option("--dri <userId>", "Directly responsible individual (user ID)").option("--parent <id>", "Parent objective ID to nest under (max depth 5)").option("--project <cuid>", "Link a project on creation").action(
5143
+ async (options, cmd) => {
5144
+ const globalOpts = cmd.optsWithGlobals();
5145
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
5146
+ try {
5147
+ const status = validateWritableGoalStatus(options.status);
5148
+ const client = getClient();
5149
+ const workspaceId = await resolveWorkspaceId(client, options.workspace);
5150
+ const goal = await client.goals.create({
5151
+ title: options.title,
5152
+ workspaceId,
5153
+ period: options.period,
5154
+ status,
5155
+ description: options.description,
5156
+ whyThisGoal: options.why,
5157
+ driUserId: options.dri,
5158
+ parentGoalId: options.parent ? parseGoalId(options.parent) : void 0,
5159
+ projectId: options.project
5160
+ });
5161
+ if (useJson) outputGoalJson(goal);
5162
+ else {
5163
+ console.log("\n\u2713 Goal created");
5164
+ outputGoalPretty(goal);
5165
+ }
5166
+ } catch (error) {
5167
+ handleError(error, useJson);
5168
+ }
5169
+ }
5170
+ );
5171
+ goals.command("update").description(
5172
+ "Update an objective. Only the flags you pass are written \u2014 everything else is left exactly as it is. For a status change use `set-status`; to re-parent use `reparent`."
5173
+ ).requiredOption("--id <n>", "Objective ID (a number)").option("-t, --title <text>", "New title").option("-d, --description <text>", 'New description ("none" clears it)').option("--why <text>", 'Why this goal matters ("none" clears it)').option("--period <period>", 'New period ("none" clears it)').option("--dri <userId>", "Directly responsible individual (user ID)").option("--status <status>", `Status: ${GOAL_WRITABLE_STATUSES.join(", ")}`).option("--project <cuid>", 'Replace the project links with this project ("none" clears them)').action(
5174
+ async (options, cmd) => {
5175
+ const globalOpts = cmd.optsWithGlobals();
5176
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
5177
+ try {
5178
+ const status = validateWritableGoalStatus(options.status);
5179
+ const clearable = (value) => value === void 0 ? void 0 : value === "none" ? null : value;
5180
+ const goal = await getClient().goals.update({
5181
+ id: parseGoalId(options.id),
5182
+ title: options.title,
5183
+ description: clearable(options.description),
5184
+ whyThisGoal: clearable(options.why),
5185
+ period: clearable(options.period),
5186
+ driUserId: options.dri,
5187
+ status,
5188
+ projectId: clearable(options.project)
5189
+ });
5190
+ if (useJson) outputGoalJson(goal);
5191
+ else {
5192
+ console.log("\n\u2713 Goal updated");
5193
+ outputGoalPretty(goal);
5194
+ }
5195
+ } catch (error) {
5196
+ handleError(error, useJson);
5197
+ }
5198
+ }
5199
+ );
5200
+ goals.command("set-status").description(
5201
+ "Set an objective's status and nothing else. Routes through the status-only procedure, so no other field can be touched."
5202
+ ).requiredOption("--id <n>", "Objective ID (a number)").requiredOption("--status <status>", `Status: ${GOAL_STATUSES.join(", ")}`).action(async (options, cmd) => {
5203
+ const globalOpts = cmd.optsWithGlobals();
5204
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
5205
+ try {
5206
+ const status = validateGoalStatus(options.status);
5207
+ const goal = await getClient().goals.setStatus({
5208
+ id: parseGoalId(options.id),
5209
+ status
5210
+ });
5211
+ if (useJson) outputGoalJson(goal);
5212
+ else {
5213
+ console.log(`
5214
+ \u2713 Goal status set to ${status}`);
5215
+ outputGoalPretty(goal);
5216
+ }
5217
+ } catch (error) {
5218
+ handleError(error, useJson);
5219
+ }
5220
+ });
5221
+ goals.command("close").description("Close out an objective \u2014 one command per goal when a quarter ends").requiredOption("--id <n>", "Objective ID (a number)").option("--status <status>", "completed (default) or archived", "completed").action(async (options, cmd) => {
5222
+ const globalOpts = cmd.optsWithGlobals();
5223
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
5224
+ try {
5225
+ const status = validateGoalStatus(options.status);
5226
+ const goal = await getClient().goals.setStatus({
5227
+ id: parseGoalId(options.id),
5228
+ status
5229
+ });
5230
+ if (useJson) outputGoalJson(goal);
5231
+ else {
5232
+ console.log(`
5233
+ \u2713 Goal closed as ${status}`);
5234
+ outputGoalPretty(goal);
5235
+ }
5236
+ } catch (error) {
5237
+ handleError(error, useJson);
5238
+ }
5239
+ });
5240
+ goals.command("reparent").description(
5241
+ "Move an objective under another (or detach it with --parent none). Writes the parent alone."
5242
+ ).requiredOption("--id <n>", "Objective ID (a number)").requiredOption("--parent <id|none>", 'New parent objective ID, or "none" to detach').action(async (options, cmd) => {
5243
+ const globalOpts = cmd.optsWithGlobals();
5244
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
5245
+ try {
5246
+ const goal = await getClient().goals.setParent({
5247
+ id: parseGoalId(options.id),
5248
+ parentGoalId: options.parent === "none" ? null : parseGoalId(options.parent)
5249
+ });
5250
+ if (useJson) outputGoalJson(goal);
5251
+ else {
5252
+ console.log(
5253
+ options.parent === "none" ? "\n\u2713 Goal detached" : "\n\u2713 Goal re-parented"
5254
+ );
5255
+ outputGoalPretty(goal);
5256
+ }
5257
+ } catch (error) {
5258
+ handleError(error, useJson);
5259
+ }
5260
+ });
5261
+ goals.command("delete").description("Delete an objective").requiredOption("--id <n>", "Objective ID (a number)").action(async (options, cmd) => {
5262
+ const globalOpts = cmd.optsWithGlobals();
5263
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
5264
+ try {
5265
+ const id = parseGoalId(options.id);
5266
+ await getClient().goals.delete(id);
5267
+ if (useJson) {
5268
+ console.log(JSON.stringify({ deleted: true, id }, null, 2));
5269
+ } else {
5270
+ console.log("\u2713 Goal deleted");
5271
+ }
5272
+ } catch (error) {
5273
+ handleError(error, useJson);
5274
+ }
5275
+ });
5276
+ goals.command("periods").description("List the conventional OKR period strings").action(async (_options, cmd) => {
5277
+ const globalOpts = cmd.optsWithGlobals();
5278
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
5279
+ try {
5280
+ const periods = await getClient().goals.periods();
5281
+ if (useJson) outputGoalPeriodsJson(periods);
5282
+ else outputGoalPeriodsPretty(periods);
5283
+ } catch (error) {
5284
+ handleError(error, useJson);
5285
+ }
5286
+ });
5287
+ goals.command("stats").description("Objective and key-result counts, average progress and confidence").option("-w, --workspace <slug|id>", "Workspace slug or ID (defaults to your default workspace)").option("--period <period>", "Period, e.g. Q3-2026").action(
5288
+ async (options, cmd) => {
5289
+ const globalOpts = cmd.optsWithGlobals();
5290
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
5291
+ try {
5292
+ const client = getClient();
5293
+ const workspaceId = await resolveWorkspaceId(client, options.workspace);
5294
+ const stats = await client.goals.stats({
5295
+ workspaceId,
5296
+ period: options.period
5297
+ });
5298
+ if (useJson) outputGoalStatsJson(stats);
5299
+ else outputGoalStatsPretty(stats);
5300
+ } catch (error) {
5301
+ handleError(error, useJson);
5302
+ }
5303
+ }
5304
+ );
5305
+ goals.addCommand(createKeyResultsCommand());
5306
+ goals.addCommand(createGoalCommentsCommand());
4447
5307
  return goals;
4448
5308
  }
5309
+ function createOkrsCommand() {
5310
+ const okrs = new Command21("okrs").description(
5311
+ "OKRs \u2014 objectives with their key results nested. Manage the parts with `goals` and `goals kr`."
5312
+ );
5313
+ okrs.command("list").description(
5314
+ "List objectives with their key results. Defaults to the current quarter \u2014 pass --period all for every period."
5315
+ ).option("-w, --workspace <slug|id>", "Workspace slug or ID (defaults to your default workspace)").option("--all-workspaces", "List across every workspace you belong to").option(
5316
+ "--period <period>",
5317
+ `Period, e.g. Q3-2026, or "all" for every period (default: ${currentPeriod()})`
5318
+ ).option("--paired-period", "Also include the period's parent annual period").option(
5319
+ "--status <status>",
5320
+ `Keep only key results with this status: ${KEY_RESULT_STATUSES.join(", ")}`
5321
+ ).option("--mine", "Only objectives and key results you are the DRI for").action(
5322
+ async (options, cmd) => {
5323
+ const globalOpts = cmd.optsWithGlobals();
5324
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
5325
+ try {
5326
+ const status = validateKeyResultStatus(options.status);
5327
+ const client = getClient();
5328
+ const workspaces = await targetWorkspaces(client, options);
5329
+ const period = options.period === "all" ? void 0 : options.period ?? currentPeriod();
5330
+ let objectives = await fanOutByWorkspace(workspaces, async (workspace) => {
5331
+ const found = await client.goals.keyResults.byObjective({
5332
+ workspaceId: workspace.id,
5333
+ period,
5334
+ includePairedPeriod: options.pairedPeriod,
5335
+ onlyMine: options.mine
5336
+ });
5337
+ return found.map((o) => ({ ...o, workspace }));
5338
+ });
5339
+ if (status) {
5340
+ objectives = objectives.map((o) => ({
5341
+ ...o,
5342
+ keyResults: o.keyResults.filter(
5343
+ (kr) => (kr.statusOverride ?? kr.status) === status
5344
+ )
5345
+ })).filter((o) => o.keyResults.length > 0);
5346
+ }
5347
+ if (useJson) outputObjectivesJson(objectives);
5348
+ else outputObjectivesPretty(objectives);
5349
+ } catch (error) {
5350
+ handleError(error, useJson);
5351
+ }
5352
+ }
5353
+ );
5354
+ okrs.command("stats").description("Objective and key-result counts, average progress and confidence").option("-w, --workspace <slug|id>", "Workspace slug or ID (defaults to your default workspace)").option("--period <period>", "Period, e.g. Q3-2026").action(
5355
+ async (options, cmd) => {
5356
+ const globalOpts = cmd.optsWithGlobals();
5357
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
5358
+ try {
5359
+ const client = getClient();
5360
+ const workspaceId = await resolveWorkspaceId(client, options.workspace);
5361
+ const stats = await client.goals.stats({
5362
+ workspaceId,
5363
+ period: options.period
5364
+ });
5365
+ if (useJson) outputGoalStatsJson(stats);
5366
+ else outputGoalStatsPretty(stats);
5367
+ } catch (error) {
5368
+ handleError(error, useJson);
5369
+ }
5370
+ }
5371
+ );
5372
+ okrs.command("periods").description("List the conventional OKR period strings").action(async (_options, cmd) => {
5373
+ const globalOpts = cmd.optsWithGlobals();
5374
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
5375
+ try {
5376
+ const periods = await getClient().goals.periods();
5377
+ if (useJson) outputGoalPeriodsJson(periods);
5378
+ else outputGoalPeriodsPretty(periods);
5379
+ } catch (error) {
5380
+ handleError(error, useJson);
5381
+ }
5382
+ });
5383
+ okrs.addCommand(createKeyResultsCommand());
5384
+ return okrs;
5385
+ }
4449
5386
 
4450
5387
  // src/index.ts
4451
5388
  import { readFileSync as readFileSync2 } from "fs";
@@ -4479,5 +5416,6 @@ program.addCommand(createSearchCommand());
4479
5416
  program.addCommand(createTicketsCommand());
4480
5417
  program.addCommand(createWorkspacesCommand());
4481
5418
  program.addCommand(createGoalsCommand());
5419
+ program.addCommand(createOkrsCommand());
4482
5420
  program.parse();
4483
5421
  //# sourceMappingURL=index.js.map