@bli-cockpit/cli 0.2.57 → 0.2.59

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.
Files changed (53) hide show
  1. package/dist/commands/browser-open.js +88 -0
  2. package/dist/commands/docs.js +27 -6
  3. package/dist/commands/doctor-report.js +17 -1
  4. package/dist/commands/doctor.js +12 -2
  5. package/dist/commands/heartbeat.js +65 -1
  6. package/dist/commands/issue-contracts.js +99 -0
  7. package/dist/commands/issue-write.js +129 -0
  8. package/dist/commands/issue.js +189 -0
  9. package/dist/commands/jarvis-answer-envelope.js +80 -0
  10. package/dist/commands/jarvis-turn.js +16 -1
  11. package/dist/commands/jarvis.js +3 -0
  12. package/dist/commands/local-args-collector-setup.js +17 -0
  13. package/dist/commands/local-args-tower-admin.js +12 -2
  14. package/dist/commands/local-args-tower-docs-msg.js +95 -6
  15. package/dist/commands/local-args-tower-search.js +50 -0
  16. package/dist/commands/local-args-tower-work.js +178 -0
  17. package/dist/commands/local-args-tower.js +7 -1
  18. package/dist/commands/local-args.js +29 -14
  19. package/dist/commands/local-command-shapes.js +12 -0
  20. package/dist/commands/local-help-commands.js +643 -0
  21. package/dist/commands/local-help.js +12 -551
  22. package/dist/commands/local.js +9 -0
  23. package/dist/commands/login.js +91 -8
  24. package/dist/commands/memory-install-claude.js +35 -15
  25. package/dist/commands/memory-install-codex-hooks.js +200 -0
  26. package/dist/commands/memory-install-codex.js +12 -2
  27. package/dist/commands/memory-install-config.js +140 -0
  28. package/dist/commands/memory-install-receipt.js +222 -0
  29. package/dist/commands/memory-install-report.js +113 -0
  30. package/dist/commands/memory-install.js +99 -276
  31. package/dist/commands/msg.js +85 -2
  32. package/dist/commands/notes-door.js +120 -0
  33. package/dist/commands/notes-reads.js +134 -0
  34. package/dist/commands/notes-writes.js +208 -0
  35. package/dist/commands/notes.js +16 -442
  36. package/dist/commands/onboard-completion.js +47 -0
  37. package/dist/commands/onboard-setup.js +82 -2
  38. package/dist/commands/ops-render-memory.js +76 -0
  39. package/dist/commands/ops-render.js +13 -1
  40. package/dist/commands/ops.js +65 -3
  41. package/dist/commands/project.js +38 -0
  42. package/dist/commands/public-root.js +1 -1
  43. package/dist/commands/search.js +122 -0
  44. package/dist/commands/setup-receipt-lines.js +71 -0
  45. package/dist/commands/setup-receipt.js +241 -0
  46. package/dist/commands/status.js +20 -1
  47. package/dist/local-state-pairing-code.js +200 -0
  48. package/dist/local-state.js +6 -0
  49. package/dist/repo-identity-fingerprint.js +88 -0
  50. package/dist/repo-identity-git.js +76 -0
  51. package/dist/repo-identity-linked-worktrees.js +81 -0
  52. package/dist/repo-identity.js +5 -222
  53. package/package.json +7 -7
