@bli-cockpit/cli 0.2.241 → 0.2.243
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/git-state.js +1 -0
- package/dist/adapters/raw-evidence-collection-state.js +22 -11
- package/dist/adapters/raw-evidence-git-diff.js +1 -0
- package/dist/agent-rules.js +8 -0
- package/dist/autostart-contract.js +12 -0
- package/dist/autostart-darwin-plist.js +2 -2
- package/dist/autostart-darwin.js +1 -1
- package/dist/autostart-remind.js +245 -0
- package/dist/autostart-self-heal.js +17 -1
- package/dist/autostart-windows-scripts.js +7 -7
- package/dist/autostart.js +16 -15
- package/dist/commands/approvals-check-where.js +14 -0
- package/dist/commands/approvals-render.js +8 -1
- package/dist/commands/approvals.js +3 -2
- package/dist/commands/autostart-command.js +10 -0
- package/dist/commands/autostart-heal.js +36 -1
- package/dist/commands/brain.js +1 -1
- package/dist/commands/cal-agenda.js +44 -0
- package/dist/commands/cal-availability-share.js +97 -0
- package/dist/commands/cal-booking.js +147 -0
- package/dist/commands/cal-join.js +62 -0
- package/dist/commands/cal-links.js +68 -0
- package/dist/commands/cal-people.js +136 -0
- package/dist/commands/cal-remind.js +176 -0
- package/dist/commands/cal-reminders.js +33 -0
- package/dist/commands/cal-suggest.js +89 -0
- package/dist/commands/cal.js +41 -1
- package/dist/commands/cycle-args.js +127 -0
- package/dist/commands/cycles.js +117 -0
- package/dist/commands/docs-comments-args.js +85 -0
- package/dist/commands/docs-comments.js +128 -0
- package/dist/commands/docs-files-args.js +49 -0
- package/dist/commands/docs-files.js +179 -0
- package/dist/commands/docs-formatting.js +45 -0
- package/dist/commands/docs-inbox-args.js +93 -0
- package/dist/commands/docs-inbox.js +126 -0
- package/dist/commands/docs-notion.js +5 -0
- package/dist/commands/docs-read.js +2 -1
- package/dist/commands/docs-recovery.js +173 -0
- package/dist/commands/docs-refs-args.js +104 -0
- package/dist/commands/docs-refs.js +153 -0
- package/dist/commands/docs-search-args.js +79 -0
- package/dist/commands/docs-search.js +64 -0
- package/dist/commands/docs-sync-args.js +82 -0
- package/dist/commands/docs-sync.js +197 -0
- package/dist/commands/docs-tree.js +136 -0
- package/dist/commands/docs.js +85 -51
- package/dist/commands/doctor-update.js +4 -1
- package/dist/commands/inbox-args.js +81 -0
- package/dist/commands/inbox.js +88 -0
- package/dist/commands/issue-batch.js +61 -0
- package/dist/commands/issue-relations.js +151 -0
- package/dist/commands/issue-write.js +38 -5
- package/dist/commands/issue.js +32 -1
- package/dist/commands/local-args-tower-approvals.js +16 -4
- package/dist/commands/local-args-tower-cal-booking.js +81 -0
- package/dist/commands/local-args-tower-cal-links.js +42 -0
- package/dist/commands/local-args-tower-cal-remind.js +74 -0
- package/dist/commands/local-args-tower-cal-share.js +41 -0
- package/dist/commands/local-args-tower-cal.js +81 -8
- package/dist/commands/local-args-tower-docs-msg.js +98 -9
- package/dist/commands/local-args-tower-msg.js +279 -10
- package/dist/commands/local-args-tower-search.js +32 -5
- package/dist/commands/local-args-tower-work.js +110 -34
- package/dist/commands/local-args-tower.js +1 -0
- package/dist/commands/local-args.js +2 -0
- package/dist/commands/local-auth.js +1 -1
- package/dist/commands/local-help-commands-tower-ops.js +2 -0
- package/dist/commands/local-help-commands-tower.js +48 -32
- package/dist/commands/local-help-commands.js +1 -0
- package/dist/commands/local-help-cycles.js +9 -0
- package/dist/commands/local-help-docs-files.js +10 -0
- package/dist/commands/local-help-docs-formatting.js +7 -0
- package/dist/commands/local-help-docs-inbox.js +10 -0
- package/dist/commands/local-help-docs-recovery.js +10 -0
- package/dist/commands/local-help-docs-refs.js +11 -0
- package/dist/commands/local-help-docs-sync.js +9 -0
- package/dist/commands/local-help-inbox.js +9 -0
- package/dist/commands/local-help-project.js +21 -0
- package/dist/commands/local-help-templates.js +9 -0
- package/dist/commands/local-help-views.js +10 -0
- package/dist/commands/local-help-work-notify.js +6 -0
- package/dist/commands/local-help.js +4 -2
- package/dist/commands/local.js +2 -0
- package/dist/commands/mcp-stdio-probe.js +1 -0
- package/dist/commands/msg-activity.js +134 -0
- package/dist/commands/msg-channel-args.js +92 -0
- package/dist/commands/msg-channel.js +125 -0
- package/dist/commands/msg-context-args.js +67 -0
- package/dist/commands/msg-context.js +65 -0
- package/dist/commands/msg-dm.js +28 -0
- package/dist/commands/msg-draft.js +193 -0
- package/dist/commands/msg-file.js +185 -0
- package/dist/commands/msg-mark-args.js +78 -0
- package/dist/commands/msg-mark.js +148 -0
- package/dist/commands/msg-notify-args.js +100 -0
- package/dist/commands/msg-notify.js +122 -0
- package/dist/commands/msg-schedule-args.js +114 -0
- package/dist/commands/msg-schedule.js +226 -0
- package/dist/commands/msg-search-args.js +71 -0
- package/dist/commands/msg-search.js +48 -0
- package/dist/commands/msg-write.js +15 -1
- package/dist/commands/msg.js +77 -39
- package/dist/commands/project-args.js +82 -0
- package/dist/commands/project.js +71 -7
- package/dist/commands/public-root.js +1 -1
- package/dist/commands/push.js +50 -0
- package/dist/commands/search.js +23 -0
- package/dist/commands/session-sync-failures.js +17 -4
- package/dist/commands/session-sync-health.js +7 -2
- package/dist/commands/session-sync-hold.js +42 -1
- package/dist/commands/session-sync-upload.js +2 -0
- package/dist/commands/session-sync.js +1 -0
- package/dist/commands/sync-doctor.js +14 -1
- package/dist/commands/sync-report.js +1 -1
- package/dist/commands/template-args.js +122 -0
- package/dist/commands/templates.js +162 -0
- package/dist/commands/view-args.js +131 -0
- package/dist/commands/views.js +138 -0
- package/dist/commands/work-notify-args.js +67 -0
- package/dist/commands/work-notify.js +63 -0
- package/dist/delivery-hold-notice.js +34 -0
- package/dist/log-rotation.js +2 -2
- package/dist/notifier.js +83 -0
- package/dist/process-runner.js +4 -0
- package/dist/repo-identity-git.js +1 -0
- package/package.json +4 -4
|
@@ -16,7 +16,13 @@
|
|
|
16
16
|
* shared machine and lands in shell history).
|
|
17
17
|
*/
|
|
18
18
|
import { optionalNonEmpty, optionalPositiveInteger, optionalUrl, parseNamedArgs, } from "./local-arg-values.js";
|
|
19
|
+
import { RELATION_VALUE_FLAGS, readRelationFlags } from "./issue-relations.js";
|
|
19
20
|
import { estimateForSize } from "./issue-contracts.js";
|
|
21
|
+
import { INBOX_ACTIONS, INBOX_BOOLEAN_FLAGS, INBOX_VALUE_FLAGS, isInboxAction, parseInboxArgs } from "./inbox-args.js";
|
|
22
|
+
import { NOTIFY_ACTIONS, NOTIFY_VALUE_FLAGS, isNotifyAction, parseNotifyArgs } from "./work-notify-args.js";
|
|
23
|
+
import { VIEW_BOOLEAN_FLAGS, VIEW_VALUE_FLAGS, parseViewArgs } from "./view-args.js";
|
|
24
|
+
import { parseTemplateArgs } from "./template-args.js";
|
|
25
|
+
import { CYCLE_BOOLEAN_FLAGS, CYCLE_VALUE_FLAGS, parseCycleArgs, readCycleField } from "./cycle-args.js";
|
|
20
26
|
const ISSUE_ACTIONS = new Set([
|
|
21
27
|
"mutation-status",
|
|
22
28
|
"board",
|
|
@@ -29,6 +35,15 @@ const ISSUE_ACTIONS = new Set([
|
|
|
29
35
|
"history",
|
|
30
36
|
"versions",
|
|
31
37
|
"restore",
|
|
38
|
+
"label",
|
|
39
|
+
"batch",
|
|
40
|
+
"relations",
|
|
41
|
+
"relate",
|
|
42
|
+
"unrelate",
|
|
43
|
+
"views",
|
|
44
|
+
"templates", "cycles",
|
|
45
|
+
...INBOX_ACTIONS,
|
|
46
|
+
...NOTIFY_ACTIONS,
|
|
32
47
|
]);
|
|
33
48
|
/** Every verb except `list` and `create` names ONE issue first. */
|
|
34
49
|
const ISSUE_ACTIONS_NEEDING_AN_ISSUE = new Set([
|
|
@@ -40,6 +55,10 @@ const ISSUE_ACTIONS_NEEDING_AN_ISSUE = new Set([
|
|
|
40
55
|
"history",
|
|
41
56
|
"versions",
|
|
42
57
|
"restore",
|
|
58
|
+
"label",
|
|
59
|
+
"relations",
|
|
60
|
+
"relate",
|
|
61
|
+
"unrelate",
|
|
43
62
|
]);
|
|
44
63
|
/**
|
|
45
64
|
* The closed state vocabulary, copied verbatim from
|
|
@@ -66,6 +85,9 @@ const ISSUE_ALLOWED_FLAGS = [
|
|
|
66
85
|
"--priority",
|
|
67
86
|
"--size",
|
|
68
87
|
"--parent",
|
|
88
|
+
"--due",
|
|
89
|
+
"--add",
|
|
90
|
+
"--remove",
|
|
69
91
|
"--file",
|
|
70
92
|
"--version",
|
|
71
93
|
"--cursor",
|
|
@@ -79,8 +101,15 @@ const ISSUE_ALLOWED_FLAGS = [
|
|
|
79
101
|
"--all",
|
|
80
102
|
"--body-stdin",
|
|
81
103
|
"--json",
|
|
104
|
+
...VIEW_BOOLEAN_FLAGS,
|
|
105
|
+
...VIEW_VALUE_FLAGS,
|
|
106
|
+
...INBOX_BOOLEAN_FLAGS,
|
|
107
|
+
...INBOX_VALUE_FLAGS,
|
|
108
|
+
...NOTIFY_VALUE_FLAGS,
|
|
109
|
+
...RELATION_VALUE_FLAGS, ...CYCLE_VALUE_FLAGS, ...CYCLE_BOOLEAN_FLAGS,
|
|
82
110
|
];
|
|
83
111
|
const ISSUE_VALUE_FLAGS = [
|
|
112
|
+
"--cycle",
|
|
84
113
|
"--request-id",
|
|
85
114
|
"--expected-revision",
|
|
86
115
|
"--home",
|
|
@@ -95,6 +124,9 @@ const ISSUE_VALUE_FLAGS = [
|
|
|
95
124
|
"--priority",
|
|
96
125
|
"--size",
|
|
97
126
|
"--parent",
|
|
127
|
+
"--due",
|
|
128
|
+
"--add",
|
|
129
|
+
"--remove",
|
|
98
130
|
"--file",
|
|
99
131
|
"--version",
|
|
100
132
|
"--cursor",
|
|
@@ -105,6 +137,10 @@ const ISSUE_VALUE_FLAGS = [
|
|
|
105
137
|
"--updated-before",
|
|
106
138
|
"--sort",
|
|
107
139
|
"--dir",
|
|
140
|
+
...VIEW_VALUE_FLAGS,
|
|
141
|
+
...INBOX_VALUE_FLAGS,
|
|
142
|
+
...NOTIFY_VALUE_FLAGS,
|
|
143
|
+
...RELATION_VALUE_FLAGS, ...CYCLE_VALUE_FLAGS,
|
|
108
144
|
];
|
|
109
145
|
export function parseIssueArgs(args) {
|
|
110
146
|
const values = parseNamedArgs(args, {
|
|
@@ -112,6 +148,25 @@ export function parseIssueArgs(args) {
|
|
|
112
148
|
valueFlags: ISSUE_VALUE_FLAGS,
|
|
113
149
|
});
|
|
114
150
|
const { action, rest } = readIssueAction(values);
|
|
151
|
+
if (action === "views")
|
|
152
|
+
return parseViewArgs(values, rest);
|
|
153
|
+
if (action === "templates")
|
|
154
|
+
return parseTemplateArgs(values, rest);
|
|
155
|
+
if (action === "cycles")
|
|
156
|
+
return parseCycleArgs(values, rest);
|
|
157
|
+
if (isNotifyAction(action))
|
|
158
|
+
return parseNotifyArgs(values, rest);
|
|
159
|
+
if (isInboxAction(action))
|
|
160
|
+
return parseInboxArgs(values, action, rest);
|
|
161
|
+
const strayNotify = NOTIFY_VALUE_FLAGS.find((flag) => values.flags.has(flag));
|
|
162
|
+
if (strayNotify)
|
|
163
|
+
throw new Error(`${strayNotify} belongs to \`cockpit ticket notifications\`.`);
|
|
164
|
+
const strayInbox = [...INBOX_VALUE_FLAGS, ...INBOX_BOOLEAN_FLAGS].find((flag) => values.flags.has(flag) || values.booleans.has(flag));
|
|
165
|
+
if (strayInbox)
|
|
166
|
+
throw new Error(`${strayInbox} belongs to \`cockpit ticket inbox\`.`);
|
|
167
|
+
const stray = [...VIEW_VALUE_FLAGS, ...VIEW_BOOLEAN_FLAGS].find((flag) => values.flags.has(flag) || values.booleans.has(flag));
|
|
168
|
+
if (stray)
|
|
169
|
+
throw new Error(`${stray} belongs to \`cockpit ticket views\`.`);
|
|
115
170
|
const { issueRef, issueRefs, moveState } = readIssueReference(action, rest);
|
|
116
171
|
const requestId = optionalNonEmpty(values.flags.get("--request-id"));
|
|
117
172
|
const expectedRevision = optionalPositiveInteger(values.flags.get("--expected-revision"), "--expected-revision");
|
|
@@ -119,11 +174,12 @@ export function parseIssueArgs(args) {
|
|
|
119
174
|
throw new Error("--request-id must be a UUID.");
|
|
120
175
|
if (action === "mutation-status" && !UUID_PATTERN.test(issueRef ?? ""))
|
|
121
176
|
throw new Error("mutation-status needs a request UUID.");
|
|
122
|
-
if ((requestId || expectedRevision !== undefined) && !["create", "update", "move", "comment", "restore"].includes(action)) {
|
|
123
|
-
throw new Error("Write-safety flags belong to create, update, move, comment or
|
|
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.");
|
|
124
179
|
}
|
|
125
180
|
validateIssueState(moveState, "issue move state must be one of: ");
|
|
126
181
|
const stateFilter = readIssueStateFilter(action, values);
|
|
182
|
+
const fields = readIssueFields(action, values);
|
|
127
183
|
const title = readIssueTitle(action, values);
|
|
128
184
|
if (action !== "board" && values.flags.has("--rows")) {
|
|
129
185
|
throw new Error("--rows widens cockpit tickets board only.");
|
|
@@ -140,7 +196,7 @@ export function parseIssueArgs(args) {
|
|
|
140
196
|
const assignee = readIssueAssignee(action, values);
|
|
141
197
|
const limit = readIssueLimit(action, values);
|
|
142
198
|
const versionNumber = readIssueVersion(action, values);
|
|
143
|
-
return buildIssueCommand(values, action, issueRef, issueRefs, moveState, stateFilter, title, priority, estimate, assignee, limit, versionNumber, page);
|
|
199
|
+
return { ...buildIssueCommand(values, action, issueRef, issueRefs, moveState, stateFilter, title, priority, estimate, assignee, limit, versionNumber, page, fields), ...readRelationFlags(action, values) };
|
|
144
200
|
}
|
|
145
201
|
const INSTANT = /^\d{4}-\d\d-\d\d(?:T\d\d:\d\d:\d\d(?:\.\d{1,6})?(?:Z|[+-]\d\d:\d\d))?$/;
|
|
146
202
|
/** BLI-21315: the cursor contract's flags. Filters and sort belong to `list`; `--cursor` also to `history`. */
|
|
@@ -190,7 +246,7 @@ function readIssueAction(values) {
|
|
|
190
246
|
const first = values.positionals[0];
|
|
191
247
|
const action = (first === undefined ? "list" : first);
|
|
192
248
|
if (!ISSUE_ACTIONS.has(action)) {
|
|
193
|
-
throw new Error(`Unknown issue command: ${first}. Try board, list, show, create, update, move, comment, history, versions, restore, or mutation-status.`);
|
|
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.`);
|
|
194
250
|
}
|
|
195
251
|
return { action, rest: values.positionals.slice(first === undefined ? 0 : 1) };
|
|
196
252
|
}
|
|
@@ -237,10 +293,11 @@ function validateIssueState(state, errorPrefix) {
|
|
|
237
293
|
function readIssueStateFilter(action, values) {
|
|
238
294
|
const stateFilter = optionalNonEmpty(values.flags.get("--state"));
|
|
239
295
|
validateIssueState(stateFilter, "--state must be one of: ");
|
|
240
|
-
if (stateFilter !== undefined && action !== "list" && action !== "board") {
|
|
241
|
-
throw new Error("--state filters `cockpit issue list`; to move an issue use `cockpit issue move <id> <state>`.");
|
|
296
|
+
if (stateFilter !== undefined && action !== "list" && action !== "board" && action !== "create") {
|
|
297
|
+
throw new Error("--state filters `cockpit issue list` and sets the starting state on `create`; to move an issue use `cockpit issue move <id> <state>`.");
|
|
242
298
|
}
|
|
243
|
-
|
|
299
|
+
// On create the same flag is the starting state; it is returned separately in readIssueFields.
|
|
300
|
+
return action === "create" ? undefined : stateFilter;
|
|
244
301
|
}
|
|
245
302
|
function readIssueTitle(action, values) {
|
|
246
303
|
const title = optionalNonEmpty(values.flags.get("--title"));
|
|
@@ -270,14 +327,54 @@ function readIssueAssignee(action, values) {
|
|
|
270
327
|
// email is refused HERE rather than travelling to Postgres as a malformed
|
|
271
328
|
// uuid, which would come back as a 500 that names nothing useful.
|
|
272
329
|
const assignee = optionalNonEmpty(values.flags.get("--assignee"));
|
|
273
|
-
if (assignee !== undefined && assignee !== "me" && assignee !== "unassigned" && !UUID_PATTERN.test(assignee)) {
|
|
274
|
-
throw new Error('--assignee must be "me", "unassigned", or a person\'s uuid (see `cockpit team`).');
|
|
330
|
+
if (assignee !== undefined && assignee !== "me" && assignee !== "unassigned" && assignee !== "none" && !UUID_PATTERN.test(assignee)) {
|
|
331
|
+
throw new Error('--assignee must be "me", "unassigned" (list), "none" (update), or a person\'s uuid (see `cockpit team`).');
|
|
275
332
|
}
|
|
276
333
|
if (assignee === "unassigned" && action !== "list" && action !== "board") {
|
|
277
|
-
throw new Error(
|
|
334
|
+
throw new Error("--assignee unassigned filters `cockpit ticket list`; to clear an owner on update pass --assignee none.");
|
|
278
335
|
}
|
|
336
|
+
if (assignee === "none" && action !== "update")
|
|
337
|
+
throw new Error("--assignee none clears the owner on `cockpit ticket update`.");
|
|
279
338
|
return assignee;
|
|
280
339
|
}
|
|
340
|
+
const CALENDAR_DAY = /^\d{4}-\d{2}-\d{2}$/;
|
|
341
|
+
/** BLI-21422: starting state, due date and label names. Omitted stays omitted; `none` is the only way to clear. */
|
|
342
|
+
function readIssueFields(action, values) {
|
|
343
|
+
const out = {};
|
|
344
|
+
const startState = optionalNonEmpty(values.flags.get("--state"));
|
|
345
|
+
if (action === "create" && startState)
|
|
346
|
+
out.startState = startState;
|
|
347
|
+
const due = optionalNonEmpty(values.flags.get("--due"));
|
|
348
|
+
if (due !== undefined) {
|
|
349
|
+
if (action !== "create" && action !== "update")
|
|
350
|
+
throw new Error("--due belongs to `cockpit ticket create` and `update`.");
|
|
351
|
+
if (due === "none") {
|
|
352
|
+
if (action === "create")
|
|
353
|
+
throw new Error("--due none clears a date on `cockpit ticket update`; leave --due off to create without one.");
|
|
354
|
+
out.due = null;
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
if (!CALENDAR_DAY.test(due) || new Date(`${due}T12:00:00Z`).toISOString().slice(0, 10) !== due) {
|
|
358
|
+
throw new Error("--due must be a calendar date as YYYY-MM-DD, or none to clear it (update).");
|
|
359
|
+
}
|
|
360
|
+
out.due = due;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
const add = (values.flagValues.get("--add") ?? []).map((v) => v.trim()).filter(Boolean);
|
|
364
|
+
const remove = (values.flagValues.get("--remove") ?? []).map((v) => v.trim()).filter(Boolean);
|
|
365
|
+
if ((add.length > 0 || remove.length > 0) && action !== "label") {
|
|
366
|
+
throw new Error("--add and --remove belong to `cockpit ticket label <id>`.");
|
|
367
|
+
}
|
|
368
|
+
if (action === "label") {
|
|
369
|
+
if (add.length === 0 && remove.length === 0)
|
|
370
|
+
throw new Error("ticket label needs --add <name> and/or --remove <name> (repeat the flag for several).");
|
|
371
|
+
if (add.length > 50 || remove.length > 50)
|
|
372
|
+
throw new Error("ticket label changes at most 50 labels per side.");
|
|
373
|
+
out.addLabels = add;
|
|
374
|
+
out.removeLabels = remove;
|
|
375
|
+
}
|
|
376
|
+
return out;
|
|
377
|
+
}
|
|
281
378
|
/** `restore` names the version it wants; there is no "restore the previous one" (BLI-21300). */
|
|
282
379
|
function readIssueVersion(action, values) {
|
|
283
380
|
const version = optionalPositiveInteger(values.flags.get("--version"), "--version");
|
|
@@ -295,7 +392,7 @@ function readIssueLimit(action, values) {
|
|
|
295
392
|
}
|
|
296
393
|
return limit;
|
|
297
394
|
}
|
|
298
|
-
function buildIssueCommand(values, action, issueRef, issueRefs, moveState, stateFilter, title, priority, estimate, assignee, limit, versionNumber, page) {
|
|
395
|
+
function buildIssueCommand(values, action, issueRef, issueRefs, moveState, stateFilter, title, priority, estimate, assignee, limit, versionNumber, page, fields) {
|
|
299
396
|
return {
|
|
300
397
|
kind: "issue",
|
|
301
398
|
requestId: optionalNonEmpty(values.flags.get("--request-id")),
|
|
@@ -313,6 +410,7 @@ function buildIssueCommand(values, action, issueRef, issueRefs, moveState, state
|
|
|
313
410
|
project: optionalNonEmpty(values.flags.get("--project")),
|
|
314
411
|
...(limit === undefined ? {} : { limit }),
|
|
315
412
|
...page,
|
|
413
|
+
...fields, ...readCycleField(action, values),
|
|
316
414
|
...(versionNumber === undefined ? {} : { versionNumber }),
|
|
317
415
|
title,
|
|
318
416
|
...(priority === undefined ? {} : { priority }),
|
|
@@ -323,26 +421,4 @@ function buildIssueCommand(values, action, issueRef, issueRefs, moveState, state
|
|
|
323
421
|
json: values.booleans.has("--json"),
|
|
324
422
|
};
|
|
325
423
|
}
|
|
326
|
-
export
|
|
327
|
-
const values = parseNamedArgs(args, {
|
|
328
|
-
allowedFlags: ["--home", "--dashboard-url", "--archived", "--cursor", "--limit", "--json"],
|
|
329
|
-
valueFlags: ["--home", "--dashboard-url", "--cursor", "--limit"],
|
|
330
|
-
});
|
|
331
|
-
const first = values.positionals[0];
|
|
332
|
-
const action = (first === undefined ? "list" : first);
|
|
333
|
-
if (action !== "list")
|
|
334
|
-
throw new Error(`Unknown project command: ${first}. The only verb is list.`);
|
|
335
|
-
const rest = values.positionals.slice(first === undefined ? 0 : 1);
|
|
336
|
-
if (rest.length > 0)
|
|
337
|
-
throw new Error(`project list does not take "${rest[0]}".`);
|
|
338
|
-
return {
|
|
339
|
-
kind: "project",
|
|
340
|
-
action,
|
|
341
|
-
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
342
|
-
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
343
|
-
includeArchived: values.booleans.has("--archived"),
|
|
344
|
-
...(optionalNonEmpty(values.flags.get("--cursor")) ? { cursor: optionalNonEmpty(values.flags.get("--cursor")) } : {}),
|
|
345
|
-
...(values.flags.has("--limit") ? { limit: optionalPositiveInteger(values.flags.get("--limit"), "--limit") } : {}),
|
|
346
|
-
json: values.booleans.has("--json"),
|
|
347
|
-
};
|
|
348
|
-
}
|
|
424
|
+
export { parseProjectArgs } from "./project-args.js";
|
|
@@ -54,4 +54,5 @@ export { parseCareersArgs } from './local-args-tower-careers.js';
|
|
|
54
54
|
export { parseRenameArgs } from './local-args-tower-rename.js';
|
|
55
55
|
export { parseGlossaryArgs } from "./glossary.js";
|
|
56
56
|
export { parseSpendArgs } from "./spend.js";
|
|
57
|
+
export { parsePushArgs } from "./push.js";
|
|
57
58
|
export { parseCiArgs } from "./ci.js";
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { parseGlossaryArgs } from "./glossary.js";
|
|
19
19
|
import { parseSpendArgs } from "./spend.js";
|
|
20
|
+
import { parsePushArgs } from "./push.js";
|
|
20
21
|
import { parseRootsArgs } from "./roots.js";
|
|
21
22
|
import { SURFACE_ALIASES } from "./local-help.js";
|
|
22
23
|
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";
|
|
@@ -82,6 +83,7 @@ const COMMAND_PARSERS = new Map([
|
|
|
82
83
|
["sources", (_command, rest) => parseSourcesArgs(rest)],
|
|
83
84
|
["glossary", (_command, rest) => parseGlossaryArgs(rest)],
|
|
84
85
|
["spend", (_command, rest) => parseSpendArgs(rest)],
|
|
86
|
+
["push", (_command, rest) => parsePushArgs(rest)],
|
|
85
87
|
["docs", (_command, rest) => parseDocsArgs(rest)],
|
|
86
88
|
["msg", (_command, rest) => parseMsgArgs(rest)],
|
|
87
89
|
["issue", (_command, rest) => parseIssueArgs(rest)],
|
|
@@ -235,7 +235,7 @@ async function inferUniqueGitEmail(roots) {
|
|
|
235
235
|
}
|
|
236
236
|
async function readGitConfigEmail(root) {
|
|
237
237
|
return new Promise((resolve) => {
|
|
238
|
-
execFile("git", ["config", "user.email"], { cwd: root, encoding: "utf8" }, (error, stdout) => {
|
|
238
|
+
execFile("git", ["config", "user.email"], { cwd: root, encoding: "utf8", windowsHide: true }, (error, stdout) => {
|
|
239
239
|
if (error) {
|
|
240
240
|
resolve(null);
|
|
241
241
|
return;
|
|
@@ -174,6 +174,8 @@ export const TOWER_OPS_COMMAND_HELP = [
|
|
|
174
174
|
" cockpit approvals show <id> the card in full: who, why, the exact content, every receipt",
|
|
175
175
|
" cockpit approvals create prepare a card; the POST /api/approvals body as JSON on stdin",
|
|
176
176
|
" cockpit approvals approve <id> [--edit] let the guarded act run; --edit reads {\"subject\":...,\"text\":...} or {\"content\":...} on stdin",
|
|
177
|
+
" --checked-sent sent|not-sent on a card whose delivery is unknown or stuck sending, what you found where it was going",
|
|
178
|
+
" (your Sent folder for mail.send, the channel for msg.send): sent closes it, not-sent sends or posts it now",
|
|
177
179
|
" cockpit approvals edit <id> [--approve] save an edit (JSON on stdin), the card stays waiting; --approve then approves it",
|
|
178
180
|
" cockpit approvals reject <id> decline it; nothing is sent",
|
|
179
181
|
" cockpit approvals rules [--active true|false] [--limit <n>]",
|
|
@@ -11,6 +11,18 @@
|
|
|
11
11
|
* Every string moved verbatim. Help output is what an intern pastes back when
|
|
12
12
|
* something breaks, so it is a user-visible contract like any other.
|
|
13
13
|
*/
|
|
14
|
+
import { DOCS_FILES_HELP } from "./local-help-docs-files.js";
|
|
15
|
+
import { DOCS_REFS_HELP } from "./local-help-docs-refs.js";
|
|
16
|
+
import { DOCS_SYNC_HELP } from "./local-help-docs-sync.js";
|
|
17
|
+
import { DOCS_FORMATTING_HELP } from "./local-help-docs-formatting.js";
|
|
18
|
+
import { DOCS_INBOX_HELP } from "./local-help-docs-inbox.js";
|
|
19
|
+
import { DOCS_RECOVERY_HELP } from "./local-help-docs-recovery.js";
|
|
20
|
+
import { PROJECT_HELP } from "./local-help-project.js";
|
|
21
|
+
import { INBOX_HELP_LINES } from "./local-help-inbox.js";
|
|
22
|
+
import { WORK_NOTIFY_HELP_LINES } from "./local-help-work-notify.js";
|
|
23
|
+
import { VIEWS_HELP_LINES } from "./local-help-views.js";
|
|
24
|
+
import { TEMPLATES_HELP_LINES } from "./local-help-templates.js";
|
|
25
|
+
import { CYCLES_HELP_LINES } from "./local-help-cycles.js";
|
|
14
26
|
/** One entry per Tower noun, in the order `cockpit --help` lists them. */
|
|
15
27
|
export const TOWER_COMMAND_HELP = [
|
|
16
28
|
[
|
|
@@ -47,19 +59,20 @@ export const TOWER_COMMAND_HELP = [
|
|
|
47
59
|
[
|
|
48
60
|
"docs",
|
|
49
61
|
[
|
|
50
|
-
"Usage: cockpit docs [list|tree|read <id|slug> [<id|slug>...]|read --parent <id> --bodies|create|update <id>|delete <id|slug> [<id|slug>...]|link <id|slug> <notion-page>|sync-status [<id|slug>]|export <id|slug> [--format md|pdf|docx|txt] [--out <path>]|mutation-status <request-uuid>|share <id|slug> --add|--remove <email>|access <id|slug
|
|
62
|
+
"Usage: cockpit docs [list|search [words]|tree|read <id|slug> [<id|slug>...]|read --parent <id> --bodies|create|update <id>|delete <id|slug> [<id|slug>...]|link <id|slug> <notion-page>|sync-status [<id|slug>]|sync-resolve <id|slug> --version <id> --keep-current|--use-kept|manifest [--under <id|slug>] [--all] [--out <file>]|export <id|slug> [--format md|pdf|docx|txt] [--out <path>]|mutation-status <request-uuid>|share <id|slug> --add|--remove <email>|access <id|slug>|comments <id|slug>|comment <id|slug>|trash|versions <id|slug>|restore <id>|favorite <id|slug>|unfavorite <id|slug>|marks] [flags]",
|
|
51
63
|
"",
|
|
52
64
|
"The Tower document library, typed. Bare `cockpit docs` lists every document you may read.",
|
|
53
65
|
"list [--parent <id>|root] [--query <text> [--word]] [--limit <n>] — every document you may see: id, visibility, body size, slug, title. Metadata only — a list never carries a body, so it stays cheap enough for an agent to call (--json was 604 KB for 155 documents before BLI-3737, and is now a few tens of KB). --json shape: { documents: [{ id, slug, title, visibility, parent_id, source, updated_at, body_chars }] }.",
|
|
54
66
|
" --parent <id> lists the documents filed directly under that document; --parent root lists the top of the tree.",
|
|
55
67
|
" --query <text> keeps the documents whose title OR body contains that text, matched case-insensitively in the database — never fuzzy, and the body still never comes back.",
|
|
56
68
|
" --word matches --query as whole words: `--query ott --word` finds \"Ott\" but not \"bottom\" or \"Otter\".",
|
|
57
|
-
" --limit <n> caps the rows (1-1000). Narrow with these before reading: `cockpit docs list --query onboarding --json` then `cockpit docs read <slug>`.",
|
|
58
|
-
"
|
|
59
|
-
"
|
|
69
|
+
" --limit <n> caps the rows (1-1000). --cursor <c> continues a list from the `More:` line (keyset paging); --all walks every page (at most 20) and prints requested/returned/unique pages and a status: exhausted, capped or partial. Narrow with these before reading: `cockpit docs list --query onboarding --json` then `cockpit docs read <slug>`.",
|
|
70
|
+
"search [words] [--word] [--title-only] [--by <me|email|id>] [--under <id|slug>] [--since YYYY-MM-DD] [--until YYYY-MM-DD] [--timezone <IANA>] [--sort relevance|updated] [--limit <n>] — finds documents by title or text over EVERY page you may read (past the 1,000-row page cap), narrowed by who created them (--by me, an exact email, or a user id), a subtree (--under a page: that page and its descendants), and the local days they were updated (--since/--until inclusive, in --timezone, default this machine's zone). Matching is literal and case-insensitive; --word matches whole words; --title-only matches titles alone. Words may be left out when --by, --under, --since or --until is given. Prints each hit with its id, then matched/returned and a coverage line: exhausted, capped (more exist) or partial (some pages could not be searched). --json shape: { query, filters, sort, matched, requested, returned, coverage, results: [{ id, title, slug, created_by, parent_id, updated_at, matched_in, snippet }] }. A bad filter is invalid_filter (400) and names the field; --under a page you cannot read finds nothing and never names it. Same door as the web box, MCP docs_search and JARVIS.",
|
|
71
|
+
"tree — the same documents nested under their parent, for a sidebar-shaped view. Walks every page and ends with a coverage line: exhausted, capped (20,000 shown, more exist) or partial (some pages could not load). favorite <id|slug> / unfavorite <id|slug> star or unstar a page for yourself (200 at most; favorites_full). marks — your favorites and your 20 most recent web-opened pages, readable only; --json: { favorites: [{ id, title, slug, marked_at }], recents: [...] }.",
|
|
72
|
+
"read <id|slug> [<id|slug>...] — one or more documents' title, slug, visibility, and body. This is the ONLY verb that returns a body. Each ref is one exact lookup (id, or slug matched exactly, any case, never fuzzy or prefix; at most 8 in flight), then one GET each — the oldest page opens like the newest and one bad ref never loses the rest.",
|
|
60
73
|
" --section \"<heading>\" — one section only, from that heading (any # level, case-insensitive) to the next heading at the same or shallower level. Mirrors `cockpit workbook --section`. A miss refuses (document_section_not_found) and names the headings that ARE there.",
|
|
61
74
|
" --offset <n> --limit <n> — a character range of the body, instead of the whole thing.",
|
|
62
|
-
" --json shape: one ref answers { document }, unchanged from before BLI-10450. Several refs answer { documents: [{ ref, ok, document } | { ref, ok: false, error, detail }] }.",
|
|
75
|
+
" --json shape: one ref answers { document }, unchanged from before BLI-10450. Several refs answer { documents: [{ ref, ok, document } | { ref, ok: false, error, detail }] }. The meta line (and --json document.revision) shows the page revision. Agent writes are never queued offline: a write that did not reach Tower fails with its reason, and `docs mutation-status <request-id>` tells whether it landed. Pass the revision you read as expected_revision.",
|
|
63
76
|
"read --parent <id>|root --bodies [--limit <n>] — every document filed directly under that parent, bodies included, in ONE call (GET /api/docs/documents?parent_id=...&bodies=true). --limit here caps the DOCUMENT COUNT (1-50), not characters. --json shape: { documents: [{ id, ..., body_markdown }] }.",
|
|
64
77
|
"create --title \"<title>\" [--parent <id>] [--visibility org|private] [--file <path>|--body-stdin] — the body comes from --file, or stdin (`cat body.md | cockpit docs create --title \"...\"`); neither means an empty body, matching the browser's own default. --json shape: { document }.",
|
|
65
78
|
"update <id|slug> [--title \"<t>\"] [--visibility org|private] [--parent <id>|--clear-parent] [--file <path>|--body-stdin] [--allow-empty] [--expected-revision <n>] [--request-id <uuid>] — only the fields you pass change; a `--parent` change IS a move, there is no separate move verb. --json shape: { document, mutation }.",
|
|
@@ -69,30 +82,38 @@ export const TOWER_COMMAND_HELP = [
|
|
|
69
82
|
" --allow-empty clears the page on purpose. A body that would empty a document that holds text is refused (refused_empty_body) unless you say so, because far more often it is a surface that lost the content than a person who meant it.",
|
|
70
83
|
"access <id|slug> — who may do what on a page and why, in plain sentences (BLI-21313): your role (author, admin, granted_editor, reader), can_read/can_edit/can_manage, the named editors you may see (the owner and administrators see all; a named editor sees only their own grant), and the grant history for the owner and administrators. The same explanation the page, its versions and its export carry. --json shape: { ok, document_id, permission: { role, can_read, can_edit, can_manage, editors, editors_complete, reasons }, events }.",
|
|
71
84
|
"share <id|slug> --add <email> | --remove <email> [--reason \"<note>\"] — owner or administrator only. Names (or removes) an editor of an ORGANIZATION page by exact email, never fuzzy. An editor can change the title and text; they cannot change visibility, move, delete or manage editors, and nobody new gains the right to read. A page you made private refuses with private_page_grant_needs_decision (sharing a private page waits for a decision) and nothing is written. Other refusals: not_grant_manager, person_not_found, grantee_is_author, document_not_found_or_unreadable. Adding someone already named, or removing someone not named, answers outcome unchanged. --json shape: { ok, outcome, reason, document_id, grantee_id, request_id, permission }.",
|
|
72
|
-
"
|
|
85
|
+
"comments <id|slug> [--status open|resolved|all] [--cursor <c>] — the page's discussion (BLI-21701): threads newest activity first, 50 per page, each with its comments (author, time, edited), a \"A comment was deleted by <who> on <when>.\" line for a deleted one, and who resolved a resolved thread. Anyone who can read the page can read it. Ends with the open/resolved counts, a `More: --cursor` line, and a coverage note when some comments could not be read. --json shape: { ok, status, can_comment, counts: { open, resolved }, threads: [{ id, resolved, resolved_at, resolved_by_name, comments: [{ id, author_name, body, created_at, edited_at, deleted, marker, can_edit, can_delete }] }], next_cursor, coverage: exhausted|capped|failed_partial, coverage_note, note }.",
|
|
86
|
+
"comment <id|slug> \"<text>\" | - [--request-id <uuid>] — starts a thread (the text is the second argument, or `-` to read stdin; 1-10,000 characters). Add --reply <thread id> to answer a thread, --edit <comment id> to replace your own comment's text (earlier text is not kept), --delete <comment id> (its author, the page owner or an administrator; a soft delete), --resolve <thread id> or --reopen <thread id>. Exactly one action. Only the page owner, administrators and named editors of an organization page can write; everyone else who can read the page is refused with not_commenter and nothing is written. Other labels, verbatim: not_comment_author, comment_not_found, thread_not_found, invalid_body, document_not_found_or_unreadable. The same --request-id twice gives one comment (outcome unchanged); resolving a resolved thread answers unchanged. --json shape: { ok, outcome, reason, document_id, thread_id, comment_id, request_id }.",
|
|
87
|
+
"delete <id|slug> [<id|slug>...] — soft-deletes one or more documents (BLI-10555): the browser's own delete, sets deleted_at, never a real SQL DELETE. Recoverable: `cockpit docs trash` lists what you may restore and `cockpit docs restore <id>` brings it back (BLI-21413). Deleting a parent does NOT delete its children; they keep their own parent_id and stay exactly where they were. Each ref is one exact lookup, then one DELETE each — one bad ref never loses the rest. --json shape: one ref answers { ok, document_id }; several answer { documents: [{ ref, ok, document_id } | { ref, ok: false, error, detail }] }.",
|
|
88
|
+
...DOCS_RECOVERY_HELP,
|
|
89
|
+
...DOCS_INBOX_HELP,
|
|
90
|
+
...DOCS_FILES_HELP,
|
|
91
|
+
...DOCS_REFS_HELP,
|
|
92
|
+
...DOCS_SYNC_HELP,
|
|
93
|
+
...DOCS_FORMATTING_HELP,
|
|
73
94
|
"link <id|slug> <notion-url|page-id> — ties the document to a Notion page so the two-way sync moves it both ways. The page is the truth from then on: the next sync pulls it over this copy and keeps the copy as a version. `link <id|slug> --clear` unties it. Refusals: invalid_notion_page, notion_page_already_linked, document_not_writable.",
|
|
74
95
|
"sync-status [<id|slug>] — every linked document's Notion sync state, or one's: synced (the body is what both sides last agreed on), tower_changed (edited here since; the next write-back pushes it), never_synced (linked, no baseline yet). Tower's half only: a Notion edit shows up at the next pull, not here. Metadata only, never a body.",
|
|
75
96
|
"export <id|slug> [--format md|pdf|docx|txt] [--out <path>] — writes the document as a file (ticket 20694): md is the stored Markdown exactly, txt is readable plain text, docx is a Word file with real headings, lists, tables and links, pdf is a paginated page with the title and last-updated date in the header. Default format md; default path is the document's slug plus the extension in the current directory (--out names a file, or a directory to put it in), and an existing file is replaced. Prints the path; --json shape: { ok, path, bytes, format }. To copy a page's text, `cockpit docs read` already does it.",
|
|
76
|
-
"A document body is never accepted on the command line — --file (safest on Windows) or a pipe only, same discipline as `cockpit notes paste`.",
|
|
77
|
-
"--json writes one machine-readable object to stdout; every reason and receipt line stays on stderr. A refusal keeps Tower's own reason label — needs_rls_client, document_not_found_or_unreadable, document_not_writable, slug_taken, circular_parent, and so on.",
|
|
78
|
-
"The command uses the existing paired device identity. Run `cockpit login` first if this machine is not paired.",
|
|
97
|
+
"A document body is never accepted on the command line — --file (safest on Windows) or a pipe only, same discipline as `cockpit notes paste`. --json writes one machine-readable object to stdout; every reason and receipt line stays on stderr. A refusal keeps Tower's own reason label — needs_rls_client, document_not_found_or_unreadable, document_not_writable, slug_taken, circular_parent, and so on. The command uses the existing paired device identity. Run `cockpit login` first if this machine is not paired.",
|
|
79
98
|
],
|
|
80
99
|
],
|
|
81
100
|
[
|
|
82
101
|
"msg",
|
|
83
102
|
[
|
|
84
|
-
"Usage: cockpit msg [channels|create <name>|dm <email>|read <channel>|unread <channel> --from <message-id>|send <channel>|edit <message-id>|delete <message-id>|outcome <request-uuid>|thread <id> --channel <channel>|show <message-id>] [flags]",
|
|
103
|
+
"Usage: cockpit msg [channels|create <name>|dm <email>...|channel <channel>|members <channel>|hide <channel>|unhide <channel>|read <channel>|unread <channel> --from <message-id>|send <channel>|edit <message-id>|delete <message-id>|outcome <request-uuid>|draft [list|show|save|discard|restore|send] <channel>|file upload|show|download|cancel|list|thread <id> --channel <channel>|show <message-id>|activity|follow <message-id>|unfollow <message-id>|react <message-id> <emoji>|notify [set|quiet|receipts]|search [words]|save <message-id>|saved|pin <message-id>|pins <channel>|forward <message-id> --to <channel>|schedule <channel> --at <time>|remind <message-id> --at <time>|scheduled [change|cancel|done <id>]|context <message-id> [--add <kind:id>|--remove <link-id>]] [flags]",
|
|
85
104
|
"",
|
|
86
105
|
"Channels and messages, typed. <channel> is a channel id, or its name with or without a leading #.",
|
|
87
106
|
"channels — every channel you are a member of (or, as a super_admin, every channel).",
|
|
88
107
|
"create <name> [--private] [--members a@x.test,b@y.test] [--description \"<text>\"] — makes a channel and prints its id. A leading # is fine; --private means membership decides who may read it, and --members names who joins at birth BY EMAIL. An address Tower does not carry refuses the whole create — no half-built channel.",
|
|
89
108
|
"dm <email> — opens (or re-opens) the direct message with one person. Idempotent: the same address always resolves to the same channel, and you never have to name yourself.",
|
|
109
|
+
"dm <email> [<email>...] — with several addresses, opens (or re-opens) the group message with exactly those people plus you; the same set always resolves to the same channel. channel <channel> [--json] — the channel's details: name, description, kind, archived, audience (a public channel is everyone in the org, not a member count), source workspace label (where it came from; it grants no access), whether you can manage it, and whether you hid it. channel <channel> --rename <name> | --describe <text> | --archive | --unarchive — changes it (one update; --rename and --describe can go together). Only the person who made the channel, its owners and its moderators can; anyone else is refused channel_manage_refused, a name in use channel_name_taken, a direct message dm_not_editable. Archiving keeps every message and link and stops new posts (channel_archived); unarchive restores posting. members <channel> [--json] — who is in a private channel or a direct message; a public channel says everyone in the org can read it. members <channel> --add <email> | --remove <email> — private channels only, by the same people who can manage it; a removed person's next read is refused. A direct message keeps the people it started with (dm_not_editable): start a new group with `msg dm a@x b@y`. hide <channel> / unhide <channel> — drops a PUBLIC channel from your own sidebar, or puts it back; it changes nothing for anyone else and nothing about reading, search, mentions or notifications. A private channel or direct message is refused hide_not_public.",
|
|
90
110
|
"read <channel> [--limit <n>] [--thread <id>] [--mark-read] — the channel's most recent top-level messages, oldest first; --thread <id> reads one thread's replies instead. The first page also says where the messages come from (for an imported Slack channel: how long ago the archive was checked, or that it is unknown) and where YOUR read line stands. Reading never moves the read line; --mark-read moves it to the newest message shown, and says plainly if that failed.",
|
|
91
111
|
"unread <channel> --from <message-id> — marks that message and everything after it unread, so the web sidebar badge and the NEW MESSAGES line start there. Opening or reading a channel never clears unseen history.",
|
|
92
|
-
"send <channel> [--thread <id>] [--request-id <uuid>] — posts a message. The content is never accepted on the command line: pipe it in, e.g. `echo \"hello\" | cockpit msg send general`. Every send carries a request id (minted when you give none); retry a send whose answer was lost with the SAME --request-id and the same text and it posts once. The same id with different text is refused request_id_reused, not applied as an edit.",
|
|
93
|
-
"edit <message-id> [--expected-revision <n>] [--request-id <uuid>] — replaces your own message's text (piped in). With --expected-revision, an edit of a message that has moved on is refused revision_conflict and nothing is saved over it; the current revision is printed. A message you did not write, or one that was deleted, is refused by name. delete <message-id> [--expected-revision <n>] [--request-id <uuid>] — deletes by tombstone: the text is cleared and every surface reads \"This message was deleted\", but the row stays so its thread, replies and reply count survive. Search and unread counts ignore it. A repeat answers already_deleted. outcome <request-uuid> — your own write receipt for a send, edit or delete: committed, not_committed (with the reason) or unknown_check_again. Run it after a timeout before retrying.",
|
|
112
|
+
"send <channel> [--thread <id>] [--request-id <uuid>] — posts a message. The content is never accepted on the command line: pipe it in, e.g. `echo \"hello\" | cockpit msg send general`. Every send carries a request id (minted when you give none); retry a send whose answer was lost with the SAME --request-id and the same text and it posts once. The same id with different text is refused request_id_reused, not applied as an edit. send <channel> --file <id> [--file <id>...] — attaches up to 10 ready files (from `msg file upload`) to the message. The text is optional with a file; it is read from stdin only when something is piped in. file upload <channel> <path|-> [--name <name>] — uploads a file of 1 byte to 25 MB straight to storage (reserve, PUT, finalize) and prints its id and state; `-` reads stdin and needs --name. A file is ready, not sent: attach it with `msg send <channel> --file <id>`. file show <id> — the descriptor (state reserved, ready, attached, cancelled or abandoned). file download <id> [--out <path>] — saves the bytes (60-second signed link; never overwrites). file cancel <id> — ends an unattached file and removes its bytes. file list <channel> — your own unsent uploads there, with coverage. A file is readable only by readers of its channel, and only once attached to a live message. Refusals keep the door's reason: file_too_large, file_not_found_or_unreadable, file_not_uploaded, file_not_ready, file_already_attached, file_wrong_channel, storage_unreachable.",
|
|
113
|
+
"edit <message-id> [--expected-revision <n>] [--request-id <uuid>] — replaces your own message's text (piped in). With --expected-revision, an edit of a message that has moved on is refused revision_conflict and nothing is saved over it; the current revision is printed. A message you did not write, or one that was deleted, is refused by name. delete <message-id> [--expected-revision <n>] [--request-id <uuid>] — deletes by tombstone: the text is cleared and every surface reads \"This message was deleted\", but the row stays so its thread, replies and reply count survive. Search and unread counts ignore it. A repeat answers already_deleted. outcome <request-uuid> — your own write receipt for a send, edit or delete: committed, not_committed (with the reason) or unknown_check_again. Run it after a timeout before retrying. draft [list|show|save|discard|restore|send] <channel> [--thread <id>] [--expected-revision <n>] [--request-id <uuid>] — the text you have not sent yet, one draft per channel and thread, the same drafts the browser autosaves. list shows all your unsent drafts (newest first, with coverage); show prints one; save takes the body on stdin (`echo \"half a thought\" | cockpit msg draft save general`) and with --expected-revision refuses draft_revision_conflict, naming the stored revision, instead of overwriting; discard keeps the text (state discarded) and restore brings it back; send posts the stored draft with a fresh request id (--request-id to retry the same send) and its revision, prints the receipt, and marks the draft sent only if it did not change meanwhile. Only you can read or change your drafts.",
|
|
94
114
|
"thread <id> --channel <channel> [--before <cursor>] [--limit <n>] — one thread's replies by the parent message's id.",
|
|
95
|
-
"show <message-id> — exact message, thread root and bounded neighbors. read/thread --before takes the preceding nextCursor; repeat until hasMore is false.",
|
|
115
|
+
"show <message-id> — exact message, thread root and bounded neighbors. read/thread --before takes the preceding nextCursor; repeat until hasMore is false. activity [--mentions|--threads] [--unread] [--before <cursor>] [--limit <n>] [--mark-read] — your Mentions (messages that mention you) and Threads (replies in threads you follow: you started it, replied natively or followed it by hand), newest first, with unread counts and coverage; items whose message you can no longer read are left out and counted as dropped. Nothing is marked read by looking; --mark-read marks the unread items just printed, and the web marks one when its message is shown. Activity covers native Tower messages only, so the Slack import adds none. follow <message-id> / unfollow <message-id> — the id is the thread's top-level message; an explicit unfollow wins over a later auto follow, and earlier items stay. react <message-id> <emoji> [--remove] — adds or takes off your reaction (idempotent). Refusals: thread_root_not_found_or_unreadable, not_a_thread_root, reaction_not_found. notify — your message notification settings (the same ones the web header menu and Settings change): the default level (all, mentions or none; mentions is the default and covers direct messages, @mentions and replies in threads you follow), quiet hours and per-channel overrides. notify set --level <all|mentions|none> changes the default; add --channel <id|name> for one channel, with --mute / --unmute, or --clear to remove the override. notify quiet <HH:MM-HH:MM> [--timezone <zone>] sets quiet hours (24-hour, may wrap midnight; the zone defaults to this machine's) and notify quiet --off removes them. notify receipts [--message <id>] [--before <cursor>] [--limit <n>] lists what was delivered, suppressed or blocked and why (never message text): outcome shown means Tower told a browser tab to show a banner; push_accepted means the push service took the phone push, not that a phone showed it. Quiet hours and levels hold back banners and phone pushes, never the message or its unread badge. Refusals: invalid_notification_level, invalid_quiet_hours, invalid_timezone. search [words] [--from <email|me>]... [--in <channel>] [--since YYYY-MM-DD] [--until YYYY-MM-DD] [--timezone <IANA>] [--replies|--top-level] [--has-file] [--before <cursor>] [--limit <n>] — message search on your own access, newest first (not ranked; `cockpit search --kind msg` is the ranked bar). Each hit prints its id, channel, author, time and snippet, then `more: --before <cursor>` or `complete`; repeat with the cursor until complete. --from takes an email, a user id or me and repeats; --in takes a channel id or #name (exact after case folding, an ambiguous name is refused); --since and --until are whole local days in --timezone, both inclusive (this machine's zone when you give none). The words may be left out when a filter is set, so `msg search --has-file` finds attachment-only messages. Inline operators work in the words as in the web box: from:<email|me> in:#name after:<day> before:<day> (exclusive) on:<day> has:file is:reply is:root. Recognised but unsupported operators (has:link, has:pin, has:reaction, is:saved, is:dm, to:, with:, during:) are refused unsupported_search_operator, naming the operator; a bad day, zone, person or channel is refused invalid_search_filter. Messages imported from Slack carry no file links, so has:file does not find them. Deleted messages, channels you cannot read and files you cannot read never appear.",
|
|
116
|
+
"save <message-id> [--remove] — saves a message for yourself (private: nobody else sees your saves) or, with --remove, unsaves it. Idempotent: a repeat answers changed false. A deleted message cannot be saved but can be unsaved. saved [--before <cursor>] [--limit <n>] — your saved messages, newest save first (1 to 100, default 50), with `more: --before <cursor>` or `complete`; a message you can no longer read is left out and counted as dropped, a deleted one shows as deleted so you can unsave it. pin <message-id> [--remove] — pins a message in its channel for everyone who can read it, or unpins it; any member who can read the channel may, and the history records who. A repeat is changed false. Refusals: pin_limit_reached (a channel holds 100 pins), pin_deleted_message, channel_archived (an archived channel's pins stay as they are), message_not_found_or_unreadable. pins <channel> — the channel's pins, newest first, with who pinned and when. forward <message-id> --to <channel> [--thread <id>] [--request-id <uuid>] — posts ONE new message into the destination holding the comment you pipe in (optional) and the source's exact link; no text, author or channel name of the source is copied, and the people who read the destination gain no access to it: its preview shows only to those who can read the source. You must be able to read the source as a member, and it must be live (forward_source_deleted); an archived destination is channel_archived. Retry a forward whose answer was lost with the SAME --request-id and it posts once. Copying a link posts nothing; `msg show <message-id>` returns the exact link and rechecks your access.", "schedule <channel> --at <time> [--timezone <zone>] [--thread <id>] [--request-id <uuid>] — posts the piped text as you at that time, from the server (your laptop can be closed). <time> is an ISO instant with an offset (2026-11-02T09:00:00+07:00) or a wall time \"YYYY-MM-DD HH:MM\" in --timezone (an IANA name such as Asia/Ho_Chi_Minh or America/Toronto; this machine's zone when omitted, and the answer names it). The answer prints the stored instant and zone. 1 minute to 120 days ahead, at most 100 pending per person. A wall time that does not exist (the spring-forward hour) is invalid_schedule_time; an hour that happens twice takes the earlier one. Delivery lands within about a minute of due. If you are no longer a member of the channel by then it fails author_lost_access and keeps the text; see `scheduled --state failed`. remind <message-id> --at <time> [--timezone <zone>] [--note <text, 500 chars>] [--request-id <uuid>] — a reminder about one message. When due it appears under `scheduled --state fired` with the exact link and posts nothing to the channel (no banner, no push). scheduled [--state <pending|sent|failed|cancelled|fired|done|open|all>] [--before <cursor>] [--limit <n>] — your schedules and reminders only (nobody else sees them); open (the default) is pending plus fired, soonest first; the finished states and all list newest first; 1 to 100, default 50, with `more: --before <cursor>` or `complete`. scheduled change <id> [--at <time>] [--timezone <zone>] [--revision <n>] — move a pending schedule and/or pipe in new text; a stale --revision is schedule_revision_conflict. scheduled cancel <id> — cancels a pending one; if it already went out you get schedule_already_sent and the message id. scheduled done <id> — marks a fired reminder done. Refusals: invalid_schedule_time, invalid_timezone, schedule_limit_reached, schedule_not_found, schedule_already_sent, schedule_revision_conflict, request_id_reused. Retry a schedule whose answer was lost with the SAME --request-id and it stores once.", "context <message-id> [--add doc:<id>|ticket:<BLI-id or uuid>|event:<id>[@<iso occurrence>]|note:<id>] [--remove <link-id>] — the Tower docs, tickets, calendar events and meeting notes pointed at from one message. With no flag it lists them (--json prints the door's body: link_id, kind, id, title, href; a ticket also has identifier and state, an event its start). Every target is read again for YOU: one you cannot open is listed as state unavailable with no title, and a link grants no access to its target. --add attaches one; only the message's author may, and the target must be readable by you (otherwise context_target_unavailable, which never says whether it exists). A message holds 10 live links (context_limit_reached); the same target twice is context_already_linked; a non-author is context_not_author. --remove <link-id> (after the message id) removes it softly: the message's author or whoever attached it may, and the history keeps it. context_link_not_found if it is gone. Web shows the same chips under the message; the keys and the Keyboard sheet are web only.",
|
|
96
117
|
"--json writes one machine-readable object to stdout; every reason and receipt line stays on stderr.",
|
|
97
118
|
"A refusal keeps Tower's own reason label — needs_rls_client, channel_not_found_or_unreadable, content_too_long, and so on.",
|
|
98
119
|
"The command uses the existing paired device identity. Run `cockpit login` first if this machine is not paired.",
|
|
@@ -101,22 +122,27 @@ export const TOWER_COMMAND_HELP = [
|
|
|
101
122
|
[
|
|
102
123
|
"issue",
|
|
103
124
|
[
|
|
104
|
-
"Usage: cockpit issue [board|list|show <id> [<id>...]|create|update <id>|move <id> <state>|comment <id>|history <id>|versions <id>|restore <id> --version <n>|mutation-status <request-uuid>] [flags]",
|
|
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]",
|
|
105
126
|
"",
|
|
106
127
|
"Tower's issue tracker. <id> is a BLI-#### identifier (BLI-3654) or an issue's uuid — both work everywhere an issue is named.",
|
|
107
128
|
"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 }] }.",
|
|
108
129
|
"board [--json] [--state <s>] [--assignee me|unassigned|<uuid>] [--project <uuid>] [--label <name>] [--rows <state:n,...>] — the Tickets board, counts, coverage and top five cards per column, through GET /api/work/board. Also cockpit tickets board.",
|
|
109
130
|
"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.",
|
|
110
131
|
" --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 }] }.",
|
|
111
|
-
"create --title \"<title>\" [--project <name|id>] [--priority 0-4] [--size S|M|L] [--assignee me|<uuid>] [--parent <id>] [--file <path>] — 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 }.",
|
|
112
|
-
"update <id> [--title \"<t>\"] [--priority 0-4] [--size S|M|L] [--assignee me|<uuid>] [--project <name|id>] [--parent <id>] [--file <path>|--body-stdin] — only the fields you pass change. State does NOT move here; use move. --json shape: { issue }.",
|
|
113
|
-
"move <id> <state> — moves an issue and records the move. States: backlog, todo, in_progress, in_review, done, canceled.",
|
|
132
|
+
"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>] [--project <name|id>] [--parent <id>] [--due YYYY-MM-DD|none] [--file <path>|--body-stdin] — only the fields you pass change: a field you leave off is never touched. --due none clears the due date; --assignee none clears the owner. 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
|
+
"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.",
|
|
114
135
|
"comment <id> — posts a comment. The body is never taken on the command line: `echo \"shipped\" | cockpit issue comment BLI-3654`.",
|
|
115
136
|
"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.",
|
|
116
137
|
"mutation-status <request-uuid> — check your own local commit and mirror delivery after a lost response; no receipt means unknown_check_again.",
|
|
117
138
|
"history <id> [--limit <n>] [--cursor <c>] — every recorded state move and reassignment, oldest first; --json carries the same page fields as list.",
|
|
118
139
|
"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 }] }.",
|
|
119
|
-
"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 }.",
|
|
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.",
|
|
141
|
+
...VIEWS_HELP_LINES,
|
|
142
|
+
...TEMPLATES_HELP_LINES,
|
|
143
|
+
...CYCLES_HELP_LINES,
|
|
144
|
+
...INBOX_HELP_LINES,
|
|
145
|
+
...WORK_NOTIFY_HELP_LINES,
|
|
120
146
|
"A project may be named instead of id'd; the name is matched exactly (case-insensitively) against `cockpit project list`.",
|
|
121
147
|
"--json writes one machine-readable object to stdout; every reason and receipt line stays on stderr.",
|
|
122
148
|
"A refusal keeps Tower's own reason label — needs_rls_client, issue_not_found_or_unreadable, issue_not_writable, invalid_state, comment_too_long, and so on.",
|
|
@@ -153,7 +179,7 @@ export const TOWER_COMMAND_HELP = [
|
|
|
153
179
|
[
|
|
154
180
|
"cal",
|
|
155
181
|
[
|
|
156
|
-
'Usage: cockpit cal [event <id>|export <id>|today|week|conflicts|calendar-change <id> --color <hex-or-provider>|view-settings|view-settings-change|next|find "<words>"|calendars|add-ical|create|duplicate <event id>|batch|undo [<change id>]|rsvp <event id> --response <accepted|declined|tentative>|change <event id>|cancel <event id> --yes|share <id>|sync <id>|sync-status [<id>]|detach <id>|delete <event id> --yes] [flags]',
|
|
182
|
+
'Usage: cockpit cal [event <id>|export <id>|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>]|suggest --with <label|email> --from <d> --to <d>|availability-share --range <start>/<end> [--link]|availability-revoke <id>|booking-page [--list|--id <id>]|booking-slots <link>|book <link> --start <iso> --name <n> --email <e>|booking-change <manage link> --start <iso>|booking-cancel <manage link>|next|find "<words>"|calendars|add-ical|create [--recheck]|duplicate <event id>|batch|undo [<change id>]|rsvp <event id> --response <accepted|declined|tentative>|change <event id>|cancel <event id> --yes|share <id>|sync <id>|sync-status [<id>]|detach <id>|delete <event id> --yes|remind|join [--event <id> --occurrence <iso>] [--print]|reminders [--on|--off] [--lead <min>] [--sound on|off]|snooze <event id> --occurrence <iso> --minutes <5|10|15>|dismiss <event id> --occurrence <iso>|links <event id> [--occurrence <iso>] [--add doc:<id>|note:<id> [--series]] [--remove <link id>]|agenda [--from <d> --to <d>]] [flags]',
|
|
157
183
|
"",
|
|
158
184
|
"Every calendar you attached, in one place — plus any calendar a colleague marked shared.",
|
|
159
185
|
"today [--offset 1] [--tz <zone>] — what is on today, in YOUR zone (this machine's, unless --tz says otherwise). --offset 1 is tomorrow, -1 yesterday.",
|
|
@@ -163,7 +189,7 @@ export const TOWER_COMMAND_HELP = [
|
|
|
163
189
|
"conflicts [--from <iso> --to <iso>] [--tz <IANA>] — own-account double-bookings with duplicate invitation merging, checked calendars and age. Visible calendars only by default; --all includes hidden calendars. All-day and free events never conflict.",
|
|
164
190
|
"event <id> [--occurrence-start <iso>] [--tz <zone>] [--json] — exact occurrence details, human recurrence, named guests/RSVP, organizer, source zone, HTTPS Join and edit capability. The selector must match a real occurrence; cancelled/missing instances are refused.",
|
|
165
191
|
"export <id> [--occurrence-start <iso>] [--json] — iCalendar text to stdout; redirect to event.ics. Recurring exports include the complete series and exceptions, keeping source zone/recurrence. No calendar credentials.",
|
|
166
|
-
"calendar-change <id> --color <hex|provider> — persistent owner colour override, or reset to provider colour. Also accepts stdin JSON {color_override, selected}. History is retained. view-settings [--json] reads your saved view settings (default view, N days, week start, weekends, density, up to 3 extra time zones) plus the calendar actions and keyboard shortcuts; never saved means the defaults, said so. view-settings-change [--view day|week|days|month] [--days 2-9] [--week-start monday|sunday] [--hide-weekends on|off] [--density normal|compact] [--zone <IANA>]... changes only what you name; --zone repeats (up to 3), --zone none clears; stdin JSON like {\"day_count\":4} works too.",
|
|
192
|
+
"calendar-change <id> --color <hex|provider> — persistent owner colour override, or reset to provider colour. Also accepts stdin JSON {color_override, selected}. History is retained. view-settings [--json] reads your saved view settings (default view, N days, week start, weekends, density, up to 3 extra time zones) plus the calendar actions and keyboard shortcuts; never saved means the defaults, said so. view-settings-change [--view day|week|days|month] [--days 2-9] [--week-start monday|sunday] [--hide-weekends on|off] [--density normal|compact] [--zone <IANA>]... changes only what you name; --zone repeats (up to 3), --zone none clears; stdin JSON like {\"day_count\":4} works too. people <label> [--json] — exact team search: a display name or alias (any case) or an email; an address nobody on the team owns comes back as typed. No fuzzy, prefix or nickname match; an ambiguous label lists every match. availability --with <label|email> [--with ...] --from <d> --to <d> [--tz <IANA>] [--account <id>] [--json] — when people are BUSY, start and end only, asked of Google with your own grant (up to 100 people, 31 days). A person Google will not answer for is unknown, never free. Stdin JSON {with|emails, from, to, tz, account_id} works too. overlays — the people pinned on your calendar (at most 10). overlays --pin <label|email> pins one, overlays --remove <email> drops one; history is kept. The web calendar shows pinned people's busy time. suggest --with <label|email> [--with ...] --from <d> --to <d> [--duration 30] [--hours 09:00-17:00] [--weekends] [--tz <IANA>] [--account <id>] [--limit <n>] [--json] — ranked common free slots for several people (up to 10 shown, 50 max; 15 to 480 minutes): everyone free first, then outside someone's local hours, then slots with an unknown person (never shown as free); Tower defaults (30 minutes, 09:00 to 17:00 your zone, no weekends) are named when used; stdin JSON works. create --recheck re-reads everyone's busy time just before writing and refuses with the reason (slot_taken, slot_unconfirmed) instead of double-booking. availability-share --range <start>/<end> [--range ...] [--duration 30] [--tz <IANA>] [--text|--link] [--expires <iso>] [--location <where>] [--meet] [--single-use] [--allow-conflicts] [--label <s>] [--json] offers your free time inside the ranges (a time without an offset is read in --tz; your own busy time is skipped unless --allow-conflicts): --text (default) prints words to paste, stores nothing, names the zone and the time it was made; --link stores a revocable link (14 days by default, 90 at most) whose times are rechecked on every open and prints the URL ONCE; a repeat of the same --key gives no second link. --list shows your links; --id <id> with --expires or --label changes one; availability-revoke <id> turns one off. Stdin JSON works. If your own calendars could not be fully read it says so. booking-page --title <t> --weekly mon/09:00-12:00 [--weekly ...] [--duration 30] [--buffer-before 0] [--buffer-after 0] [--notice 240] [--window 14] [--meet] [--location <s>] [--single-use] [--tz <IANA>] makes a public booking page on your own Google calendar (30 minutes, no buffers, 4 hours notice, 14 days by default; up to 21 weekly ranges) and prints its link ONCE; --list shows pages and upcoming bookings; --id <id> with --rotate, --close|--open or any field above changes one; stdin JSON works. A guest (no Tower login needed) runs booking-slots <link>, book <link> --start <slot start> --name <n> --email <e> [--note <s>] (3 upcoming bookings per email per page), booking-change <manage link> --start <iso> (the old manage link stops working; the new one shows once) and booking-cancel <manage link>; the manage link expires when the meeting starts. Laptop reminders (slice 11): reminders shows or sets your switch (--on|--off, --lead 0 to 60 minutes, default 10, --sound on|off); they are OFF until you turn them on. Once on, a scheduled `cockpit cal remind` tick (every minute, installed with the sync agent) shows one TOWER banner per meeting per lead time across all your laptops, never one for a meeting you declined or that was cancelled, and records a banner more than 5 minutes late as missed. join opens the link of the meeting running now or starting within 15 minutes (or --event <id> --occurrence <iso>); --print only prints it; a missing or non-https link is refused by name. snooze <event id> --occurrence <iso> --minutes 5|10|15 and dismiss <event id> --occurrence <iso> change one nudge. next leaves out meetings you declined (the door takes ?declined=1 to keep them). Event links (slice 12): links <event id> [--occurrence <iso>] lists YOUR docs and meeting notes linked to that occurrence plus its series, each read again for you (a doc or note you can no longer open prints as unavailable, no title), a note showing its status and recording, alternate and missed counts, and suggests notes the bot recorded for exactly this event (never by title); --add doc:<id>|note:<id> --occurrence <iso> (or --series for every occurrence) attaches one you can read now (20 per event; a repeat is already_linked); --remove <link id> removes it and the history keeps it. A link is private to you and gives attendees no access. agenda [--from <d> --to <d>] [--tz <IANA>] [--json] lists events plus tickets due in the window as DUE lines (all-day markers, never busy time, never sent to Google), with how many rows were read.",
|
|
167
193
|
"calendars — every calendar, its provider (google_oauth or ical_url), whether it is shared, and when it last synced. The ids other verbs take are on the last line.",
|
|
168
194
|
"add-ical [--name \"<what to call it>\"] — attaches a personal calendar by its SECRET iCal ADDRESS, read from stdin. There is no flag for it:",
|
|
169
195
|
' macOS: printf "%s" "https://calendar.google.com/calendar/ical/…/basic.ics" | cockpit cal add-ical --name "Personal"',
|
|
@@ -189,17 +215,7 @@ export const TOWER_COMMAND_HELP = [
|
|
|
189
215
|
"The command uses the existing paired device identity. Run `cockpit login` first if this machine is not paired.",
|
|
190
216
|
],
|
|
191
217
|
],
|
|
192
|
-
|
|
193
|
-
"project",
|
|
194
|
-
[
|
|
195
|
-
"Usage: cockpit project [list] [--archived] [--limit <n>] [--cursor <c>] [--json]",
|
|
196
|
-
"",
|
|
197
|
-
"The projects issues are filed under. Bare `cockpit project` lists them.",
|
|
198
|
-
"list [--archived] [--limit <n>] [--cursor <c>] — id, active/archived, name. --archived includes archived projects. A page is at most 500; when more exist the output (and --json: next_cursor, has_more, coverage, requested, returned, unique) carries the cursor to pass back as --cursor.",
|
|
199
|
-
"There is no create/update/delete verb: `GET /api/work/projects` is the whole of Tower's project door today.",
|
|
200
|
-
"--json writes one machine-readable object to stdout; every reason and receipt line stays on stderr.",
|
|
201
|
-
],
|
|
202
|
-
],
|
|
218
|
+
PROJECT_HELP,
|
|
203
219
|
[
|
|
204
220
|
"speed",
|
|
205
221
|
[
|
|
@@ -265,7 +281,7 @@ export const TOWER_COMMAND_HELP = [
|
|
|
265
281
|
"--categories prints the list Tower offers THIS account, in the order the browser's Command-K",
|
|
266
282
|
"chips use, so the names are never a guess; an unknown --kind is refused by the door with that list.",
|
|
267
283
|
"Omit --kind and every category is searched.",
|
|
268
|
-
"--limit caps the number of results (default 20, maximum 50).",
|
|
284
|
+
"--limit caps the number of results (default 20, maximum 50). Document filters (--by, --under, --since, --until, --timezone, --sort, --title-only, --word) work only with --kind doc and mean what they do in `cockpit docs search`; the answer says degraded.doc = filtered_lexical_only. Message filters (--from, --in, --since, --until, --timezone, --replies, --top-level, --has-file) work only with --kind msg and mean what they do in `cockpit msg search`; any other --kind is refused msg_filters_need_msg_kind. Filtered messages come from keyword matching only (the answer says filtered_lexical_only), newest first and not ranked; the words may be left out. Page them with `cockpit msg search --before <cursor>`. Usage: cockpit search [\"<words>\"] --kind msg [--from <email|me>]... [--in <channel>] [--since YYYY-MM-DD] [--until YYYY-MM-DD] [--timezone <IANA>] [--replies|--top-level] [--has-file].",
|
|
269
285
|
"--json writes the door's whole answer to stdout, hits, per-kind counts, failures and all.",
|
|
270
286
|
"",
|
|
271
287
|
"Every result is scoped by what YOU may read: most corpora (and Careers always) are searched on",
|
|
@@ -23,6 +23,7 @@ import { TOWER_OPS_COMMAND_HELP } from "./local-help-commands-tower-ops.js";
|
|
|
23
23
|
/** One manual per collector command; the Tower nouns are spread in from their own file. */
|
|
24
24
|
const COMMAND_HELP = new Map([
|
|
25
25
|
["glossary", ["Usage: cockpit glossary list|add <wrong> --to <name>|remove <wrong> [--json]", "Quote phrases. Removing a form prevents future corrections of it."]],
|
|
26
|
+
["push", ["Usage: cockpit push status | test [--dashboard-url <url>] [--json]", "Phone notifications. status lists your saved devices and whether the server has its push keys; test sends a test push to every saved device. Turn a phone on from Settings on the phone."]],
|
|
26
27
|
["spend", ["Usage: cockpit spend memory [--days 1|7|30] [--dashboard-url <url>] [--json]", "Spend by memory operation (write, recall, dream, update_forget, backfill, label, exam, predictor, people, who, intentions, salience, chat, other), ranked top first with list and real dollars, share, trend against the previous window, and how much of the real OpenAI bill the ledger explains. What it does not explain is shown as untagged. A super_admin reads it."]],
|
|
27
28
|
["roots", ["Usage: cockpit roots add <dir> [--allow-home-root] [--json]", "Approves a work directory, saves an approval receipt, and adds it to collection. Run cockpit doctor afterward."]],
|
|
28
29
|
// BLI-3709: the Tower nouns live next door, split along the same seam the
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** `cockpit ticket cycles` help (BLI-21809, P12), one line a verb, joined into the `issue` help. */
|
|
2
|
+
export const CYCLES_HELP_LINES = [
|
|
3
|
+
"cycles [list] [--phase upcoming|active|ended|closed] [--limit <n>] [--cursor <c>] — native cycles, dates shown with their timezone. --json shape: { cycles: [{ id, name, description, starts_on, ends_on, timezone, starts_at, ends_at, phase, closed_at, linear_cycle_id, updated_at }], next_cursor, has_more, coverage, requested, returned, unique }.",
|
|
4
|
+
"cycles show <id|name> — one cycle, its factual progress (done, canceled, open, points and how many tickets carry an estimate), the imported-but-unassigned count when known, and its tickets. A name is exact and case-insensitive; two cycles with one name are cycle_ref_ambiguous and the message lists their ids (use an id). --json shape: { cycle, progress, imported_unassigned, issues, next_cursor, has_more, coverage, href }.",
|
|
5
|
+
"cycles create --name \"<n>\" --starts YYYY-MM-DD --ends YYYY-MM-DD [--timezone <IANA zone>] [--description <text|->] [--request-id <uuid>] — makes a cycle. `--description -` reads the text from stdin. The same --request-id returns the same cycle.",
|
|
6
|
+
"cycles update <id|name> [--name <n>] [--starts <day>] [--ends <day>] [--timezone <zone>] [--description <text|-|none>] [--expected-updated-at <ts>] [--request-id <uuid>] — changes only the fields you give. `cycles update <ref> --close` or `--reopen` travels alone: a closed cycle refuses new tickets (cycle_closed). A stale --expected-updated-at is revision_conflict.",
|
|
7
|
+
"cycles map <linear-cycle-id> [--request-id <uuid>] — links an imported Linear cycle to a native one so it is never merged by name. It never moves a ticket; assign tickets with `ticket update <id> --cycle <name|id>`.",
|
|
8
|
+
"ticket create|update --cycle <name|id> — puts the ticket in a cycle; `update --cycle none` takes it out. Refusals: cycle_closed, cycle_ref_ambiguous, cycle_not_found_or_unreadable.",
|
|
9
|
+
];
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `cockpit docs files|file` lines of the docs `--help` (BLI-21786), split from `local-help-commands-tower.ts`
|
|
3
|
+
* to keep that file under the readability ratchet.
|
|
4
|
+
*/
|
|
5
|
+
export const DOCS_FILES_HELP = [
|
|
6
|
+
"files <id|slug> — the page's files, newest first (a page keeps its files: png, jpeg, gif, webp inline; pdf, txt, md, csv, json, zip, svg as downloads). Anyone who can read the page lists them; a page you cannot read is document_not_found_or_unreadable. Shows `can_edit` for you, and `coverage` (complete, or capped at 100). --json shape: { ok, document_id, files: [{ id, name, media_type, byte_size, alt_text, state, image, href, markdown, created_at }], coverage, returned, can_edit }.",
|
|
7
|
+
"file upload <id|slug> <path> [--alt <text>] — attaches a local file to a page you may edit (at most 25 MB, 26,214,400 bytes): reserve, PUT the bytes straight to storage, finish. Prints the Markdown to paste into the body (`` for an image, `[name](/api/docs/attachments/<id>/download)` otherwise); it edits NO body, so add the line with `docs update`. Labels, verbatim: attachment_too_large, attachment_type_refused, attachment_write_refused, storage_unreachable, attachment_not_uploaded. --json shape: { ok, file: { id, state, markdown, href, ... } }.",
|
|
8
|
+
"file show <fileId> | file remove <fileId> — the descriptor, or a soft remove (the row becomes `removed`, the bytes are kept, a reader of the page sees \"This file was removed\"). Remove needs edit rights on the page: a plain reader gets attachment_write_refused. A file on a page you cannot read is attachment_not_found_or_unreadable. Removing twice answers the same.",
|
|
9
|
+
"file download <fileId> [--out <path>] — fetches the bytes through a 60-second signed link that Tower checks against the page's access every time. Never overwrites: an existing path is out_exists. Prints the path and size. --json shape: { ok, file, path, bytes }.",
|
|
10
|
+
];
|