exponential-cli 1.11.0 → 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 +293 -34
- 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
|
|
|
@@ -4211,18 +4404,8 @@ function createTicketsCommand() {
|
|
|
4211
4404
|
);
|
|
4212
4405
|
}
|
|
4213
4406
|
const client = getClient();
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
if (options.product) {
|
|
4217
|
-
workspaceId = await resolveWorkspaceId(client, options.workspace);
|
|
4218
|
-
productId = await resolveProductId(
|
|
4219
|
-
client,
|
|
4220
|
-
workspaceId,
|
|
4221
|
-
options.product
|
|
4222
|
-
);
|
|
4223
|
-
}
|
|
4224
|
-
const list = await client.tickets.list({
|
|
4225
|
-
productId,
|
|
4407
|
+
const workspaceId = await resolveWorkspaceId(client, options.workspace);
|
|
4408
|
+
const filters = {
|
|
4226
4409
|
status,
|
|
4227
4410
|
type,
|
|
4228
4411
|
featureId: options.feature,
|
|
@@ -4230,7 +4413,27 @@ function createTicketsCommand() {
|
|
|
4230
4413
|
assigneeId: options.assignee,
|
|
4231
4414
|
prUrl: options.pr,
|
|
4232
4415
|
branchName: options.branch
|
|
4233
|
-
}
|
|
4416
|
+
};
|
|
4417
|
+
let list;
|
|
4418
|
+
if (options.product) {
|
|
4419
|
+
const productId = await resolveProductId(
|
|
4420
|
+
client,
|
|
4421
|
+
workspaceId,
|
|
4422
|
+
options.product
|
|
4423
|
+
);
|
|
4424
|
+
list = await client.tickets.list({ productId, ...filters });
|
|
4425
|
+
} else {
|
|
4426
|
+
const products = await client.products.list(workspaceId);
|
|
4427
|
+
const settled = await Promise.allSettled(
|
|
4428
|
+
products.map((p) => client.tickets.list({ productId: p.id, ...filters }))
|
|
4429
|
+
);
|
|
4430
|
+
list = settled.flatMap((r) => r.status === "fulfilled" ? r.value : []);
|
|
4431
|
+
settled.forEach((r, i) => {
|
|
4432
|
+
if (r.status !== "rejected") return;
|
|
4433
|
+
const reason = r.reason instanceof Error ? r.reason.message : String(r.reason);
|
|
4434
|
+
console.error(`Warning: skipped product ${products[i].slug}: ${reason}`);
|
|
4435
|
+
});
|
|
4436
|
+
}
|
|
4234
4437
|
let filtered = list;
|
|
4235
4438
|
if (options.label.length > 0) {
|
|
4236
4439
|
const labelIds = new Set(
|
|
@@ -4799,10 +5002,17 @@ function createKeyResultsCommand() {
|
|
|
4799
5002
|
try {
|
|
4800
5003
|
const status = validateKeyResultStatus(options.status);
|
|
4801
5004
|
const client = getClient();
|
|
4802
|
-
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
|
+
}
|
|
4803
5013
|
const list = await client.goals.keyResults.list({
|
|
4804
5014
|
workspaceId,
|
|
4805
|
-
goalId
|
|
5015
|
+
goalId,
|
|
4806
5016
|
period: options.period,
|
|
4807
5017
|
status,
|
|
4808
5018
|
onlyMine: options.mine
|
|
@@ -5160,15 +5370,25 @@ function createGoalsCommand() {
|
|
|
5160
5370
|
);
|
|
5161
5371
|
goals.command("update").description(
|
|
5162
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`."
|
|
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)').
|
|
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(
|
|
5164
5377
|
async (options, cmd) => {
|
|
5165
5378
|
const globalOpts = cmd.optsWithGlobals();
|
|
5166
5379
|
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
5167
5380
|
try {
|
|
5168
5381
|
const status = validateWritableGoalStatus(options.status);
|
|
5169
5382
|
const clearable = (value) => value === void 0 ? void 0 : value === "none" ? null : value;
|
|
5170
|
-
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({
|
|
5171
5390
|
id: parseGoalId(options.id),
|
|
5391
|
+
workspaceId,
|
|
5172
5392
|
title: options.title,
|
|
5173
5393
|
description: clearable(options.description),
|
|
5174
5394
|
whyThisGoal: clearable(options.why),
|
|
@@ -5248,21 +5468,60 @@ function createGoalsCommand() {
|
|
|
5248
5468
|
handleError(error, useJson);
|
|
5249
5469
|
}
|
|
5250
5470
|
});
|
|
5251
|
-
goals.command("delete").description(
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
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);
|
|
5261
5522
|
}
|
|
5262
|
-
} catch (error) {
|
|
5263
|
-
handleError(error, useJson);
|
|
5264
5523
|
}
|
|
5265
|
-
|
|
5524
|
+
);
|
|
5266
5525
|
goals.command("periods").description("List the conventional OKR period strings").action(async (_options, cmd) => {
|
|
5267
5526
|
const globalOpts = cmd.optsWithGlobals();
|
|
5268
5527
|
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|