@@ -0,0 +1,134 @@
1
+ /**
2
+ * `cockpit notes` reads: the library, one note, and your own shelf. Sibling
3
+ * of `notes.ts`, named in its header.
4
+ */
5
+ import { writeLine } from "./cli-io.js";
6
+ import { ask, fail, emit, sayScope, READ_DEADLINE_MS } from "./notes-door.js";
7
+ function libraryQuery(command) {
8
+ const query = new URLSearchParams();
9
+ if (command.series)
10
+ query.set("series", command.series);
11
+ if (command.meetingKind)
12
+ query.set("kind", command.meetingKind);
13
+ if (command.since)
14
+ query.set("since", command.since);
15
+ if (command.until)
16
+ query.set("until", command.until);
17
+ if (command.limit !== undefined)
18
+ query.set("limit", String(command.limit));
19
+ const rendered = query.toString();
20
+ return rendered === "" ? "" : `?${rendered}`;
21
+ }
22
+ export async function listNotes(command, door) {
23
+ const answer = await ask(door, {
24
+ path: `/api/notes/library${libraryQuery(command)}`,
25
+ method: "GET",
26
+ label: "notes list",
27
+ timeoutMs: READ_DEADLINE_MS,
28
+ });
29
+ if (!answer.ok)
30
+ return fail(door, answer.reason, answer.detail);
31
+ const body = answer.body;
32
+ if (door.json)
33
+ return emit(door, body);
34
+ sayScope(door, body);
35
+ const series = body.series ?? [];
36
+ if (series.length === 0) {
37
+ writeLine(door.io.stdout, "No meeting notes matched.");
38
+ return 0;
39
+ }
40
+ for (const one of series) {
41
+ writeLine(door.io.stdout, "");
42
+ writeLine(door.io.stdout, `${one.heading} (${one.notes.length})`);
43
+ for (const note of one.notes) {
44
+ const who = note.participants.length > 0 ? ` — ${note.participants.join(", ")}` : "";
45
+ writeLine(door.io.stdout, ` ${note.meetingDate} ${note.id} ${note.fileName}${who}`);
46
+ }
47
+ }
48
+ writeLine(door.io.stdout, "");
49
+ writeLine(door.io.stdout, `${body.count ?? 0} note(s)${body.more ? ", and more exist beyond the limit" : ""}.`);
50
+ return 0;
51
+ }
52
+ export async function listShelves(command, door) {
53
+ const answer = await ask(door, {
54
+ path: `/api/notes/library${libraryQuery(command)}`,
55
+ method: "GET",
56
+ label: "notes shelves",
57
+ timeoutMs: READ_DEADLINE_MS,
58
+ });
59
+ if (!answer.ok)
60
+ return fail(door, answer.reason, answer.detail);
61
+ const body = answer.body;
62
+ const shelves = (body.series ?? []).map((one) => ({
63
+ shelf: one.heading,
64
+ notes: one.notes.length,
65
+ // A shelf somebody typed, rather than the one a kind implies. Only the
66
+ // first kind is reported: a shelf is free text and can hold any of them.
67
+ custom: (body.categories ?? []).includes(one.heading),
68
+ }));
69
+ if (door.json)
70
+ return emit(door, { scope: body.scope, shelves });
71
+ sayScope(door, body);
72
+ if (shelves.length === 0) {
73
+ writeLine(door.io.stdout, "No shelves yet.");
74
+ return 0;
75
+ }
76
+ for (const shelf of shelves) {
77
+ writeLine(door.io.stdout, `${String(shelf.notes).padStart(4)} ${shelf.shelf}${shelf.custom ? "" : " (from the meeting kind)"}`);
78
+ }
79
+ return 0;
80
+ }
81
+ export async function showNote(command, door) {
82
+ const answer = await ask(door, {
83
+ path: `/api/notes/library/${encodeURIComponent(command.noteId ?? "")}`,
84
+ method: "GET",
85
+ label: "notes show",
86
+ timeoutMs: READ_DEADLINE_MS,
87
+ });
88
+ if (!answer.ok)
89
+ return fail(door, answer.reason, answer.detail);
90
+ const body = answer.body;
91
+ if (door.json)
92
+ return emit(door, body);
93
+ const note = body.note;
94
+ if (!note)
95
+ return fail(door, "no_note_in_answer", "Tower answered without a note.");
96
+ sayScope(door, body);
97
+ writeLine(door.io.stdout, note.title);
98
+ writeLine(door.io.stdout, `${note.meetingDate} · ${note.shelf} · ${note.fileName} · ${note.lineCount} lines`);
99
+ if (note.participants.length > 0) {
100
+ writeLine(door.io.stdout, `In the room: ${note.participants.join(", ")}`);
101
+ }
102
+ writeLine(door.io.stdout, note.visibility);
103
+ writeLine(door.io.stdout, "");
104
+ writeLine(door.io.stdout, note.content);
105
+ return 0;
106
+ }
107
+ export async function showShelf(command, door) {
108
+ const query = command.limit === undefined ? "" : `?limit=${command.limit}`;
109
+ const answer = await ask(door, {
110
+ path: `/api/notes/shelf${query}`,
111
+ method: "GET",
112
+ label: "notes shelf",
113
+ timeoutMs: READ_DEADLINE_MS,
114
+ });
115
+ if (!answer.ok)
116
+ return fail(door, answer.reason, answer.detail);
117
+ const body = answer.body;
118
+ if (door.json)
119
+ return emit(door, body);
120
+ sayScope(door, body);
121
+ const notes = body.notes ?? [];
122
+ if (notes.length === 0) {
123
+ writeLine(door.io.stdout, "You have not put any notes in yet.");
124
+ return 0;
125
+ }
126
+ for (const note of notes) {
127
+ const counts = note.countsKnown
128
+ ? `${note.statements} statements, ${note.openToTheTeam} open to the team, ${note.keptBack} kept back`
129
+ : "counts unknown on this server";
130
+ writeLine(door.io.stdout, `${note.meetingDate} ${note.id} ${note.shared ? "shared " : "yours "} ${note.name}`);
131
+ writeLine(door.io.stdout, ` ${counts}`);
132
+ }
133
+ return 0;
134
+ }
@@ -0,0 +1,208 @@
1
+ /**
2
+ * `cockpit notes` writes: putting a note in from a file or the clipboard,
3
+ * sharing it and taking it back, and moving it to another shelf. Sibling of
4
+ * `notes.ts`, named in its header.
5
+ */
6
+ import { isInteractiveStdin, readLine, readPipedText, writeLine, yesByDefault } from "./cli-io.js";
7
+ import { NOTE_SLOW_UPLOAD_BYTES, decodeTextBytes, noteFileRefusalSentence, readNoteFile, } from "./notes-file.js";
8
+ import { ask, emit, fail, sayUpload, errorText, TAG, READ_DEADLINE_MS } from "./notes-door.js";
9
+ /**
10
+ * The upload route's own ceiling is `maxDuration = 300` — reading a note is one
11
+ * model call over the whole file. The client waits slightly longer so the
12
+ * server's named failure wins the race whenever it manages to send one.
13
+ */
14
+ const UPLOAD_DEADLINE_MS = 305_000;
15
+ const PASTE_MAX_CHARS = 2_000_000;
16
+ export async function uploadNotes(command, door) {
17
+ const paths = command.paths ?? [];
18
+ const results = [];
19
+ let worstExit = 0;
20
+ for (const filePath of paths) {
21
+ // Read and screen locally BEFORE any network call — a refusal here never
22
+ // reaches the dashboard, same discipline as `cockpit jarvis --image`.
23
+ const read = await readNoteFile(filePath);
24
+ if (!read.ok) {
25
+ const sentence = noteFileRefusalSentence(read.refusal, filePath);
26
+ writeLine(door.io.stderr, `${TAG} file refused ${JSON.stringify({ reason: read.refusal, detail: read.detail })}`);
27
+ if (!door.json)
28
+ writeLine(door.io.stderr, sentence);
29
+ results.push({ path: filePath, ok: false, reason: read.refusal });
30
+ worstExit = 1;
31
+ continue;
32
+ }
33
+ if (read.bytes.byteLength >= NOTE_SLOW_UPLOAD_BYTES) {
34
+ // The route reads the whole note with one model call and may take
35
+ // minutes. A still cursor reads as a hang, so say what is happening.
36
+ writeLine(door.io.stderr, `Reading ${read.fileName} (${Math.round(read.bytes.byteLength / 1024)} KB). This can take a couple of minutes.`);
37
+ }
38
+ const form = new FormData();
39
+ form.set("file", new File([new Uint8Array(read.bytes)], read.fileName));
40
+ if (command.exclude)
41
+ form.set("exclusions", command.exclude);
42
+ const answer = await ask(door, {
43
+ path: "/api/notes/upload",
44
+ method: "POST",
45
+ label: "notes upload",
46
+ timeoutMs: UPLOAD_DEADLINE_MS,
47
+ body: form,
48
+ });
49
+ if (!answer.ok) {
50
+ writeLine(door.io.stderr, `${TAG} upload failed ${JSON.stringify({ reason: answer.reason })}`);
51
+ if (!door.json)
52
+ writeLine(door.io.stderr, `${read.fileName}: ${answer.detail}`);
53
+ results.push({ path: filePath, ok: false, reason: answer.reason });
54
+ worstExit = 1;
55
+ continue;
56
+ }
57
+ const body = answer.body;
58
+ results.push({ path: filePath, ok: body.stored === true, body });
59
+ if (body.stored !== true)
60
+ worstExit = 1;
61
+ if (!door.json)
62
+ sayUpload(door, body);
63
+ writeLine(door.io.stderr, `${TAG} upload answered ${JSON.stringify({
64
+ stored: body.stored === true,
65
+ note_id: body.noteId ?? null,
66
+ scope: body.scope ?? null,
67
+ byte_size: read.bytes.byteLength,
68
+ extension: read.extension,
69
+ })}`);
70
+ }
71
+ if (door.json) {
72
+ emit(door, { ok: worstExit === 0, uploaded: results.length, results });
73
+ }
74
+ return worstExit;
75
+ }
76
+ export async function pasteNote(command, door) {
77
+ let text;
78
+ if (command.filePath) {
79
+ const read = await readNoteFile(command.filePath);
80
+ if (!read.ok) {
81
+ const sentence = noteFileRefusalSentence(read.refusal, command.filePath);
82
+ writeLine(door.io.stderr, `${TAG} file refused ${JSON.stringify({ reason: read.refusal, detail: read.detail })}`);
83
+ return fail(door, read.refusal, sentence);
84
+ }
85
+ const decoded = decodeTextBytes(read.bytes);
86
+ if (!decoded.ok) {
87
+ writeLine(door.io.stderr, `${TAG} file refused ${JSON.stringify({ reason: decoded.reason })}`);
88
+ return fail(door, decoded.reason, "That file is not text this command can decode (it may be a binary or an unusual encoding). Nothing was sent.");
89
+ }
90
+ text = decoded.text;
91
+ }
92
+ else {
93
+ if (isInteractiveStdin(door.io)) {
94
+ return fail(door, "nothing_piped", "Pass --file <path> (safest on Windows), or pipe the note in "
95
+ + "(`pbpaste | cockpit notes paste` on macOS; on Windows use PowerShell 7 — "
96
+ + "Windows PowerShell 5.1 turns non-ASCII into `?` on pipes).");
97
+ }
98
+ try {
99
+ text = await readPipedText(door.io.stdin, {
100
+ maxChars: PASTE_MAX_CHARS,
101
+ overflowMessage: `A pasted note is limited to ${PASTE_MAX_CHARS} characters. Save it to a file and use --file instead.`,
102
+ });
103
+ }
104
+ catch (error) {
105
+ return fail(door, "paste_too_long", errorText(error));
106
+ }
107
+ }
108
+ if (text.trim() === "") {
109
+ return fail(door, "empty_paste", "There was nothing to paste. Nothing was sent.");
110
+ }
111
+ const form = new FormData();
112
+ form.set("text", text);
113
+ if (command.name)
114
+ form.set("name", command.name);
115
+ if (command.exclude)
116
+ form.set("exclusions", command.exclude);
117
+ // Bytes, not `.length` (BLI-3482). `NOTE_SLOW_UPLOAD_BYTES` is a BYTE
118
+ // threshold, and the file path above already compares `bytes.byteLength`
119
+ // against it; `text.length` counts UTF-16 units, so a note in any non-Latin
120
+ // script was measured at a third to a half of the size actually being sent
121
+ // and the "this will take a while" line stayed silent through the wait.
122
+ const pastedBytes = Buffer.byteLength(text, "utf8");
123
+ if (pastedBytes >= NOTE_SLOW_UPLOAD_BYTES) {
124
+ writeLine(door.io.stderr, `Reading ${Math.round(pastedBytes / 1024)} KB of pasted text. This can take a couple of minutes.`);
125
+ }
126
+ const answer = await ask(door, {
127
+ path: "/api/notes/upload",
128
+ method: "POST",
129
+ label: "notes paste",
130
+ timeoutMs: UPLOAD_DEADLINE_MS,
131
+ body: form,
132
+ });
133
+ if (!answer.ok)
134
+ return fail(door, answer.reason, answer.detail);
135
+ const body = answer.body;
136
+ writeLine(door.io.stderr, `${TAG} paste answered ${JSON.stringify({
137
+ stored: body.stored === true,
138
+ note_id: body.noteId ?? null,
139
+ scope: body.scope ?? null,
140
+ chars: text.length,
141
+ byte_size: pastedBytes,
142
+ named_by_caller: Boolean(command.name),
143
+ })}`);
144
+ if (door.json)
145
+ return emit(door, body, body.stored === true ? 0 : 1);
146
+ sayUpload(door, body);
147
+ return body.stored === true ? 0 : 1;
148
+ }
149
+ export async function shareNote(command, door) {
150
+ const wantsToShare = command.action === "share";
151
+ // Only sharing asks. Taking a note back narrows who can read it, and nobody
152
+ // needs to be talked out of that.
153
+ if (wantsToShare && !command.yes) {
154
+ if (!isInteractiveStdin(door.io)) {
155
+ return fail(door, "confirmation_required", "Sharing a note lets everyone signed in read it. Pass --yes to do it without being asked.");
156
+ }
157
+ const answer = await readLine(door.io, "Share this note with everyone signed in? [Y/n] ");
158
+ if (!yesByDefault(answer)) {
159
+ writeLine(door.io.stderr, `${TAG} share declined ${JSON.stringify({ reason: "operator_said_no" })}`);
160
+ writeLine(door.io.stdout, "Left alone. Nothing changed.");
161
+ return 0;
162
+ }
163
+ }
164
+ const answer = await ask(door, {
165
+ path: "/api/notes/share",
166
+ method: "POST",
167
+ label: wantsToShare ? "notes share" : "notes unshare",
168
+ timeoutMs: READ_DEADLINE_MS,
169
+ body: { note_id: command.noteId, share: wantsToShare },
170
+ });
171
+ if (!answer.ok)
172
+ return fail(door, answer.reason, answer.detail);
173
+ const body = answer.body;
174
+ writeLine(door.io.stderr, `${TAG} ${wantsToShare ? "shared" : "taken back"} ${JSON.stringify({
175
+ note_id: command.noteId ?? null,
176
+ stored: body.stored === true,
177
+ })}`);
178
+ if (door.json)
179
+ return emit(door, body);
180
+ sayUpload(door, body);
181
+ return 0;
182
+ }
183
+ export async function moveNote(command, door) {
184
+ const answer = await ask(door, {
185
+ path: "/api/notes/move",
186
+ method: "POST",
187
+ label: "notes move",
188
+ timeoutMs: READ_DEADLINE_MS,
189
+ // `--clear-shelf` sends the empty string, which is what the browser's own
190
+ // move box sends when a person empties it.
191
+ body: { note_id: command.noteId, category: command.clearShelf ? "" : command.to },
192
+ });
193
+ if (!answer.ok)
194
+ return fail(door, answer.reason, answer.detail);
195
+ const body = answer.body;
196
+ writeLine(door.io.stderr, `${TAG} move answered ${JSON.stringify({
197
+ note_id: command.noteId ?? null,
198
+ ok: body.ok === true,
199
+ reason: body.reason ?? null,
200
+ cleared: Boolean(command.clearShelf),
201
+ })}`);
202
+ if (door.json)
203
+ return emit(door, body, body.ok === true ? 0 : 1);
204
+ writeLine(door.io.stdout, body.headline ?? "Tower answered without a sentence.");
205
+ for (const line of body.lines ?? [])
206
+ writeLine(door.io.stdout, line);
207
+ return body.ok === true ? 0 : 1;
208
+ }