@bli-cockpit/cli 0.2.242 → 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 +222 -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-auth.js +1 -1
- package/dist/commands/local-help-commands-tower-ops.js +2 -0
- package/dist/commands/local-help-commands-tower.js +47 -31
- 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 +2 -2
- 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-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 +62 -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/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
|
@@ -35,6 +35,9 @@ export async function createIssue(command, door) {
|
|
|
35
35
|
...(command.priority === undefined ? {} : { priority: command.priority }),
|
|
36
36
|
...(command.estimate === undefined ? {} : { estimate: command.estimate }),
|
|
37
37
|
...(command.assignee ? { assignee_id: command.assignee } : {}),
|
|
38
|
+
...(command.startState ? { state: command.startState } : {}),
|
|
39
|
+
...(command.due ? { due_date: command.due } : {}),
|
|
40
|
+
...(command.cycle ? { cycle: command.cycle } : {}),
|
|
38
41
|
},
|
|
39
42
|
});
|
|
40
43
|
if (!answer.ok)
|
|
@@ -60,7 +63,7 @@ export async function updateIssue(command, door) {
|
|
|
60
63
|
description = body.text;
|
|
61
64
|
}
|
|
62
65
|
if (!changesAnything(command, description, refs)) {
|
|
63
|
-
return failAgentDoor(door, TAG, "invalid_body", "issue update needs at least one of --title, --priority, --size, --assignee, --project, --parent, or a description on --body-stdin/--file.");
|
|
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.");
|
|
64
67
|
}
|
|
65
68
|
const requestId = command.requestId ?? randomUUID();
|
|
66
69
|
writeLine(door.io.stderr, `${TAG} request ${JSON.stringify({ request_id: requestId, expected_revision: command.expectedRevision })}`);
|
|
@@ -74,9 +77,11 @@ export async function updateIssue(command, door) {
|
|
|
74
77
|
...(description !== undefined ? { description } : {}),
|
|
75
78
|
...(command.priority === undefined ? {} : { priority: command.priority }),
|
|
76
79
|
...(command.estimate === undefined ? {} : { estimate: command.estimate }),
|
|
77
|
-
...(command.assignee ? { assignee_id: command.assignee } : {}),
|
|
80
|
+
...(command.assignee ? { assignee_id: command.assignee === "none" ? null : command.assignee } : {}),
|
|
81
|
+
...(command.due !== undefined ? { due_date: command.due } : {}),
|
|
78
82
|
...(refs.projectId ? { project_id: refs.projectId } : {}),
|
|
79
83
|
...(refs.parentId ? { parent_id: refs.parentId } : {}),
|
|
84
|
+
...(command.cycle === undefined ? {} : { cycle: command.cycle }),
|
|
80
85
|
},
|
|
81
86
|
});
|
|
82
87
|
if (!answer.ok)
|
|
@@ -137,8 +142,34 @@ export async function commentIssue(command, door) {
|
|
|
137
142
|
writeLine(door.io.stdout, `Commented on ${ref} (${comment?.id ?? ""}).`);
|
|
138
143
|
return 0;
|
|
139
144
|
}
|
|
145
|
+
/** `ticket label <id> --add <name> --remove <name>`: one receipt, one history row (BLI-21422). */
|
|
146
|
+
export async function labelIssue(command, door) {
|
|
147
|
+
const ref = command.issueRef ?? "";
|
|
148
|
+
const requestId = command.requestId ?? randomUUID();
|
|
149
|
+
writeLine(door.io.stderr, `${TAG} request ${JSON.stringify({ request_id: requestId, expected_revision: command.expectedRevision })}`);
|
|
150
|
+
const answer = await askTicketWrite(door, {
|
|
151
|
+
path: `/api/work/issues/${encodeURIComponent(ref)}/labels`,
|
|
152
|
+
method: "POST",
|
|
153
|
+
label: "issue label",
|
|
154
|
+
timeoutMs: WRITE_DEADLINE_MS,
|
|
155
|
+
body: { request_id: requestId, expected_revision: command.expectedRevision,
|
|
156
|
+
...(command.addLabels?.length ? { add: command.addLabels } : {}),
|
|
157
|
+
...(command.removeLabels?.length ? { remove: command.removeLabels } : {}),
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
if (!answer.ok)
|
|
161
|
+
return refuseWrite(door, answer, requestId);
|
|
162
|
+
const body = answer.body;
|
|
163
|
+
const names = (body.labels ?? []).map((l) => l.name);
|
|
164
|
+
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
|
+
writeLine(door.io.stderr, `${TAG} local commit receipt ${JSON.stringify(answer.body.mutation ?? { outcome: "unknown_check_again", request_id: requestId })}`);
|
|
166
|
+
if (door.json)
|
|
167
|
+
return emitAgentDoor(door, { ok: true, issue: maybeWithSizeLabel(body.issue), labels: body.labels ?? [], mutation: answer.body.mutation });
|
|
168
|
+
writeLine(door.io.stdout, `${body.issue?.identifier ?? ref} labels: ${names.length ? names.join(", ") : "none"}.`);
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
140
171
|
/** A truncated or missing response body cannot be called a successful ticket write. */
|
|
141
|
-
async function askTicketWrite(door, options) {
|
|
172
|
+
export async function askTicketWrite(door, options) {
|
|
142
173
|
try {
|
|
143
174
|
const answer = await askAgentDoor(door, options);
|
|
144
175
|
if (!answer.ok)
|
|
@@ -161,11 +192,13 @@ function changesAnything(command, description, refs) {
|
|
|
161
192
|
|| command.priority !== undefined
|
|
162
193
|
|| command.estimate !== undefined
|
|
163
194
|
|| command.assignee !== undefined
|
|
195
|
+
|| command.due !== undefined
|
|
164
196
|
|| refs.projectId !== undefined
|
|
165
|
-
|| refs.parentId !== undefined
|
|
197
|
+
|| refs.parentId !== undefined
|
|
198
|
+
|| command.cycle !== undefined;
|
|
166
199
|
}
|
|
167
200
|
/** Says where the local commit stands, then fails with the door's own reason. */
|
|
168
|
-
function refuseWrite(door, answer, requestId) {
|
|
201
|
+
export function refuseWrite(door, answer, requestId) {
|
|
169
202
|
writeLine(door.io.stderr, `${TAG} check local commit with cockpit ticket mutation-status ${requestId}; retry unchanged input with --request-id ${requestId}.`);
|
|
170
203
|
const mutation = answer.body?.mutation ?? { request_id: requestId, outcome: "unknown_check_again" };
|
|
171
204
|
if (door.json) {
|
package/dist/commands/issue.js
CHANGED
|
@@ -33,7 +33,14 @@ 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
35
|
import { READ_DEADLINE_MS, TAG, resolveProjectId, sizeForEstimate, withSizeLabel, } from "./issue-contracts.js";
|
|
36
|
-
import { commentIssue, createIssue, moveIssue, updateIssue } from "./issue-write.js";
|
|
36
|
+
import { commentIssue, createIssue, labelIssue, moveIssue, updateIssue } from "./issue-write.js";
|
|
37
|
+
import { batchIssues } from "./issue-batch.js";
|
|
38
|
+
import { runInbox } from "./inbox.js";
|
|
39
|
+
import { runWorkNotify } from "./work-notify.js";
|
|
40
|
+
import { runViews } from "./views.js";
|
|
41
|
+
import { runTemplates } from "./templates.js";
|
|
42
|
+
import { runCycles } from "./cycles.js";
|
|
43
|
+
import { relateIssue, showRelations, unrelateIssue } from "./issue-relations.js";
|
|
37
44
|
import { listIssueVersions, restoreIssueVersion } from "./issue-versions.js";
|
|
38
45
|
export async function runIssue(command, io) {
|
|
39
46
|
const door = await openAgentDoor("issue", command, io);
|
|
@@ -62,12 +69,36 @@ export async function runIssue(command, io) {
|
|
|
62
69
|
return moveIssue(command, door);
|
|
63
70
|
case "comment":
|
|
64
71
|
return commentIssue(command, door);
|
|
72
|
+
case "label":
|
|
73
|
+
return labelIssue(command, door);
|
|
74
|
+
case "relations":
|
|
75
|
+
return showRelations(command, door);
|
|
76
|
+
case "relate":
|
|
77
|
+
return relateIssue(command, door);
|
|
78
|
+
case "unrelate":
|
|
79
|
+
return unrelateIssue(command, door);
|
|
65
80
|
case "history":
|
|
66
81
|
return issueHistory(command, door);
|
|
67
82
|
case "versions":
|
|
68
83
|
return listIssueVersions(command, door);
|
|
69
84
|
case "restore":
|
|
70
85
|
return restoreIssueVersion(command, door);
|
|
86
|
+
case "batch":
|
|
87
|
+
return batchIssues(command, door);
|
|
88
|
+
case "views":
|
|
89
|
+
return runViews(command, io, door);
|
|
90
|
+
case "templates":
|
|
91
|
+
return runTemplates(command, io, door);
|
|
92
|
+
case "cycles":
|
|
93
|
+
return runCycles(command, io, door);
|
|
94
|
+
case "inbox":
|
|
95
|
+
case "read":
|
|
96
|
+
case "unread":
|
|
97
|
+
case "subscribe":
|
|
98
|
+
case "unsubscribe":
|
|
99
|
+
return runInbox(command, io, door);
|
|
100
|
+
case "notifications":
|
|
101
|
+
return runWorkNotify(command, io, door);
|
|
71
102
|
}
|
|
72
103
|
}
|
|
73
104
|
function issueLine(issue, directory) {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* cockpit approvals [list] [--status open|done|pending|executing|executed|failed|rejected] [--limit <n>] [--before <cursor>] [--json]
|
|
5
5
|
* cockpit approvals show <id>
|
|
6
6
|
* cockpit approvals create (the card as JSON on stdin)
|
|
7
|
-
* cockpit approvals approve <id> [--edit] (--edit: the edit as JSON on stdin)
|
|
7
|
+
* cockpit approvals approve <id> [--edit] [--checked-sent sent|not-sent] (--edit: the edit as JSON on stdin)
|
|
8
8
|
* cockpit approvals edit <id> [--approve] (the edit as JSON on stdin)
|
|
9
9
|
* cockpit approvals reject <id>
|
|
10
10
|
* cockpit approvals rules [--active true|false] [--limit <n>]
|
|
@@ -25,19 +25,19 @@ const FLAGS_BY_ACTION = {
|
|
|
25
25
|
list: ["--status", "--limit", "--before"],
|
|
26
26
|
show: [],
|
|
27
27
|
create: [],
|
|
28
|
-
approve: ["--edit"],
|
|
28
|
+
approve: ["--edit", "--checked-sent"],
|
|
29
29
|
edit: ["--approve"],
|
|
30
30
|
reject: [],
|
|
31
31
|
rules: ["--active", "--limit"],
|
|
32
32
|
"rules-create": ["--any-audience", "--label"],
|
|
33
33
|
"rules-revoke": ["--reason"],
|
|
34
34
|
};
|
|
35
|
-
const OWN_FLAGS = ["--status", "--limit", "--before", "--edit", "--approve", "--active", "--any-audience", "--label", "--reason"];
|
|
35
|
+
const OWN_FLAGS = ["--status", "--limit", "--before", "--edit", "--checked-sent", "--approve", "--active", "--any-audience", "--label", "--reason"];
|
|
36
36
|
const COMMON_FLAGS = ["--home", "--dashboard-url", "--json"];
|
|
37
37
|
export function parseApprovalsArgs(args) {
|
|
38
38
|
const values = parseNamedArgs(args, {
|
|
39
39
|
allowedFlags: [...OWN_FLAGS, ...COMMON_FLAGS],
|
|
40
|
-
valueFlags: ["--status", "--limit", "--before", "--active", "--label", "--reason", "--home", "--dashboard-url"],
|
|
40
|
+
valueFlags: ["--status", "--limit", "--before", "--checked-sent", "--active", "--label", "--reason", "--home", "--dashboard-url"],
|
|
41
41
|
});
|
|
42
42
|
const { action, words } = readAction(values.positionals);
|
|
43
43
|
const given = [...values.flags.keys(), ...values.booleans].filter(flag => OWN_FLAGS.includes(flag));
|
|
@@ -60,6 +60,7 @@ export function parseApprovalsArgs(args) {
|
|
|
60
60
|
if (limit !== undefined && limit > LIST_MAX_LIMIT)
|
|
61
61
|
throw new Error(`--limit is at most ${LIST_MAX_LIMIT}.`);
|
|
62
62
|
const active = readActive(values.flags.get("--active"));
|
|
63
|
+
const deliveryChecked = readCheckedSent(values.flags.get("--checked-sent"));
|
|
63
64
|
return {
|
|
64
65
|
kind: "approvals",
|
|
65
66
|
action,
|
|
@@ -69,6 +70,7 @@ export function parseApprovalsArgs(args) {
|
|
|
69
70
|
...(optionalNonEmpty(values.flags.get("--before")) ? { before: optionalNonEmpty(values.flags.get("--before")) } : {}),
|
|
70
71
|
...(values.booleans.has("--edit") ? { withEdit: true } : {}),
|
|
71
72
|
...(values.booleans.has("--approve") ? { thenApprove: true } : {}),
|
|
73
|
+
...(deliveryChecked ? { deliveryChecked } : {}),
|
|
72
74
|
...(active !== undefined ? { active } : {}),
|
|
73
75
|
...(values.booleans.has("--any-audience") ? { anyAudience: true } : {}),
|
|
74
76
|
...(optionalNonEmpty(values.flags.get("--label")) ? { label: optionalNonEmpty(values.flags.get("--label")) } : {}),
|
|
@@ -93,6 +95,16 @@ function readAction(positionals) {
|
|
|
93
95
|
return { action: first, words: first };
|
|
94
96
|
throw new Error(ONE_VERB);
|
|
95
97
|
}
|
|
98
|
+
/** BLI-21954: what the person found where it was going (Sent, or the channel), on a card whose delivery is unknown. Typed with a hyphen, sent as the door's word. */
|
|
99
|
+
function readCheckedSent(raw) {
|
|
100
|
+
if (raw === undefined)
|
|
101
|
+
return undefined;
|
|
102
|
+
if (raw === "sent")
|
|
103
|
+
return "sent";
|
|
104
|
+
if (raw === "not-sent" || raw === "not_sent")
|
|
105
|
+
return "not_sent";
|
|
106
|
+
throw new Error("--checked-sent wants sent (it went out: Sent for an email, the channel for a message; mark it done) or not-sent (it did not: send or post it now).");
|
|
107
|
+
}
|
|
96
108
|
function readActive(raw) {
|
|
97
109
|
if (raw === undefined)
|
|
98
110
|
return undefined;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BLI-21556 `cockpit cal booking-page|booking-slots|book|booking-change|booking-cancel`: the flags, read into a plain
|
|
3
|
+
* shape. Shape only; the door owns every limit (ranges, duration, buffers, notice, window, the guest cap).
|
|
4
|
+
*/
|
|
5
|
+
import { optionalNonEmpty } from "./local-arg-values.js";
|
|
6
|
+
export const BOOKING_ACTIONS = ["booking-page", "booking-slots", "book", "booking-change", "booking-cancel"];
|
|
7
|
+
/** The guest verbs: they take a page link or token (or a management link or token) and need no device token. */
|
|
8
|
+
export const BOOKING_GUEST_ACTIONS = ["booking-slots", "book", "booking-change", "booking-cancel"];
|
|
9
|
+
export const BOOKING_BOOLEANS = ["--rotate", "--close", "--open"];
|
|
10
|
+
export const BOOKING_VALUE_FLAGS = ["--weekly", "--buffer-before", "--buffer-after", "--notice", "--window", "--start", "--email", "--note"];
|
|
11
|
+
const OWN_FLAGS = [...BOOKING_BOOLEANS, ...BOOKING_VALUE_FLAGS];
|
|
12
|
+
const DAYS = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"];
|
|
13
|
+
/** `mon/09:00-12:00`, `mon,wed/13:00-17:00` or `1/09:00-12:00` -> weekly ranges, or null when it is not that shape. */
|
|
14
|
+
export function parseWeekly(raw) {
|
|
15
|
+
const match = /^([A-Za-z0-9,]+)\/(\d{2}:\d{2})-(\d{2}:\d{2})$/.exec(raw.trim());
|
|
16
|
+
if (!match)
|
|
17
|
+
return null;
|
|
18
|
+
const days = match[1].split(",").map((day) => {
|
|
19
|
+
const lower = day.toLowerCase();
|
|
20
|
+
const named = DAYS.findIndex((name) => lower === name || lower === name + "day" || lower.startsWith(name));
|
|
21
|
+
return named >= 0 ? named + 1 : Number(day);
|
|
22
|
+
});
|
|
23
|
+
if (days.some((day) => !Number.isInteger(day) || day < 1 || day > 7))
|
|
24
|
+
return null;
|
|
25
|
+
return days.map((weekday) => ({ weekday, start: match[2], end: match[3] }));
|
|
26
|
+
}
|
|
27
|
+
function wholeNumber(values, flag) {
|
|
28
|
+
const raw = optionalNonEmpty(values.flags.get(flag));
|
|
29
|
+
if (raw === undefined)
|
|
30
|
+
return undefined;
|
|
31
|
+
if (!/^\d+$/.test(raw))
|
|
32
|
+
throw new Error(`${flag} takes a whole number.`);
|
|
33
|
+
return Number(raw);
|
|
34
|
+
}
|
|
35
|
+
export function readBookingFlags(action, values) {
|
|
36
|
+
if (!BOOKING_ACTIONS.includes(action)) {
|
|
37
|
+
for (const flag of OWN_FLAGS)
|
|
38
|
+
if (values.flags.has(flag) || values.booleans.has(flag))
|
|
39
|
+
throw new Error(`cal ${action} does not take ${flag}; it belongs to the booking verbs.`);
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
const weekly = (values.flagValues.get("--weekly") ?? []).map((raw) => raw.trim()).filter(Boolean);
|
|
43
|
+
for (const raw of weekly)
|
|
44
|
+
if (!parseWeekly(raw))
|
|
45
|
+
throw new Error("--weekly takes <day>/<HH:MM>-<HH:MM>, like mon/09:00-12:00 or mon,wed/13:00-17:00.");
|
|
46
|
+
const start = optionalNonEmpty(values.flags.get("--start"));
|
|
47
|
+
const email = optionalNonEmpty(values.flags.get("--email"));
|
|
48
|
+
if (action === "book" && (!start || !optionalNonEmpty(values.flags.get("--name")) || !email))
|
|
49
|
+
throw new Error("cal book needs --start <slot start>, --name \"<you>\" and --email <you@x.com>.");
|
|
50
|
+
if (action === "booking-change" && !start)
|
|
51
|
+
throw new Error("cal booking-change needs --start <new slot start>.");
|
|
52
|
+
if (action !== "booking-page") {
|
|
53
|
+
for (const flag of ["--weekly", "--buffer-before", "--buffer-after", "--notice", "--window", "--rotate", "--close", "--open"])
|
|
54
|
+
if (values.flags.has(flag) || values.booleans.has(flag))
|
|
55
|
+
throw new Error(`cal ${action} does not take ${flag}; it belongs to cal booking-page.`);
|
|
56
|
+
return { ...(start ? { bookingStart: start } : {}), ...(email ? { guestEmail: email } : {}), ...(optionalNonEmpty(values.flags.get("--note")) ? { guestNote: optionalNonEmpty(values.flags.get("--note")) } : {}) };
|
|
57
|
+
}
|
|
58
|
+
if (values.booleans.has("--close") && values.booleans.has("--open"))
|
|
59
|
+
throw new Error("cal booking-page takes --close or --open, not both.");
|
|
60
|
+
const id = optionalNonEmpty(values.flags.get("--id"));
|
|
61
|
+
const list = values.booleans.has("--list");
|
|
62
|
+
if (list && id)
|
|
63
|
+
throw new Error("cal booking-page takes --list or --id, not both.");
|
|
64
|
+
const edits = values.booleans.has("--rotate") || values.booleans.has("--close") || values.booleans.has("--open");
|
|
65
|
+
if (edits && !id)
|
|
66
|
+
throw new Error("--rotate, --close and --open change an existing page: add --id <page id> (from --list).");
|
|
67
|
+
const numbers = {
|
|
68
|
+
bufferBefore: wholeNumber(values, "--buffer-before"), bufferAfter: wholeNumber(values, "--buffer-after"),
|
|
69
|
+
minNotice: wholeNumber(values, "--notice"), windowDays: wholeNumber(values, "--window"),
|
|
70
|
+
};
|
|
71
|
+
return {
|
|
72
|
+
bookingMode: list ? "list" : id ? "change" : "create",
|
|
73
|
+
...(id ? { bookingId: id } : {}),
|
|
74
|
+
...(weekly.length ? { bookingWeekly: weekly } : {}),
|
|
75
|
+
...Object.fromEntries(Object.entries(numbers).filter(([, value]) => value !== undefined)),
|
|
76
|
+
...(values.booleans.has("--rotate") ? { rotate: true } : {}),
|
|
77
|
+
...(values.booleans.has("--close") ? { bookingClosed: true } : values.booleans.has("--open") ? { bookingClosed: false } : {}),
|
|
78
|
+
...(values.booleans.has("--single-use") ? { singleUse: true } : {}),
|
|
79
|
+
...(optionalNonEmpty(values.flags.get("--duration")) ? { duration: wholeNumber(values, "--duration") } : {}),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BLI-21588 `cockpit cal links <event id>` and `cockpit cal agenda`: the flags, read into a plain shape. Shape only;
|
|
3
|
+
* the doors own the limits (20 links per event, the occurrence rules, who may read the target).
|
|
4
|
+
*/
|
|
5
|
+
import { optionalNonEmpty } from "./local-arg-values.js";
|
|
6
|
+
export const LINKS_ACTIONS = ["links", "agenda"];
|
|
7
|
+
/** `links <event id>` names one event first. */
|
|
8
|
+
export const LINKS_SUBJECT_ACTIONS = ["links"];
|
|
9
|
+
export const LINKS_BOOLEANS = ["--series"];
|
|
10
|
+
export const LINKS_VALUE_FLAGS = ["--add"];
|
|
11
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
12
|
+
/** `doc:<uuid>` or `note:<uuid>`; anything else is named, never guessed. */
|
|
13
|
+
export function readLinkTarget(raw) {
|
|
14
|
+
const [kind, id, ...extra] = raw.split(":");
|
|
15
|
+
if ((kind !== "doc" && kind !== "note") || !id || extra.length > 0 || !UUID.test(id)) {
|
|
16
|
+
throw new Error("--add takes doc:<id> or note:<id>, where <id> is the document's or note's uuid.");
|
|
17
|
+
}
|
|
18
|
+
return { kind, id: id.toLowerCase() };
|
|
19
|
+
}
|
|
20
|
+
export function readLinksFlags(action, values) {
|
|
21
|
+
const add = optionalNonEmpty(values.flags.get("--add"));
|
|
22
|
+
const remove = action === "links" ? optionalNonEmpty(values.flags.get("--remove")) : undefined;
|
|
23
|
+
const series = values.booleans.has("--series");
|
|
24
|
+
if (action !== "links") {
|
|
25
|
+
for (const flag of ["--add", "--series"]) {
|
|
26
|
+
if (values.flags.has(flag) || values.booleans.has(flag))
|
|
27
|
+
throw new Error(`cal ${action} does not take ${flag}; it belongs to cal links.`);
|
|
28
|
+
}
|
|
29
|
+
return {};
|
|
30
|
+
}
|
|
31
|
+
if (add && remove)
|
|
32
|
+
throw new Error("cal links takes --add or --remove, not both.");
|
|
33
|
+
if (series && !add)
|
|
34
|
+
throw new Error("--series goes with --add: it links the whole series instead of one occurrence.");
|
|
35
|
+
if (add && !series && !optionalNonEmpty(values.flags.get("--occurrence") ?? values.flags.get("--occurrence-start"))) {
|
|
36
|
+
throw new Error("cal links --add needs --occurrence <the occurrence's ISO start> or --series.");
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
...(add ? { linkAdd: readLinkTarget(add), ...(series ? { linkSeries: true } : {}) } : {}),
|
|
40
|
+
...(remove ? { linkRemove: remove } : {}),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BLI-21564 `cockpit cal remind|join|reminders|snooze|dismiss`: the flags, read into a plain shape. Shape only; the
|
|
3
|
+
* doors own every limit (lead 0 to 60, snooze 5/10/15, the occurrence rules).
|
|
4
|
+
*/
|
|
5
|
+
import { optionalNonEmpty } from "./local-arg-values.js";
|
|
6
|
+
export const REMIND_ACTIONS = ["remind", "join", "reminders", "snooze", "dismiss"];
|
|
7
|
+
/** Verbs that name one event first: `snooze <event id> --occurrence <iso> --minutes 10`. */
|
|
8
|
+
export const REMIND_SUBJECT_ACTIONS = ["snooze", "dismiss"];
|
|
9
|
+
export const REMIND_BOOLEANS = ["--on", "--off", "--print"];
|
|
10
|
+
export const REMIND_VALUE_FLAGS = ["--event", "--lead", "--sound", "--minutes"];
|
|
11
|
+
const OWN_FLAGS = [...REMIND_BOOLEANS, ...REMIND_VALUE_FLAGS];
|
|
12
|
+
function wholeNumber(values, flag) {
|
|
13
|
+
const raw = optionalNonEmpty(values.flags.get(flag));
|
|
14
|
+
if (raw === undefined)
|
|
15
|
+
return undefined;
|
|
16
|
+
if (!/^\d+$/.test(raw))
|
|
17
|
+
throw new Error(`${flag} takes a whole number of minutes.`);
|
|
18
|
+
return Number(raw);
|
|
19
|
+
}
|
|
20
|
+
export function readRemindFlags(action, values) {
|
|
21
|
+
const owns = (flag) => {
|
|
22
|
+
if (flag === "--print" || flag === "--event")
|
|
23
|
+
return action === "join";
|
|
24
|
+
if (flag === "--minutes")
|
|
25
|
+
return action === "snooze";
|
|
26
|
+
return action === "reminders";
|
|
27
|
+
};
|
|
28
|
+
for (const flag of OWN_FLAGS) {
|
|
29
|
+
if ((values.flags.has(flag) || values.booleans.has(flag)) && !owns(flag)) {
|
|
30
|
+
throw new Error(`cal ${action} does not take ${flag}; it belongs to the cal remind verbs (remind, join, reminders, snooze, dismiss).`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const occurrence = optionalNonEmpty(values.flags.get("--occurrence") ?? values.flags.get("--occurrence-start"));
|
|
34
|
+
if (action === "snooze" || action === "dismiss") {
|
|
35
|
+
if (!occurrence)
|
|
36
|
+
throw new Error(`cal ${action} needs --occurrence <the occurrence's ISO start>, as shown by cal next.`);
|
|
37
|
+
if (action === "dismiss")
|
|
38
|
+
return {};
|
|
39
|
+
const minutes = wholeNumber(values, "--minutes");
|
|
40
|
+
if (minutes === undefined || ![5, 10, 15].includes(minutes))
|
|
41
|
+
throw new Error("cal snooze needs --minutes 5, 10 or 15.");
|
|
42
|
+
return { snoozeMinutes: minutes };
|
|
43
|
+
}
|
|
44
|
+
if (action === "join") {
|
|
45
|
+
const event = optionalNonEmpty(values.flags.get("--event"));
|
|
46
|
+
if (event && !occurrence)
|
|
47
|
+
throw new Error("cal join --event needs --occurrence <the occurrence's ISO start>.");
|
|
48
|
+
if (!event && occurrence)
|
|
49
|
+
throw new Error("cal join --occurrence needs --event <event id>.");
|
|
50
|
+
return { ...(event ? { remindEvent: event } : {}), ...(values.booleans.has("--print") ? { printOnly: true } : {}) };
|
|
51
|
+
}
|
|
52
|
+
if (action !== "reminders")
|
|
53
|
+
return {};
|
|
54
|
+
if (values.booleans.has("--on") && values.booleans.has("--off"))
|
|
55
|
+
throw new Error("cal reminders takes --on or --off, not both.");
|
|
56
|
+
const patch = {};
|
|
57
|
+
if (values.booleans.has("--on"))
|
|
58
|
+
patch.enabled = true;
|
|
59
|
+
if (values.booleans.has("--off"))
|
|
60
|
+
patch.enabled = false;
|
|
61
|
+
const lead = wholeNumber(values, "--lead");
|
|
62
|
+
if (lead !== undefined) {
|
|
63
|
+
if (lead > 60)
|
|
64
|
+
throw new Error("--lead takes 0 to 60 minutes.");
|
|
65
|
+
patch.lead_minutes = lead;
|
|
66
|
+
}
|
|
67
|
+
const sound = optionalNonEmpty(values.flags.get("--sound"));
|
|
68
|
+
if (sound !== undefined) {
|
|
69
|
+
if (sound !== "on" && sound !== "off")
|
|
70
|
+
throw new Error("--sound takes on or off.");
|
|
71
|
+
patch.sound = sound === "on";
|
|
72
|
+
}
|
|
73
|
+
return Object.keys(patch).length ? { reminderPatch: patch } : {};
|
|
74
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BLI-21548 `cockpit cal availability-share|availability-revoke`: the flags, read into a plain shape.
|
|
3
|
+
* Shape only; the door owns every limit (ranges, duration, expiry).
|
|
4
|
+
*/
|
|
5
|
+
import { optionalNonEmpty } from "./local-arg-values.js";
|
|
6
|
+
export const SHARE_ACTIONS = ["availability-share", "availability-revoke"];
|
|
7
|
+
export const SHARE_BOOLEANS = ["--list", "--text", "--link", "--single-use", "--allow-conflicts"];
|
|
8
|
+
export const SHARE_VALUE_FLAGS = ["--range", "--expires", "--label", "--id"];
|
|
9
|
+
const SHARE_FLAGS = [...SHARE_BOOLEANS, ...SHARE_VALUE_FLAGS];
|
|
10
|
+
export function readShareFlags(action, values) {
|
|
11
|
+
if (action !== "availability-share") {
|
|
12
|
+
for (const flag of SHARE_FLAGS) {
|
|
13
|
+
if (action === "booking-page" && ["--list", "--id", "--single-use"].includes(flag))
|
|
14
|
+
continue;
|
|
15
|
+
if (values.flags.has(flag) || values.booleans.has(flag))
|
|
16
|
+
throw new Error(`cal ${action} does not take ${flag}; it belongs to cal availability-share.`);
|
|
17
|
+
}
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
const ranges = (values.flagValues.get("--range") ?? []).map((range) => range.trim()).filter(Boolean);
|
|
21
|
+
for (const range of ranges)
|
|
22
|
+
if (range.split("/").length !== 2 || range.split("/").some((end) => !end.trim()))
|
|
23
|
+
throw new Error("--range takes <start>/<end>, like 2026-10-20T10:00/2026-10-20T17:00 (times without an offset are read in --tz).");
|
|
24
|
+
const modes = ["--list", "--text", "--link"].filter((flag) => values.booleans.has(flag));
|
|
25
|
+
if (modes.length > 1)
|
|
26
|
+
throw new Error(`cal availability-share takes one of --list, --text or --link, not ${modes.join(" and ")}.`);
|
|
27
|
+
const id = optionalNonEmpty(values.flags.get("--id"));
|
|
28
|
+
const duration = optionalNonEmpty(values.flags.get("--duration"));
|
|
29
|
+
if (duration !== undefined && !/^\d+$/.test(duration))
|
|
30
|
+
throw new Error("--duration takes a whole number of minutes.");
|
|
31
|
+
return {
|
|
32
|
+
...(ranges.length ? { shareRanges: ranges } : {}),
|
|
33
|
+
shareMode: values.booleans.has("--list") ? "list" : id ? "change" : values.booleans.has("--link") ? "link" : "text",
|
|
34
|
+
...(id ? { shareId: id } : {}),
|
|
35
|
+
...(duration === undefined ? {} : { duration: Number(duration) }),
|
|
36
|
+
...(optionalNonEmpty(values.flags.get("--expires")) ? { shareExpires: optionalNonEmpty(values.flags.get("--expires")) } : {}),
|
|
37
|
+
...(optionalNonEmpty(values.flags.get("--label")) ? { shareLabel: optionalNonEmpty(values.flags.get("--label")) } : {}),
|
|
38
|
+
...(values.booleans.has("--single-use") ? { singleUse: true } : {}),
|
|
39
|
+
...(values.booleans.has("--allow-conflicts") ? { allowConflicts: true } : {}),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -19,8 +19,12 @@
|
|
|
19
19
|
* would be computed in UTC — "today" would end at 5pm in Vancouver.
|
|
20
20
|
*/
|
|
21
21
|
import { optionalNonEmpty, optionalPositiveInteger, optionalUrl, parseNamedArgs, } from "./local-arg-values.js";
|
|
22
|
+
import { BOOKING_ACTIONS, BOOKING_BOOLEANS, BOOKING_GUEST_ACTIONS, BOOKING_VALUE_FLAGS, readBookingFlags } from "./local-args-tower-cal-booking.js";
|
|
23
|
+
import { LINKS_ACTIONS, LINKS_BOOLEANS, LINKS_SUBJECT_ACTIONS, LINKS_VALUE_FLAGS, readLinksFlags } from "./local-args-tower-cal-links.js";
|
|
24
|
+
import { readRemindFlags, REMIND_ACTIONS, REMIND_BOOLEANS, REMIND_SUBJECT_ACTIONS, REMIND_VALUE_FLAGS } from "./local-args-tower-cal-remind.js";
|
|
25
|
+
import { readShareFlags, SHARE_ACTIONS, SHARE_BOOLEANS, SHARE_VALUE_FLAGS } from "./local-args-tower-cal-share.js";
|
|
22
26
|
const CAL_ACTIONS = new Set([
|
|
23
|
-
"event", "export", "duplicate", "batch", "undo", "conflicts", "calendar-change", "view-settings", "view-settings-change",
|
|
27
|
+
"event", "export", "duplicate", "batch", "undo", "conflicts", "calendar-change", "view-settings", "view-settings-change", "people", "availability", "overlays", "suggest", ...SHARE_ACTIONS, ...BOOKING_ACTIONS, ...REMIND_ACTIONS, ...LINKS_ACTIONS,
|
|
24
28
|
"today",
|
|
25
29
|
"week",
|
|
26
30
|
"next",
|
|
@@ -36,7 +40,7 @@ const CAL_ACTIONS = new Set([
|
|
|
36
40
|
"delete",
|
|
37
41
|
]);
|
|
38
42
|
/** Every verb that names ONE thing first — a calendar id, or (delete) an event id. */
|
|
39
|
-
const CAL_ACTIONS_NEEDING_A_SUBJECT = new Set(["event", "export", "duplicate", "calendar-change", "sync", "detach", "share", "delete", "change", "cancel", "rsvp"]);
|
|
43
|
+
const CAL_ACTIONS_NEEDING_A_SUBJECT = new Set([...BOOKING_GUEST_ACTIONS, ...REMIND_SUBJECT_ACTIONS, ...LINKS_SUBJECT_ACTIONS, "availability-revoke", "event", "export", "duplicate", "calendar-change", "sync", "detach", "share", "delete", "change", "cancel", "rsvp"]);
|
|
40
44
|
/** `sync-status [<calendar id>]` — every calendar, or one's. Ticket 10730. */
|
|
41
45
|
const CAL_ACTIONS_WITH_OPTIONAL_SUBJECT = new Set(["sync-status", "undo"]);
|
|
42
46
|
const CAL_ALLOWED_FLAGS = [
|
|
@@ -73,6 +77,8 @@ const CAL_ALLOWED_FLAGS = [
|
|
|
73
77
|
"--reason",
|
|
74
78
|
"--scope", "--occurrence", "--response", "--comment", "--reminder", "--visibility", "--transparency", "--key",
|
|
75
79
|
"--view", "--days", "--week-start", "--hide-weekends", "--density", "--zone",
|
|
80
|
+
"--with", "--pin", "--remove",
|
|
81
|
+
"--duration", "--weekends", "--recheck", ...SHARE_BOOLEANS, ...SHARE_VALUE_FLAGS, ...BOOKING_BOOLEANS, ...BOOKING_VALUE_FLAGS, ...REMIND_BOOLEANS, ...REMIND_VALUE_FLAGS, ...LINKS_BOOLEANS, ...LINKS_VALUE_FLAGS,
|
|
76
82
|
];
|
|
77
83
|
const CAL_VALUE_FLAGS = [
|
|
78
84
|
"--home",
|
|
@@ -98,6 +104,7 @@ const CAL_VALUE_FLAGS = [
|
|
|
98
104
|
"--reason",
|
|
99
105
|
"--scope", "--occurrence", "--response", "--comment", "--reminder", "--visibility", "--transparency", "--key",
|
|
100
106
|
"--view", "--days", "--week-start", "--hide-weekends", "--density", "--zone",
|
|
107
|
+
"--with", "--pin", "--remove", "--duration", ...SHARE_VALUE_FLAGS, ...BOOKING_VALUE_FLAGS, ...REMIND_VALUE_FLAGS, ...LINKS_VALUE_FLAGS,
|
|
101
108
|
];
|
|
102
109
|
export function parseCalArgs(args) {
|
|
103
110
|
const values = parseNamedArgs(args, { allowedFlags: CAL_ALLOWED_FLAGS, valueFlags: CAL_VALUE_FLAGS });
|
|
@@ -111,13 +118,13 @@ export function parseCalArgs(args) {
|
|
|
111
118
|
const limit = optionalPositiveInteger(values.flags.get("--limit"), "--limit");
|
|
112
119
|
const offset = readCalOffset(values);
|
|
113
120
|
const command = buildCalCommand(values, action, subject, query, title, startsAt, endsAt, offset, limit);
|
|
114
|
-
return { ...command, ...readCalEventScope(action, values), ...readViewSettingsPatch(action, values) };
|
|
121
|
+
return { ...command, ...readCalEventScope(action, values), ...readViewSettingsPatch(action, values), ...readPeopleFlags(action, values), ...readShareFlags(action, values), ...readBookingFlags(action, values), ...readRemindFlags(action, values), ...readLinksFlags(action, values) };
|
|
115
122
|
}
|
|
116
123
|
function readCalAction(values) {
|
|
117
124
|
const first = values.positionals[0];
|
|
118
125
|
const action = (first === undefined ? "today" : first);
|
|
119
126
|
if (!CAL_ACTIONS.has(action)) {
|
|
120
|
-
throw new Error(`Unknown cal command: ${first}. Try today, week, conflicts, calendar-change, view-settings, view-settings-change, next, find, calendars, add-ical, create, duplicate, batch, undo, share, detach, sync, sync-status, or delete.`);
|
|
127
|
+
throw new Error(`Unknown cal command: ${first}. Try today, week, conflicts, calendar-change, view-settings, view-settings-change, people, availability, overlays, suggest, availability-share, availability-revoke, booking-page, booking-slots, book, booking-change, booking-cancel, remind, join, reminders, snooze, dismiss, links, agenda, next, find, calendars, add-ical, create, duplicate, batch, undo, share, detach, sync, sync-status, or delete.`);
|
|
121
128
|
}
|
|
122
129
|
return { action, rest: values.positionals.slice(first === undefined ? 0 : 1) };
|
|
123
130
|
}
|
|
@@ -127,9 +134,17 @@ function readCalSubjectOrQuery(action, rest) {
|
|
|
127
134
|
if (CAL_ACTIONS_NEEDING_A_SUBJECT.has(action)) {
|
|
128
135
|
subject = optionalNonEmpty(rest[0]);
|
|
129
136
|
if (!subject) {
|
|
130
|
-
throw new Error(action === "
|
|
131
|
-
?
|
|
132
|
-
:
|
|
137
|
+
throw new Error(action === "availability-revoke"
|
|
138
|
+
? "cal availability-revoke needs a share id — take one from `cockpit cal availability-share --list`."
|
|
139
|
+
: BOOKING_GUEST_ACTIONS.includes(action)
|
|
140
|
+
? `cal ${action} needs the booking link (or its token) the host or Tower gave you.`
|
|
141
|
+
: action === "snooze" || action === "dismiss"
|
|
142
|
+
? `cal ${action} needs an event id and --occurrence — take both from cal next.`
|
|
143
|
+
: action === "links"
|
|
144
|
+
? "cal links needs an event id — take one from a window verb's output (today/week/next/find)."
|
|
145
|
+
: action === "delete" || action === "duplicate" || action === "change" || action === "cancel" || action === "rsvp"
|
|
146
|
+
? `cal ${action} needs an event id — take one from a window verb's output (today/week/next/find).`
|
|
147
|
+
: `cal ${action} needs a calendar id — take one from \`cockpit cal calendars\`.`);
|
|
133
148
|
}
|
|
134
149
|
if (rest.length > 1)
|
|
135
150
|
throw new Error(`cal ${action} takes one id, not ${rest.length}.`);
|
|
@@ -144,6 +159,11 @@ function readCalSubjectOrQuery(action, rest) {
|
|
|
144
159
|
if (!query)
|
|
145
160
|
throw new Error('cal find needs words: cockpit cal find "standup".');
|
|
146
161
|
}
|
|
162
|
+
else if (action === "people") {
|
|
163
|
+
query = optionalNonEmpty(rest.join(" "));
|
|
164
|
+
if (!query)
|
|
165
|
+
throw new Error('cal people needs a name or an email: cockpit cal people "Vee".');
|
|
166
|
+
}
|
|
147
167
|
else if (rest.length > 0) {
|
|
148
168
|
throw new Error(`cal ${action} does not take "${rest[0]}".`);
|
|
149
169
|
}
|
|
@@ -200,7 +220,7 @@ function buildCalCommand(values, action, subject, query, title, startsAt, endsAt
|
|
|
200
220
|
// `commands/cal.ts` rather than here, so the parser stays pure.
|
|
201
221
|
timeZone: optionalNonEmpty(values.flags.get("--tz")),
|
|
202
222
|
...(offset === undefined ? {} : { offset }),
|
|
203
|
-
...(optionalPositiveInteger(values.flags.get("--hours"), "--hours") === undefined
|
|
223
|
+
...(action === "suggest" || optionalPositiveInteger(values.flags.get("--hours"), "--hours") === undefined
|
|
204
224
|
? {}
|
|
205
225
|
: { hours: optionalPositiveInteger(values.flags.get("--hours"), "--hours") }),
|
|
206
226
|
from: optionalNonEmpty(values.flags.get("--from")),
|
|
@@ -370,4 +390,57 @@ function readViewSettingsPatch(action, values) {
|
|
|
370
390
|
patch.extra_zones = zones;
|
|
371
391
|
}
|
|
372
392
|
return Object.keys(patch).length ? { viewSettings: patch } : {};
|
|
393
|
+
}
|
|
394
|
+
const PEOPLE_FLAG_KEYS = ["--with", "--pin", "--remove"];
|
|
395
|
+
const SUGGEST_FLAG_KEYS = ["--duration", "--weekends"];
|
|
396
|
+
/** BLI-21522: `availability --with` (repeatable), `overlays --pin|--remove`. Each belongs to one verb. */
|
|
397
|
+
function readPeopleFlags(action, values) {
|
|
398
|
+
const owner = { "--with": "availability", "--pin": "overlays", "--remove": "overlays" };
|
|
399
|
+
for (const flag of PEOPLE_FLAG_KEYS) {
|
|
400
|
+
if (values.flags.has(flag) && owner[flag] !== action && !(flag === "--with" && action === "suggest") && !(flag === "--remove" && action === "links"))
|
|
401
|
+
throw new Error(`cal ${action} does not take ${flag}; it belongs to cal ${owner[flag]}.`);
|
|
402
|
+
}
|
|
403
|
+
for (const flag of SUGGEST_FLAG_KEYS) {
|
|
404
|
+
if ((values.flags.has(flag) || values.booleans.has(flag)) && action !== "suggest" && !(flag === "--duration" && (action === "availability-share" || action === "booking-page")))
|
|
405
|
+
throw new Error(`cal ${action} does not take ${flag}; it belongs to cal suggest.`);
|
|
406
|
+
}
|
|
407
|
+
if (values.booleans.has("--recheck") && action !== "create")
|
|
408
|
+
throw new Error(`cal ${action} does not take --recheck; it belongs to cal create.`);
|
|
409
|
+
if (action === "create" && values.booleans.has("--recheck"))
|
|
410
|
+
return { recheck: true };
|
|
411
|
+
if (action === "suggest")
|
|
412
|
+
return readSuggestFlags(values);
|
|
413
|
+
if (action === "availability") {
|
|
414
|
+
const labels = (values.flagValues.get("--with") ?? []).map((label) => label.trim()).filter(Boolean);
|
|
415
|
+
return labels.length ? { peopleWith: labels } : {};
|
|
416
|
+
}
|
|
417
|
+
if (action === "overlays") {
|
|
418
|
+
const pin = optionalNonEmpty(values.flags.get("--pin"));
|
|
419
|
+
const remove = optionalNonEmpty(values.flags.get("--remove"));
|
|
420
|
+
if (pin && remove)
|
|
421
|
+
throw new Error("cal overlays takes --pin or --remove, not both.");
|
|
422
|
+
return { ...(pin ? { pinLabel: pin } : {}), ...(remove ? { removeEmail: remove } : {}) };
|
|
423
|
+
}
|
|
424
|
+
return {};
|
|
425
|
+
}
|
|
426
|
+
/** BLI-21542 `suggest --with ... --duration 30 --hours 09:00-17:00 --weekends`: shape only; the door owns the limits. */
|
|
427
|
+
function readSuggestFlags(values) {
|
|
428
|
+
const labels = (values.flagValues.get("--with") ?? []).map((label) => label.trim()).filter(Boolean);
|
|
429
|
+
const out = labels.length ? { peopleWith: labels } : {};
|
|
430
|
+
const duration = optionalNonEmpty(values.flags.get("--duration"));
|
|
431
|
+
if (duration !== undefined) {
|
|
432
|
+
if (!/^\d+$/.test(duration))
|
|
433
|
+
throw new Error("--duration takes a whole number of minutes.");
|
|
434
|
+
out.duration = Number(duration);
|
|
435
|
+
}
|
|
436
|
+
const hours = optionalNonEmpty(values.flags.get("--hours"));
|
|
437
|
+
if (hours !== undefined) {
|
|
438
|
+
const match = /^(\d{1,2}:\d{2})-(\d{1,2}:\d{2})$/.exec(hours);
|
|
439
|
+
if (!match)
|
|
440
|
+
throw new Error("--hours takes a range like 09:00-17:00.");
|
|
441
|
+
out.hoursRange = { start: match[1], end: match[2] };
|
|
442
|
+
}
|
|
443
|
+
if (values.booleans.has("--weekends"))
|
|
444
|
+
out.weekends = true;
|
|
445
|
+
return out;
|
|
373
446
|
}
|