@bli-cockpit/cli 0.2.237 → 0.2.238
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/attribution-core-fallbacks.js +15 -11
- package/dist/adapters/attribution-core-score.js +111 -87
- package/dist/adapters/car-state.js +38 -35
- package/dist/adapters/claude-attribution-discovery.js +81 -56
- package/dist/adapters/claude-attribution-score.js +108 -94
- package/dist/adapters/claude-attribution-signals.js +52 -42
- package/dist/adapters/codex-attribution.js +42 -33
- package/dist/adapters/codex-session-signals.js +11 -9
- package/dist/adapters/git-state.js +56 -50
- package/dist/adapters/local-sources.js +52 -43
- package/dist/adapters/raw-evidence-git-diff.js +4 -2
- package/dist/adapters/raw-evidence-sanitize.js +15 -12
- package/dist/adapters/raw-evidence-transcript-file.js +32 -31
- package/dist/autostart-self-heal.js +18 -2
- package/dist/commands/agent-session-report.js +51 -59
- package/dist/commands/agent-setup-observed.js +30 -23
- package/dist/commands/agent-setup.js +96 -45
- package/dist/commands/autostart-heal.js +8 -2
- package/dist/commands/autostart-reading.js +3 -0
- package/dist/commands/backfill-evidence-outcomes.js +9 -6
- package/dist/commands/backfill-issues.js +3 -0
- package/dist/commands/backfill-scan.js +27 -22
- package/dist/commands/backfill-upload.js +69 -60
- package/dist/commands/backfill.js +26 -21
- package/dist/commands/brief-done.js +4 -1
- package/dist/commands/brief-edit.js +71 -46
- package/dist/commands/cal-detail.js +35 -0
- package/dist/commands/cal-overlay.js +73 -30
- package/dist/commands/cal.js +36 -23
- package/dist/commands/clean.js +4 -1
- package/dist/commands/docs-read.js +5 -2
- package/dist/commands/doctor-backfill.js +88 -71
- package/dist/commands/doctor-pipeline.js +59 -46
- package/dist/commands/heartbeat-post.js +116 -0
- package/dist/commands/heartbeat-token-lock.js +46 -33
- package/dist/commands/heartbeat.js +43 -125
- package/dist/commands/install-update.js +17 -15
- package/dist/commands/issue-show.js +27 -28
- package/dist/commands/issue-write.js +70 -23
- package/dist/commands/issue.js +68 -37
- package/dist/commands/jarvis-live-answer.js +99 -0
- package/dist/commands/jarvis-render.js +1 -94
- package/dist/commands/jarvis-trace.js +30 -22
- package/dist/commands/jarvis-turn-report.js +124 -0
- package/dist/commands/jarvis-turn.js +108 -182
- package/dist/commands/jarvis.js +26 -17
- package/dist/commands/local-args-collector-setup.js +82 -84
- package/dist/commands/local-args-collector-work.js +37 -38
- package/dist/commands/local-args-tower-admin.js +155 -142
- package/dist/commands/local-args-tower-cal.js +52 -50
- package/dist/commands/local-args-tower-careers.js +73 -54
- package/dist/commands/local-args-tower-docs-msg.js +55 -45
- package/dist/commands/local-args-tower-github.js +26 -20
- package/dist/commands/local-args-tower-hits.js +32 -20
- package/dist/commands/local-args-tower-manager.js +85 -4
- package/dist/commands/local-args-tower-memory.js +139 -117
- package/dist/commands/local-args-tower-notes.js +346 -0
- package/dist/commands/local-args-tower-pages.js +100 -378
- package/dist/commands/local-args-tower-settings.js +28 -29
- package/dist/commands/local-args-tower-work.js +52 -33
- package/dist/commands/local-args.js +80 -129
- package/dist/commands/local-help-commands-tower-ops.js +15 -4
- package/dist/commands/local-help-commands-tower.js +6 -2
- package/dist/commands/local-help-commands.js +342 -341
- package/dist/commands/local-help.js +87 -83
- package/dist/commands/local-run-guards.js +100 -0
- package/dist/commands/local.js +137 -206
- package/dist/commands/login.js +13 -9
- package/dist/commands/logs.js +55 -38
- package/dist/commands/mail-attachments.js +44 -26
- package/dist/commands/manager-render-home.js +208 -0
- package/dist/commands/manager-render.js +7 -3
- package/dist/commands/manager-requests.js +33 -0
- package/dist/commands/manager.js +39 -5
- package/dist/commands/memory-consolidation.js +18 -9
- package/dist/commands/memory-daemon-probe.js +37 -28
- package/dist/commands/memory-install-config.js +64 -60
- package/dist/commands/memory-install.js +21 -41
- package/dist/commands/memory-requeue.js +26 -19
- package/dist/commands/memory-rules.js +26 -18
- package/dist/commands/memory-search-print.js +26 -0
- package/dist/commands/memory-store.js +14 -30
- package/dist/commands/memory-verb-list.js +49 -48
- package/dist/commands/notes-accounts.js +49 -32
- package/dist/commands/notes-sync-loop.js +115 -89
- package/dist/commands/notes-wisprflow-login.js +44 -23
- package/dist/commands/notes-writes.js +115 -100
- package/dist/commands/notes.js +33 -55
- package/dist/commands/onboard-flows.js +69 -51
- package/dist/commands/onboard.js +11 -9
- package/dist/commands/ops-render-memory.js +10 -7
- package/dist/commands/ops-render.js +33 -30
- package/dist/commands/ops.js +3 -0
- package/dist/commands/public-root.js +1 -1
- package/dist/commands/session-sync-outside-roots.js +40 -36
- package/dist/commands/session-sync-plan.js +14 -10
- package/dist/commands/session-sync-upload.js +24 -22
- package/dist/commands/session-sync.js +19 -8
- package/dist/commands/slack.js +25 -16
- package/dist/commands/sources-check.js +13 -7
- package/dist/commands/sources-discovery.js +25 -17
- package/dist/commands/sources-verdict.js +19 -18
- package/dist/commands/speed.js +23 -19
- package/dist/commands/start.js +20 -16
- package/dist/commands/sync-doctor.js +52 -48
- package/dist/commands/sync.js +22 -16
- package/dist/commands/usage-telemetry-toml.js +71 -45
- package/dist/commands/usage.js +91 -67
- package/dist/commands/workbook-render.js +10 -7
- package/dist/evidence-reconcile-client.js +75 -64
- package/dist/evidence-redelivery-plan.js +37 -34
- package/dist/evidence-redelivery.js +32 -29
- package/dist/evidence-transcripts-client.js +20 -20
- package/dist/evidence-upload-object.js +86 -89
- package/dist/evidence-upload-plan.js +94 -83
- package/dist/evidence-upload-terminal.js +45 -50
- package/dist/image-description-session.js +156 -159
- package/dist/local-state-pairing-code.js +39 -31
- package/dist/local-state-status.js +9 -2
- package/dist/onboarding-roots.js +5 -8
- package/dist/process-runner.js +8 -8
- package/dist/raw-evidence-gc.js +33 -38
- package/dist/raw-evidence-stuck-summary.js +7 -14
- package/dist/repo-identity.js +26 -23
- package/dist/scheduled-self-update.js +11 -15
- package/dist/second-install.js +3 -0
- package/dist/slim/codex-rules.js +23 -36
- package/dist/sync-lock.js +5 -15
- package/dist/tower-client.js +36 -53
- package/dist/tower-stream.js +7 -24
- package/dist/upload-agent-artifacts.js +58 -64
- package/dist/upload-envelope-build.js +60 -59
- package/dist/upload-evidence-delivery-reconcile.js +58 -56
- package/dist/upload-evidence-delivery-summary.js +32 -34
- package/dist/upload-ingest-receipt.js +20 -23
- package/dist/upload-session-reports-queue.js +4 -1
- package/dist/upload-session-reports-wire.js +64 -68
- package/dist/upload-sync.js +171 -157
- package/package.json +2 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
3
|
-
* page (`cockpit brief`)
|
|
4
|
-
*
|
|
2
|
+
* The commands that read a page instead of asking a question: the daily
|
|
3
|
+
* page (`cockpit brief`) and the document library (`cockpit workbook`); meeting
|
|
4
|
+
* notes (`cockpit notes`) live in `local-args-tower-notes.ts`. Split out of local-args-tower.ts (BLI-3636).
|
|
5
5
|
*
|
|
6
6
|
* Every parser moved verbatim: same flags, same defaults, same refusal
|
|
7
7
|
* sentences. The three names `local-args.ts` re-exports from this family
|
|
@@ -9,83 +9,109 @@
|
|
|
9
9
|
* `./local-args-tower.js`, the address they have always had.
|
|
10
10
|
*/
|
|
11
11
|
import { optionalNonEmpty, optionalPositiveInteger, optionalUrl, parseNamedArgs, } from "./local-arg-values.js";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
12
|
+
const BRIEF_ACTIONS = ["read", "edit", "rewrite", "history", "status", "done", "undone"];
|
|
13
|
+
const BRIEF_ALLOWED_FLAGS = [
|
|
14
|
+
"--home",
|
|
15
|
+
"--dashboard-url",
|
|
16
|
+
"--for",
|
|
17
|
+
"--as",
|
|
18
|
+
"--who",
|
|
19
|
+
"--version",
|
|
20
|
+
"--date",
|
|
21
|
+
"--delta",
|
|
22
|
+
"--against",
|
|
23
|
+
"--days",
|
|
24
|
+
"--tldr",
|
|
25
|
+
"--full",
|
|
26
|
+
"--versions",
|
|
27
|
+
"--claims",
|
|
28
|
+
"--render",
|
|
29
|
+
"--missed",
|
|
30
|
+
"--reason",
|
|
31
|
+
"--wait",
|
|
32
|
+
"--no-wait",
|
|
33
|
+
"--json",
|
|
34
|
+
];
|
|
35
|
+
const BRIEF_VALUE_FLAGS = [
|
|
36
|
+
"--home",
|
|
37
|
+
"--dashboard-url",
|
|
38
|
+
"--for",
|
|
39
|
+
"--as",
|
|
40
|
+
"--who",
|
|
41
|
+
"--version",
|
|
42
|
+
"--date",
|
|
43
|
+
"--against",
|
|
44
|
+
"--days",
|
|
45
|
+
"--reason",
|
|
46
|
+
];
|
|
26
47
|
export function parseBriefArgs(args) {
|
|
27
|
-
const values = parseNamedArgs(args, {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
"--reason",
|
|
61
|
-
],
|
|
62
|
-
});
|
|
48
|
+
const values = parseNamedArgs(args, { allowedFlags: BRIEF_ALLOWED_FLAGS, valueFlags: BRIEF_VALUE_FLAGS });
|
|
49
|
+
const action = readBriefAction(values.positionals[0]);
|
|
50
|
+
const item = readBriefItem(action, values.positionals);
|
|
51
|
+
const tldr = readBriefTldr(values);
|
|
52
|
+
const { reason, noWait } = readBriefRewriteAndReasonFlags(values, action);
|
|
53
|
+
const { date, against, delta, days } = readBriefPastDayFlags(values, action);
|
|
54
|
+
const people = readBriefPeople(values);
|
|
55
|
+
const { render, missed } = readBriefStatusFlags(values, action, people.named);
|
|
56
|
+
return {
|
|
57
|
+
kind: "brief",
|
|
58
|
+
action,
|
|
59
|
+
...(item ? { item } : {}),
|
|
60
|
+
render,
|
|
61
|
+
missed,
|
|
62
|
+
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
63
|
+
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
64
|
+
subject: people.forPerson ?? people.asPerson ?? people.whoPerson,
|
|
65
|
+
version: optionalNonEmpty(values.flags.get("--version")),
|
|
66
|
+
...(date ? { date } : {}),
|
|
67
|
+
...(delta ? { delta: true } : {}),
|
|
68
|
+
...(against ? { against } : {}),
|
|
69
|
+
...(days !== undefined ? { days } : {}),
|
|
70
|
+
tldr,
|
|
71
|
+
versions: values.booleans.has("--versions"),
|
|
72
|
+
claims: values.booleans.has("--claims"),
|
|
73
|
+
...(reason ? { reason } : {}),
|
|
74
|
+
// Waiting is the default; only an explicit `--no-wait` turns it off. A
|
|
75
|
+
// person who typed `rewrite` wants the page, not a receipt.
|
|
76
|
+
...(noWait ? { wait: false } : {}),
|
|
77
|
+
json: values.booleans.has("--json"),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function readBriefAction(first) {
|
|
63
81
|
// Bare `cockpit brief` reads the page, which is what somebody typing it almost
|
|
64
82
|
// always wants — the same shape `cockpit notes` and `cockpit scout` have.
|
|
65
83
|
// `status` (BLI-3462) answers why a brief was or was not delivered.
|
|
66
|
-
const first = values.positionals[0];
|
|
67
84
|
const action = (first === undefined ? "read" : first);
|
|
68
|
-
if (!
|
|
85
|
+
if (!BRIEF_ACTIONS.includes(action)) {
|
|
69
86
|
throw new Error(`Unknown brief command: ${first}. Try done, undone, edit, rewrite, history or status, or nothing to read it.`);
|
|
70
87
|
}
|
|
88
|
+
return action;
|
|
89
|
+
}
|
|
90
|
+
/** The item `done`/`undone` tick off, and a refusal for any positional the verb does not take. */
|
|
91
|
+
function readBriefItem(action, positionals) {
|
|
71
92
|
// BLI-3605: `done` and `undone` are the only brief verbs that take a
|
|
72
93
|
// positional, and they REQUIRE one, "which item" is the whole command, and
|
|
73
94
|
// a bare `cockpit brief done` that guessed at the top row would tick off
|
|
74
95
|
// somebody's work for them.
|
|
75
96
|
const ticksAnItem = action === "done" || action === "undone";
|
|
76
|
-
const item = ticksAnItem ?
|
|
97
|
+
const item = ticksAnItem ? positionals[1] : undefined;
|
|
77
98
|
if (ticksAnItem && !item) {
|
|
78
99
|
throw new Error(`brief ${action} needs the item: a ticket like BLI-3511, or a claim id from \`cockpit brief --claims\`.`);
|
|
79
100
|
}
|
|
80
|
-
const positionalsAllowed =
|
|
81
|
-
if (
|
|
82
|
-
throw new Error(`brief ${action} does not take "${
|
|
101
|
+
const positionalsAllowed = positionals[0] === undefined ? 0 : ticksAnItem ? 2 : 1;
|
|
102
|
+
if (positionals.length > positionalsAllowed) {
|
|
103
|
+
throw new Error(`brief ${action} does not take "${positionals[positionalsAllowed]}".`);
|
|
83
104
|
}
|
|
105
|
+
return item;
|
|
106
|
+
}
|
|
107
|
+
function readBriefTldr(values) {
|
|
84
108
|
const tldr = values.booleans.has("--tldr");
|
|
85
|
-
|
|
86
|
-
if (tldr && full) {
|
|
109
|
+
if (tldr && values.booleans.has("--full")) {
|
|
87
110
|
throw new Error("brief accepts either --tldr or --full, not both.");
|
|
88
111
|
}
|
|
112
|
+
return tldr;
|
|
113
|
+
}
|
|
114
|
+
function readBriefRewriteAndReasonFlags(values, action) {
|
|
89
115
|
const wait = values.booleans.has("--wait");
|
|
90
116
|
const noWait = values.booleans.has("--no-wait");
|
|
91
117
|
if (wait && noWait) {
|
|
@@ -103,7 +129,10 @@ export function parseBriefArgs(args) {
|
|
|
103
129
|
if (action !== "rewrite" && (wait || noWait)) {
|
|
104
130
|
throw new Error("--wait and --no-wait belong to `cockpit brief rewrite`.");
|
|
105
131
|
}
|
|
106
|
-
|
|
132
|
+
return { reason, noWait };
|
|
133
|
+
}
|
|
134
|
+
/** Reading a past day (BLI-3484). */
|
|
135
|
+
function readBriefPastDayFlags(values, action) {
|
|
107
136
|
const date = optionalNonEmpty(values.flags.get("--date"));
|
|
108
137
|
const against = optionalNonEmpty(values.flags.get("--against"));
|
|
109
138
|
const delta = values.booleans.has("--delta");
|
|
@@ -126,6 +155,9 @@ export function parseBriefArgs(args) {
|
|
|
126
155
|
if (action === "history" && (values.booleans.has("--tldr") || values.booleans.has("--claims"))) {
|
|
127
156
|
throw new Error("brief history lists days; --tldr and --claims belong to reading a page.");
|
|
128
157
|
}
|
|
158
|
+
return { date, against, delta, days };
|
|
159
|
+
}
|
|
160
|
+
function readBriefPeople(values) {
|
|
129
161
|
// `--as` is accepted as an alias so the two conversational commands read the
|
|
130
162
|
// same way; `cockpit jarvis --as <person>` has meant this since BLI-3380.
|
|
131
163
|
// `--who` is the third spelling, and it exists because `cockpit brief status
|
|
@@ -137,6 +169,9 @@ export function parseBriefArgs(args) {
|
|
|
137
169
|
if (new Set(named).size > 1) {
|
|
138
170
|
throw new Error("brief --for, --as and --who must name the same person.");
|
|
139
171
|
}
|
|
172
|
+
return { forPerson, asPerson, whoPerson, named };
|
|
173
|
+
}
|
|
174
|
+
function readBriefStatusFlags(values, action, named) {
|
|
140
175
|
const render = values.booleans.has("--render");
|
|
141
176
|
if (render && action !== "status") {
|
|
142
177
|
throw new Error("brief --render belongs to `cockpit brief status`; the page is printed by default.");
|
|
@@ -148,323 +183,10 @@ export function parseBriefArgs(args) {
|
|
|
148
183
|
if (missed && named.length > 0) {
|
|
149
184
|
throw new Error("brief status --missed answers for everybody; it does not take --for, --as or --who.");
|
|
150
185
|
}
|
|
151
|
-
return {
|
|
152
|
-
kind: "brief",
|
|
153
|
-
action,
|
|
154
|
-
...(item ? { item } : {}),
|
|
155
|
-
render,
|
|
156
|
-
missed,
|
|
157
|
-
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
158
|
-
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
159
|
-
subject: forPerson ?? asPerson ?? whoPerson,
|
|
160
|
-
version: optionalNonEmpty(values.flags.get("--version")),
|
|
161
|
-
...(date ? { date } : {}),
|
|
162
|
-
...(delta ? { delta: true } : {}),
|
|
163
|
-
...(against ? { against } : {}),
|
|
164
|
-
...(days !== undefined ? { days } : {}),
|
|
165
|
-
tldr,
|
|
166
|
-
versions: values.booleans.has("--versions"),
|
|
167
|
-
claims: values.booleans.has("--claims"),
|
|
168
|
-
...(reason ? { reason } : {}),
|
|
169
|
-
// Waiting is the default; only an explicit `--no-wait` turns it off. A
|
|
170
|
-
// person who typed `rewrite` wants the page, not a receipt.
|
|
171
|
-
...(noWait ? { wait: false } : {}),
|
|
172
|
-
json: values.booleans.has("--json"),
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* The notetakers `cockpit notes connect` will actually seal a key for
|
|
177
|
-
* (BLI-4391). It mirrors `CONNECTABLE_NOTES_PROVIDERS` in the dashboard, and
|
|
178
|
-
* the door is still the authority — this list only saves a round trip and a
|
|
179
|
-
* sealed key for a provider with no reader.
|
|
180
|
-
*
|
|
181
|
-
* `otter` and `granola` are deliberately absent: Otter's public API is
|
|
182
|
-
* Enterprise-only and Granola still runs on its own server-side cron.
|
|
183
|
-
*/
|
|
184
|
-
const CONNECTABLE_PROVIDERS = ["fellow", "circleback", "wisprflow"];
|
|
185
|
-
/**
|
|
186
|
-
* BLI-12926. Providers with no API key at all: the person signs in through
|
|
187
|
-
* their browser and `connect` runs that sign-in itself. `--key-stdin` is
|
|
188
|
-
* optional for these, and carries a pre-built sign-in bundle when given.
|
|
189
|
-
*/
|
|
190
|
-
const PROVIDERS_SIGNED_IN_BY_BROWSER = ["wisprflow"];
|
|
191
|
-
/** The provider whose API is per workspace, so its account needs a subdomain. */
|
|
192
|
-
const PROVIDERS_NEEDING_A_WORKSPACE = ["fellow"];
|
|
193
|
-
const NOTES_ACTIONS = new Set([
|
|
194
|
-
"folders", "mkdir", "rmdir", "rename",
|
|
195
|
-
"list",
|
|
196
|
-
"show",
|
|
197
|
-
"shelf",
|
|
198
|
-
"shelves",
|
|
199
|
-
"upload",
|
|
200
|
-
"paste",
|
|
201
|
-
"share",
|
|
202
|
-
"unshare",
|
|
203
|
-
"summarize",
|
|
204
|
-
"move",
|
|
205
|
-
"place",
|
|
206
|
-
// BLI-4383: the notetaker a person connects, and the meetings it records.
|
|
207
|
-
"accounts",
|
|
208
|
-
"connect",
|
|
209
|
-
"detach",
|
|
210
|
-
"sync",
|
|
211
|
-
"sync-status",
|
|
212
|
-
// BLI-12926: the alternates of a meeting several notetakers recorded.
|
|
213
|
-
"recordings",
|
|
214
|
-
"recording",
|
|
215
|
-
// BLI-4394: arm that notetaker's webhook with the vendor's signing secret.
|
|
216
|
-
"webhook",
|
|
217
|
-
// Ticket 10718: the Granola cron's own last-several-runs health.
|
|
218
|
-
"granola-status",
|
|
219
|
-
"transcription-stats",
|
|
220
|
-
"bot-upcoming",
|
|
221
|
-
]);
|
|
222
|
-
/** Actions whose first positional is the note it acts on. */
|
|
223
|
-
const NOTES_ACTIONS_NEEDING_A_NOTE = new Set([
|
|
224
|
-
"show",
|
|
225
|
-
"share",
|
|
226
|
-
"unshare",
|
|
227
|
-
"summarize",
|
|
228
|
-
"move",
|
|
229
|
-
"place",
|
|
230
|
-
// BLI-12926: `recordings` names the note, `recording` names the recording.
|
|
231
|
-
"recordings",
|
|
232
|
-
"recording",
|
|
233
|
-
]);
|
|
234
|
-
/**
|
|
235
|
-
* BLI-4383. Actions whose first positional is a NOTETAKER ACCOUNT id, not a
|
|
236
|
-
* note id. `sync` takes one too, and is handled beside these rather than in
|
|
237
|
-
* this set because its id is checked at the door instead of here.
|
|
238
|
-
*/
|
|
239
|
-
const NOTES_ACTIONS_NEEDING_AN_ACCOUNT = new Set(["detach", "webhook"]);
|
|
240
|
-
export function parseNotesArgs(args) {
|
|
241
|
-
const values = parseNamedArgs(args, {
|
|
242
|
-
allowedFlags: [
|
|
243
|
-
"--wait",
|
|
244
|
-
"--home",
|
|
245
|
-
"--dashboard-url",
|
|
246
|
-
"--file",
|
|
247
|
-
"--name",
|
|
248
|
-
"--exclude",
|
|
249
|
-
"--to",
|
|
250
|
-
"--folder",
|
|
251
|
-
"--clear-shelf",
|
|
252
|
-
"--apply",
|
|
253
|
-
"--series",
|
|
254
|
-
"--kind",
|
|
255
|
-
"--since",
|
|
256
|
-
"--until",
|
|
257
|
-
"--limit",
|
|
258
|
-
"--month",
|
|
259
|
-
"--offset",
|
|
260
|
-
"--yes",
|
|
261
|
-
"--json",
|
|
262
|
-
"--tree",
|
|
263
|
-
// BLI-4383. `--key-stdin` is a FLAG and not a value: the key itself is
|
|
264
|
-
// read from stdin and never from argv.
|
|
265
|
-
"--provider",
|
|
266
|
-
"--key-stdin",
|
|
267
|
-
// BLI-4391. Fellow's API is per WORKSPACE, and a subdomain is a label,
|
|
268
|
-
// not a secret, so this one carries a value while the key never does.
|
|
269
|
-
"--workspace",
|
|
270
|
-
// BLI-4394. `--secret-stdin` is a FLAG and not a value, for the same
|
|
271
|
-
// reason `--key-stdin` is: the signing secret never touches argv.
|
|
272
|
-
"--secret-stdin",
|
|
273
|
-
"--show-webhook",
|
|
274
|
-
// BLI-18006. `sync <id> --repair-truncated` re-reads Wispr Flow notes whose
|
|
275
|
-
// transcript was stored cut short. A FLAG, not a verb: it is the same sync
|
|
276
|
-
// door with a different request.
|
|
277
|
-
"--repair-truncated",
|
|
278
|
-
],
|
|
279
|
-
valueFlags: [
|
|
280
|
-
"--home",
|
|
281
|
-
"--dashboard-url",
|
|
282
|
-
"--file",
|
|
283
|
-
"--name",
|
|
284
|
-
"--exclude",
|
|
285
|
-
"--to",
|
|
286
|
-
"--folder",
|
|
287
|
-
"--series",
|
|
288
|
-
"--kind",
|
|
289
|
-
"--since",
|
|
290
|
-
"--until",
|
|
291
|
-
"--limit",
|
|
292
|
-
"--month",
|
|
293
|
-
"--offset",
|
|
294
|
-
"--provider",
|
|
295
|
-
"--workspace",
|
|
296
|
-
"--show-webhook",
|
|
297
|
-
],
|
|
298
|
-
});
|
|
299
|
-
// Bare `cockpit notes` is the list, which is the thing a person typing it
|
|
300
|
-
// almost always wants — same reasoning as bare `cockpit` running convergence.
|
|
301
|
-
const first = values.positionals[0];
|
|
302
|
-
const action = (first === undefined ? "list" : first);
|
|
303
|
-
if (!NOTES_ACTIONS.has(action)) {
|
|
304
|
-
throw new Error(`Unknown notes command: ${first}. Try list, show, shelf, shelves, upload, paste, share, unshare, summarize, move, place, accounts, connect, detach, sync, sync-status, recordings, recording, webhook, granola-status, or transcription-stats.`);
|
|
305
|
-
}
|
|
306
|
-
const rest = values.positionals.slice(first === undefined ? 0 : 1);
|
|
307
|
-
const json = values.booleans.has("--json");
|
|
308
|
-
// `--json` implies `--yes`: a machine-readable run has nobody to prompt, and
|
|
309
|
-
// failing it for want of a confirmation flag it cannot see is a worse
|
|
310
|
-
// surprise than an explicit share it already asked for by name.
|
|
311
|
-
const yes = values.booleans.has("--yes") || json;
|
|
312
|
-
let noteId;
|
|
313
|
-
if (NOTES_ACTIONS_NEEDING_A_NOTE.has(action)) {
|
|
314
|
-
noteId = optionalNonEmpty(rest[0]);
|
|
315
|
-
if (!noteId)
|
|
316
|
-
throw new Error(`notes ${action} needs a note id.`);
|
|
317
|
-
if (rest.length > 1) {
|
|
318
|
-
throw new Error(`notes ${action} takes one note id, not ${rest.length}.`);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
let accountId;
|
|
322
|
-
if (NOTES_ACTIONS_NEEDING_AN_ACCOUNT.has(action) || action === "sync" || action === "sync-status") {
|
|
323
|
-
accountId = optionalNonEmpty(rest[0]);
|
|
324
|
-
if (!accountId && NOTES_ACTIONS_NEEDING_AN_ACCOUNT.has(action)) {
|
|
325
|
-
throw new Error(`notes ${action} needs a notetaker account id.`);
|
|
326
|
-
}
|
|
327
|
-
if (rest.length > 1) {
|
|
328
|
-
throw new Error(`notes ${action} takes one account id, not ${rest.length}.`);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
// BLI-4394. `--show-webhook <id>` is a flag on `accounts` rather than a verb
|
|
332
|
-
// of its own, because "what is this one's webhook URL?" is the question a
|
|
333
|
-
// person asks WHILE looking at the list.
|
|
334
|
-
const showWebhook = optionalNonEmpty(values.flags.get("--show-webhook"));
|
|
335
|
-
if (showWebhook !== undefined) {
|
|
336
|
-
if (action !== "accounts") {
|
|
337
|
-
throw new Error("--show-webhook belongs to `cockpit notes accounts --show-webhook <account-id>`.");
|
|
338
|
-
}
|
|
339
|
-
accountId = showWebhook;
|
|
340
|
-
}
|
|
341
|
-
if (action === "webhook") {
|
|
342
|
-
if (!values.booleans.has("--secret-stdin")) {
|
|
343
|
-
// Naming the flag is the point: it says out loud that the signing secret
|
|
344
|
-
// is not an argument, so nobody goes looking for a `--secret` that will
|
|
345
|
-
// never exist.
|
|
346
|
-
throw new Error('notes webhook reads the signing secret from stdin. Say so: printf "%s" "<secret>" | cockpit notes webhook <account-id> --secret-stdin');
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
else if (values.booleans.has("--secret-stdin")) {
|
|
350
|
-
throw new Error("--secret-stdin belongs to `cockpit notes webhook <account-id> --secret-stdin`.");
|
|
351
|
-
}
|
|
352
|
-
const provider = optionalNonEmpty(values.flags.get("--provider"));
|
|
353
|
-
const workspace = optionalNonEmpty(values.flags.get("--workspace"));
|
|
354
|
-
if (action === "connect") {
|
|
355
|
-
if (!provider) {
|
|
356
|
-
throw new Error(`notes connect needs --provider ${CONNECTABLE_PROVIDERS.join("|")}.`);
|
|
357
|
-
}
|
|
358
|
-
if (!CONNECTABLE_PROVIDERS.includes(provider)) {
|
|
359
|
-
throw new Error(`notes connect does not know the provider "${provider}". Tower reads ${CONNECTABLE_PROVIDERS.join(" and ")}.`);
|
|
360
|
-
}
|
|
361
|
-
if (PROVIDERS_NEEDING_A_WORKSPACE.includes(provider) && !workspace) {
|
|
362
|
-
throw new Error(`notes connect --provider ${provider} needs --workspace <subdomain>: its API lives at https://<subdomain>.fellow.app, so a key alone cannot address anything.`);
|
|
363
|
-
}
|
|
364
|
-
if (workspace !== undefined && !/^[a-z0-9-]{1,63}$/.test(workspace)) {
|
|
365
|
-
throw new Error("--workspace is a workspace subdomain: lowercase letters, digits and hyphens, 1 to 63 characters.");
|
|
366
|
-
}
|
|
367
|
-
if (!values.booleans.has("--key-stdin") && !PROVIDERS_SIGNED_IN_BY_BROWSER.includes(provider)) {
|
|
368
|
-
// Naming the flag is the point: it says out loud that the key is not an
|
|
369
|
-
// argument, so nobody goes looking for a `--key` that will never exist.
|
|
370
|
-
throw new Error('notes connect reads the key from stdin. Say so: printf "%s" "<key>" | cockpit notes connect --provider fellow --workspace <subdomain> --key-stdin');
|
|
371
|
-
}
|
|
372
|
-
if (rest.length > 0)
|
|
373
|
-
throw new Error(`notes connect does not take "${rest[0]}".`);
|
|
374
|
-
}
|
|
375
|
-
let paths;
|
|
376
|
-
if (action === "upload") {
|
|
377
|
-
// Explicit paths only. No globbing happens here — a shell that expands
|
|
378
|
-
// `*.md` hands us the names it found, and a shell that does not would have
|
|
379
|
-
// this command silently uploading a file literally called `*.md`.
|
|
380
|
-
paths = rest.filter((value) => value.trim() !== "");
|
|
381
|
-
if (paths.length === 0)
|
|
382
|
-
throw new Error("notes upload needs at least one file path.");
|
|
383
|
-
}
|
|
384
|
-
else if (action === "mkdir" || action === "rmdir" || action === "rename") {
|
|
385
|
-
if (rest.length !== 1 || !rest[0]?.trim())
|
|
386
|
-
throw new Error(`notes ${action} needs one folder path.`);
|
|
387
|
-
}
|
|
388
|
-
else if (action !== "paste"
|
|
389
|
-
&& action !== "connect"
|
|
390
|
-
&& action !== "sync"
|
|
391
|
-
&& action !== "sync-status"
|
|
392
|
-
&& !NOTES_ACTIONS_NEEDING_A_NOTE.has(action)
|
|
393
|
-
&& !NOTES_ACTIONS_NEEDING_AN_ACCOUNT.has(action)
|
|
394
|
-
&& rest.length > 0) {
|
|
395
|
-
throw new Error(`notes ${action} does not take "${rest[0]}".`);
|
|
396
|
-
}
|
|
397
|
-
if (action === "rename" && !optionalNonEmpty(values.flags.get("--name")))
|
|
398
|
-
throw new Error("notes rename needs --name <name>.");
|
|
399
|
-
const to = optionalNonEmpty(values.flags.get("--to"));
|
|
400
|
-
const clearShelf = values.booleans.has("--clear-shelf");
|
|
401
|
-
if (action === "move") {
|
|
402
|
-
if (to && clearShelf) {
|
|
403
|
-
throw new Error("notes move accepts either --to or --clear-shelf, not both.");
|
|
404
|
-
}
|
|
405
|
-
if (!to && !clearShelf && !values.flags.get("--folder")) {
|
|
406
|
-
throw new Error('notes move needs --to "<shelf>", --clear-shelf, or --folder "<path>".');
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
const repairTruncated = values.booleans.has("--repair-truncated");
|
|
410
|
-
if (repairTruncated && action !== "sync") {
|
|
411
|
-
throw new Error("--repair-truncated belongs to `cockpit notes sync <account-id> --repair-truncated`.");
|
|
412
|
-
}
|
|
413
|
-
const limit = optionalPositiveInteger(values.flags.get("--limit"), "--limit");
|
|
414
|
-
const month = optionalNonEmpty(values.flags.get("--month"));
|
|
415
|
-
if (month !== undefined) {
|
|
416
|
-
if (action !== "transcription-stats") {
|
|
417
|
-
throw new Error("--month belongs to `cockpit notes transcription-stats --month YYYY-MM`.");
|
|
418
|
-
}
|
|
419
|
-
if (!/^\d{4}-(0[1-9]|1[0-2])$/.test(month)) {
|
|
420
|
-
throw new Error("--month is a calendar month like 2026-10.");
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
// BLI-18006: a long note is read in parts, like `cockpit docs read --offset`.
|
|
424
|
-
const offsetRaw = optionalNonEmpty(values.flags.get("--offset"));
|
|
425
|
-
let offset;
|
|
426
|
-
if (offsetRaw !== undefined) {
|
|
427
|
-
if (action !== "show")
|
|
428
|
-
throw new Error("--offset belongs to `cockpit notes show <id> --offset <characters> --limit <characters>`.");
|
|
429
|
-
offset = Number(offsetRaw);
|
|
430
|
-
if (!Number.isInteger(offset) || offset < 0)
|
|
431
|
-
throw new Error("--offset must be a whole number 0 or greater.");
|
|
432
|
-
}
|
|
433
|
-
return {
|
|
434
|
-
kind: "notes",
|
|
435
|
-
...(values.booleans.has("--wait") ? { wait: true } : {}),
|
|
436
|
-
action,
|
|
437
|
-
...(values.flags.has("--folder") ? { folder: values.flags.get("--folder") } : {}),
|
|
438
|
-
...(action === "mkdir" || action === "rmdir" || action === "rename" ? { folder: rest[0] } : {}),
|
|
439
|
-
...(values.booleans.has("--tree") ? { tree: true } : {}),
|
|
440
|
-
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
441
|
-
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
442
|
-
...(noteId ? { noteId } : {}),
|
|
443
|
-
...(accountId ? { accountId } : {}),
|
|
444
|
-
...(provider ? { provider } : {}),
|
|
445
|
-
...(workspace ? { workspace } : {}),
|
|
446
|
-
...(showWebhook ? { showWebhook: true } : {}),
|
|
447
|
-
...(action === "connect" && values.booleans.has("--key-stdin") ? { keyStdin: true } : {}),
|
|
448
|
-
...(paths ? { paths } : {}),
|
|
449
|
-
filePath: optionalNonEmpty(values.flags.get("--file")),
|
|
450
|
-
name: optionalNonEmpty(values.flags.get("--name")),
|
|
451
|
-
exclude: optionalNonEmpty(values.flags.get("--exclude")),
|
|
452
|
-
...(to ? { to } : {}),
|
|
453
|
-
...(clearShelf ? { clearShelf } : {}),
|
|
454
|
-
// BLI-4058. `place` says where a note belongs; only `--apply` moves it.
|
|
455
|
-
...(values.booleans.has("--apply") ? { apply: true } : {}),
|
|
456
|
-
series: optionalNonEmpty(values.flags.get("--series")),
|
|
457
|
-
meetingKind: optionalNonEmpty(values.flags.get("--kind")),
|
|
458
|
-
since: optionalNonEmpty(values.flags.get("--since")),
|
|
459
|
-
until: optionalNonEmpty(values.flags.get("--until")),
|
|
460
|
-
...(limit === undefined ? {} : { limit }),
|
|
461
|
-
...(month ? { month } : {}),
|
|
462
|
-
...(offset === undefined ? {} : { offset }),
|
|
463
|
-
...(repairTruncated ? { repairTruncated: true } : {}),
|
|
464
|
-
yes,
|
|
465
|
-
json,
|
|
466
|
-
};
|
|
186
|
+
return { render, missed };
|
|
467
187
|
}
|
|
188
|
+
// Meeting notes have their own argument table; these names keep the address they have always had.
|
|
189
|
+
export { parseNotesArgs } from "./local-args-tower-notes.js";
|
|
468
190
|
/** The narrowest width worth wrapping to; below it every line is one word. */
|
|
469
191
|
export const WORKBOOK_MIN_WIDTH = 20;
|
|
470
192
|
/**
|
|
@@ -13,37 +13,36 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { optionalNonEmpty, optionalUrl, parseNamedArgs } from "./local-arg-values.js";
|
|
15
15
|
const SETTINGS_SECTIONS = ["personal", "switches", "models", "env", "cli-floor"];
|
|
16
|
+
const SETTINGS_ALLOWED_FLAGS = [
|
|
17
|
+
"--home",
|
|
18
|
+
"--dashboard-url",
|
|
19
|
+
"--json",
|
|
20
|
+
"--chat-model",
|
|
21
|
+
"--brief-model",
|
|
22
|
+
"--chat",
|
|
23
|
+
"--memory",
|
|
24
|
+
"--note-reader",
|
|
25
|
+
"--project",
|
|
26
|
+
"--file",
|
|
27
|
+
"--id",
|
|
28
|
+
"--content-stdin",
|
|
29
|
+
"--yes",
|
|
30
|
+
];
|
|
31
|
+
const SETTINGS_VALUE_FLAGS = [
|
|
32
|
+
"--home",
|
|
33
|
+
"--dashboard-url",
|
|
34
|
+
"--chat-model",
|
|
35
|
+
"--brief-model",
|
|
36
|
+
"--chat",
|
|
37
|
+
"--memory",
|
|
38
|
+
"--note-reader",
|
|
39
|
+
"--project",
|
|
40
|
+
"--file",
|
|
41
|
+
"--id",
|
|
42
|
+
];
|
|
16
43
|
/** `cockpit settings [section] [verb] [args]`: the section picker. */
|
|
17
44
|
export function parseSettingsArgs(args) {
|
|
18
|
-
const values = parseNamedArgs(args, {
|
|
19
|
-
allowedFlags: [
|
|
20
|
-
"--home",
|
|
21
|
-
"--dashboard-url",
|
|
22
|
-
"--json",
|
|
23
|
-
"--chat-model",
|
|
24
|
-
"--brief-model",
|
|
25
|
-
"--chat",
|
|
26
|
-
"--memory",
|
|
27
|
-
"--note-reader",
|
|
28
|
-
"--project",
|
|
29
|
-
"--file",
|
|
30
|
-
"--id",
|
|
31
|
-
"--content-stdin",
|
|
32
|
-
"--yes",
|
|
33
|
-
],
|
|
34
|
-
valueFlags: [
|
|
35
|
-
"--home",
|
|
36
|
-
"--dashboard-url",
|
|
37
|
-
"--chat-model",
|
|
38
|
-
"--brief-model",
|
|
39
|
-
"--chat",
|
|
40
|
-
"--memory",
|
|
41
|
-
"--note-reader",
|
|
42
|
-
"--project",
|
|
43
|
-
"--file",
|
|
44
|
-
"--id",
|
|
45
|
-
],
|
|
46
|
-
});
|
|
45
|
+
const values = parseNamedArgs(args, { allowedFlags: SETTINGS_ALLOWED_FLAGS, valueFlags: SETTINGS_VALUE_FLAGS });
|
|
47
46
|
const [rawSection, ...rest] = values.positionals;
|
|
48
47
|
const common = {
|
|
49
48
|
homeDir: optionalNonEmpty(values.flags.get("--home")),
|