exponential-cli 1.10.0 → 1.11.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 +935 -7
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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
|
|
2466
|
+
if (byId) return byId;
|
|
2127
2467
|
const bySlug = workspaces.find((w) => w.slug === candidate);
|
|
2128
|
-
if (bySlug) return bySlug
|
|
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)"}`
|
|
@@ -4338,19 +4678,313 @@ Members (${members.length})
|
|
|
4338
4678
|
|
|
4339
4679
|
// src/commands/goals.ts
|
|
4340
4680
|
import { Command as Command21 } from "commander";
|
|
4681
|
+
var GOAL_STATUSES = [
|
|
4682
|
+
"planned",
|
|
4683
|
+
"active",
|
|
4684
|
+
"completed",
|
|
4685
|
+
"archived",
|
|
4686
|
+
"on-hold"
|
|
4687
|
+
];
|
|
4688
|
+
var GOAL_WRITABLE_STATUSES = [
|
|
4689
|
+
"planned",
|
|
4690
|
+
"active",
|
|
4691
|
+
"completed",
|
|
4692
|
+
"archived"
|
|
4693
|
+
];
|
|
4694
|
+
var KEY_RESULT_STATUSES = [
|
|
4695
|
+
"not-started",
|
|
4696
|
+
"on-track",
|
|
4697
|
+
"at-risk",
|
|
4698
|
+
"off-track",
|
|
4699
|
+
"achieved"
|
|
4700
|
+
];
|
|
4701
|
+
var KEY_RESULT_UNITS = [
|
|
4702
|
+
"percent",
|
|
4703
|
+
"count",
|
|
4704
|
+
"currency",
|
|
4705
|
+
"hours",
|
|
4706
|
+
"custom"
|
|
4707
|
+
];
|
|
4708
|
+
function currentPeriod() {
|
|
4709
|
+
const now = /* @__PURE__ */ new Date();
|
|
4710
|
+
return `Q${Math.floor(now.getMonth() / 3) + 1}-${now.getFullYear()}`;
|
|
4711
|
+
}
|
|
4712
|
+
async function targetWorkspaces(client, options) {
|
|
4713
|
+
if (options.allWorkspaces) {
|
|
4714
|
+
if (options.workspace) {
|
|
4715
|
+
throw new Error("--workspace and --all-workspaces are mutually exclusive.");
|
|
4716
|
+
}
|
|
4717
|
+
return await client.workspaces.list();
|
|
4718
|
+
}
|
|
4719
|
+
return [await resolveWorkspace(client, options.workspace)];
|
|
4720
|
+
}
|
|
4721
|
+
async function fanOutByWorkspace(workspaces, read) {
|
|
4722
|
+
const settled = await Promise.allSettled(workspaces.map(read));
|
|
4723
|
+
const items = [];
|
|
4724
|
+
const skipped = [];
|
|
4725
|
+
settled.forEach((result, i) => {
|
|
4726
|
+
if (result.status === "fulfilled") {
|
|
4727
|
+
items.push(...result.value);
|
|
4728
|
+
return;
|
|
4729
|
+
}
|
|
4730
|
+
const reason = result.reason instanceof Error ? result.reason.message : String(result.reason);
|
|
4731
|
+
skipped.push(`${workspaces[i].slug}: ${reason}`);
|
|
4732
|
+
});
|
|
4733
|
+
if (skipped.length === workspaces.length) {
|
|
4734
|
+
const [first] = settled.filter((r) => r.status === "rejected");
|
|
4735
|
+
throw first.reason;
|
|
4736
|
+
}
|
|
4737
|
+
for (const line of skipped) {
|
|
4738
|
+
console.error(`Warning: skipped workspace ${line}`);
|
|
4739
|
+
}
|
|
4740
|
+
return items;
|
|
4741
|
+
}
|
|
4341
4742
|
function parseGoalId(value) {
|
|
4342
4743
|
const id = Number(value);
|
|
4343
4744
|
if (!Number.isInteger(id)) {
|
|
4344
4745
|
throw new Error(
|
|
4345
|
-
`Goal id must be a whole number, got "${value}".
|
|
4746
|
+
`Goal id must be a whole number, got "${value}". List them with \`exponential goals list\`.`
|
|
4346
4747
|
);
|
|
4347
4748
|
}
|
|
4348
4749
|
return id;
|
|
4349
4750
|
}
|
|
4350
|
-
function
|
|
4351
|
-
const
|
|
4352
|
-
|
|
4751
|
+
function parseNumber(value, label) {
|
|
4752
|
+
const n = Number(value);
|
|
4753
|
+
if (!Number.isFinite(n)) {
|
|
4754
|
+
throw new Error(`${label} must be a number, got "${value}".`);
|
|
4755
|
+
}
|
|
4756
|
+
return n;
|
|
4757
|
+
}
|
|
4758
|
+
function validateGoalStatus(value) {
|
|
4759
|
+
if (!value) return void 0;
|
|
4760
|
+
if (!GOAL_STATUSES.includes(value)) {
|
|
4761
|
+
throw new Error(`Invalid status "${value}". Valid: ${GOAL_STATUSES.join(", ")}`);
|
|
4762
|
+
}
|
|
4763
|
+
return value;
|
|
4764
|
+
}
|
|
4765
|
+
function validateWritableGoalStatus(value) {
|
|
4766
|
+
if (!value) return void 0;
|
|
4767
|
+
if (!GOAL_WRITABLE_STATUSES.includes(value)) {
|
|
4768
|
+
const extra = value === "on-hold" ? " Use `goals set-status` for on-hold." : "";
|
|
4769
|
+
throw new Error(
|
|
4770
|
+
`Invalid status "${value}". Valid: ${GOAL_WRITABLE_STATUSES.join(", ")}.${extra}`
|
|
4771
|
+
);
|
|
4772
|
+
}
|
|
4773
|
+
return value;
|
|
4774
|
+
}
|
|
4775
|
+
function validateKeyResultStatus(value) {
|
|
4776
|
+
if (!value) return void 0;
|
|
4777
|
+
if (!KEY_RESULT_STATUSES.includes(value)) {
|
|
4778
|
+
throw new Error(
|
|
4779
|
+
`Invalid status "${value}". Valid: ${KEY_RESULT_STATUSES.join(", ")}`
|
|
4780
|
+
);
|
|
4781
|
+
}
|
|
4782
|
+
return value;
|
|
4783
|
+
}
|
|
4784
|
+
function validateKeyResultUnit(value) {
|
|
4785
|
+
if (!value) return void 0;
|
|
4786
|
+
if (!KEY_RESULT_UNITS.includes(value)) {
|
|
4787
|
+
throw new Error(`Invalid unit "${value}". Valid: ${KEY_RESULT_UNITS.join(", ")}`);
|
|
4788
|
+
}
|
|
4789
|
+
return value;
|
|
4790
|
+
}
|
|
4791
|
+
function createKeyResultsCommand() {
|
|
4792
|
+
const kr = new Command21("kr").description(
|
|
4793
|
+
"Key results on an objective. Ids are CUIDs; --goal takes the objective's integer id."
|
|
4353
4794
|
);
|
|
4795
|
+
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(
|
|
4796
|
+
async (options, cmd) => {
|
|
4797
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
4798
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
4799
|
+
try {
|
|
4800
|
+
const status = validateKeyResultStatus(options.status);
|
|
4801
|
+
const client = getClient();
|
|
4802
|
+
const workspaceId = options.workspace ? await resolveWorkspaceId(client, options.workspace) : void 0;
|
|
4803
|
+
const list = await client.goals.keyResults.list({
|
|
4804
|
+
workspaceId,
|
|
4805
|
+
goalId: options.goal ? parseGoalId(options.goal) : void 0,
|
|
4806
|
+
period: options.period,
|
|
4807
|
+
status,
|
|
4808
|
+
onlyMine: options.mine
|
|
4809
|
+
});
|
|
4810
|
+
if (useJson) outputKeyResultsJson(list);
|
|
4811
|
+
else outputKeyResultsPretty(list);
|
|
4812
|
+
} catch (error) {
|
|
4813
|
+
handleError(error, useJson);
|
|
4814
|
+
}
|
|
4815
|
+
}
|
|
4816
|
+
);
|
|
4817
|
+
kr.command("get <id>").description("Get a key result by CUID").action(async (id, _options, cmd) => {
|
|
4818
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
4819
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
4820
|
+
try {
|
|
4821
|
+
const keyResult = await getClient().goals.keyResults.get(id);
|
|
4822
|
+
if (useJson) outputKeyResultJson(keyResult);
|
|
4823
|
+
else outputKeyResultPretty(keyResult);
|
|
4824
|
+
} catch (error) {
|
|
4825
|
+
handleError(error, useJson);
|
|
4826
|
+
}
|
|
4827
|
+
});
|
|
4828
|
+
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(
|
|
4829
|
+
async (options, cmd) => {
|
|
4830
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
4831
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
4832
|
+
try {
|
|
4833
|
+
const unit = validateKeyResultUnit(options.unit);
|
|
4834
|
+
const client = getClient();
|
|
4835
|
+
const goalId = parseGoalId(options.goal);
|
|
4836
|
+
const startValue = options.start ? parseNumber(options.start, "--start") : void 0;
|
|
4837
|
+
let period = options.period;
|
|
4838
|
+
let workspaceId;
|
|
4839
|
+
if (!period) {
|
|
4840
|
+
const goal = await client.goals.get(goalId);
|
|
4841
|
+
if (!goal.period) {
|
|
4842
|
+
throw new Error(
|
|
4843
|
+
`Objective #${goalId} has no period, so --period is required. See \`exponential goals periods\`.`
|
|
4844
|
+
);
|
|
4845
|
+
}
|
|
4846
|
+
period = goal.period;
|
|
4847
|
+
workspaceId = goal.workspaceId ?? void 0;
|
|
4848
|
+
}
|
|
4849
|
+
const keyResult = await client.goals.keyResults.create({
|
|
4850
|
+
goalId,
|
|
4851
|
+
workspaceId,
|
|
4852
|
+
title: options.title,
|
|
4853
|
+
targetValue: parseNumber(options.target, "--target"),
|
|
4854
|
+
startValue,
|
|
4855
|
+
currentValue: options.current ? parseNumber(options.current, "--current") : startValue,
|
|
4856
|
+
unit,
|
|
4857
|
+
period,
|
|
4858
|
+
description: options.description,
|
|
4859
|
+
driUserId: options.dri
|
|
4860
|
+
});
|
|
4861
|
+
if (useJson) outputKeyResultJson(keyResult);
|
|
4862
|
+
else {
|
|
4863
|
+
console.log("\n\u2713 Key result created");
|
|
4864
|
+
outputKeyResultPretty(keyResult);
|
|
4865
|
+
}
|
|
4866
|
+
} catch (error) {
|
|
4867
|
+
handleError(error, useJson);
|
|
4868
|
+
}
|
|
4869
|
+
}
|
|
4870
|
+
);
|
|
4871
|
+
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(
|
|
4872
|
+
async (options, cmd) => {
|
|
4873
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
4874
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
4875
|
+
try {
|
|
4876
|
+
const status = validateKeyResultStatus(options.status);
|
|
4877
|
+
const unit = validateKeyResultUnit(options.unit);
|
|
4878
|
+
const keyResult = await getClient().goals.keyResults.update({
|
|
4879
|
+
id: options.id,
|
|
4880
|
+
title: options.title,
|
|
4881
|
+
description: options.description,
|
|
4882
|
+
currentValue: options.current ? parseNumber(options.current, "--current") : void 0,
|
|
4883
|
+
targetValue: options.target ? parseNumber(options.target, "--target") : void 0,
|
|
4884
|
+
startValue: options.start ? parseNumber(options.start, "--start") : void 0,
|
|
4885
|
+
unit,
|
|
4886
|
+
status,
|
|
4887
|
+
confidence: options.confidence ? parseNumber(options.confidence, "--confidence") : void 0,
|
|
4888
|
+
driUserId: options.dri,
|
|
4889
|
+
goalId: options.goal ? parseGoalId(options.goal) : void 0
|
|
4890
|
+
});
|
|
4891
|
+
if (useJson) outputKeyResultJson(keyResult);
|
|
4892
|
+
else {
|
|
4893
|
+
console.log("\n\u2713 Key result updated");
|
|
4894
|
+
outputKeyResultPretty(keyResult);
|
|
4895
|
+
}
|
|
4896
|
+
} catch (error) {
|
|
4897
|
+
handleError(error, useJson);
|
|
4898
|
+
}
|
|
4899
|
+
}
|
|
4900
|
+
);
|
|
4901
|
+
kr.command("checkin").description(
|
|
4902
|
+
"Record a progress check-in. Moves the value AND re-derives the status from where it lands between start and target."
|
|
4903
|
+
).requiredOption("--id <cuid>", "Key result CUID").requiredOption("--value <n>", "New current value").option("--note <text>", "Check-in note").action(
|
|
4904
|
+
async (options, cmd) => {
|
|
4905
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
4906
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
4907
|
+
try {
|
|
4908
|
+
const checkIn = await getClient().goals.keyResults.checkIn({
|
|
4909
|
+
id: options.id,
|
|
4910
|
+
value: parseNumber(options.value, "--value"),
|
|
4911
|
+
note: options.note
|
|
4912
|
+
});
|
|
4913
|
+
if (useJson) outputCheckInJson(checkIn);
|
|
4914
|
+
else {
|
|
4915
|
+
console.log("\n\u2713 Check-in recorded");
|
|
4916
|
+
outputCheckInPretty(checkIn);
|
|
4917
|
+
}
|
|
4918
|
+
} catch (error) {
|
|
4919
|
+
handleError(error, useJson);
|
|
4920
|
+
}
|
|
4921
|
+
}
|
|
4922
|
+
);
|
|
4923
|
+
kr.command("delete").description("Delete a key result").requiredOption("--id <cuid>", "Key result CUID").action(async (options, cmd) => {
|
|
4924
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
4925
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
4926
|
+
try {
|
|
4927
|
+
await getClient().goals.keyResults.delete(options.id);
|
|
4928
|
+
if (useJson) {
|
|
4929
|
+
console.log(JSON.stringify({ deleted: true, id: options.id }, null, 2));
|
|
4930
|
+
} else {
|
|
4931
|
+
console.log("\u2713 Key result deleted");
|
|
4932
|
+
}
|
|
4933
|
+
} catch (error) {
|
|
4934
|
+
handleError(error, useJson);
|
|
4935
|
+
}
|
|
4936
|
+
});
|
|
4937
|
+
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(
|
|
4938
|
+
async (options, cmd) => {
|
|
4939
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
4940
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
4941
|
+
try {
|
|
4942
|
+
if (!options.project === !options.feature) {
|
|
4943
|
+
throw new Error("Pass exactly one of --project or --feature.");
|
|
4944
|
+
}
|
|
4945
|
+
const client = getClient();
|
|
4946
|
+
const result = options.project ? await client.goals.keyResults.linkProject({
|
|
4947
|
+
keyResultId: options.id,
|
|
4948
|
+
projectId: options.project
|
|
4949
|
+
}) : await client.goals.keyResults.linkFeature({
|
|
4950
|
+
keyResultId: options.id,
|
|
4951
|
+
featureId: options.feature
|
|
4952
|
+
});
|
|
4953
|
+
if (useJson) console.log(JSON.stringify(result, null, 2));
|
|
4954
|
+
else console.log(`
|
|
4955
|
+
\u2713 ${options.project ? "Project" : "Feature"} linked`);
|
|
4956
|
+
} catch (error) {
|
|
4957
|
+
handleError(error, useJson);
|
|
4958
|
+
}
|
|
4959
|
+
}
|
|
4960
|
+
);
|
|
4961
|
+
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(
|
|
4962
|
+
async (options, cmd) => {
|
|
4963
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
4964
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
4965
|
+
try {
|
|
4966
|
+
if (!options.project === !options.feature) {
|
|
4967
|
+
throw new Error("Pass exactly one of --project or --feature.");
|
|
4968
|
+
}
|
|
4969
|
+
const client = getClient();
|
|
4970
|
+
const result = options.project ? await client.goals.keyResults.unlinkProject({
|
|
4971
|
+
keyResultId: options.id,
|
|
4972
|
+
projectId: options.project
|
|
4973
|
+
}) : await client.goals.keyResults.unlinkFeature({
|
|
4974
|
+
keyResultId: options.id,
|
|
4975
|
+
featureId: options.feature
|
|
4976
|
+
});
|
|
4977
|
+
if (useJson) console.log(JSON.stringify(result, null, 2));
|
|
4978
|
+
else console.log(`
|
|
4979
|
+
\u2713 ${options.project ? "Project" : "Feature"} unlinked`);
|
|
4980
|
+
} catch (error) {
|
|
4981
|
+
handleError(error, useJson);
|
|
4982
|
+
}
|
|
4983
|
+
}
|
|
4984
|
+
);
|
|
4985
|
+
return kr;
|
|
4986
|
+
}
|
|
4987
|
+
function createGoalCommentsCommand() {
|
|
4354
4988
|
const comment = new Command21("comment").description(
|
|
4355
4989
|
"Read and post comments on a goal. Mention teammates with --mention."
|
|
4356
4990
|
);
|
|
@@ -4443,9 +5077,302 @@ function createGoalsCommand() {
|
|
|
4443
5077
|
handleError(error, useJson);
|
|
4444
5078
|
}
|
|
4445
5079
|
});
|
|
4446
|
-
|
|
5080
|
+
return comment;
|
|
5081
|
+
}
|
|
5082
|
+
function createGoalsCommand() {
|
|
5083
|
+
const goals = new Command21("goals").description(
|
|
5084
|
+
"Manage goals (objectives) and their key results. Objectives have integer ids; key results are CUIDs."
|
|
5085
|
+
);
|
|
5086
|
+
goals.command("list").description(
|
|
5087
|
+
"List objectives, each with the projects linked to it \u2014 the join that ties an action (which carries a projectId) back to a goal."
|
|
5088
|
+
).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(
|
|
5089
|
+
async (options, cmd) => {
|
|
5090
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
5091
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
5092
|
+
try {
|
|
5093
|
+
const status = validateGoalStatus(options.status);
|
|
5094
|
+
const client = getClient();
|
|
5095
|
+
const workspaces = await targetWorkspaces(client, options);
|
|
5096
|
+
if (options.tree) {
|
|
5097
|
+
const trees = await fanOutByWorkspace(
|
|
5098
|
+
workspaces,
|
|
5099
|
+
(w) => client.goals.tree({ workspaceId: w.id, status })
|
|
5100
|
+
);
|
|
5101
|
+
const filtered = options.period ? trees.filter((g) => g.period === options.period) : trees;
|
|
5102
|
+
if (useJson) outputGoalTreeJson(filtered);
|
|
5103
|
+
else outputGoalTreePretty(filtered);
|
|
5104
|
+
return;
|
|
5105
|
+
}
|
|
5106
|
+
const list = await fanOutByWorkspace(workspaces, async (workspace) => {
|
|
5107
|
+
const goals2 = await client.goals.list({
|
|
5108
|
+
workspaceId: workspace.id,
|
|
5109
|
+
period: options.period,
|
|
5110
|
+
status
|
|
5111
|
+
});
|
|
5112
|
+
return goals2.map((g) => ({ ...g, workspace: g.workspace ?? workspace }));
|
|
5113
|
+
});
|
|
5114
|
+
if (useJson) outputGoalsJson(list);
|
|
5115
|
+
else outputGoalsPretty(list);
|
|
5116
|
+
} catch (error) {
|
|
5117
|
+
handleError(error, useJson);
|
|
5118
|
+
}
|
|
5119
|
+
}
|
|
5120
|
+
);
|
|
5121
|
+
goals.command("get <id>").description("Get an objective by its integer ID").action(async (id, _options, cmd) => {
|
|
5122
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
5123
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
5124
|
+
try {
|
|
5125
|
+
const goal = await getClient().goals.get(parseGoalId(id));
|
|
5126
|
+
if (useJson) outputGoalJson(goal);
|
|
5127
|
+
else outputGoalPretty(goal);
|
|
5128
|
+
} catch (error) {
|
|
5129
|
+
handleError(error, useJson);
|
|
5130
|
+
}
|
|
5131
|
+
});
|
|
5132
|
+
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(
|
|
5133
|
+
async (options, cmd) => {
|
|
5134
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
5135
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
5136
|
+
try {
|
|
5137
|
+
const status = validateWritableGoalStatus(options.status);
|
|
5138
|
+
const client = getClient();
|
|
5139
|
+
const workspaceId = await resolveWorkspaceId(client, options.workspace);
|
|
5140
|
+
const goal = await client.goals.create({
|
|
5141
|
+
title: options.title,
|
|
5142
|
+
workspaceId,
|
|
5143
|
+
period: options.period,
|
|
5144
|
+
status,
|
|
5145
|
+
description: options.description,
|
|
5146
|
+
whyThisGoal: options.why,
|
|
5147
|
+
driUserId: options.dri,
|
|
5148
|
+
parentGoalId: options.parent ? parseGoalId(options.parent) : void 0,
|
|
5149
|
+
projectId: options.project
|
|
5150
|
+
});
|
|
5151
|
+
if (useJson) outputGoalJson(goal);
|
|
5152
|
+
else {
|
|
5153
|
+
console.log("\n\u2713 Goal created");
|
|
5154
|
+
outputGoalPretty(goal);
|
|
5155
|
+
}
|
|
5156
|
+
} catch (error) {
|
|
5157
|
+
handleError(error, useJson);
|
|
5158
|
+
}
|
|
5159
|
+
}
|
|
5160
|
+
);
|
|
5161
|
+
goals.command("update").description(
|
|
5162
|
+
"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`."
|
|
5163
|
+
).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(
|
|
5164
|
+
async (options, cmd) => {
|
|
5165
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
5166
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
5167
|
+
try {
|
|
5168
|
+
const status = validateWritableGoalStatus(options.status);
|
|
5169
|
+
const clearable = (value) => value === void 0 ? void 0 : value === "none" ? null : value;
|
|
5170
|
+
const goal = await getClient().goals.update({
|
|
5171
|
+
id: parseGoalId(options.id),
|
|
5172
|
+
title: options.title,
|
|
5173
|
+
description: clearable(options.description),
|
|
5174
|
+
whyThisGoal: clearable(options.why),
|
|
5175
|
+
period: clearable(options.period),
|
|
5176
|
+
driUserId: options.dri,
|
|
5177
|
+
status,
|
|
5178
|
+
projectId: clearable(options.project)
|
|
5179
|
+
});
|
|
5180
|
+
if (useJson) outputGoalJson(goal);
|
|
5181
|
+
else {
|
|
5182
|
+
console.log("\n\u2713 Goal updated");
|
|
5183
|
+
outputGoalPretty(goal);
|
|
5184
|
+
}
|
|
5185
|
+
} catch (error) {
|
|
5186
|
+
handleError(error, useJson);
|
|
5187
|
+
}
|
|
5188
|
+
}
|
|
5189
|
+
);
|
|
5190
|
+
goals.command("set-status").description(
|
|
5191
|
+
"Set an objective's status and nothing else. Routes through the status-only procedure, so no other field can be touched."
|
|
5192
|
+
).requiredOption("--id <n>", "Objective ID (a number)").requiredOption("--status <status>", `Status: ${GOAL_STATUSES.join(", ")}`).action(async (options, cmd) => {
|
|
5193
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
5194
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
5195
|
+
try {
|
|
5196
|
+
const status = validateGoalStatus(options.status);
|
|
5197
|
+
const goal = await getClient().goals.setStatus({
|
|
5198
|
+
id: parseGoalId(options.id),
|
|
5199
|
+
status
|
|
5200
|
+
});
|
|
5201
|
+
if (useJson) outputGoalJson(goal);
|
|
5202
|
+
else {
|
|
5203
|
+
console.log(`
|
|
5204
|
+
\u2713 Goal status set to ${status}`);
|
|
5205
|
+
outputGoalPretty(goal);
|
|
5206
|
+
}
|
|
5207
|
+
} catch (error) {
|
|
5208
|
+
handleError(error, useJson);
|
|
5209
|
+
}
|
|
5210
|
+
});
|
|
5211
|
+
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) => {
|
|
5212
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
5213
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
5214
|
+
try {
|
|
5215
|
+
const status = validateGoalStatus(options.status);
|
|
5216
|
+
const goal = await getClient().goals.setStatus({
|
|
5217
|
+
id: parseGoalId(options.id),
|
|
5218
|
+
status
|
|
5219
|
+
});
|
|
5220
|
+
if (useJson) outputGoalJson(goal);
|
|
5221
|
+
else {
|
|
5222
|
+
console.log(`
|
|
5223
|
+
\u2713 Goal closed as ${status}`);
|
|
5224
|
+
outputGoalPretty(goal);
|
|
5225
|
+
}
|
|
5226
|
+
} catch (error) {
|
|
5227
|
+
handleError(error, useJson);
|
|
5228
|
+
}
|
|
5229
|
+
});
|
|
5230
|
+
goals.command("reparent").description(
|
|
5231
|
+
"Move an objective under another (or detach it with --parent none). Writes the parent alone."
|
|
5232
|
+
).requiredOption("--id <n>", "Objective ID (a number)").requiredOption("--parent <id|none>", 'New parent objective ID, or "none" to detach').action(async (options, cmd) => {
|
|
5233
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
5234
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
5235
|
+
try {
|
|
5236
|
+
const goal = await getClient().goals.setParent({
|
|
5237
|
+
id: parseGoalId(options.id),
|
|
5238
|
+
parentGoalId: options.parent === "none" ? null : parseGoalId(options.parent)
|
|
5239
|
+
});
|
|
5240
|
+
if (useJson) outputGoalJson(goal);
|
|
5241
|
+
else {
|
|
5242
|
+
console.log(
|
|
5243
|
+
options.parent === "none" ? "\n\u2713 Goal detached" : "\n\u2713 Goal re-parented"
|
|
5244
|
+
);
|
|
5245
|
+
outputGoalPretty(goal);
|
|
5246
|
+
}
|
|
5247
|
+
} catch (error) {
|
|
5248
|
+
handleError(error, useJson);
|
|
5249
|
+
}
|
|
5250
|
+
});
|
|
5251
|
+
goals.command("delete").description("Delete an objective").requiredOption("--id <n>", "Objective ID (a number)").action(async (options, cmd) => {
|
|
5252
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
5253
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
5254
|
+
try {
|
|
5255
|
+
const id = parseGoalId(options.id);
|
|
5256
|
+
await getClient().goals.delete(id);
|
|
5257
|
+
if (useJson) {
|
|
5258
|
+
console.log(JSON.stringify({ deleted: true, id }, null, 2));
|
|
5259
|
+
} else {
|
|
5260
|
+
console.log("\u2713 Goal deleted");
|
|
5261
|
+
}
|
|
5262
|
+
} catch (error) {
|
|
5263
|
+
handleError(error, useJson);
|
|
5264
|
+
}
|
|
5265
|
+
});
|
|
5266
|
+
goals.command("periods").description("List the conventional OKR period strings").action(async (_options, cmd) => {
|
|
5267
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
5268
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
5269
|
+
try {
|
|
5270
|
+
const periods = await getClient().goals.periods();
|
|
5271
|
+
if (useJson) outputGoalPeriodsJson(periods);
|
|
5272
|
+
else outputGoalPeriodsPretty(periods);
|
|
5273
|
+
} catch (error) {
|
|
5274
|
+
handleError(error, useJson);
|
|
5275
|
+
}
|
|
5276
|
+
});
|
|
5277
|
+
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(
|
|
5278
|
+
async (options, cmd) => {
|
|
5279
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
5280
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
5281
|
+
try {
|
|
5282
|
+
const client = getClient();
|
|
5283
|
+
const workspaceId = await resolveWorkspaceId(client, options.workspace);
|
|
5284
|
+
const stats = await client.goals.stats({
|
|
5285
|
+
workspaceId,
|
|
5286
|
+
period: options.period
|
|
5287
|
+
});
|
|
5288
|
+
if (useJson) outputGoalStatsJson(stats);
|
|
5289
|
+
else outputGoalStatsPretty(stats);
|
|
5290
|
+
} catch (error) {
|
|
5291
|
+
handleError(error, useJson);
|
|
5292
|
+
}
|
|
5293
|
+
}
|
|
5294
|
+
);
|
|
5295
|
+
goals.addCommand(createKeyResultsCommand());
|
|
5296
|
+
goals.addCommand(createGoalCommentsCommand());
|
|
4447
5297
|
return goals;
|
|
4448
5298
|
}
|
|
5299
|
+
function createOkrsCommand() {
|
|
5300
|
+
const okrs = new Command21("okrs").description(
|
|
5301
|
+
"OKRs \u2014 objectives with their key results nested. Manage the parts with `goals` and `goals kr`."
|
|
5302
|
+
);
|
|
5303
|
+
okrs.command("list").description(
|
|
5304
|
+
"List objectives with their key results. Defaults to the current quarter \u2014 pass --period all for every period."
|
|
5305
|
+
).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(
|
|
5306
|
+
"--period <period>",
|
|
5307
|
+
`Period, e.g. Q3-2026, or "all" for every period (default: ${currentPeriod()})`
|
|
5308
|
+
).option("--paired-period", "Also include the period's parent annual period").option(
|
|
5309
|
+
"--status <status>",
|
|
5310
|
+
`Keep only key results with this status: ${KEY_RESULT_STATUSES.join(", ")}`
|
|
5311
|
+
).option("--mine", "Only objectives and key results you are the DRI for").action(
|
|
5312
|
+
async (options, cmd) => {
|
|
5313
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
5314
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
5315
|
+
try {
|
|
5316
|
+
const status = validateKeyResultStatus(options.status);
|
|
5317
|
+
const client = getClient();
|
|
5318
|
+
const workspaces = await targetWorkspaces(client, options);
|
|
5319
|
+
const period = options.period === "all" ? void 0 : options.period ?? currentPeriod();
|
|
5320
|
+
let objectives = await fanOutByWorkspace(workspaces, async (workspace) => {
|
|
5321
|
+
const found = await client.goals.keyResults.byObjective({
|
|
5322
|
+
workspaceId: workspace.id,
|
|
5323
|
+
period,
|
|
5324
|
+
includePairedPeriod: options.pairedPeriod,
|
|
5325
|
+
onlyMine: options.mine
|
|
5326
|
+
});
|
|
5327
|
+
return found.map((o) => ({ ...o, workspace }));
|
|
5328
|
+
});
|
|
5329
|
+
if (status) {
|
|
5330
|
+
objectives = objectives.map((o) => ({
|
|
5331
|
+
...o,
|
|
5332
|
+
keyResults: o.keyResults.filter(
|
|
5333
|
+
(kr) => (kr.statusOverride ?? kr.status) === status
|
|
5334
|
+
)
|
|
5335
|
+
})).filter((o) => o.keyResults.length > 0);
|
|
5336
|
+
}
|
|
5337
|
+
if (useJson) outputObjectivesJson(objectives);
|
|
5338
|
+
else outputObjectivesPretty(objectives);
|
|
5339
|
+
} catch (error) {
|
|
5340
|
+
handleError(error, useJson);
|
|
5341
|
+
}
|
|
5342
|
+
}
|
|
5343
|
+
);
|
|
5344
|
+
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(
|
|
5345
|
+
async (options, cmd) => {
|
|
5346
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
5347
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
5348
|
+
try {
|
|
5349
|
+
const client = getClient();
|
|
5350
|
+
const workspaceId = await resolveWorkspaceId(client, options.workspace);
|
|
5351
|
+
const stats = await client.goals.stats({
|
|
5352
|
+
workspaceId,
|
|
5353
|
+
period: options.period
|
|
5354
|
+
});
|
|
5355
|
+
if (useJson) outputGoalStatsJson(stats);
|
|
5356
|
+
else outputGoalStatsPretty(stats);
|
|
5357
|
+
} catch (error) {
|
|
5358
|
+
handleError(error, useJson);
|
|
5359
|
+
}
|
|
5360
|
+
}
|
|
5361
|
+
);
|
|
5362
|
+
okrs.command("periods").description("List the conventional OKR period strings").action(async (_options, cmd) => {
|
|
5363
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
5364
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
5365
|
+
try {
|
|
5366
|
+
const periods = await getClient().goals.periods();
|
|
5367
|
+
if (useJson) outputGoalPeriodsJson(periods);
|
|
5368
|
+
else outputGoalPeriodsPretty(periods);
|
|
5369
|
+
} catch (error) {
|
|
5370
|
+
handleError(error, useJson);
|
|
5371
|
+
}
|
|
5372
|
+
});
|
|
5373
|
+
okrs.addCommand(createKeyResultsCommand());
|
|
5374
|
+
return okrs;
|
|
5375
|
+
}
|
|
4449
5376
|
|
|
4450
5377
|
// src/index.ts
|
|
4451
5378
|
import { readFileSync as readFileSync2 } from "fs";
|
|
@@ -4479,5 +5406,6 @@ program.addCommand(createSearchCommand());
|
|
|
4479
5406
|
program.addCommand(createTicketsCommand());
|
|
4480
5407
|
program.addCommand(createWorkspacesCommand());
|
|
4481
5408
|
program.addCommand(createGoalsCommand());
|
|
5409
|
+
program.addCommand(createOkrsCommand());
|
|
4482
5410
|
program.parse();
|
|
4483
5411
|
//# sourceMappingURL=index.js.map
|