@bli-cockpit/cli 0.2.244 → 0.2.246
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/commands/delivery.js +37 -0
- package/dist/commands/issue-contracts.js +65 -4
- package/dist/commands/issue-list-page.js +34 -6
- package/dist/commands/issue-page-args.js +64 -0
- package/dist/commands/issue-relations.js +3 -2
- package/dist/commands/issue-show.js +13 -2
- package/dist/commands/issue-versions.js +5 -8
- package/dist/commands/issue-write.js +71 -16
- package/dist/commands/issue.js +22 -3
- package/dist/commands/local-args-tower-milestone.js +120 -0
- package/dist/commands/local-args-tower-work.js +44 -51
- package/dist/commands/local-args-tower.js +3 -1
- package/dist/commands/local-args.js +4 -1
- package/dist/commands/local-command-shapes-milestone.js +1 -0
- package/dist/commands/local-help-commands-tower-ops.js +8 -0
- package/dist/commands/local-help-commands-tower.js +6 -3
- package/dist/commands/local-help-milestone.js +23 -0
- package/dist/commands/local-help-project.js +1 -0
- package/dist/commands/local-help.js +4 -0
- package/dist/commands/local-run-ops.js +2 -0
- package/dist/commands/local.js +3 -0
- package/dist/commands/milestone.js +157 -0
- package/dist/commands/project-args.js +78 -2
- package/dist/commands/project.js +52 -0
- package/dist/commands/public-root.js +1 -1
- package/dist/staging-gate.js +24 -3
- package/package.json +2 -2
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cockpit milestone` argument parsing (BLI-22360).
|
|
3
|
+
*
|
|
4
|
+
* list [--project <ref>] [--archived]
|
|
5
|
+
* show <ref> [--project <ref>] [--limit <n>] [--cursor <c>]
|
|
6
|
+
* create --project <ref> --name "<n>" [--description <text|->] [--target YYYY-MM-DD]
|
|
7
|
+
* update <ref> [--project <ref>] [--name <n>] [--description <text|-|none>] [--target <day|none>] [--expected-updated-at <ts>]
|
|
8
|
+
* archive|restore <ref> [--project <ref>] [--expected-updated-at <ts>]
|
|
9
|
+
* add|remove <ref> <ticket> [<ticket> ...] [--project <ref>]
|
|
10
|
+
*
|
|
11
|
+
* A milestone ref is an id or an exact, case-insensitive name; two milestones with one name are refused by the door
|
|
12
|
+
* (`milestone_ref_ambiguous`) with their ids, never guessed. `--request-id` belongs to every write.
|
|
13
|
+
*/
|
|
14
|
+
import { optionalNonEmpty, optionalPositiveInteger, optionalUrl, parseNamedArgs } from "./local-arg-values.js";
|
|
15
|
+
const ACTIONS = ["list", "show", "create", "update", "archive", "restore", "add", "remove"];
|
|
16
|
+
const WRITES = ["create", "update", "archive", "restore", "add", "remove"];
|
|
17
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
18
|
+
const DAY = /^\d{4}-\d{2}-\d{2}$/;
|
|
19
|
+
export const MILESTONE_TICKETS_MAX = 50;
|
|
20
|
+
function target(value, action) {
|
|
21
|
+
if (value === undefined)
|
|
22
|
+
return undefined;
|
|
23
|
+
if (value.trim().toLowerCase() === "none") {
|
|
24
|
+
if (action !== "update")
|
|
25
|
+
throw new Error("--target none clears a date on `milestone update`; leave --target off instead.");
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
if (!DAY.test(value) || new Date(`${value}T12:00:00Z`).toISOString().slice(0, 10) !== value) {
|
|
29
|
+
throw new Error("--target must be a calendar date as YYYY-MM-DD, or none.");
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
export function parseMilestoneArgs(args) {
|
|
34
|
+
const values = parseNamedArgs(args, {
|
|
35
|
+
allowedFlags: [
|
|
36
|
+
"--home", "--dashboard-url", "--json", "--archived", "--project", "--name", "--description", "--target",
|
|
37
|
+
"--limit", "--cursor", "--request-id", "--expected-updated-at",
|
|
38
|
+
],
|
|
39
|
+
valueFlags: [
|
|
40
|
+
"--home", "--dashboard-url", "--project", "--name", "--description", "--target",
|
|
41
|
+
"--limit", "--cursor", "--request-id", "--expected-updated-at",
|
|
42
|
+
],
|
|
43
|
+
});
|
|
44
|
+
const first = values.positionals[0];
|
|
45
|
+
const action = (first === undefined ? "list" : first);
|
|
46
|
+
if (!ACTIONS.includes(action)) {
|
|
47
|
+
throw new Error(`Unknown milestone command: ${first}. Try list, show, create, update, archive, restore, add or remove.`);
|
|
48
|
+
}
|
|
49
|
+
const rest = values.positionals.slice(first === undefined ? 0 : 1);
|
|
50
|
+
const given = (flag) => values.flags.has(flag) || values.booleans.has(flag);
|
|
51
|
+
let ref;
|
|
52
|
+
let tickets = [];
|
|
53
|
+
if (action === "list" || action === "create") {
|
|
54
|
+
if (rest.length > 0)
|
|
55
|
+
throw new Error(`milestone ${action} does not take "${rest[0]}".`);
|
|
56
|
+
}
|
|
57
|
+
else if (action === "add" || action === "remove") {
|
|
58
|
+
if (rest.length < 2)
|
|
59
|
+
throw new Error(`milestone ${action} takes a milestone and at least one ticket: milestone ${action} <milestone> BLI-123 [BLI-124 ...].`);
|
|
60
|
+
ref = rest[0];
|
|
61
|
+
tickets = [...new Set(rest.slice(1).flatMap((t) => t.split(",")).map((t) => t.trim()).filter(Boolean))];
|
|
62
|
+
if (tickets.length > MILESTONE_TICKETS_MAX)
|
|
63
|
+
throw new Error(`At most ${MILESTONE_TICKETS_MAX} tickets per call.`);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
if (rest.length !== 1)
|
|
67
|
+
throw new Error(`milestone ${action} takes exactly one milestone id or name.`);
|
|
68
|
+
ref = rest[0];
|
|
69
|
+
}
|
|
70
|
+
if (action !== "create" && action !== "update" && (given("--name") || given("--description") || given("--target"))) {
|
|
71
|
+
throw new Error("--name, --description and --target belong to milestone create and update.");
|
|
72
|
+
}
|
|
73
|
+
if (action !== "list" && given("--archived"))
|
|
74
|
+
throw new Error("--archived belongs to milestone list.");
|
|
75
|
+
if (action !== "show" && (given("--limit") || given("--cursor")))
|
|
76
|
+
throw new Error("--limit and --cursor belong to milestone show.");
|
|
77
|
+
if (!WRITES.includes(action) && given("--request-id"))
|
|
78
|
+
throw new Error("--request-id belongs to the milestone writes.");
|
|
79
|
+
if (!["update", "archive", "restore"].includes(action) && given("--expected-updated-at")) {
|
|
80
|
+
throw new Error("--expected-updated-at belongs to milestone update, archive and restore.");
|
|
81
|
+
}
|
|
82
|
+
const project = optionalNonEmpty(values.flags.get("--project"));
|
|
83
|
+
const name = optionalNonEmpty(values.flags.get("--name"));
|
|
84
|
+
if (action === "create" && (!project || !name))
|
|
85
|
+
throw new Error('milestone create needs --project <project> and --name "<name>".');
|
|
86
|
+
const rawDescription = values.flags.get("--description");
|
|
87
|
+
const description = rawDescription === undefined ? undefined
|
|
88
|
+
: action === "update" && rawDescription.trim().toLowerCase() === "none" ? null : rawDescription;
|
|
89
|
+
const targetDate = target(values.flags.get("--target"), action);
|
|
90
|
+
if (action === "update" && name === undefined && description === undefined && targetDate === undefined) {
|
|
91
|
+
throw new Error("milestone update needs --name, --description (or none) or --target (or none). Archive with `milestone archive`.");
|
|
92
|
+
}
|
|
93
|
+
const requestId = optionalNonEmpty(values.flags.get("--request-id"));
|
|
94
|
+
if (requestId && !UUID.test(requestId))
|
|
95
|
+
throw new Error("--request-id must be a UUID.");
|
|
96
|
+
const expectedUpdatedAt = optionalNonEmpty(values.flags.get("--expected-updated-at"));
|
|
97
|
+
if (expectedUpdatedAt && Number.isNaN(Date.parse(expectedUpdatedAt))) {
|
|
98
|
+
throw new Error("--expected-updated-at must be the updated_at timestamp you last read.");
|
|
99
|
+
}
|
|
100
|
+
const limit = optionalPositiveInteger(values.flags.get("--limit"), "--limit");
|
|
101
|
+
const cursor = optionalNonEmpty(values.flags.get("--cursor"));
|
|
102
|
+
return {
|
|
103
|
+
kind: "milestone",
|
|
104
|
+
action,
|
|
105
|
+
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
106
|
+
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
107
|
+
...(ref === undefined ? {} : { ref }),
|
|
108
|
+
...(project ? { project } : {}),
|
|
109
|
+
includeArchived: values.booleans.has("--archived"),
|
|
110
|
+
...(name ? { name } : {}),
|
|
111
|
+
...(description === undefined ? {} : { description }),
|
|
112
|
+
...(targetDate === undefined ? {} : { targetDate }),
|
|
113
|
+
tickets,
|
|
114
|
+
...(limit === undefined ? {} : { limit }),
|
|
115
|
+
...(cursor ? { cursor } : {}),
|
|
116
|
+
...(requestId ? { requestId } : {}),
|
|
117
|
+
...(expectedUpdatedAt ? { expectedUpdatedAt } : {}),
|
|
118
|
+
json: values.booleans.has("--json"),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
@@ -23,6 +23,7 @@ import { NOTIFY_ACTIONS, NOTIFY_VALUE_FLAGS, isNotifyAction, parseNotifyArgs } f
|
|
|
23
23
|
import { VIEW_BOOLEAN_FLAGS, VIEW_VALUE_FLAGS, parseViewArgs } from "./view-args.js";
|
|
24
24
|
import { parseTemplateArgs } from "./template-args.js";
|
|
25
25
|
import { CYCLE_BOOLEAN_FLAGS, CYCLE_VALUE_FLAGS, parseCycleArgs, readCycleField } from "./cycle-args.js";
|
|
26
|
+
import { readArchivedFlag, readIssuePage } from "./issue-page-args.js";
|
|
26
27
|
const ISSUE_ACTIONS = new Set([
|
|
27
28
|
"mutation-status",
|
|
28
29
|
"board",
|
|
@@ -37,6 +38,9 @@ const ISSUE_ACTIONS = new Set([
|
|
|
37
38
|
"restore",
|
|
38
39
|
"label",
|
|
39
40
|
"batch",
|
|
41
|
+
// BLI-22367: reversible archive. `restore` stays the description-version verb.
|
|
42
|
+
"archive",
|
|
43
|
+
"unarchive",
|
|
40
44
|
"relations",
|
|
41
45
|
"relate",
|
|
42
46
|
"unrelate",
|
|
@@ -56,6 +60,8 @@ const ISSUE_ACTIONS_NEEDING_AN_ISSUE = new Set([
|
|
|
56
60
|
"versions",
|
|
57
61
|
"restore",
|
|
58
62
|
"label",
|
|
63
|
+
"archive",
|
|
64
|
+
"unarchive",
|
|
59
65
|
"relations",
|
|
60
66
|
"relate",
|
|
61
67
|
"unrelate",
|
|
@@ -99,6 +105,7 @@ const ISSUE_ALLOWED_FLAGS = [
|
|
|
99
105
|
"--sort",
|
|
100
106
|
"--dir",
|
|
101
107
|
"--all",
|
|
108
|
+
"--archived",
|
|
102
109
|
"--body-stdin",
|
|
103
110
|
"--json",
|
|
104
111
|
...VIEW_BOOLEAN_FLAGS,
|
|
@@ -174,9 +181,10 @@ export function parseIssueArgs(args) {
|
|
|
174
181
|
throw new Error("--request-id must be a UUID.");
|
|
175
182
|
if (action === "mutation-status" && !UUID_PATTERN.test(issueRef ?? ""))
|
|
176
183
|
throw new Error("mutation-status needs a request UUID.");
|
|
177
|
-
if ((requestId || expectedRevision !== undefined) && !["create", "update", "move", "comment", "restore", "label", "batch", "relate", "unrelate"].includes(action)) {
|
|
178
|
-
throw new Error("Write-safety flags belong to create, update, move, comment, restore, label, relate, unrelate or batch.");
|
|
184
|
+
if ((requestId || expectedRevision !== undefined) && !["create", "update", "move", "comment", "restore", "label", "batch", "relate", "unrelate", "archive", "unarchive"].includes(action)) {
|
|
185
|
+
throw new Error("Write-safety flags belong to create, update, move, comment, restore, label, relate, unrelate, archive, unarchive or batch.");
|
|
179
186
|
}
|
|
187
|
+
const archived = readArchivedFlag(action, values);
|
|
180
188
|
validateIssueState(moveState, "issue move state must be one of: ");
|
|
181
189
|
const stateFilter = readIssueStateFilter(action, values);
|
|
182
190
|
const fields = readIssueFields(action, values);
|
|
@@ -192,61 +200,18 @@ export function parseIssueArgs(args) {
|
|
|
192
200
|
if (action === "board" && boardProject && !UUID_PATTERN.test(boardProject))
|
|
193
201
|
throw new Error("board --project requires a uuid.");
|
|
194
202
|
const priority = readIssuePriority(values);
|
|
195
|
-
const estimate = readIssueSize(values);
|
|
203
|
+
const estimate = readIssueSize(action, values);
|
|
204
|
+
checkNoneClears(action, values);
|
|
196
205
|
const assignee = readIssueAssignee(action, values);
|
|
197
206
|
const limit = readIssueLimit(action, values);
|
|
198
207
|
const versionNumber = readIssueVersion(action, values);
|
|
199
|
-
return { ...buildIssueCommand(values, action, issueRef, issueRefs, moveState, stateFilter, title, priority, estimate, assignee, limit, versionNumber, page, fields), ...readRelationFlags(action, values) };
|
|
200
|
-
}
|
|
201
|
-
const INSTANT = /^\d{4}-\d\d-\d\d(?:T\d\d:\d\d:\d\d(?:\.\d{1,6})?(?:Z|[+-]\d\d:\d\d))?$/;
|
|
202
|
-
/** BLI-21315: the cursor contract's flags. Filters and sort belong to `list`; `--cursor` also to `history`. */
|
|
203
|
-
function readIssuePage(action, values) {
|
|
204
|
-
const named = ["--q", "--created-after", "--created-before", "--updated-after", "--updated-before", "--sort", "--dir"];
|
|
205
|
-
for (const flag of named) {
|
|
206
|
-
if (values.flags.has(flag) && action !== "list")
|
|
207
|
-
throw new Error(`${flag} belongs to \`cockpit ticket list\`.`);
|
|
208
|
-
}
|
|
209
|
-
if (values.booleans.has("--all") && action !== "list")
|
|
210
|
-
throw new Error("--all belongs to `cockpit ticket list`.");
|
|
211
|
-
if (values.flags.has("--cursor") && action !== "list" && action !== "history") {
|
|
212
|
-
throw new Error("--cursor belongs to `cockpit ticket list` and `cockpit ticket history`.");
|
|
213
|
-
}
|
|
214
|
-
const instants = {};
|
|
215
|
-
for (const flag of ["--created-after", "--created-before", "--updated-after", "--updated-before"]) {
|
|
216
|
-
const value = optionalNonEmpty(values.flags.get(flag));
|
|
217
|
-
if (value !== undefined && (!INSTANT.test(value) || !Number.isFinite(Date.parse(value)))) {
|
|
218
|
-
throw new Error(`${flag} must be a UTC instant like 2026-10-01T00:00:00Z or a date like 2026-10-01.`);
|
|
219
|
-
}
|
|
220
|
-
instants[flag] = value;
|
|
221
|
-
}
|
|
222
|
-
const sort = optionalNonEmpty(values.flags.get("--sort"));
|
|
223
|
-
if (sort !== undefined && sort !== "updated_at" && sort !== "created_at")
|
|
224
|
-
throw new Error("--sort must be updated_at or created_at.");
|
|
225
|
-
const dir = optionalNonEmpty(values.flags.get("--dir"));
|
|
226
|
-
if (dir !== undefined && dir !== "asc" && dir !== "desc")
|
|
227
|
-
throw new Error("--dir must be asc or desc.");
|
|
228
|
-
const all = values.booleans.has("--all");
|
|
229
|
-
const cursor = optionalNonEmpty(values.flags.get("--cursor"));
|
|
230
|
-
if (all && cursor)
|
|
231
|
-
throw new Error("--all walks from the start; drop --cursor, or pass --cursor without --all to read one page.");
|
|
232
|
-
const q = optionalNonEmpty(values.flags.get("--q"));
|
|
233
|
-
return {
|
|
234
|
-
...(cursor ? { cursor } : {}),
|
|
235
|
-
...(q ? { q } : {}),
|
|
236
|
-
...(instants["--created-after"] ? { createdAfter: instants["--created-after"] } : {}),
|
|
237
|
-
...(instants["--created-before"] ? { createdBefore: instants["--created-before"] } : {}),
|
|
238
|
-
...(instants["--updated-after"] ? { updatedAfter: instants["--updated-after"] } : {}),
|
|
239
|
-
...(instants["--updated-before"] ? { updatedBefore: instants["--updated-before"] } : {}),
|
|
240
|
-
...(sort ? { sort } : {}),
|
|
241
|
-
...(dir ? { dir } : {}),
|
|
242
|
-
...(all ? { all } : {}),
|
|
243
|
-
};
|
|
208
|
+
return { ...buildIssueCommand(values, action, issueRef, issueRefs, moveState, stateFilter, title, priority, estimate, assignee, limit, versionNumber, page, fields), ...readRelationFlags(action, values), ...(archived ? { archived } : {}) };
|
|
244
209
|
}
|
|
245
210
|
function readIssueAction(values) {
|
|
246
211
|
const first = values.positionals[0];
|
|
247
212
|
const action = (first === undefined ? "list" : first);
|
|
248
213
|
if (!ISSUE_ACTIONS.has(action)) {
|
|
249
|
-
throw new Error(`Unknown issue command: ${first}. Try board, list, show, create, update, move, comment, label, relations, relate, unrelate, views, templates, cycles, inbox, read, unread, subscribe, unsubscribe, notifications, history, versions, restore, batch, or mutation-status.`);
|
|
214
|
+
throw new Error(`Unknown issue command: ${first}. Try board, list, show, create, update, move, comment, label, archive, unarchive, relations, relate, unrelate, views, templates, cycles, inbox, read, unread, subscribe, unsubscribe, notifications, history, versions, restore, batch, or mutation-status.`);
|
|
250
215
|
}
|
|
251
216
|
return { action, rest: values.positionals.slice(first === undefined ? 0 : 1) };
|
|
252
217
|
}
|
|
@@ -318,9 +283,37 @@ function readIssuePriority(values) {
|
|
|
318
283
|
}
|
|
319
284
|
return priority;
|
|
320
285
|
}
|
|
321
|
-
|
|
286
|
+
/**
|
|
287
|
+
* `--size S|M|L`, or `none` on update to clear the size (BLI-22367). Only ever what the person typed: no surface
|
|
288
|
+
* picks a size for them.
|
|
289
|
+
*/
|
|
290
|
+
function readIssueSize(action, values) {
|
|
322
291
|
const raw = optionalNonEmpty(values.flags.get("--size"));
|
|
323
|
-
|
|
292
|
+
if (raw === undefined)
|
|
293
|
+
return undefined;
|
|
294
|
+
if (raw.toLowerCase() === "none") {
|
|
295
|
+
if (action !== "update")
|
|
296
|
+
throw new Error("--size none clears a size on `cockpit ticket update`; leave --size off to create without one.");
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
return estimateForSize(raw);
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* ONE spelling clears a field on update: `none` (BLI-22367). `--parent none` and `--project none` join the existing
|
|
303
|
+
* `--assignee none`, `--due none`, `--cycle none` and `--size none`. On create there is nothing to clear, so `none`
|
|
304
|
+
* is refused rather than looked up as a ticket or project called "none".
|
|
305
|
+
*/
|
|
306
|
+
function checkNoneClears(action, values) {
|
|
307
|
+
for (const flag of ["--parent", "--project"]) {
|
|
308
|
+
const value = optionalNonEmpty(values.flags.get(flag));
|
|
309
|
+
if (value?.toLowerCase() !== "none")
|
|
310
|
+
continue;
|
|
311
|
+
if (action === "update")
|
|
312
|
+
continue;
|
|
313
|
+
throw new Error(action === "create"
|
|
314
|
+
? `${flag} none clears it on \`cockpit ticket update\`; leave ${flag} off to create without one.`
|
|
315
|
+
: `${flag} none clears it on \`cockpit ticket update\` only.`);
|
|
316
|
+
}
|
|
324
317
|
}
|
|
325
318
|
function readIssueAssignee(action, values) {
|
|
326
319
|
// An assignee is `me`, `unassigned`, or a person's uuid. A name or an
|
|
@@ -33,6 +33,7 @@ export { SCOUT_MIN_PREFIX_LENGTH, parseScoutArgs, parseOpsArgs, SLACK_WORKSPACE_
|
|
|
33
33
|
export { parseDocsArgs, parseMsgArgs, } from "./local-args-tower-docs-msg.js";
|
|
34
34
|
export { ISSUE_STATES, parseIssueArgs, parseProjectArgs, } from "./local-args-tower-work.js";
|
|
35
35
|
export { parseGithubArgs } from "./local-args-tower-github.js";
|
|
36
|
+
export { parseMilestoneArgs } from "./local-args-tower-milestone.js";
|
|
36
37
|
export { parseSearchArgs } from "./local-args-tower-search.js";
|
|
37
38
|
export { parseModelsArgs } from "./local-args-tower-models.js";
|
|
38
39
|
export { parseMailArgs } from "./local-args-tower-mail.js";
|
|
@@ -55,4 +56,5 @@ export { parseRenameArgs } from './local-args-tower-rename.js';
|
|
|
55
56
|
export { parseGlossaryArgs } from "./glossary.js";
|
|
56
57
|
export { parseSpendArgs } from "./spend.js";
|
|
57
58
|
export { parsePushArgs } from "./push.js";
|
|
58
|
-
export { parseCiArgs } from "./ci.js";
|
|
59
|
+
export { parseCiArgs } from "./ci.js";
|
|
60
|
+
export { parseDeliveryArgs } from "./delivery.js";
|
|
@@ -19,9 +19,10 @@ import { parseGlossaryArgs } from "./glossary.js";
|
|
|
19
19
|
import { parseSpendArgs } from "./spend.js";
|
|
20
20
|
import { parsePushArgs } from "./push.js";
|
|
21
21
|
import { parseRootsArgs } from "./roots.js";
|
|
22
|
+
import { parseMilestoneArgs } from "./local-args-tower-milestone.js";
|
|
22
23
|
import { SURFACE_ALIASES } from "./local-help.js";
|
|
23
24
|
import { parseAgentRulesArgs, parseAnalyzeArgs, parseAutostartArgs, parseBackfillArgs, parseCleanArgs, parseDoctorArgs, parseInstallArgs, parseLoginArgs, parseMemoryArgs, parseLogoutArgs, parseOnboardArgs, parseReleaseArgs, parseServeArgs, parseSessionsArgs, parseSourcesArgs, parseStartArgs, parseStatusArgs, parseSyncArgs, parseUpdateArgs, } from "./local-args-collector.js";
|
|
24
|
-
import { parseCiArgs, parseBriefArgs, parseCorrectArgs, parseDocsArgs, parseIssueArgs, parseJarvisArgs, parseMailArgs, parseCalArgs, parseModelArgs, parseMsgArgs, parseNotesArgs, parseOpsArgs, parseProjectArgs, parseModelsArgs, parseScoutArgs, parseGithubArgs, parseSearchArgs, parseSettingsArgs, parseSlackArgs, parseTeamArgs, parseWorkbookArgs, parseUsageArgs, parseSetupArgs, parseSpeedArgs, parseLogsArgs, parseBrainArgs, parseHitsArgs, parseIntentionsArgs, parseWhoArgs, parsePeopleArgs, parseApprovalsArgs, parseDrillsArgs, parseManagerArgs, parseImageArgs, parseCareersArgs, parseRenameArgs, } from "./local-args-tower.js";
|
|
25
|
+
import { parseCiArgs, parseDeliveryArgs, parseBriefArgs, parseCorrectArgs, parseDocsArgs, parseIssueArgs, parseJarvisArgs, parseMailArgs, parseCalArgs, parseModelArgs, parseMsgArgs, parseNotesArgs, parseOpsArgs, parseProjectArgs, parseModelsArgs, parseScoutArgs, parseGithubArgs, parseSearchArgs, parseSettingsArgs, parseSlackArgs, parseTeamArgs, parseWorkbookArgs, parseUsageArgs, parseSetupArgs, parseSpeedArgs, parseLogsArgs, parseBrainArgs, parseHitsArgs, parseIntentionsArgs, parseWhoArgs, parsePeopleArgs, parseApprovalsArgs, parseDrillsArgs, parseManagerArgs, parseImageArgs, parseCareersArgs, parseRenameArgs, } from "./local-args-tower.js";
|
|
25
26
|
// `normalizeUrl` has always been part of this module's surface — `local.ts` and
|
|
26
27
|
// `local-auth.ts` import it from here — so it stays exported from this address
|
|
27
28
|
// even though it now lives next door. The same goes for the four names the
|
|
@@ -64,6 +65,7 @@ const COMMAND_PARSERS = new Map([
|
|
|
64
65
|
["models", (_command, rest) => parseModelsArgs(rest)],
|
|
65
66
|
["scout", (_command, rest) => parseScoutArgs(rest)],
|
|
66
67
|
["ci", (_command, rest) => parseCiArgs(rest)],
|
|
68
|
+
["delivery", (_command, rest) => parseDeliveryArgs(rest)],
|
|
67
69
|
["ops", (_command, rest) => parseOpsArgs(rest)],
|
|
68
70
|
["slack", (_command, rest) => parseSlackArgs(rest)],
|
|
69
71
|
["settings", (_command, rest) => parseSettingsArgs(rest)],
|
|
@@ -90,6 +92,7 @@ const COMMAND_PARSERS = new Map([
|
|
|
90
92
|
["mail", (_command, rest) => parseMailArgs(rest)],
|
|
91
93
|
["cal", (_command, rest) => parseCalArgs(rest)],
|
|
92
94
|
["project", (_command, rest) => parseProjectArgs(rest)],
|
|
95
|
+
["milestone", (_command, rest) => parseMilestoneArgs(rest)],
|
|
93
96
|
["search", (_command, rest) => parseSearchArgs(rest)],
|
|
94
97
|
["github", (_command, rest) => parseGithubArgs(rest)],
|
|
95
98
|
["release", (_command, rest) => parseReleaseArgs(rest)],
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -395,6 +395,14 @@ export const TOWER_OPS_COMMAND_HELP = [
|
|
|
395
395
|
"ci",
|
|
396
396
|
["Usage: cockpit ci report [--repo <owner/name>] [--json]", "Reads the daily stored CI report, as-of age and coverage. MCP twin: ci_report."],
|
|
397
397
|
],
|
|
398
|
+
[
|
|
399
|
+
"delivery",
|
|
400
|
+
[
|
|
401
|
+
"Usage: cockpit delivery report [--days <1-30>] [--end <YYYY-MM-DD>] [--review-minutes <1-240>] [--json]",
|
|
402
|
+
"Delivery scoreboard: two equal periods (before and after, default 7 days each, ending before --end, default today UTC) of merged PRs split gated and ungated, ready-to-merged median and p90, main CI red time, failed production builds, coverage and the ruleset as GitHub returned it.",
|
|
403
|
+
"--review-minutes is an assumption for the review-hours line, not a measurement. Counts show their denominators; no verdict is given. MCP twin: delivery_read. Web: TEAM > Delivery.",
|
|
404
|
+
],
|
|
405
|
+
],
|
|
398
406
|
[
|
|
399
407
|
"ops",
|
|
400
408
|
[
|
|
@@ -18,6 +18,7 @@ import { DOCS_FORMATTING_HELP } from "./local-help-docs-formatting.js";
|
|
|
18
18
|
import { DOCS_INBOX_HELP } from "./local-help-docs-inbox.js";
|
|
19
19
|
import { DOCS_RECOVERY_HELP } from "./local-help-docs-recovery.js";
|
|
20
20
|
import { PROJECT_HELP } from "./local-help-project.js";
|
|
21
|
+
import { MILESTONE_HELP } from "./local-help-milestone.js";
|
|
21
22
|
import { INBOX_HELP_LINES } from "./local-help-inbox.js";
|
|
22
23
|
import { WORK_NOTIFY_HELP_LINES } from "./local-help-work-notify.js";
|
|
23
24
|
import { VIEWS_HELP_LINES } from "./local-help-views.js";
|
|
@@ -122,7 +123,7 @@ export const TOWER_COMMAND_HELP = [
|
|
|
122
123
|
[
|
|
123
124
|
"issue",
|
|
124
125
|
[
|
|
125
|
-
"Usage: cockpit issue [board|list|show <id> [<id>...]|create|update <id>|move <id> <state>|comment <id>|label <id>|relations <id>|relate <id>|unrelate <id>|views [list|show|save|update|favorite|unfavorite|settings]|templates [list|show|versions|save|update|apply]|cycles [list|show|create|update|map]|inbox|read <notification-id>...|unread <notification-id>...|subscribe <id>|unsubscribe <id>|notifications [set|receipts]|history <id>|versions <id>|restore <id> --version <n>|batch|mutation-status <request-uuid>] [flags]",
|
|
126
|
+
"Usage: cockpit issue [board|list|show <id> [<id>...]|create|update <id>|move <id> <state>|comment <id>|label <id>|archive <id>|unarchive <id>|relations <id>|relate <id>|unrelate <id>|views [list|show|save|update|favorite|unfavorite|settings]|templates [list|show|versions|save|update|apply]|cycles [list|show|create|update|map]|inbox|read <notification-id>...|unread <notification-id>...|subscribe <id>|unsubscribe <id>|notifications [set|receipts]|history <id>|versions <id>|restore <id> --version <n>|batch|mutation-status <request-uuid>] [flags]",
|
|
126
127
|
"",
|
|
127
128
|
"Tower's issue tracker. <id> is a BLI-#### identifier (BLI-3654) or an issue's uuid — both work everywhere an issue is named.",
|
|
128
129
|
"list [--state <s>] [--assignee me|unassigned|<uuid>] [--project <name|id>] [--label <name>] [--q <text>] [--created-after|--created-before|--updated-after|--updated-before <utc instant|date>] [--sort updated_at|created_at] [--dir desc|asc] [--limit <n>] [--cursor <c>] [--all] — the issues you may see, most recently updated first. A page is 100 by default and at most 500. Filters run in the database BEFORE the page, so a label or title match deep in the set is found. --q is a case-insensitive title contains (% and _ are literal); *-after is inclusive, *-before is exclusive. --json adds next_cursor, has_more, coverage (complete|capped|failed_partial), requested, returned, unique: pass next_cursor back as --cursor with the same other flags. --all walks every page and streams ONE JSON OBJECT PER LINE (NDJSON; rows carry assignee_id, no names), then a final {\"type\":\"coverage\",...} line naming the coverage; an interrupted walk ends with coverage failed_partial, the reason and the resume cursor, and exits 1. Each row carries assignee_name/assignee_email alongside the bare assignee_id (BLI-10450, one extra `cockpit team members` call for the whole page, never per row), and a `size` label (S/M/L) alongside the raw `estimate` when it matches Tower's own scale (BLI-10575). --json shape: { issues: [{ id, identifier, title, state, priority, estimate, size, assignee_id, assignee_name, assignee_email, project_id, parent_id, updated_at }] }.",
|
|
@@ -130,14 +131,15 @@ export const TOWER_COMMAND_HELP = [
|
|
|
130
131
|
"show <id> [<id>...] — one or more issues: title, state, priority, size, assignee (name and email, not just its id), description, and every comment on each. Several ids read in one call — one bad id never loses the rest.",
|
|
131
132
|
" --json shape: one id answers { issue, comments }, unchanged from before BLI-10450 (plus `estimate`/`size`, BLI-10575). Several ids answer { issues: [{ ref, ok, issue, comments } | { ref, ok: false, error, detail }] }.",
|
|
132
133
|
"create --title \"<title>\" [--project <name|id>] [--priority 0-4] [--size S|M|L] [--assignee me|<uuid>] [--parent <id>] [--state <state>] [--due YYYY-MM-DD] [--file <path>] — --state is the starting state (default backlog); --due sets a due date. The DESCRIPTION comes from --file or stdin (`cat plan.md | cockpit issue create --title \"...\"`); neither means no description. --size maps onto the existing `estimate` column, Tower's own scale: S=1, M=3, L=5 (BLI-10575) — an estimate outside that scale (an imported Linear point value, say) just has no size label on a read. --json shape: { issue }.",
|
|
133
|
-
"update <id> [--title \"<t>\"] [--priority 0-4] [--size S|M|L] [--assignee me|<uuid
|
|
134
|
+
"update <id> [--title \"<t>\"] [--priority 0-4] [--size S|M|L|none] [--assignee me|<uuid>|none] [--project <name|id>|none] [--parent <id>|none] [--cycle <name|id>|none] [--due YYYY-MM-DD|none] [--file <path>|--body-stdin] — only the fields you pass change: a field you leave off is never touched. `none` is the ONE spelling that clears: --size none, --assignee none, --project none, --parent none (top-level again), --cycle none and --due none (BLI-22367). A committed write prints \"Saved in Tower (revision N). Linear copy: pending|delivered|failed|not mirrored (reason)\"; a refusal says \"not committed: nothing changed\" unless the answer was genuinely lost (then check mutation-status). A size already on the ticket that is not S, M or L (an imported estimate such as 8) stays as it is until you pass --size. State does NOT move here; use move. --json shape: { issue }.",
|
|
134
135
|
"label <id> [--add <label>]... [--remove <label>]... [--request-id <uuid>] [--expected-revision <n>] — adds and removes EXISTING labels (names match exactly, ignoring case; a uuid also works) in one receipt, one history row and one revision step. An unknown name is refused by name and nothing changes; labels are never created here. Any member may detach. --json shape: { issue, labels, mutation }. relations <id> [--json] — what blocks what: Blocks, Blocked by, Related, Duplicate of, Duplicates, and Not in Tower (an imported Linear edge to a ticket Tower does not have, shown as text). A sub-ticket is NOT a blocker and is not listed here. Edges to a deleted or unreadable ticket are left out and counted (coverage.hidden). --json shape: { relations: { blocks, blocked_by, related, duplicates, duplicate_of, unmapped, coverage } }, each item { relation_id, kind, source: native|linear, issue: { id, identifier, title, state } }. relate <id> --blocks <id> | --blocked-by <id> | --related <id> | --duplicate-of <id> [--request-id <uuid>] [--expected-revision <n>] — exactly one kind flag; records one edge as one receipt, one history row and one revision step (--blocked-by stores the edge from the other ticket). Refusals by name: relation_exists, relation_inverse_blocks, relation_self, relation_target_not_found_or_unreadable. unrelate <id> --relation <relation-id> | the same kind flag plus the other ticket — removes the edge (kept as removed, never re-imported). Refusal: relation_not_found. Setting --parent so a ticket would sit under its own sub-ticket is refused as circular_parent, a chain deeper than 200 as hierarchy_too_deep. --json shape: { issue, relation_id, mutation }. Also: move <id> <state> — moves an issue and records the move. States: backlog, todo, in_progress, in_review, done, canceled.",
|
|
136
|
+
"archive <id> | unarchive <id> [--request-id <uuid>] [--expected-revision <n>] — archive HIDES a ticket from lists, the board and counts; nothing is deleted, `show` still reads it (marked ARCHIVED), and unarchive brings it back with every field, comment and history row (BLI-22367). One receipt, one history row, one revision step. Tower-only: the Linear copy (if any) is not touched, so the receipt says not mirrored (archive_is_tower_only). Refusals: issue_already_archived, issue_not_archived, revision_conflict, request_id_reused. `list --archived [--q <text>] [--project <name|id>] [--limit <n>] [--cursor <c>] [--all]` lists archived tickets only, most recently archived first. --json shape: { issue, mutation }.",
|
|
135
137
|
"comment <id> — posts a comment. The body is never taken on the command line: `echo \"shipped\" | cockpit issue comment BLI-3654`.",
|
|
136
138
|
"Writes accept --request-id <uuid> and --expected-revision <n>. Keep the UUID and identical fields across retries; JSON includes mutation receipt and mirror delivery separately.",
|
|
137
139
|
"mutation-status <request-uuid> — check your own local commit and mirror delivery after a lost response; no receipt means unknown_check_again.",
|
|
138
140
|
"history <id> [--limit <n>] [--cursor <c>] — every recorded state move and reassignment, oldest first; --json carries the same page fields as list.",
|
|
139
141
|
"versions <id> [--limit <n>] — every saved description, newest first, each with its number, id, author, source and time (BLI-21300). --json shape: { versions: [{ id, version, markdown, author_id, source, restored_from_version, created_at }] }.",
|
|
140
|
-
"restore <id> --version <n> [--request-id <uuid>] [--expected-revision <n>] — writes version <n>'s text back as a NEW version; nothing is rewritten, so the restore can itself be undone. A stale revision conflicts and changes nothing. --json shape: { issue, mutation }. batch [--file <path>] [--request-id <uuid>] — changes up to 50 tickets in one call (BLI-21551). Reads { \"items\": [...] } (or a bare array) from stdin or --file, never argv. Each item is { issue, expected_revision?, request_id?, state? | priority? | assignee? | size? | project? | due_date? }: a state move OR field changes, not both, and one item per ticket. It is NOT all-or-nothing: every item is its own change with its own receipt, one stderr line each. Exit 0 only when every item committed, 1 otherwise; the batch request id is printed, and a retry with --request-id writes nothing twice. --json shape: { ok, request_id, requested, committed, refused, unknown, items: [{ index, issue, outcome, reason?, message?, request_id, revision?, row? }] }. Example: echo '{\"items\":[{\"issue\":\"BLI-1\",\"priority\":2},{\"issue\":\"BLI-2\",\"state\":\"done\"}]}' | cockpit ticket batch --json.",
|
|
142
|
+
"restore <id> --version <n> [--request-id <uuid>] [--expected-revision <n>] — writes version <n>'s text back as a NEW version; nothing is rewritten, so the restore can itself be undone. A stale revision conflicts and changes nothing. --json shape: { issue, mutation }. batch [--file <path>] [--request-id <uuid>] — changes up to 50 tickets in one call (BLI-21551). Reads { \"items\": [...] } (or a bare array) from stdin or --file, never argv. Each item is { issue, expected_revision?, request_id?, state? | priority? | assignee? | size? | project? | parent? | cycle? | due_date? } (\"none\" or null clears assignee, size, project, parent, cycle and due_date): a state move OR field changes, not both, and one item per ticket. It is NOT all-or-nothing: every item is its own change with its own receipt, one stderr line each. Exit 0 only when every item committed, 1 otherwise; the batch request id is printed, and a retry with --request-id writes nothing twice. --json shape: { ok, request_id, requested, committed, refused, unknown, items: [{ index, issue, outcome, reason?, message?, request_id, revision?, row? }] }. Example: echo '{\"items\":[{\"issue\":\"BLI-1\",\"priority\":2},{\"issue\":\"BLI-2\",\"state\":\"done\"}]}' | cockpit ticket batch --json.",
|
|
141
143
|
...VIEWS_HELP_LINES,
|
|
142
144
|
...TEMPLATES_HELP_LINES,
|
|
143
145
|
...CYCLES_HELP_LINES,
|
|
@@ -216,6 +218,7 @@ export const TOWER_COMMAND_HELP = [
|
|
|
216
218
|
],
|
|
217
219
|
],
|
|
218
220
|
PROJECT_HELP,
|
|
221
|
+
MILESTONE_HELP,
|
|
219
222
|
[
|
|
220
223
|
"speed",
|
|
221
224
|
[
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** `cockpit milestone` help (BLI-22360), kept beside the parser in local-args-tower-milestone.ts. */
|
|
2
|
+
export const MILESTONE_HELP = [
|
|
3
|
+
"milestone",
|
|
4
|
+
[
|
|
5
|
+
"Usage: cockpit milestone [list] [--project <id|name>] [--archived] [--json]",
|
|
6
|
+
" cockpit milestone show <id|name> [--project <id|name>] [--limit <n>] [--cursor <c>] [--json]",
|
|
7
|
+
' cockpit milestone create --project <id|name> --name "<name>" [--description <text|->] [--target YYYY-MM-DD] [--request-id <uuid>] [--json]',
|
|
8
|
+
" cockpit milestone update <id|name> [--project <p>] [--name <n>] [--description <text|-|none>] [--target <day|none>] [--expected-updated-at <ts>] [--request-id <uuid>] [--json]",
|
|
9
|
+
" cockpit milestone archive|restore <id|name> [--project <p>] [--expected-updated-at <ts>] [--request-id <uuid>] [--json]",
|
|
10
|
+
" cockpit milestone add|remove <id|name> <ticket> [<ticket> ...] [--project <p>] [--request-id <uuid>] [--json]",
|
|
11
|
+
"",
|
|
12
|
+
"Project milestones: a dated goal inside ONE project, with the tickets that make it up. Not Tower Manager's timeline milestones.",
|
|
13
|
+
"list — a project's milestones (every project's without --project), target date first, each with its progress. --archived includes archived ones.",
|
|
14
|
+
"show <ref> — the milestone, its progress and a page of its tickets. <ref> is an id or an exact name (case-folded, never fuzzy); --project narrows a name to one project; two milestones with one name are milestone_ref_ambiguous and the message lists their ids.",
|
|
15
|
+
"Progress is a count: done of (done + open) tickets. Canceled tickets are shown but left out of the total; estimates never weight it. Points (estimate sum over the estimated tickets only) and how many tickets carry an estimate are printed beside it. A milestone with more than 5,000 tickets says its counts are a floor.",
|
|
16
|
+
"create / update — name 1 to 120 characters, unique among the project's active milestones; `--description -` reads stdin, `--description none` and `--target none` clear (update). A milestone never moves to another project.",
|
|
17
|
+
"archive / restore — soft: tickets keep their milestone, nothing is deleted, there is no delete verb. An archived milestone refuses new tickets (milestone_archived).",
|
|
18
|
+
"add / remove <milestone> <tickets...> — up to 50 ticket ids or identifiers, one receipt each; one refusal never stops the others. A ticket must already be in the milestone's project (milestone_project_mismatch); a ticket is in at most one milestone, so add moves it from another. remove only takes out tickets that are in this milestone. Moving a ticket to another project takes it out of its milestone.",
|
|
19
|
+
"Milestones are Tower-only during the Linear dual run: a milestone write is delivery tower_only, and a ticket's milestone change says Linear copy not_mirrored (milestone_not_mirrored). The same --request-id replays the first receipts.",
|
|
20
|
+
"Refusals keep Tower's label: milestone_not_found_or_unreadable, milestone_ref_ambiguous, milestone_name_required, milestone_name_taken, milestone_date_invalid, milestone_already_archived, milestone_not_archived, milestone_archived, milestone_project_mismatch, project_archived, project_not_found_or_unreadable, revision_conflict, request_id_reused, invalid_body.",
|
|
21
|
+
"--json writes one machine-readable object to stdout; every reason and receipt line stays on stderr.",
|
|
22
|
+
],
|
|
23
|
+
];
|
|
@@ -16,6 +16,7 @@ export const PROJECT_HELP = [
|
|
|
16
16
|
"Every write returns a receipt with delivery tower_only: a project write never reaches Linear and never queues a Linear change. The same --request-id replays the first receipt; with another body it is request_id_reused.",
|
|
17
17
|
"--expected-updated-at is the updated_at you last read (shown by show and --json); a stale one is revision_conflict with the current project and changes nothing.",
|
|
18
18
|
"Refusals keep Tower's label: project_name_required, project_name_taken, project_owned_by_linear (a project imported from Linear is named there; archive and restore still work), project_already_archived, project_not_archived, project_not_found_or_unreadable, revision_conflict, request_id_reused, invalid_body.",
|
|
19
|
+
"BLI-22360 planning fields on create and update: --lead <email|me|none>, --member <email[,email]|none> (the whole set; repeatable), --add-member / --remove-member (update), --status backlog|planned|started|paused|completed|canceled|none, --priority none|urgent|high|medium|low (or 0-4), --start / --target <YYYY-MM-DD|none> with --start-resolution / --target-resolution month|quarter|halfYear|year|day (a date without one is an exact day), --content-file <path> or --content <text|none> (the long body; --description stays the short summary). People are matched by exact email, never by name. show prints them, the project's milestones and their progress (see `cockpit milestone`). Refusals: project_status_invalid, project_dates_invalid, project_person_not_found.",
|
|
19
20
|
"--json writes one machine-readable object to stdout; every reason and receipt line stays on stderr.",
|
|
20
21
|
],
|
|
21
22
|
];
|
|
@@ -53,6 +53,7 @@ export const rootCommandNames = new Set([
|
|
|
53
53
|
"models",
|
|
54
54
|
"scout",
|
|
55
55
|
"ci",
|
|
56
|
+
"delivery",
|
|
56
57
|
"ops",
|
|
57
58
|
"slack",
|
|
58
59
|
"settings",
|
|
@@ -79,6 +80,7 @@ export const rootCommandNames = new Set([
|
|
|
79
80
|
"mail",
|
|
80
81
|
"cal",
|
|
81
82
|
"project",
|
|
83
|
+
"milestone",
|
|
82
84
|
"search",
|
|
83
85
|
"github",
|
|
84
86
|
"release",
|
|
@@ -120,6 +122,7 @@ const COMMAND_SUMMARY_LINES = [
|
|
|
120
122
|
" cockpit models [list|show <provider:model>|compare <provider:model> <provider:model> [...]] [--highlight] [--dashboard-url <url>] [--json]",
|
|
121
123
|
" cockpit scout [board|start|dismiss|undo <experiment-id>] [--days <n>] [--dashboard-url <url>] [--json]",
|
|
122
124
|
" cockpit ci report [--repo <owner/name>] [--json]",
|
|
125
|
+
" cockpit delivery report [--days <1-30>] [--end <YYYY-MM-DD>] [--review-minutes <1-240>] [--json]",
|
|
123
126
|
" cockpit ops [status [--job <id>] [--skips] [--memory] [--models] | recompile --person <email|name|id> [--dry-run]] [--dashboard-url <url>] [--json]",
|
|
124
127
|
" cockpit slack [connect [--workspace bli|blue_pearl] | disconnect --workspace bli|blue_pearl | coverage [--workspace bli|blue_pearl] [--stale-only] [--list] | read [--person <p>] [--channel <c>] [--query <text> [--word]] [--since <YYYY-MM-DD>] [--until <YYYY-MM-DD>] [--limit <n>] [--format compact|full]] [--json]",
|
|
125
128
|
" cockpit settings [personal [--chat-model <key>] [--brief-model <key>] | switches [set <key> <value>] | models [set --chat <key>] [--memory <id>] | cli-floor [<version>] | env list|set --project <p> --file <f> --content-stdin|delete --id <uuid> [--yes]] [--json]",
|
|
@@ -147,6 +150,7 @@ const COMMAND_SUMMARY_LINES = [
|
|
|
147
150
|
" cockpit mail [accounts|add-imap --address <a>|inbox|read <thread>|search \"<words>\"|send --account <id> --to <a> --subject <s>|draft --account <id> --to <a> --subject <s> [--reply-to <message id>]|attachment <id> --out <path>|sync <account>|detach <account>] [--account <id>] [--limit <n>] [--unread] [--label <l>] [--file <path>] [--attach <path>]... [--dashboard-url <url>] [--json]",
|
|
148
151
|
" cockpit cal [event <id> [--occurrence-start <iso>]|export <id> [--occurrence-start <iso>]|today|week|conflicts|calendar-change <id> --color <hex-or-provider>|view-settings|view-settings-change|people <label>|availability --with <label|email> --from <d> --to <d>|overlays [--pin <label|email>|--remove <email>]|next|find \"<words>\"|calendars|add-ical|create --calendar <id> --title <t> --at <iso> --until <iso>|duplicate <event id> [--keep-invitees]|batch|undo [<change id>]|share <id> --org-visible|--private|sync <id> [--full]|detach <id>] [--tz <zone>] [--offset <n>] [--hours <n>] [--from <d> --to <d>] [--calendar <id>] [--limit <n>] [--all] [--dashboard-url <url>] [--json]",
|
|
149
152
|
" cockpit project [list] [--archived] [--dashboard-url <url>] [--json]",
|
|
153
|
+
" cockpit milestone [list|show|create|update|archive|restore|add|remove] [--project <p>] [--json]",
|
|
150
154
|
" cockpit github <pr|merge|retitle|rerun> <owner/repo> <n> [--claim <state>] [--method squash|merge|rebase] [--title \"<text>\"] [--run <id>] [--confirm <code>] [--dashboard-url <url>] [--json]",
|
|
151
155
|
` cockpit search "<words>" [--kind <category,...>] [--limit <n>] [--dashboard-url <url>] [--json]`,
|
|
152
156
|
" cockpit search --categories [--dashboard-url <url>] [--json]",
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { runScout } from "./scout.js";
|
|
2
2
|
import { runCi } from "./ci.js";
|
|
3
|
+
import { runDelivery } from "./delivery.js";
|
|
3
4
|
import { runOps } from "./ops.js";
|
|
4
5
|
/** Dispatch the operational report commands. */
|
|
5
6
|
export function runOpsCommand(command, io) {
|
|
6
7
|
switch (command.kind) {
|
|
7
8
|
case "scout": return runScout(command, io);
|
|
8
9
|
case "ci": return runCi(command, io);
|
|
10
|
+
case "delivery": return runDelivery(command, io);
|
|
9
11
|
case "ops": return runOps(command, io);
|
|
10
12
|
}
|
|
11
13
|
}
|
package/dist/commands/local.js
CHANGED
|
@@ -47,6 +47,7 @@ import { runIssue } from "./issue.js";
|
|
|
47
47
|
import { runCal } from "./cal.js";
|
|
48
48
|
import { runMail } from "./mail.js";
|
|
49
49
|
import { runProject } from "./project.js";
|
|
50
|
+
import { runMilestone } from "./milestone.js";
|
|
50
51
|
import { runGithub } from "./github.js";
|
|
51
52
|
import { runSearch } from "./search.js";
|
|
52
53
|
import { runCareers } from "./careers.js";
|
|
@@ -186,6 +187,7 @@ async function runTowerCommand(command, io) {
|
|
|
186
187
|
case "models": return await runModels(command, io);
|
|
187
188
|
case "scout":
|
|
188
189
|
case "ci":
|
|
190
|
+
case "delivery":
|
|
189
191
|
case "ops":
|
|
190
192
|
return await runOpsCommand(command, io);
|
|
191
193
|
case "slack": return await runSlack(command, io);
|
|
@@ -203,6 +205,7 @@ async function runTowerCommand(command, io) {
|
|
|
203
205
|
case "issue": return await runIssue(command, io);
|
|
204
206
|
case "mail": return await runMail(command, io);
|
|
205
207
|
case "project": return await runProject(command, io);
|
|
208
|
+
case "milestone": return await runMilestone(command, io);
|
|
206
209
|
case "search": return await runSearch(command, io);
|
|
207
210
|
case "github": return await runGithub(command, io);
|
|
208
211
|
case "careers": return await runCareers(command, io);
|