@bli-cockpit/cli 0.2.243 → 0.2.245
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/adapters/raw-evidence-budget-admission.js +63 -0
- package/dist/adapters/raw-evidence-collection-state.js +6 -61
- 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-collector-sources.js +2 -1
- 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 +1 -0
- package/dist/commands/local-args.js +2 -0
- package/dist/commands/local-command-shapes-milestone.js +1 -0
- package/dist/commands/local-help-commands-tower-ops.js +4 -4
- package/dist/commands/local-help-commands-tower.js +6 -3
- package/dist/commands/local-help-commands.js +3 -1
- package/dist/commands/local-help-milestone.js +23 -0
- package/dist/commands/local-help-project.js +1 -0
- package/dist/commands/local-help.js +3 -1
- package/dist/commands/local.js +2 -0
- package/dist/commands/manager-hints.js +4 -2
- package/dist/commands/manager-limits.json +3 -1
- package/dist/commands/manager-read-reasons.js +1 -0
- package/dist/commands/manager-render-home.js +13 -5
- package/dist/commands/manager-render.js +3 -0
- package/dist/commands/manager.js +3 -1
- 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/commands/session-sync-outside-roots.js +72 -14
- package/dist/commands/sources-check.js +63 -2
- package/dist/commands/sources-discovery.js +1 -1
- package/dist/commands/sources-reconciliation.js +87 -0
- package/dist/evidence-transcripts-client.js +1 -0
- package/package.json +4 -4
|
@@ -10,7 +10,7 @@ import { randomUUID } from "node:crypto";
|
|
|
10
10
|
*/
|
|
11
11
|
import { askAgentDoor, emitAgentDoor, failAgentDoor, } from "./agent-door.js";
|
|
12
12
|
import { writeLine } from "./cli-io.js";
|
|
13
|
-
import { TAG, WRITE_DEADLINE_MS, maybeWithSizeLabel, readBody, resolveRefs, } from "./issue-contracts.js";
|
|
13
|
+
import { TAG, WRITE_DEADLINE_MS, maybeWithSizeLabel, readBody, resolveRefs, receiptAfterRefusal, savedLine, } from "./issue-contracts.js";
|
|
14
14
|
export async function createIssue(command, door) {
|
|
15
15
|
if (!command.title)
|
|
16
16
|
return failAgentDoor(door, TAG, "invalid_body", "issue create needs --title.");
|
|
@@ -44,10 +44,11 @@ export async function createIssue(command, door) {
|
|
|
44
44
|
return refuseWrite(door, answer, requestId);
|
|
45
45
|
const issue = maybeWithSizeLabel(answer.body.issue);
|
|
46
46
|
writeLine(door.io.stderr, `${TAG} created ${JSON.stringify({ issue_id: issue?.id ?? null, identifier: issue?.identifier ?? null, description_bytes: Buffer.byteLength(body.text, "utf8") })}`);
|
|
47
|
-
|
|
47
|
+
reportCommit(door, answer.body.mutation, requestId);
|
|
48
48
|
if (door.json)
|
|
49
49
|
return emitAgentDoor(door, { ok: true, issue, mutation: answer.body.mutation });
|
|
50
50
|
writeLine(door.io.stdout, `Created ${issue?.identifier ?? ""} — ${issue?.title ?? ""} (${issue?.id ?? ""}).`);
|
|
51
|
+
printSaved(door, answer.body.mutation);
|
|
51
52
|
return 0;
|
|
52
53
|
}
|
|
53
54
|
export async function updateIssue(command, door) {
|
|
@@ -63,7 +64,7 @@ export async function updateIssue(command, door) {
|
|
|
63
64
|
description = body.text;
|
|
64
65
|
}
|
|
65
66
|
if (!changesAnything(command, description, refs)) {
|
|
66
|
-
return failAgentDoor(door, TAG, "invalid_body", "issue update needs at least one of --title, --priority, --size, --assignee, --due, --project, --parent, --cycle, or a description on --body-stdin/--file.");
|
|
67
|
+
return failAgentDoor(door, TAG, "invalid_body", "issue update needs at least one of --title, --priority, --size, --assignee, --due, --project, --parent, --cycle (none clears size, assignee, due, project, parent and cycle), or a description on --body-stdin/--file.");
|
|
67
68
|
}
|
|
68
69
|
const requestId = command.requestId ?? randomUUID();
|
|
69
70
|
writeLine(door.io.stderr, `${TAG} request ${JSON.stringify({ request_id: requestId, expected_revision: command.expectedRevision })}`);
|
|
@@ -79,8 +80,8 @@ export async function updateIssue(command, door) {
|
|
|
79
80
|
...(command.estimate === undefined ? {} : { estimate: command.estimate }),
|
|
80
81
|
...(command.assignee ? { assignee_id: command.assignee === "none" ? null : command.assignee } : {}),
|
|
81
82
|
...(command.due !== undefined ? { due_date: command.due } : {}),
|
|
82
|
-
...(refs.projectId ? { project_id: refs.projectId } : {}),
|
|
83
|
-
...(refs.parentId ? { parent_id: refs.parentId } : {}),
|
|
83
|
+
...(refs.projectId !== undefined ? { project_id: refs.projectId } : {}),
|
|
84
|
+
...(refs.parentId !== undefined ? { parent_id: refs.parentId } : {}),
|
|
84
85
|
...(command.cycle === undefined ? {} : { cycle: command.cycle }),
|
|
85
86
|
},
|
|
86
87
|
});
|
|
@@ -88,10 +89,11 @@ export async function updateIssue(command, door) {
|
|
|
88
89
|
return refuseWrite(door, answer, requestId);
|
|
89
90
|
const issue = maybeWithSizeLabel(answer.body.issue);
|
|
90
91
|
writeLine(door.io.stderr, `${TAG} updated ${JSON.stringify({ issue_id: issue?.id ?? null, identifier: issue?.identifier ?? null })}`);
|
|
91
|
-
|
|
92
|
+
reportCommit(door, answer.body.mutation, requestId);
|
|
92
93
|
if (door.json)
|
|
93
94
|
return emitAgentDoor(door, { ok: true, issue, mutation: answer.body.mutation });
|
|
94
95
|
writeLine(door.io.stdout, `Updated ${issue?.identifier ?? ref} — ${issue?.title ?? ""}.`);
|
|
96
|
+
printSaved(door, answer.body.mutation);
|
|
95
97
|
return 0;
|
|
96
98
|
}
|
|
97
99
|
export async function moveIssue(command, door) {
|
|
@@ -109,10 +111,11 @@ export async function moveIssue(command, door) {
|
|
|
109
111
|
return refuseWrite(door, answer, requestId);
|
|
110
112
|
const issue = answer.body.issue;
|
|
111
113
|
writeLine(door.io.stderr, `${TAG} moved ${JSON.stringify({ issue_id: issue?.id ?? null, identifier: issue?.identifier ?? null, to_state: command.moveState })}`);
|
|
112
|
-
|
|
114
|
+
reportCommit(door, answer.body.mutation, requestId);
|
|
113
115
|
if (door.json)
|
|
114
116
|
return emitAgentDoor(door, { ok: true, issue, mutation: answer.body.mutation });
|
|
115
117
|
writeLine(door.io.stdout, `${issue?.identifier ?? ref} is now ${issue?.state ?? command.moveState}.`);
|
|
118
|
+
printSaved(door, answer.body.mutation);
|
|
116
119
|
return 0;
|
|
117
120
|
}
|
|
118
121
|
export async function commentIssue(command, door) {
|
|
@@ -136,10 +139,11 @@ export async function commentIssue(command, door) {
|
|
|
136
139
|
return refuseWrite(door, answer, requestId);
|
|
137
140
|
const comment = answer.body.comment;
|
|
138
141
|
writeLine(door.io.stderr, `${TAG} commented ${JSON.stringify({ comment_id: comment?.id ?? null, byte_size: Buffer.byteLength(body.text, "utf8") })}`);
|
|
139
|
-
|
|
142
|
+
reportCommit(door, answer.body.mutation, requestId);
|
|
140
143
|
if (door.json)
|
|
141
144
|
return emitAgentDoor(door, { ok: true, comment, mutation: answer.body.mutation });
|
|
142
145
|
writeLine(door.io.stdout, `Commented on ${ref} (${comment?.id ?? ""}).`);
|
|
146
|
+
printSaved(door, answer.body.mutation);
|
|
143
147
|
return 0;
|
|
144
148
|
}
|
|
145
149
|
/** `ticket label <id> --add <name> --remove <name>`: one receipt, one history row (BLI-21422). */
|
|
@@ -162,10 +166,41 @@ export async function labelIssue(command, door) {
|
|
|
162
166
|
const body = answer.body;
|
|
163
167
|
const names = (body.labels ?? []).map((l) => l.name);
|
|
164
168
|
writeLine(door.io.stderr, `${TAG} labelled ${JSON.stringify({ issue_id: body.issue?.id ?? null, added: command.addLabels?.length ?? 0, removed: command.removeLabels?.length ?? 0, now: names.length })}`);
|
|
165
|
-
|
|
169
|
+
reportCommit(door, answer.body.mutation, requestId);
|
|
166
170
|
if (door.json)
|
|
167
171
|
return emitAgentDoor(door, { ok: true, issue: maybeWithSizeLabel(body.issue), labels: body.labels ?? [], mutation: answer.body.mutation });
|
|
168
172
|
writeLine(door.io.stdout, `${body.issue?.identifier ?? ref} labels: ${names.length ? names.join(", ") : "none"}.`);
|
|
173
|
+
printSaved(door, answer.body.mutation);
|
|
174
|
+
return 0;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* `ticket archive <id>` / `ticket unarchive <id>` (BLI-22367): hide a ticket from lists, the board and counts,
|
|
178
|
+
* or bring it back. Nothing is deleted; the ticket stays readable with `show` and every field, comment and
|
|
179
|
+
* history row survives. Tower-only: the receipt says the Linear copy is not mirrored (archive_is_tower_only).
|
|
180
|
+
*/
|
|
181
|
+
export async function archiveIssue(command, door, archived) {
|
|
182
|
+
const ref = command.issueRef ?? "";
|
|
183
|
+
const verb = archived ? "archive" : "unarchive";
|
|
184
|
+
const requestId = command.requestId ?? randomUUID();
|
|
185
|
+
writeLine(door.io.stderr, `${TAG} request ${JSON.stringify({ request_id: requestId, expected_revision: command.expectedRevision, action: verb })}`);
|
|
186
|
+
const answer = await askTicketWrite(door, {
|
|
187
|
+
path: `/api/work/issues/${encodeURIComponent(ref)}/${verb}`,
|
|
188
|
+
method: "POST",
|
|
189
|
+
label: `issue ${verb}`,
|
|
190
|
+
timeoutMs: WRITE_DEADLINE_MS,
|
|
191
|
+
body: { request_id: requestId, ...(command.expectedRevision === undefined ? {} : { expected_revision: command.expectedRevision }) },
|
|
192
|
+
});
|
|
193
|
+
if (!answer.ok)
|
|
194
|
+
return refuseWrite(door, answer, requestId);
|
|
195
|
+
const issue = answer.body.issue;
|
|
196
|
+
writeLine(door.io.stderr, `${TAG} ${archived ? "archived" : "unarchived"} ${JSON.stringify({ issue_id: issue?.id ?? null, identifier: issue?.identifier ?? null })}`);
|
|
197
|
+
reportCommit(door, answer.body.mutation, requestId);
|
|
198
|
+
if (door.json)
|
|
199
|
+
return emitAgentDoor(door, { ok: true, issue: maybeWithSizeLabel(issue), mutation: answer.body.mutation });
|
|
200
|
+
writeLine(door.io.stdout, archived
|
|
201
|
+
? `Archived ${issue?.identifier ?? ref}. It is hidden from lists and the board; \`cockpit ticket unarchive ${issue?.identifier ?? ref}\` brings it back.`
|
|
202
|
+
: `Unarchived ${issue?.identifier ?? ref}. It is back in lists and on the board.`);
|
|
203
|
+
printSaved(door, answer.body.mutation);
|
|
169
204
|
return 0;
|
|
170
205
|
}
|
|
171
206
|
/** A truncated or missing response body cannot be called a successful ticket write. */
|
|
@@ -197,16 +232,36 @@ function changesAnything(command, description, refs) {
|
|
|
197
232
|
|| refs.parentId !== undefined
|
|
198
233
|
|| command.cycle !== undefined;
|
|
199
234
|
}
|
|
200
|
-
/**
|
|
235
|
+
/** The local commit receipt on stderr, plus the "Saved in Tower ... Linear copy: ..." sentence when --json owns stdout. */
|
|
236
|
+
export function reportCommit(door, mutation, requestId) {
|
|
237
|
+
writeLine(door.io.stderr, `${TAG} local commit receipt ${JSON.stringify(mutation ?? { outcome: "unknown_check_again", request_id: requestId })}`);
|
|
238
|
+
const line = savedLine(mutation);
|
|
239
|
+
if (door.json && line)
|
|
240
|
+
writeLine(door.io.stderr, `${TAG} ${line}`);
|
|
241
|
+
}
|
|
242
|
+
/** The same sentence under a text answer (contract C1): the Tower save first, the Linear copy second. */
|
|
243
|
+
export function printSaved(door, mutation) {
|
|
244
|
+
const line = savedLine(mutation);
|
|
245
|
+
if (line)
|
|
246
|
+
writeLine(door.io.stdout, line);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Says where the local commit stands, then fails with the door's own reason. A known refusal says nothing changed
|
|
250
|
+
* (`not_committed`); only a genuinely unknown answer asks for a status check and an identical retry (BLI-22367).
|
|
251
|
+
*/
|
|
201
252
|
export function refuseWrite(door, answer, requestId) {
|
|
202
|
-
|
|
203
|
-
|
|
253
|
+
const mutation = receiptAfterRefusal(answer, requestId);
|
|
254
|
+
if (mutation.outcome === "not_committed") {
|
|
255
|
+
writeLine(door.io.stderr, `${TAG} not committed: nothing changed ${JSON.stringify({ reason: mutation.reason ?? answer.reason, request_id: requestId })}. Fix the input and send it again with a new request id.`);
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
writeLine(door.io.stderr, `${TAG} result unknown: check local commit with cockpit ticket mutation-status ${requestId}; retry unchanged input with --request-id ${requestId}.`);
|
|
259
|
+
}
|
|
204
260
|
if (door.json) {
|
|
205
|
-
writeLine(door.io.stderr, `${TAG} refused ${JSON.stringify({ reason: answer.reason, request_id: requestId })}`);
|
|
261
|
+
writeLine(door.io.stderr, `${TAG} refused ${JSON.stringify({ reason: answer.reason, request_id: requestId, outcome: mutation.outcome })}`);
|
|
206
262
|
return emitAgentDoor(door, { ok: false, error: answer.reason, detail: answer.detail, mutation }, 1);
|
|
207
263
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
writeLine(door.io.stderr, `${TAG} conflict ${JSON.stringify({ request_id: requestId, outcome: receipt.outcome, current_state: receipt.current.state, current_revision: receipt.current.revision })}`);
|
|
264
|
+
if (mutation.current)
|
|
265
|
+
writeLine(door.io.stderr, `${TAG} conflict ${JSON.stringify({ request_id: requestId, outcome: mutation.outcome, current_state: mutation.current.state, current_revision: mutation.current.revision })}`);
|
|
211
266
|
return failAgentDoor(door, TAG, answer.reason, answer.detail);
|
|
212
267
|
}
|
package/dist/commands/issue.js
CHANGED
|
@@ -32,8 +32,8 @@ import { cacheAgeLabel, cacheCoverage, readThroughCache } from "./read-cache.js"
|
|
|
32
32
|
import { readTicketBoard } from "./tickets-board.js";
|
|
33
33
|
import { showIssue } from "./issue-show.js";
|
|
34
34
|
import { addIssuePageQuery, pageFieldsOf, pageFooter, streamAllIssues } from "./issue-list-page.js";
|
|
35
|
-
import { READ_DEADLINE_MS, TAG, resolveProjectId, sizeForEstimate, withSizeLabel, } from "./issue-contracts.js";
|
|
36
|
-
import { commentIssue, createIssue, labelIssue, moveIssue, updateIssue } from "./issue-write.js";
|
|
35
|
+
import { READ_DEADLINE_MS, TAG, resolveProjectId, sizeForEstimate, withSizeLabel, savedLine, } from "./issue-contracts.js";
|
|
36
|
+
import { archiveIssue, commentIssue, createIssue, labelIssue, moveIssue, updateIssue } from "./issue-write.js";
|
|
37
37
|
import { batchIssues } from "./issue-batch.js";
|
|
38
38
|
import { runInbox } from "./inbox.js";
|
|
39
39
|
import { runWorkNotify } from "./work-notify.js";
|
|
@@ -52,6 +52,8 @@ export async function runIssue(command, io) {
|
|
|
52
52
|
return failAgentDoor(door, TAG, answer.reason, answer.detail);
|
|
53
53
|
if (door.json)
|
|
54
54
|
return emitAgentDoor(door, { ok: true, ...answer.body });
|
|
55
|
+
const receipt = answer.body.mutation;
|
|
56
|
+
writeLine(door.io.stdout, statusSentence(receipt, command.issueRef ?? ""));
|
|
55
57
|
writeLine(door.io.stdout, JSON.stringify(answer.body));
|
|
56
58
|
return 0;
|
|
57
59
|
}
|
|
@@ -71,6 +73,10 @@ export async function runIssue(command, io) {
|
|
|
71
73
|
return commentIssue(command, door);
|
|
72
74
|
case "label":
|
|
73
75
|
return labelIssue(command, door);
|
|
76
|
+
case "archive":
|
|
77
|
+
return archiveIssue(command, door, true);
|
|
78
|
+
case "unarchive":
|
|
79
|
+
return archiveIssue(command, door, false);
|
|
74
80
|
case "relations":
|
|
75
81
|
return showRelations(command, door);
|
|
76
82
|
case "relate":
|
|
@@ -101,11 +107,21 @@ export async function runIssue(command, io) {
|
|
|
101
107
|
return runWorkNotify(command, io, door);
|
|
102
108
|
}
|
|
103
109
|
}
|
|
110
|
+
/** `mutation-status` in words: committed (with the Linear copy), refused (nothing changed), or no receipt yet. */
|
|
111
|
+
export function statusSentence(receipt, requestId) {
|
|
112
|
+
if (receipt?.outcome === "committed")
|
|
113
|
+
return savedLine(receipt) ?? "Saved in Tower.";
|
|
114
|
+
if (receipt?.outcome === "not_committed")
|
|
115
|
+
return `Not committed: nothing changed (${receipt.reason ?? "refused"}).`;
|
|
116
|
+
return `No receipt for ${requestId} yet: the write may still be in flight, or it never reached Tower (a refusal before the write leaves no receipt). Retry the identical input with --request-id ${requestId}.`;
|
|
117
|
+
}
|
|
104
118
|
function issueLine(issue, directory) {
|
|
105
119
|
const assignee = issue.assignee_id ? ` [${assigneeLabel(issue.assignee_id, directory)}]` : "";
|
|
106
120
|
const size = sizeForEstimate(issue.estimate ?? null);
|
|
107
121
|
const sizeTag = size ? ` (${size})` : "";
|
|
108
|
-
|
|
122
|
+
const archived = issue.archived_at;
|
|
123
|
+
const archivedTag = archived ? ` (archived ${archived.slice(0, 10)})` : "";
|
|
124
|
+
return `${issue.identifier.padEnd(9)} ${issue.state.padEnd(11)} ${issue.title}${sizeTag}${assignee}${archivedTag}`;
|
|
109
125
|
}
|
|
110
126
|
/**
|
|
111
127
|
* What the list did not show (BLI-10447): a capped window of a larger set,
|
|
@@ -202,6 +218,9 @@ async function listIssues(command, door) {
|
|
|
202
218
|
/** The list query for the filters given, or the exit code of the project lookup that failed. */
|
|
203
219
|
async function buildIssueListQuery(command, door) {
|
|
204
220
|
const query = new URLSearchParams();
|
|
221
|
+
// BLI-22367: archived tickets only; every other list hides them.
|
|
222
|
+
if (command.archived)
|
|
223
|
+
query.set("archived", "only");
|
|
205
224
|
if (command.stateFilter)
|
|
206
225
|
query.set("state", command.stateFilter);
|
|
207
226
|
if (command.assignee)
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { optionalNonEmpty, optionalPositiveInteger, optionalUrl, parseNamedArgs, } from "./local-arg-values.js";
|
|
10
10
|
export function parseSourcesArgs(args) {
|
|
11
11
|
const values = parseNamedArgs(args, {
|
|
12
|
-
allowedFlags: ["--older-than", "--home", "--dashboard-url", "--json"],
|
|
12
|
+
allowedFlags: ["--older-than", "--home", "--dashboard-url", "--json", "--reconcile"],
|
|
13
13
|
valueFlags: ["--older-than", "--home", "--dashboard-url"],
|
|
14
14
|
});
|
|
15
15
|
const action = values.positionals[0] ?? "check";
|
|
@@ -17,6 +17,7 @@ export function parseSourcesArgs(args) {
|
|
|
17
17
|
throw new Error("sources has one verb: `cockpit sources check [--older-than <days>] [--json]`.");
|
|
18
18
|
}
|
|
19
19
|
return {
|
|
20
|
+
reconcile: values.booleans.has("--reconcile"),
|
|
20
21
|
kind: "sources",
|
|
21
22
|
action: "check",
|
|
22
23
|
olderThanDays: optionalPositiveInteger(values.flags.get("--older-than"), "--older-than") ?? null,
|
|
@@ -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";
|
|
@@ -19,6 +19,7 @@ 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
25
|
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";
|
|
@@ -90,6 +91,7 @@ const COMMAND_PARSERS = new Map([
|
|
|
90
91
|
["mail", (_command, rest) => parseMailArgs(rest)],
|
|
91
92
|
["cal", (_command, rest) => parseCalArgs(rest)],
|
|
92
93
|
["project", (_command, rest) => parseProjectArgs(rest)],
|
|
94
|
+
["milestone", (_command, rest) => parseMilestoneArgs(rest)],
|
|
93
95
|
["search", (_command, rest) => parseSearchArgs(rest)],
|
|
94
96
|
["github", (_command, rest) => parseGithubArgs(rest)],
|
|
95
97
|
["release", (_command, rest) => parseReleaseArgs(rest)],
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -226,10 +226,10 @@ export const TOWER_OPS_COMMAND_HELP = [
|
|
|
226
226
|
" \"YYYY-MM-DD: \" dates a bullet, without it the bullet is undated. none clears --next or --blocker. blocked needs a blocker (given, or already on the card),",
|
|
227
227
|
" and a blocker goes only on a Blocked card: sent with any other health it is refused (summary_blocker_needs_blocked); sending only a new health drops the stored one.",
|
|
228
228
|
" --version is summary.updatedAt from `home <client> --json`, or none when the client has no card yet (then --health, --headline and at least one --bullet are all needed).",
|
|
229
|
-
" Limits: headline 140 characters, each bullet 160, next and blocker 200; a longer text is refused here with its length. A day spelled with a month name at the start of a bullet (\"Oct 3: x\") is refused, never saved undated. --version none is for a client with no card: on one that has a card the write is refused with \"This client already has a card: pass its version\". The edit is
|
|
230
|
-
"
|
|
229
|
+
" Limits: headline 140 characters, each bullet 160, next and blocker 200; a longer text is refused here with its length. A day spelled with a month name at the start of a bullet (\"Oct 3: x\") is refused, never saved undated. --version none is for a client with no card: on one that has a card the write is refused with \"This client already has a card: pass its version\". The edit is stored as written by your agent",
|
|
230
|
+
" (source agent, \"Written by <you>'s agent\"): Tower's check never rewrites it, whatever changes; your agent's next edit, or a person's on the page, replaces it.",
|
|
231
231
|
" A saved card that says On track while an open action is more than 14 days overdue is saved anyway and prints a \"Warning: ...\" line naming the action overdue longest.",
|
|
232
|
-
" Write the card after your item writes:
|
|
232
|
+
" Write the card after your item writes: the check will not rewrite it to take them in.",
|
|
233
233
|
" Health rule: an open action more than 14 days overdue, as `cockpit manager home` shows it (Tower's today, America/Vancouver), means the card is at_risk (or blocked, with a named blocker).",
|
|
234
234
|
" Do not set on_track over one; tell the person instead. The headline or a bullet must name the action overdue longest, with the day it was due.",
|
|
235
235
|
" cockpit manager list <client> [--timeline] [--needs-review] [--status open|done|dropped|closed|all] [--owner <name>|--no-owner] [--overdue] [--kind <kind>] [--search <text>] [--archived] [--review dismissed|all]",
|
|
@@ -261,7 +261,7 @@ export const TOWER_OPS_COMMAND_HELP = [
|
|
|
261
261
|
" --version and --timeline stay flags. A field given as a flag and in the file is refused, so is an unknown key. The file gets the flags' own checks. Example: cat fields.json | cockpit manager summary Acme --version none --fields-file -",
|
|
262
262
|
"Free text (the flags above and every value in a fields file) that holds a provider key (an sk-, rk_, sb_secret_, ghp_, github_pat_, AKIA/ASIA, xoxb- key, any JWT, a PEM private key) or a password given a value (\"password: x\") is refused, naming the flag or key; ordinary words, a commit sha, a URL and the words \"service-role key\" are plain text.",
|
|
263
263
|
"On a flag, free text (--headline, --bullet, --title, --detail, --next, --blocker, --owners, --source-note) goes in SINGLE quotes so the shell leaves $ alone: \"$12k\" in double quotes loses the $12k (zsh and bash read it as a variable). In bash and zsh write an apostrophe inside single quotes as '\\'' (end the quote, a backslash-escaped apostrophe, reopen); in PowerShell double the apostrophe ('Rowan''s deck'). The command prints what it saved: check it is your words.",
|
|
264
|
-
"<client> is the client's id, or its exact name (any case; `clients` lists them); a name that matches no client, or several, is refused with the names. Ids and versions are printed by list, queue and show. Items you add are
|
|
264
|
+
"<client> is the client's id, or its exact name (any case; `clients` lists them); a name that matches no client, or several, is refused with the names. Ids and versions are printed by list, queue and show. Items you add here are stored as added by your agent, not yet checked (they count in needs review until a person approves or dismisses them), never as you having checked them; text tagged [HR] is refused (item_hr_tagged, summary_hr_tagged).",
|
|
265
265
|
"--version is the item's version exactly as list or show prints it, copied as it is. Every change to an existing item names it: if someone or a sync changed the item",
|
|
266
266
|
"since, the write is refused with a sentence saying so and nothing is written; run show again for the new version and retry.",
|
|
267
267
|
"--owners on an action takes, as the web form says, \"One or more people, separated by commas\" (\"Rowan Ash, Juniper Twig\"), and it REPLACES the whole list. Leave it out and the owners stay;",
|