exponential-cli 1.11.1 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -2
- package/dist/index.js +304 -21
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -130,14 +130,69 @@ exponential actions reschedule --ids id1,id2 --to 2026-08-10
|
|
|
130
130
|
> would report 6 actions while 43 sat overdue. Pass `--due-only` for the
|
|
131
131
|
> previous output shape.
|
|
132
132
|
|
|
133
|
+
### Goals and OKRs
|
|
134
|
+
|
|
135
|
+
Objectives are goals; they carry **integer** ids. Their key results carry CUIDs
|
|
136
|
+
and live under `goals kr`.
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
# List a workspace's objectives, or the annual → quarterly cascade
|
|
140
|
+
exponential goals list --workspace clear --period Q3-2026
|
|
141
|
+
exponential goals list --workspace clear --tree
|
|
142
|
+
exponential goals list --all-workspaces # "what am I neglecting" is cross-workspace
|
|
143
|
+
|
|
144
|
+
exponential goals get 46
|
|
145
|
+
exponential goals create --workspace clear --title "Ship the CLI" --period Q3-2026
|
|
146
|
+
|
|
147
|
+
# Close a quarter. set-status/close write ONLY the status column.
|
|
148
|
+
exponential goals close --id 46 # → completed
|
|
149
|
+
exponential goals set-status --id 46 --status on-hold
|
|
150
|
+
|
|
151
|
+
# Everything else is a partial update: fields you don't pass are left alone,
|
|
152
|
+
# and "none" is how you clear one.
|
|
153
|
+
exponential goals update --id 46 --title "Ship the CLI, properly"
|
|
154
|
+
exponential goals update --id 46 --workspace none # make it personal
|
|
155
|
+
exponential goals reparent --id 47 --parent 46 # or --parent none
|
|
156
|
+
exponential goals delete --id 46 # refuses if it has key results
|
|
157
|
+
|
|
158
|
+
exponential goals periods
|
|
159
|
+
exponential goals stats --workspace clear --period Q3-2026
|
|
160
|
+
|
|
161
|
+
# The quarter at a glance: objectives with their key results nested
|
|
162
|
+
exponential okrs list --workspace clear
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Key results:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
exponential goals kr list --goal 46
|
|
169
|
+
exponential goals kr list --workspace clear --status at-risk
|
|
170
|
+
exponential goals kr create --goal 46 --title "Weekly active agents" --target 500 --unit count
|
|
171
|
+
exponential goals kr checkin --id <cuid> --value 120 --note "post-launch bump"
|
|
172
|
+
exponential goals kr update --id <cuid> --target 600
|
|
173
|
+
exponential goals kr link --id <cuid> --feature <feature-cuid> # or --project <cuid>
|
|
174
|
+
exponential goals kr unlink --id <cuid> --project <cuid>
|
|
175
|
+
exponential goals kr delete --id <cuid>
|
|
176
|
+
```
|
|
177
|
+
|
|
133
178
|
### Projects
|
|
134
179
|
|
|
135
180
|
```bash
|
|
136
181
|
# List all projects
|
|
137
182
|
exponential projects list
|
|
138
183
|
|
|
139
|
-
# List projects in a workspace
|
|
140
|
-
exponential projects list --workspace
|
|
184
|
+
# List projects in a workspace (slug or CUID)
|
|
185
|
+
exponential projects list --workspace clear
|
|
186
|
+
|
|
187
|
+
# One project, with its objectives, key results, DRI and actions
|
|
188
|
+
exponential projects get <cuid|slug>
|
|
189
|
+
|
|
190
|
+
# Partial update — unnamed fields are preserved
|
|
191
|
+
exponential projects update --id <cuid> --name "KR support" --status COMPLETED
|
|
192
|
+
exponential projects update --id <cuid> --product none
|
|
193
|
+
|
|
194
|
+
# Refuses while the project still has actions or OKR links
|
|
195
|
+
exponential projects delete --id <cuid> [--force]
|
|
141
196
|
```
|
|
142
197
|
|
|
143
198
|
### Workspaces
|
package/dist/index.js
CHANGED
|
@@ -899,6 +899,17 @@ ${chalk3[getFeatureStatusColor(feature.status)](`[${feature.status}]`)} ${chalk3
|
|
|
899
899
|
if (feature.priority != null) {
|
|
900
900
|
console.log(` ${chalk3.magenta("Priority:")} ${feature.priority}`);
|
|
901
901
|
}
|
|
902
|
+
if (feature.goal) {
|
|
903
|
+
console.log(` ${chalk3.blue("Objective:")} ${feature.goal.title} ${chalk3.gray(`(${feature.goal.id})`)}`);
|
|
904
|
+
}
|
|
905
|
+
if (feature.keyResultLinks?.length) {
|
|
906
|
+
console.log(` ${chalk3.blue("Key results:")}`);
|
|
907
|
+
for (const link of feature.keyResultLinks) {
|
|
908
|
+
const kr = link.keyResult;
|
|
909
|
+
const progress = kr.currentValue != null && kr.targetValue != null ? `${kr.currentValue}/${kr.targetValue}${kr.unit && kr.unit !== "custom" ? ` ${kr.unit}` : ""}, ` : "";
|
|
910
|
+
console.log(` \xB7 ${kr.title} ${chalk3.gray(`(${progress}${kr.period}, ${kr.id})`)}`);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
902
913
|
if (feature._count?.tickets != null) {
|
|
903
914
|
console.log(` ${chalk3.cyan("Tickets:")} ${feature._count.tickets}`);
|
|
904
915
|
}
|
|
@@ -923,9 +934,26 @@ Features (${features.length} total)`));
|
|
|
923
934
|
const ticketCount = f._count?.tickets != null ? chalk3.gray(` \u2014 ${f._count.tickets} tickets`) : "";
|
|
924
935
|
console.log(` ${statusBadge} ${chalk3.bold(f.name)}${ticketCount}`);
|
|
925
936
|
console.log(chalk3.gray(` ID: ${f.id}`));
|
|
937
|
+
if (f.keyResultLinks?.length) {
|
|
938
|
+
const titles = f.keyResultLinks.map((l) => l.keyResult.title).join("; ");
|
|
939
|
+
console.log(chalk3.gray(` KRs: ${titles}`));
|
|
940
|
+
}
|
|
926
941
|
}
|
|
927
942
|
console.log();
|
|
928
943
|
}
|
|
944
|
+
function summarizeFeatureKeyResult(link) {
|
|
945
|
+
const kr = link.keyResult;
|
|
946
|
+
return {
|
|
947
|
+
keyResultId: kr.id,
|
|
948
|
+
title: kr.title,
|
|
949
|
+
period: kr.period,
|
|
950
|
+
goalId: kr.goalId,
|
|
951
|
+
status: kr.status ?? null,
|
|
952
|
+
currentValue: kr.currentValue ?? null,
|
|
953
|
+
targetValue: kr.targetValue ?? null,
|
|
954
|
+
unit: kr.unit ?? null
|
|
955
|
+
};
|
|
956
|
+
}
|
|
929
957
|
function transformFeature(feature) {
|
|
930
958
|
return {
|
|
931
959
|
id: feature.id,
|
|
@@ -938,6 +966,12 @@ function transformFeature(feature) {
|
|
|
938
966
|
priority: feature.priority,
|
|
939
967
|
goalId: feature.goalId,
|
|
940
968
|
goal: feature.goal ?? null,
|
|
969
|
+
// Flattened out of the join row (ADR-0050): callers building a per-feature
|
|
970
|
+
// "which key results does this move" column want the key results, not the
|
|
971
|
+
// edge rows. `goal` above is the coarser Objective alignment and is a
|
|
972
|
+
// different question - the two are allowed to disagree, so both ride along.
|
|
973
|
+
// Progress values are present on `features get` and absent on `list`.
|
|
974
|
+
keyResults: feature.keyResultLinks?.map(summarizeFeatureKeyResult) ?? null,
|
|
941
975
|
counts: feature._count ?? null,
|
|
942
976
|
createdAt: new Date(feature.createdAt).toISOString(),
|
|
943
977
|
updatedAt: new Date(feature.updatedAt).toISOString()
|
|
@@ -1748,6 +1782,63 @@ OKRs (${objectives.length} objectives, ${totalKeyResults} key results)`)
|
|
|
1748
1782
|
}
|
|
1749
1783
|
console.log();
|
|
1750
1784
|
}
|
|
1785
|
+
function outputProjectJson(project) {
|
|
1786
|
+
console.log(JSON.stringify({
|
|
1787
|
+
...transformProject(project),
|
|
1788
|
+
// The detail read has no `workspace` relation to expand, unlike the list
|
|
1789
|
+
// read — so `transformProject` alone would emit `workspace: null` and no id
|
|
1790
|
+
// at all, which reads as "personal project" rather than "not fetched".
|
|
1791
|
+
workspaceId: project.workspaceId,
|
|
1792
|
+
slug: project.slug ?? null,
|
|
1793
|
+
productId: project.productId ?? null,
|
|
1794
|
+
driId: project.driId ?? null,
|
|
1795
|
+
dri: project.dri ?? null,
|
|
1796
|
+
team: project.team ?? null,
|
|
1797
|
+
goals: project.goals ?? [],
|
|
1798
|
+
keyResults: project.keyResults?.map((k) => ({
|
|
1799
|
+
id: k.keyResultId,
|
|
1800
|
+
title: k.keyResult?.title ?? null,
|
|
1801
|
+
goal: k.keyResult?.goal ?? null
|
|
1802
|
+
})) ?? [],
|
|
1803
|
+
outcomes: project.outcomes ?? [],
|
|
1804
|
+
actionCount: project.actions?.length ?? null,
|
|
1805
|
+
createdAt: project.createdAt ? new Date(project.createdAt).toISOString() : null
|
|
1806
|
+
}, null, 2));
|
|
1807
|
+
}
|
|
1808
|
+
function outputProjectPretty(project) {
|
|
1809
|
+
console.log(chalk3.gray("\u2500".repeat(50)));
|
|
1810
|
+
console.log(`
|
|
1811
|
+
${chalk3.bold(project.name)}`);
|
|
1812
|
+
console.log(chalk3.gray(` ID: ${project.id}`));
|
|
1813
|
+
if (project.status) console.log(` ${chalk3.cyan("Status:")} ${project.status}`);
|
|
1814
|
+
if (project.priority) console.log(` ${chalk3.magenta("Priority:")} ${project.priority}`);
|
|
1815
|
+
if (project.workspace) {
|
|
1816
|
+
console.log(` ${chalk3.yellow("Workspace:")} ${project.workspace.name} (${project.workspace.slug})`);
|
|
1817
|
+
}
|
|
1818
|
+
if (project.description) {
|
|
1819
|
+
console.log(` ${chalk3.gray("Description:")} ${project.description.substring(0, 200)}${project.description.length > 200 ? "..." : ""}`);
|
|
1820
|
+
}
|
|
1821
|
+
if (project.dri) {
|
|
1822
|
+
console.log(` ${chalk3.magenta("DRI:")} ${project.dri.name ?? project.dri.email ?? project.dri.id}`);
|
|
1823
|
+
}
|
|
1824
|
+
if (project.goals?.length) {
|
|
1825
|
+
console.log(` ${chalk3.cyan("Objectives:")}`);
|
|
1826
|
+
for (const g of project.goals) {
|
|
1827
|
+
console.log(` - #${g.id} ${g.title}`);
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
if (project.keyResults?.length) {
|
|
1831
|
+
console.log(` ${chalk3.cyan("Key results:")}`);
|
|
1832
|
+
for (const k of project.keyResults) {
|
|
1833
|
+
const objective = k.keyResult?.goal ? chalk3.gray(` (objective #${k.keyResult.goal.id})`) : "";
|
|
1834
|
+
console.log(` - ${k.keyResult?.title ?? k.keyResultId} ${chalk3.gray(`[${k.keyResultId}]`)}${objective}`);
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
if (project.actions?.length) {
|
|
1838
|
+
console.log(` ${chalk3.cyan("Actions:")} ${project.actions.length}`);
|
|
1839
|
+
}
|
|
1840
|
+
console.log();
|
|
1841
|
+
}
|
|
1751
1842
|
|
|
1752
1843
|
// src/commands/actions.ts
|
|
1753
1844
|
var VALID_PRIORITIES = [
|
|
@@ -3279,6 +3370,17 @@ function summarizeTicket(ticket) {
|
|
|
3279
3370
|
function ticketLabel(ticket) {
|
|
3280
3371
|
return ticket.shortId ?? (ticket.number != null ? `#${ticket.number}` : ticket.id);
|
|
3281
3372
|
}
|
|
3373
|
+
function parseFeatureGoalId(value) {
|
|
3374
|
+
if (value === void 0) return void 0;
|
|
3375
|
+
if (value === "none") return null;
|
|
3376
|
+
const id = Number(value);
|
|
3377
|
+
if (!Number.isInteger(id)) {
|
|
3378
|
+
throw new Error(
|
|
3379
|
+
`--goal must be a whole number or "none", got "${value}". Find one with \`exponential goals list\`.`
|
|
3380
|
+
);
|
|
3381
|
+
}
|
|
3382
|
+
return id;
|
|
3383
|
+
}
|
|
3282
3384
|
function validateFeatureStatus(value) {
|
|
3283
3385
|
if (!value) return void 0;
|
|
3284
3386
|
if (!FEATURE_STATUSES.includes(value)) {
|
|
@@ -3358,7 +3460,10 @@ function createFeaturesCommand() {
|
|
|
3358
3460
|
}
|
|
3359
3461
|
}
|
|
3360
3462
|
);
|
|
3361
|
-
features.command("update").description("Update an existing feature").requiredOption("--id <id>", "Feature CUID").option("-n, --name <name>", "New name").option("-d, --description <text>", "New description").option("--vision <text>", "New vision text").option("--status <status>", `Status: ${FEATURE_STATUSES.join(", ")}`).option("--priority <n>", "Priority 0-4", parseInt).option("--effort <n>", "Effort estimate", parseFloat).option("--area <id>", 'Area CUID ("none" to clear)').
|
|
3463
|
+
features.command("update").description("Update an existing feature").requiredOption("--id <id>", "Feature CUID").option("-n, --name <name>", "New name").option("-d, --description <text>", "New description").option("--vision <text>", "New vision text").option("--status <status>", `Status: ${FEATURE_STATUSES.join(", ")}`).option("--priority <n>", "Priority 0-4", parseInt).option("--effort <n>", "Effort estimate", parseFloat).option("--area <id>", 'Area CUID ("none" to clear)').option(
|
|
3464
|
+
"--goal <id|none>",
|
|
3465
|
+
'Objective (goal) id this feature serves ("none" to clear). For progress rollup, link the finer Feature\u2192key-result edge with `goals kr link --feature`.'
|
|
3466
|
+
).action(
|
|
3362
3467
|
async (options, cmd) => {
|
|
3363
3468
|
const globalOpts = cmd.optsWithGlobals();
|
|
3364
3469
|
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
@@ -3373,7 +3478,8 @@ function createFeaturesCommand() {
|
|
|
3373
3478
|
status,
|
|
3374
3479
|
priority: options.priority,
|
|
3375
3480
|
effort: options.effort,
|
|
3376
|
-
areaId: options.area === "none" ? null : options.area
|
|
3481
|
+
areaId: options.area === "none" ? null : options.area,
|
|
3482
|
+
goalId: parseFeatureGoalId(options.goal)
|
|
3377
3483
|
});
|
|
3378
3484
|
if (useJson) outputFeatureJson(feature);
|
|
3379
3485
|
else {
|
|
@@ -4033,15 +4139,41 @@ function createProductsCommand() {
|
|
|
4033
4139
|
|
|
4034
4140
|
// src/commands/projects.ts
|
|
4035
4141
|
import { Command as Command17 } from "commander";
|
|
4142
|
+
var PROJECT_STATUSES = [
|
|
4143
|
+
"ACTIVE",
|
|
4144
|
+
"ON_HOLD",
|
|
4145
|
+
"COMPLETED",
|
|
4146
|
+
"CANCELLED"
|
|
4147
|
+
];
|
|
4148
|
+
var PROJECT_PRIORITIES = ["HIGH", "MEDIUM", "LOW", "NONE"];
|
|
4149
|
+
function validateProjectStatus(value) {
|
|
4150
|
+
if (!value) return void 0;
|
|
4151
|
+
if (!PROJECT_STATUSES.includes(value)) {
|
|
4152
|
+
throw new Error(
|
|
4153
|
+
`Invalid project status "${value}". Valid: ${PROJECT_STATUSES.join(", ")}`
|
|
4154
|
+
);
|
|
4155
|
+
}
|
|
4156
|
+
return value;
|
|
4157
|
+
}
|
|
4158
|
+
function validateProjectPriority(value) {
|
|
4159
|
+
if (!value) return void 0;
|
|
4160
|
+
if (!PROJECT_PRIORITIES.includes(value)) {
|
|
4161
|
+
throw new Error(
|
|
4162
|
+
`Invalid project priority "${value}". Valid: ${PROJECT_PRIORITIES.join(", ")}`
|
|
4163
|
+
);
|
|
4164
|
+
}
|
|
4165
|
+
return value;
|
|
4166
|
+
}
|
|
4036
4167
|
function createProjectsCommand() {
|
|
4037
4168
|
const projects = new Command17("projects").description("Manage projects");
|
|
4038
|
-
projects.command("list").description("List all projects").option("--workspace <id>", "
|
|
4169
|
+
projects.command("list").description("List all projects").option("--workspace <slug|id>", "Workspace slug or CUID").option("--include-actions", "Include actions in output").action(async (options, cmd) => {
|
|
4039
4170
|
const globalOpts = cmd.optsWithGlobals();
|
|
4040
4171
|
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
4041
4172
|
try {
|
|
4042
4173
|
const client = getClient();
|
|
4174
|
+
const workspaceId = options.workspace ? await resolveWorkspaceId(client, options.workspace) : void 0;
|
|
4043
4175
|
const projects2 = await client.projects.list({
|
|
4044
|
-
workspaceId
|
|
4176
|
+
workspaceId,
|
|
4045
4177
|
includeActions: options.includeActions
|
|
4046
4178
|
});
|
|
4047
4179
|
if (useJson) {
|
|
@@ -4053,6 +4185,101 @@ function createProjectsCommand() {
|
|
|
4053
4185
|
handleError(error, useJson);
|
|
4054
4186
|
}
|
|
4055
4187
|
});
|
|
4188
|
+
projects.command("get <id>").description(
|
|
4189
|
+
"Get one project with its objectives, key results, DRI and actions \u2014 none of which `projects list` carries. Accepts a CUID, a slug, or the slug-cuid form from app URLs."
|
|
4190
|
+
).action(async (id, _options, cmd) => {
|
|
4191
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
4192
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
4193
|
+
try {
|
|
4194
|
+
const client = getClient();
|
|
4195
|
+
const project = await client.projects.get(id);
|
|
4196
|
+
if (useJson) outputProjectJson(project);
|
|
4197
|
+
else outputProjectPretty(project);
|
|
4198
|
+
} catch (error) {
|
|
4199
|
+
handleError(error, useJson);
|
|
4200
|
+
}
|
|
4201
|
+
});
|
|
4202
|
+
projects.command("update").description(
|
|
4203
|
+
"Update a project. Only the fields you pass are written; the rest are read back from the project and re-sent unchanged."
|
|
4204
|
+
).requiredOption("--id <cuid>", "Project CUID").option("-n, --name <name>", "New name").option("-d, --description <text>", "New description").option("--status <status>", `Status: ${PROJECT_STATUSES.join(", ")}`).option("--priority <priority>", `Priority: ${PROJECT_PRIORITIES.join(", ")}`).option("--product <cuid|none>", 'Product to file the project under ("none" to unlink)').option("--dri <userId|none>", 'Directly responsible individual ("none" to clear)').action(
|
|
4205
|
+
async (options, cmd) => {
|
|
4206
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
4207
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
4208
|
+
try {
|
|
4209
|
+
const status = validateProjectStatus(options.status);
|
|
4210
|
+
const priority = validateProjectPriority(options.priority);
|
|
4211
|
+
const client = getClient();
|
|
4212
|
+
const updated = await client.projects.update({
|
|
4213
|
+
id: options.id,
|
|
4214
|
+
name: options.name,
|
|
4215
|
+
description: options.description,
|
|
4216
|
+
status,
|
|
4217
|
+
priority,
|
|
4218
|
+
productId: options.product === void 0 ? void 0 : options.product === "none" ? null : options.product,
|
|
4219
|
+
driId: options.dri === void 0 ? void 0 : options.dri === "none" ? null : options.dri
|
|
4220
|
+
});
|
|
4221
|
+
if (useJson) outputProjectJson(updated);
|
|
4222
|
+
else {
|
|
4223
|
+
console.log("\n\u2713 Project updated");
|
|
4224
|
+
outputProjectPretty(updated);
|
|
4225
|
+
}
|
|
4226
|
+
} catch (error) {
|
|
4227
|
+
handleError(error, useJson);
|
|
4228
|
+
}
|
|
4229
|
+
}
|
|
4230
|
+
);
|
|
4231
|
+
projects.command("delete").description(
|
|
4232
|
+
"Delete a project. Refuses while it still has actions or is linked to an objective or key result, unless --force."
|
|
4233
|
+
).requiredOption("--id <cuid>", "Project CUID").option("--force", "Delete anyway, taking the actions and links with it").action(async (options, cmd) => {
|
|
4234
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
4235
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
4236
|
+
try {
|
|
4237
|
+
const client = getClient();
|
|
4238
|
+
const project = await client.projects.get(options.id);
|
|
4239
|
+
const actions = project.actions ?? [];
|
|
4240
|
+
const goals = project.goals ?? [];
|
|
4241
|
+
const keyResults = project.keyResults ?? [];
|
|
4242
|
+
const blockers = [
|
|
4243
|
+
actions.length ? `${actions.length} action(s)` : null,
|
|
4244
|
+
goals.length ? `${goals.length} objective link(s)` : null,
|
|
4245
|
+
keyResults.length ? `${keyResults.length} key result link(s)` : null
|
|
4246
|
+
].filter(Boolean);
|
|
4247
|
+
if (blockers.length > 0 && !options.force) {
|
|
4248
|
+
const payload2 = {
|
|
4249
|
+
deleted: false,
|
|
4250
|
+
id: project.id,
|
|
4251
|
+
reason: `has ${blockers.join(", ")}`,
|
|
4252
|
+
actionCount: actions.length,
|
|
4253
|
+
goals: goals.map((g) => ({ id: g.id, title: g.title })),
|
|
4254
|
+
keyResults: keyResults.map((k) => ({ id: k.keyResultId }))
|
|
4255
|
+
};
|
|
4256
|
+
if (useJson) console.log(JSON.stringify(payload2, null, 2));
|
|
4257
|
+
else {
|
|
4258
|
+
console.error(
|
|
4259
|
+
`Project "${project.name}" has ${blockers.join(", ")}.
|
|
4260
|
+
Re-run with --force to delete it anyway.`
|
|
4261
|
+
);
|
|
4262
|
+
}
|
|
4263
|
+
process.exitCode = 1;
|
|
4264
|
+
return;
|
|
4265
|
+
}
|
|
4266
|
+
await client.projects.delete(project.id);
|
|
4267
|
+
const payload = {
|
|
4268
|
+
deleted: true,
|
|
4269
|
+
id: project.id,
|
|
4270
|
+
actionsDeleted: actions.length
|
|
4271
|
+
};
|
|
4272
|
+
if (useJson) console.log(JSON.stringify(payload, null, 2));
|
|
4273
|
+
else {
|
|
4274
|
+
console.log("\u2713 Project deleted");
|
|
4275
|
+
if (actions.length) {
|
|
4276
|
+
console.log(` ${actions.length} action(s) went with it`);
|
|
4277
|
+
}
|
|
4278
|
+
}
|
|
4279
|
+
} catch (error) {
|
|
4280
|
+
handleError(error, useJson);
|
|
4281
|
+
}
|
|
4282
|
+
});
|
|
4056
4283
|
return projects;
|
|
4057
4284
|
}
|
|
4058
4285
|
|
|
@@ -4809,10 +5036,17 @@ function createKeyResultsCommand() {
|
|
|
4809
5036
|
try {
|
|
4810
5037
|
const status = validateKeyResultStatus(options.status);
|
|
4811
5038
|
const client = getClient();
|
|
4812
|
-
const
|
|
5039
|
+
const goalId = options.goal ? parseGoalId(options.goal) : void 0;
|
|
5040
|
+
let workspaceId;
|
|
5041
|
+
if (options.workspace) {
|
|
5042
|
+
workspaceId = await resolveWorkspaceId(client, options.workspace);
|
|
5043
|
+
} else if (goalId !== void 0) {
|
|
5044
|
+
const goal = await client.goals.get(goalId);
|
|
5045
|
+
workspaceId = goal.workspaceId ?? void 0;
|
|
5046
|
+
}
|
|
4813
5047
|
const list = await client.goals.keyResults.list({
|
|
4814
5048
|
workspaceId,
|
|
4815
|
-
goalId
|
|
5049
|
+
goalId,
|
|
4816
5050
|
period: options.period,
|
|
4817
5051
|
status,
|
|
4818
5052
|
onlyMine: options.mine
|
|
@@ -5170,15 +5404,25 @@ function createGoalsCommand() {
|
|
|
5170
5404
|
);
|
|
5171
5405
|
goals.command("update").description(
|
|
5172
5406
|
"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)').
|
|
5407
|
+
).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)').option(
|
|
5408
|
+
"-w, --workspace <slug|id|none>",
|
|
5409
|
+
'Re-home the objective; "none" makes it personal'
|
|
5410
|
+
).action(
|
|
5174
5411
|
async (options, cmd) => {
|
|
5175
5412
|
const globalOpts = cmd.optsWithGlobals();
|
|
5176
5413
|
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
5177
5414
|
try {
|
|
5178
5415
|
const status = validateWritableGoalStatus(options.status);
|
|
5179
5416
|
const clearable = (value) => value === void 0 ? void 0 : value === "none" ? null : value;
|
|
5180
|
-
const
|
|
5417
|
+
const client = getClient();
|
|
5418
|
+
let workspaceId;
|
|
5419
|
+
if (options.workspace === "none") workspaceId = null;
|
|
5420
|
+
else if (options.workspace !== void 0) {
|
|
5421
|
+
workspaceId = await resolveWorkspaceId(client, options.workspace);
|
|
5422
|
+
}
|
|
5423
|
+
const goal = await client.goals.update({
|
|
5181
5424
|
id: parseGoalId(options.id),
|
|
5425
|
+
workspaceId,
|
|
5182
5426
|
title: options.title,
|
|
5183
5427
|
description: clearable(options.description),
|
|
5184
5428
|
whyThisGoal: clearable(options.why),
|
|
@@ -5258,21 +5502,60 @@ function createGoalsCommand() {
|
|
|
5258
5502
|
handleError(error, useJson);
|
|
5259
5503
|
}
|
|
5260
5504
|
});
|
|
5261
|
-
goals.command("delete").description(
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5505
|
+
goals.command("delete").description(
|
|
5506
|
+
"Delete an objective. Refuses while it still has key results \u2014 deleting an objective deletes them too (KeyResult.goalId cascades), taking their check-in history with them."
|
|
5507
|
+
).requiredOption("--id <n>", "Objective ID (a number)").option(
|
|
5508
|
+
"--with-key-results",
|
|
5509
|
+
"Delete the objective and cascade-delete its key results (destructive)"
|
|
5510
|
+
).action(
|
|
5511
|
+
async (options, cmd) => {
|
|
5512
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
5513
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
5514
|
+
try {
|
|
5515
|
+
const id = parseGoalId(options.id);
|
|
5516
|
+
const client = getClient();
|
|
5517
|
+
const goal = await client.goals.get(id);
|
|
5518
|
+
const keyResults = goal.keyResults ?? [];
|
|
5519
|
+
const children = goal.childGoals ?? [];
|
|
5520
|
+
if (keyResults.length > 0 && !options.withKeyResults) {
|
|
5521
|
+
const payload2 = {
|
|
5522
|
+
deleted: false,
|
|
5523
|
+
id,
|
|
5524
|
+
reason: `has ${keyResults.length} key result(s)`,
|
|
5525
|
+
keyResults: keyResults.map((kr) => ({ id: kr.id }))
|
|
5526
|
+
};
|
|
5527
|
+
if (useJson) console.log(JSON.stringify(payload2, null, 2));
|
|
5528
|
+
else {
|
|
5529
|
+
console.error(
|
|
5530
|
+
`Objective #${id} has ${keyResults.length} key result(s). Deleting it deletes them too.
|
|
5531
|
+
Re-run with --with-key-results to go ahead, or move them with \`goals kr update --id <cuid> --goal <other>\`.`
|
|
5532
|
+
);
|
|
5533
|
+
}
|
|
5534
|
+
process.exitCode = 1;
|
|
5535
|
+
return;
|
|
5536
|
+
}
|
|
5537
|
+
await client.goals.delete(id);
|
|
5538
|
+
const payload = {
|
|
5539
|
+
deleted: true,
|
|
5540
|
+
id,
|
|
5541
|
+
keyResultsDeleted: keyResults.map((kr) => ({ id: kr.id })),
|
|
5542
|
+
childGoalsDetached: children.map((c) => ({ id: c.id, title: c.title }))
|
|
5543
|
+
};
|
|
5544
|
+
if (useJson) console.log(JSON.stringify(payload, null, 2));
|
|
5545
|
+
else {
|
|
5546
|
+
console.log("\u2713 Goal deleted");
|
|
5547
|
+
if (keyResults.length) {
|
|
5548
|
+
console.log(` ${keyResults.length} key result(s) deleted with it`);
|
|
5549
|
+
}
|
|
5550
|
+
if (children.length) {
|
|
5551
|
+
console.log(` ${children.length} sub-goal(s) left detached`);
|
|
5552
|
+
}
|
|
5553
|
+
}
|
|
5554
|
+
} catch (error) {
|
|
5555
|
+
handleError(error, useJson);
|
|
5271
5556
|
}
|
|
5272
|
-
} catch (error) {
|
|
5273
|
-
handleError(error, useJson);
|
|
5274
5557
|
}
|
|
5275
|
-
|
|
5558
|
+
);
|
|
5276
5559
|
goals.command("periods").description("List the conventional OKR period strings").action(async (_options, cmd) => {
|
|
5277
5560
|
const globalOpts = cmd.optsWithGlobals();
|
|
5278
5561
|
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|