exponential-cli 1.11.1 → 1.11.2
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 +270 -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
|
@@ -1748,6 +1748,63 @@ OKRs (${objectives.length} objectives, ${totalKeyResults} key results)`)
|
|
|
1748
1748
|
}
|
|
1749
1749
|
console.log();
|
|
1750
1750
|
}
|
|
1751
|
+
function outputProjectJson(project) {
|
|
1752
|
+
console.log(JSON.stringify({
|
|
1753
|
+
...transformProject(project),
|
|
1754
|
+
// The detail read has no `workspace` relation to expand, unlike the list
|
|
1755
|
+
// read — so `transformProject` alone would emit `workspace: null` and no id
|
|
1756
|
+
// at all, which reads as "personal project" rather than "not fetched".
|
|
1757
|
+
workspaceId: project.workspaceId,
|
|
1758
|
+
slug: project.slug ?? null,
|
|
1759
|
+
productId: project.productId ?? null,
|
|
1760
|
+
driId: project.driId ?? null,
|
|
1761
|
+
dri: project.dri ?? null,
|
|
1762
|
+
team: project.team ?? null,
|
|
1763
|
+
goals: project.goals ?? [],
|
|
1764
|
+
keyResults: project.keyResults?.map((k) => ({
|
|
1765
|
+
id: k.keyResultId,
|
|
1766
|
+
title: k.keyResult?.title ?? null,
|
|
1767
|
+
goal: k.keyResult?.goal ?? null
|
|
1768
|
+
})) ?? [],
|
|
1769
|
+
outcomes: project.outcomes ?? [],
|
|
1770
|
+
actionCount: project.actions?.length ?? null,
|
|
1771
|
+
createdAt: project.createdAt ? new Date(project.createdAt).toISOString() : null
|
|
1772
|
+
}, null, 2));
|
|
1773
|
+
}
|
|
1774
|
+
function outputProjectPretty(project) {
|
|
1775
|
+
console.log(chalk3.gray("\u2500".repeat(50)));
|
|
1776
|
+
console.log(`
|
|
1777
|
+
${chalk3.bold(project.name)}`);
|
|
1778
|
+
console.log(chalk3.gray(` ID: ${project.id}`));
|
|
1779
|
+
if (project.status) console.log(` ${chalk3.cyan("Status:")} ${project.status}`);
|
|
1780
|
+
if (project.priority) console.log(` ${chalk3.magenta("Priority:")} ${project.priority}`);
|
|
1781
|
+
if (project.workspace) {
|
|
1782
|
+
console.log(` ${chalk3.yellow("Workspace:")} ${project.workspace.name} (${project.workspace.slug})`);
|
|
1783
|
+
}
|
|
1784
|
+
if (project.description) {
|
|
1785
|
+
console.log(` ${chalk3.gray("Description:")} ${project.description.substring(0, 200)}${project.description.length > 200 ? "..." : ""}`);
|
|
1786
|
+
}
|
|
1787
|
+
if (project.dri) {
|
|
1788
|
+
console.log(` ${chalk3.magenta("DRI:")} ${project.dri.name ?? project.dri.email ?? project.dri.id}`);
|
|
1789
|
+
}
|
|
1790
|
+
if (project.goals?.length) {
|
|
1791
|
+
console.log(` ${chalk3.cyan("Objectives:")}`);
|
|
1792
|
+
for (const g of project.goals) {
|
|
1793
|
+
console.log(` - #${g.id} ${g.title}`);
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
if (project.keyResults?.length) {
|
|
1797
|
+
console.log(` ${chalk3.cyan("Key results:")}`);
|
|
1798
|
+
for (const k of project.keyResults) {
|
|
1799
|
+
const objective = k.keyResult?.goal ? chalk3.gray(` (objective #${k.keyResult.goal.id})`) : "";
|
|
1800
|
+
console.log(` - ${k.keyResult?.title ?? k.keyResultId} ${chalk3.gray(`[${k.keyResultId}]`)}${objective}`);
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
if (project.actions?.length) {
|
|
1804
|
+
console.log(` ${chalk3.cyan("Actions:")} ${project.actions.length}`);
|
|
1805
|
+
}
|
|
1806
|
+
console.log();
|
|
1807
|
+
}
|
|
1751
1808
|
|
|
1752
1809
|
// src/commands/actions.ts
|
|
1753
1810
|
var VALID_PRIORITIES = [
|
|
@@ -3279,6 +3336,17 @@ function summarizeTicket(ticket) {
|
|
|
3279
3336
|
function ticketLabel(ticket) {
|
|
3280
3337
|
return ticket.shortId ?? (ticket.number != null ? `#${ticket.number}` : ticket.id);
|
|
3281
3338
|
}
|
|
3339
|
+
function parseFeatureGoalId(value) {
|
|
3340
|
+
if (value === void 0) return void 0;
|
|
3341
|
+
if (value === "none") return null;
|
|
3342
|
+
const id = Number(value);
|
|
3343
|
+
if (!Number.isInteger(id)) {
|
|
3344
|
+
throw new Error(
|
|
3345
|
+
`--goal must be a whole number or "none", got "${value}". Find one with \`exponential goals list\`.`
|
|
3346
|
+
);
|
|
3347
|
+
}
|
|
3348
|
+
return id;
|
|
3349
|
+
}
|
|
3282
3350
|
function validateFeatureStatus(value) {
|
|
3283
3351
|
if (!value) return void 0;
|
|
3284
3352
|
if (!FEATURE_STATUSES.includes(value)) {
|
|
@@ -3358,7 +3426,10 @@ function createFeaturesCommand() {
|
|
|
3358
3426
|
}
|
|
3359
3427
|
}
|
|
3360
3428
|
);
|
|
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)').
|
|
3429
|
+
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(
|
|
3430
|
+
"--goal <id|none>",
|
|
3431
|
+
'Objective (goal) id this feature serves ("none" to clear). For progress rollup, link the finer Feature\u2192key-result edge with `goals kr link --feature`.'
|
|
3432
|
+
).action(
|
|
3362
3433
|
async (options, cmd) => {
|
|
3363
3434
|
const globalOpts = cmd.optsWithGlobals();
|
|
3364
3435
|
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
@@ -3373,7 +3444,8 @@ function createFeaturesCommand() {
|
|
|
3373
3444
|
status,
|
|
3374
3445
|
priority: options.priority,
|
|
3375
3446
|
effort: options.effort,
|
|
3376
|
-
areaId: options.area === "none" ? null : options.area
|
|
3447
|
+
areaId: options.area === "none" ? null : options.area,
|
|
3448
|
+
goalId: parseFeatureGoalId(options.goal)
|
|
3377
3449
|
});
|
|
3378
3450
|
if (useJson) outputFeatureJson(feature);
|
|
3379
3451
|
else {
|
|
@@ -4033,15 +4105,41 @@ function createProductsCommand() {
|
|
|
4033
4105
|
|
|
4034
4106
|
// src/commands/projects.ts
|
|
4035
4107
|
import { Command as Command17 } from "commander";
|
|
4108
|
+
var PROJECT_STATUSES = [
|
|
4109
|
+
"ACTIVE",
|
|
4110
|
+
"ON_HOLD",
|
|
4111
|
+
"COMPLETED",
|
|
4112
|
+
"CANCELLED"
|
|
4113
|
+
];
|
|
4114
|
+
var PROJECT_PRIORITIES = ["HIGH", "MEDIUM", "LOW", "NONE"];
|
|
4115
|
+
function validateProjectStatus(value) {
|
|
4116
|
+
if (!value) return void 0;
|
|
4117
|
+
if (!PROJECT_STATUSES.includes(value)) {
|
|
4118
|
+
throw new Error(
|
|
4119
|
+
`Invalid project status "${value}". Valid: ${PROJECT_STATUSES.join(", ")}`
|
|
4120
|
+
);
|
|
4121
|
+
}
|
|
4122
|
+
return value;
|
|
4123
|
+
}
|
|
4124
|
+
function validateProjectPriority(value) {
|
|
4125
|
+
if (!value) return void 0;
|
|
4126
|
+
if (!PROJECT_PRIORITIES.includes(value)) {
|
|
4127
|
+
throw new Error(
|
|
4128
|
+
`Invalid project priority "${value}". Valid: ${PROJECT_PRIORITIES.join(", ")}`
|
|
4129
|
+
);
|
|
4130
|
+
}
|
|
4131
|
+
return value;
|
|
4132
|
+
}
|
|
4036
4133
|
function createProjectsCommand() {
|
|
4037
4134
|
const projects = new Command17("projects").description("Manage projects");
|
|
4038
|
-
projects.command("list").description("List all projects").option("--workspace <id>", "
|
|
4135
|
+
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
4136
|
const globalOpts = cmd.optsWithGlobals();
|
|
4040
4137
|
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
4041
4138
|
try {
|
|
4042
4139
|
const client = getClient();
|
|
4140
|
+
const workspaceId = options.workspace ? await resolveWorkspaceId(client, options.workspace) : void 0;
|
|
4043
4141
|
const projects2 = await client.projects.list({
|
|
4044
|
-
workspaceId
|
|
4142
|
+
workspaceId,
|
|
4045
4143
|
includeActions: options.includeActions
|
|
4046
4144
|
});
|
|
4047
4145
|
if (useJson) {
|
|
@@ -4053,6 +4151,101 @@ function createProjectsCommand() {
|
|
|
4053
4151
|
handleError(error, useJson);
|
|
4054
4152
|
}
|
|
4055
4153
|
});
|
|
4154
|
+
projects.command("get <id>").description(
|
|
4155
|
+
"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."
|
|
4156
|
+
).action(async (id, _options, cmd) => {
|
|
4157
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
4158
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
4159
|
+
try {
|
|
4160
|
+
const client = getClient();
|
|
4161
|
+
const project = await client.projects.get(id);
|
|
4162
|
+
if (useJson) outputProjectJson(project);
|
|
4163
|
+
else outputProjectPretty(project);
|
|
4164
|
+
} catch (error) {
|
|
4165
|
+
handleError(error, useJson);
|
|
4166
|
+
}
|
|
4167
|
+
});
|
|
4168
|
+
projects.command("update").description(
|
|
4169
|
+
"Update a project. Only the fields you pass are written; the rest are read back from the project and re-sent unchanged."
|
|
4170
|
+
).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(
|
|
4171
|
+
async (options, cmd) => {
|
|
4172
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
4173
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
4174
|
+
try {
|
|
4175
|
+
const status = validateProjectStatus(options.status);
|
|
4176
|
+
const priority = validateProjectPriority(options.priority);
|
|
4177
|
+
const client = getClient();
|
|
4178
|
+
const updated = await client.projects.update({
|
|
4179
|
+
id: options.id,
|
|
4180
|
+
name: options.name,
|
|
4181
|
+
description: options.description,
|
|
4182
|
+
status,
|
|
4183
|
+
priority,
|
|
4184
|
+
productId: options.product === void 0 ? void 0 : options.product === "none" ? null : options.product,
|
|
4185
|
+
driId: options.dri === void 0 ? void 0 : options.dri === "none" ? null : options.dri
|
|
4186
|
+
});
|
|
4187
|
+
if (useJson) outputProjectJson(updated);
|
|
4188
|
+
else {
|
|
4189
|
+
console.log("\n\u2713 Project updated");
|
|
4190
|
+
outputProjectPretty(updated);
|
|
4191
|
+
}
|
|
4192
|
+
} catch (error) {
|
|
4193
|
+
handleError(error, useJson);
|
|
4194
|
+
}
|
|
4195
|
+
}
|
|
4196
|
+
);
|
|
4197
|
+
projects.command("delete").description(
|
|
4198
|
+
"Delete a project. Refuses while it still has actions or is linked to an objective or key result, unless --force."
|
|
4199
|
+
).requiredOption("--id <cuid>", "Project CUID").option("--force", "Delete anyway, taking the actions and links with it").action(async (options, cmd) => {
|
|
4200
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
4201
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
4202
|
+
try {
|
|
4203
|
+
const client = getClient();
|
|
4204
|
+
const project = await client.projects.get(options.id);
|
|
4205
|
+
const actions = project.actions ?? [];
|
|
4206
|
+
const goals = project.goals ?? [];
|
|
4207
|
+
const keyResults = project.keyResults ?? [];
|
|
4208
|
+
const blockers = [
|
|
4209
|
+
actions.length ? `${actions.length} action(s)` : null,
|
|
4210
|
+
goals.length ? `${goals.length} objective link(s)` : null,
|
|
4211
|
+
keyResults.length ? `${keyResults.length} key result link(s)` : null
|
|
4212
|
+
].filter(Boolean);
|
|
4213
|
+
if (blockers.length > 0 && !options.force) {
|
|
4214
|
+
const payload2 = {
|
|
4215
|
+
deleted: false,
|
|
4216
|
+
id: project.id,
|
|
4217
|
+
reason: `has ${blockers.join(", ")}`,
|
|
4218
|
+
actionCount: actions.length,
|
|
4219
|
+
goals: goals.map((g) => ({ id: g.id, title: g.title })),
|
|
4220
|
+
keyResults: keyResults.map((k) => ({ id: k.keyResultId }))
|
|
4221
|
+
};
|
|
4222
|
+
if (useJson) console.log(JSON.stringify(payload2, null, 2));
|
|
4223
|
+
else {
|
|
4224
|
+
console.error(
|
|
4225
|
+
`Project "${project.name}" has ${blockers.join(", ")}.
|
|
4226
|
+
Re-run with --force to delete it anyway.`
|
|
4227
|
+
);
|
|
4228
|
+
}
|
|
4229
|
+
process.exitCode = 1;
|
|
4230
|
+
return;
|
|
4231
|
+
}
|
|
4232
|
+
await client.projects.delete(project.id);
|
|
4233
|
+
const payload = {
|
|
4234
|
+
deleted: true,
|
|
4235
|
+
id: project.id,
|
|
4236
|
+
actionsDeleted: actions.length
|
|
4237
|
+
};
|
|
4238
|
+
if (useJson) console.log(JSON.stringify(payload, null, 2));
|
|
4239
|
+
else {
|
|
4240
|
+
console.log("\u2713 Project deleted");
|
|
4241
|
+
if (actions.length) {
|
|
4242
|
+
console.log(` ${actions.length} action(s) went with it`);
|
|
4243
|
+
}
|
|
4244
|
+
}
|
|
4245
|
+
} catch (error) {
|
|
4246
|
+
handleError(error, useJson);
|
|
4247
|
+
}
|
|
4248
|
+
});
|
|
4056
4249
|
return projects;
|
|
4057
4250
|
}
|
|
4058
4251
|
|
|
@@ -4809,10 +5002,17 @@ function createKeyResultsCommand() {
|
|
|
4809
5002
|
try {
|
|
4810
5003
|
const status = validateKeyResultStatus(options.status);
|
|
4811
5004
|
const client = getClient();
|
|
4812
|
-
const
|
|
5005
|
+
const goalId = options.goal ? parseGoalId(options.goal) : void 0;
|
|
5006
|
+
let workspaceId;
|
|
5007
|
+
if (options.workspace) {
|
|
5008
|
+
workspaceId = await resolveWorkspaceId(client, options.workspace);
|
|
5009
|
+
} else if (goalId !== void 0) {
|
|
5010
|
+
const goal = await client.goals.get(goalId);
|
|
5011
|
+
workspaceId = goal.workspaceId ?? void 0;
|
|
5012
|
+
}
|
|
4813
5013
|
const list = await client.goals.keyResults.list({
|
|
4814
5014
|
workspaceId,
|
|
4815
|
-
goalId
|
|
5015
|
+
goalId,
|
|
4816
5016
|
period: options.period,
|
|
4817
5017
|
status,
|
|
4818
5018
|
onlyMine: options.mine
|
|
@@ -5170,15 +5370,25 @@ function createGoalsCommand() {
|
|
|
5170
5370
|
);
|
|
5171
5371
|
goals.command("update").description(
|
|
5172
5372
|
"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)').
|
|
5373
|
+
).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(
|
|
5374
|
+
"-w, --workspace <slug|id|none>",
|
|
5375
|
+
'Re-home the objective; "none" makes it personal'
|
|
5376
|
+
).action(
|
|
5174
5377
|
async (options, cmd) => {
|
|
5175
5378
|
const globalOpts = cmd.optsWithGlobals();
|
|
5176
5379
|
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
5177
5380
|
try {
|
|
5178
5381
|
const status = validateWritableGoalStatus(options.status);
|
|
5179
5382
|
const clearable = (value) => value === void 0 ? void 0 : value === "none" ? null : value;
|
|
5180
|
-
const
|
|
5383
|
+
const client = getClient();
|
|
5384
|
+
let workspaceId;
|
|
5385
|
+
if (options.workspace === "none") workspaceId = null;
|
|
5386
|
+
else if (options.workspace !== void 0) {
|
|
5387
|
+
workspaceId = await resolveWorkspaceId(client, options.workspace);
|
|
5388
|
+
}
|
|
5389
|
+
const goal = await client.goals.update({
|
|
5181
5390
|
id: parseGoalId(options.id),
|
|
5391
|
+
workspaceId,
|
|
5182
5392
|
title: options.title,
|
|
5183
5393
|
description: clearable(options.description),
|
|
5184
5394
|
whyThisGoal: clearable(options.why),
|
|
@@ -5258,21 +5468,60 @@ function createGoalsCommand() {
|
|
|
5258
5468
|
handleError(error, useJson);
|
|
5259
5469
|
}
|
|
5260
5470
|
});
|
|
5261
|
-
goals.command("delete").description(
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5471
|
+
goals.command("delete").description(
|
|
5472
|
+
"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."
|
|
5473
|
+
).requiredOption("--id <n>", "Objective ID (a number)").option(
|
|
5474
|
+
"--with-key-results",
|
|
5475
|
+
"Delete the objective and cascade-delete its key results (destructive)"
|
|
5476
|
+
).action(
|
|
5477
|
+
async (options, cmd) => {
|
|
5478
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
5479
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
5480
|
+
try {
|
|
5481
|
+
const id = parseGoalId(options.id);
|
|
5482
|
+
const client = getClient();
|
|
5483
|
+
const goal = await client.goals.get(id);
|
|
5484
|
+
const keyResults = goal.keyResults ?? [];
|
|
5485
|
+
const children = goal.childGoals ?? [];
|
|
5486
|
+
if (keyResults.length > 0 && !options.withKeyResults) {
|
|
5487
|
+
const payload2 = {
|
|
5488
|
+
deleted: false,
|
|
5489
|
+
id,
|
|
5490
|
+
reason: `has ${keyResults.length} key result(s)`,
|
|
5491
|
+
keyResults: keyResults.map((kr) => ({ id: kr.id }))
|
|
5492
|
+
};
|
|
5493
|
+
if (useJson) console.log(JSON.stringify(payload2, null, 2));
|
|
5494
|
+
else {
|
|
5495
|
+
console.error(
|
|
5496
|
+
`Objective #${id} has ${keyResults.length} key result(s). Deleting it deletes them too.
|
|
5497
|
+
Re-run with --with-key-results to go ahead, or move them with \`goals kr update --id <cuid> --goal <other>\`.`
|
|
5498
|
+
);
|
|
5499
|
+
}
|
|
5500
|
+
process.exitCode = 1;
|
|
5501
|
+
return;
|
|
5502
|
+
}
|
|
5503
|
+
await client.goals.delete(id);
|
|
5504
|
+
const payload = {
|
|
5505
|
+
deleted: true,
|
|
5506
|
+
id,
|
|
5507
|
+
keyResultsDeleted: keyResults.map((kr) => ({ id: kr.id })),
|
|
5508
|
+
childGoalsDetached: children.map((c) => ({ id: c.id, title: c.title }))
|
|
5509
|
+
};
|
|
5510
|
+
if (useJson) console.log(JSON.stringify(payload, null, 2));
|
|
5511
|
+
else {
|
|
5512
|
+
console.log("\u2713 Goal deleted");
|
|
5513
|
+
if (keyResults.length) {
|
|
5514
|
+
console.log(` ${keyResults.length} key result(s) deleted with it`);
|
|
5515
|
+
}
|
|
5516
|
+
if (children.length) {
|
|
5517
|
+
console.log(` ${children.length} sub-goal(s) left detached`);
|
|
5518
|
+
}
|
|
5519
|
+
}
|
|
5520
|
+
} catch (error) {
|
|
5521
|
+
handleError(error, useJson);
|
|
5271
5522
|
}
|
|
5272
|
-
} catch (error) {
|
|
5273
|
-
handleError(error, useJson);
|
|
5274
5523
|
}
|
|
5275
|
-
|
|
5524
|
+
);
|
|
5276
5525
|
goals.command("periods").description("List the conventional OKR period strings").action(async (_options, cmd) => {
|
|
5277
5526
|
const globalOpts = cmd.optsWithGlobals();
|
|
5278
5527
|
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|