@bli-cockpit/cli 0.2.184 → 0.2.186
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/commands/local-help-commands-tower.js +1 -1
- package/dist/commands/memory-project-label.js +9 -0
- package/dist/commands/memory-store.js +3 -2
- package/dist/commands/notes-audio-direct.js +77 -0
- package/dist/commands/notes-file.js +24 -6
- package/dist/commands/notes-reads.js +6 -0
- package/dist/commands/notes-writes.js +53 -23
- package/dist/commands/public-root.js +1 -1
- package/package.json +3 -3
|
@@ -342,7 +342,7 @@ export const TOWER_COMMAND_HELP = [
|
|
|
342
342
|
"notes",
|
|
343
343
|
[
|
|
344
344
|
"Usage: cockpit notes [list|show <id>|shelf|shelves|folders|mkdir <path>|rmdir <path>|rename <path>|upload <paths...> [--wait]|paste|share <id>|unshare <id>|move <id>|accounts|connect|detach <id>|sync <id>|sync-status [<id>]|recordings <note-id>|recording <id>] [flags]",
|
|
345
|
-
"Audio uploads: MP3, M4A, WAV, WebM and OGG, up to 4 MB
|
|
345
|
+
"Audio uploads: MP3, M4A, WAV, WebM and OGG, up to 1 GB. Over 4 MB (a whole meeting) the file goes straight to storage and is transcribed in the background; --wait then polls for up to 4 hours and prints progress, otherwise for at most 330 seconds.",
|
|
346
346
|
"",
|
|
347
347
|
"folders [--tree] [--json]: list folders and visible note counts.",
|
|
348
348
|
"mkdir <path> [--json]: create a folder and any missing ancestors.",
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Cross-project search always names the project, after the date and before the author. */
|
|
2
|
+
export function memoryProjectPrefix(row) {
|
|
3
|
+
if (typeof row["project"] !== "string" || !row["project"])
|
|
4
|
+
return "";
|
|
5
|
+
const date = typeof row["createdAt"] === "string" ? new Date(row["createdAt"]) : null;
|
|
6
|
+
const day = date && !Number.isNaN(date.getTime()) ? `[${date.toISOString().slice(0, 10)}] ` : "";
|
|
7
|
+
const author = typeof row["savedBy"] === "string" && row["savedBy"] ? `(from ${row["savedBy"]}) ` : "";
|
|
8
|
+
return `${day}[${row["project"]}] ${author}`;
|
|
9
|
+
}
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
* `ps` shows an argument list, shell history keeps it, and a memory with a
|
|
43
43
|
* newline in it cannot be quoted correctly anyway.
|
|
44
44
|
*/
|
|
45
|
+
import { memoryProjectPrefix } from "./memory-project-label.js";
|
|
45
46
|
import { resolveContainerTag } from "@bli-cockpit/memory-mcp/dist/container-tag.js";
|
|
46
47
|
import { askAgentDoor, emitAgentDoor, failAgentDoor, openAgentDoor, } from "./agent-door.js";
|
|
47
48
|
import { isInteractiveStdin, readPipedText, writeLine } from "./cli-io.js";
|
|
@@ -208,7 +209,7 @@ async function searchMemory(command, door) {
|
|
|
208
209
|
}
|
|
209
210
|
else {
|
|
210
211
|
for (const row of results) {
|
|
211
|
-
writeLine(door.io.stdout, `${similarityLabel(row)}${oneLine(row["memory"])}`);
|
|
212
|
+
writeLine(door.io.stdout, `${similarityLabel(row)}${memoryProjectPrefix(row)}${oneLine(row["memory"])}`);
|
|
212
213
|
writeLine(door.io.stdout, ` ${String(row["id"] ?? "")} ${String(row["containerTag"] ?? "")}${filedByLabel(row)}`);
|
|
213
214
|
}
|
|
214
215
|
}
|
|
@@ -217,7 +218,7 @@ async function searchMemory(command, door) {
|
|
|
217
218
|
writeLine(door.io.stdout, "Recent context:");
|
|
218
219
|
for (const row of recent) {
|
|
219
220
|
const author = typeof row["savedBy"] === "string" && row["savedBy"] ? `(from ${row["savedBy"]}) ` : "";
|
|
220
|
-
writeLine(door.io.stdout, ` - ${author}${oneLine(row["preview"] ?? row["memory"])}`);
|
|
221
|
+
writeLine(door.io.stdout, ` - ${memoryProjectPrefix(row)}${row["project"] ? "" : author}${oneLine(row["preview"] ?? row["memory"])}`);
|
|
221
222
|
}
|
|
222
223
|
}
|
|
223
224
|
if (body.degraded) {
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cockpit notes upload` for a whole-meeting recording (BLI-12963).
|
|
3
|
+
*
|
|
4
|
+
* A recording over 4 MB cannot pass through the dashboard's upload route (a
|
|
5
|
+
* Vercel function accepts 4.5 MB). So the file goes straight to storage:
|
|
6
|
+
*
|
|
7
|
+
* 1. ask Tower for a signed upload URL POST /api/notes/audio/target
|
|
8
|
+
* 2. PUT the file from disk to that URL (streamed; never held in memory)
|
|
9
|
+
* 3. tell Tower it is there POST /api/notes/audio/saved
|
|
10
|
+
*
|
|
11
|
+
* Tower then transcribes it in the background and the note shows progress.
|
|
12
|
+
* Step 2 goes to the storage host with the signed URL ONLY: the device token is
|
|
13
|
+
* sent to Tower's own routes in steps 1 and 3 and never to anyone else.
|
|
14
|
+
*
|
|
15
|
+
* Nothing here throws; every failure comes back as a closed reason label and
|
|
16
|
+
* the sentence Tower (or this file) wrote for it.
|
|
17
|
+
*/
|
|
18
|
+
import { createReadStream } from "node:fs";
|
|
19
|
+
import { Readable } from "node:stream";
|
|
20
|
+
import { writeLine } from "./cli-io.js";
|
|
21
|
+
import { ask, TAG } from "./notes-door.js";
|
|
22
|
+
import { noteAudioMime } from "./notes-file.js";
|
|
23
|
+
/** A signed upload URL lives two hours; the PUT must finish inside that. */
|
|
24
|
+
const PUT_DEADLINE_MS = 2 * 60 * 60 * 1000;
|
|
25
|
+
const SMALL_DEADLINE_MS = 60_000;
|
|
26
|
+
export async function uploadAudioDirect(door, input) {
|
|
27
|
+
const started = Date.now();
|
|
28
|
+
const mediaType = noteAudioMime(input.fileName) ?? "";
|
|
29
|
+
const target = await ask(door, {
|
|
30
|
+
path: "/api/notes/audio/target", method: "POST", label: "notes audio target", timeoutMs: SMALL_DEADLINE_MS,
|
|
31
|
+
body: { file_name: input.fileName, bytes: input.size, media_type: mediaType },
|
|
32
|
+
});
|
|
33
|
+
if (!target.ok)
|
|
34
|
+
return { ok: false, reason: target.reason, detail: target.detail };
|
|
35
|
+
const issued = target.body;
|
|
36
|
+
const url = issued.upload?.url;
|
|
37
|
+
if (!issued.objectKey || !url) {
|
|
38
|
+
return { ok: false, reason: "target_unreadable", detail: "Tower answered without an upload link." };
|
|
39
|
+
}
|
|
40
|
+
writeLine(door.io.stderr, `Uploading ${input.fileName} (${(input.size / 1_000_000).toFixed(1)} MB) straight to storage...`);
|
|
41
|
+
let put;
|
|
42
|
+
try {
|
|
43
|
+
put = await door.io.fetch(url, {
|
|
44
|
+
method: issued.upload?.method ?? "PUT",
|
|
45
|
+
headers: { ...(issued.upload?.headers ?? {}), "content-length": String(input.size) },
|
|
46
|
+
body: Readable.toWeb(createReadStream(input.filePath)),
|
|
47
|
+
// Node's fetch requires this for a streamed request body.
|
|
48
|
+
duplex: "half",
|
|
49
|
+
signal: AbortSignal.timeout(PUT_DEADLINE_MS),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
writeLine(door.io.stderr, `${TAG} storage put failed ${JSON.stringify({ reason: "storage_unreachable", bytes: input.size })}`);
|
|
54
|
+
return { ok: false, reason: "storage_unreachable",
|
|
55
|
+
detail: `Could not reach storage to upload the file (${error instanceof Error ? error.name : "error"}). Nothing was saved; try again.` };
|
|
56
|
+
}
|
|
57
|
+
if (!put.ok) {
|
|
58
|
+
// The storage host's reply can quote the object path; only the status leaves this function.
|
|
59
|
+
writeLine(door.io.stderr, `${TAG} storage put refused ${JSON.stringify({ reason: `storage_http_${put.status}`, bytes: input.size })}`);
|
|
60
|
+
return { ok: false, reason: `storage_http_${put.status}`,
|
|
61
|
+
detail: put.status === 413
|
|
62
|
+
? "Storage refused the file as too large. An admin may need to raise the project's storage file size limit."
|
|
63
|
+
: `Storage refused the upload (HTTP ${put.status}). Nothing was saved; try again.` };
|
|
64
|
+
}
|
|
65
|
+
writeLine(door.io.stderr, `${TAG} storage put ok ${JSON.stringify({ bytes: input.size, elapsed_ms: Date.now() - started })}`);
|
|
66
|
+
const saved = await ask(door, {
|
|
67
|
+
path: "/api/notes/audio/saved", method: "POST", label: "notes audio saved", timeoutMs: SMALL_DEADLINE_MS,
|
|
68
|
+
body: {
|
|
69
|
+
object_key: issued.objectKey, file_name: input.fileName,
|
|
70
|
+
...(input.exclude ? { exclusions: input.exclude } : {}),
|
|
71
|
+
...(input.folder ? { folder_path: input.folder } : {}),
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
if (!saved.ok)
|
|
75
|
+
return { ok: false, reason: saved.reason, detail: saved.detail };
|
|
76
|
+
return { ok: true, body: saved.body };
|
|
77
|
+
}
|
|
@@ -39,6 +39,15 @@ export function noteAudioMime(name) {
|
|
|
39
39
|
return mime[name.split(".").at(-1)?.toLowerCase() ?? ""];
|
|
40
40
|
}
|
|
41
41
|
export const NOTE_FILE_MAX_BYTES = 20 * 1024 * 1024;
|
|
42
|
+
/**
|
|
43
|
+
* The most audio that goes THROUGH the upload route (a Vercel function accepts
|
|
44
|
+
* 4.5 MB per request). Bigger audio goes straight to storage on a signed URL
|
|
45
|
+
* and is transcribed in the background (BLI-12963); this mirrors
|
|
46
|
+
* `AUDIO_MAX_BYTES` in the dashboard's `lib/notes/audio.ts`.
|
|
47
|
+
*/
|
|
48
|
+
export const AUDIO_INLINE_MAX_BYTES = 4_000_000;
|
|
49
|
+
/** The most audio the direct path takes; mirrors the bucket's `file_size_limit`. */
|
|
50
|
+
export const AUDIO_DIRECT_MAX_BYTES = 1_000_000_000;
|
|
42
51
|
/**
|
|
43
52
|
* Above this, the terminal says it is working before it starts. The route's own
|
|
44
53
|
* ceiling is `maxDuration = 300` because reading a note is one model call over
|
|
@@ -56,7 +65,7 @@ export function noteFileRefusalSentence(refusal, filePath) {
|
|
|
56
65
|
case "file_empty":
|
|
57
66
|
return `There is nothing in that file: ${filePath}`;
|
|
58
67
|
case "file_too_big":
|
|
59
|
-
return "That file is too big. Audio is limited to 4 MB; other notes to 20 MB.";
|
|
68
|
+
return "That file is too big. Audio is limited to 1 GB (4 MB for anything but `cockpit notes upload`); other notes to 20 MB.";
|
|
60
69
|
case "looks_like_a_key_file":
|
|
61
70
|
// The same rule the server's gate applies, said the same way: the name is
|
|
62
71
|
// all it takes to decide, and looking inside to be sure would already be
|
|
@@ -70,7 +79,7 @@ export function noteFileRefusalSentence(refusal, filePath) {
|
|
|
70
79
|
* The secret-shaped-name check runs FIRST and on the name alone — the bytes are
|
|
71
80
|
* never read for a file it turns away.
|
|
72
81
|
*/
|
|
73
|
-
export async function readNoteFile(filePath) {
|
|
82
|
+
export async function readNoteFile(filePath, options = {}) {
|
|
74
83
|
const fileName = path.basename(filePath);
|
|
75
84
|
const extension = path.extname(filePath).toLowerCase();
|
|
76
85
|
if (isSecretLikePathSegment(fileName)) {
|
|
@@ -92,10 +101,19 @@ export async function readNoteFile(filePath) {
|
|
|
92
101
|
}
|
|
93
102
|
if (size === 0)
|
|
94
103
|
return { ok: false, refusal: "file_empty", detail: "byte_size_0" };
|
|
95
|
-
if (noteAudioMime(fileName)
|
|
96
|
-
|
|
104
|
+
if (noteAudioMime(fileName)) {
|
|
105
|
+
// Only `cockpit notes upload` has a direct-to-storage path; every other
|
|
106
|
+
// caller wants the bytes, and still refuses audio the route cannot take.
|
|
107
|
+
const ceiling = options.allowDirectAudio ? AUDIO_DIRECT_MAX_BYTES : AUDIO_INLINE_MAX_BYTES;
|
|
108
|
+
if (size > ceiling) {
|
|
109
|
+
return { ok: false, refusal: "file_too_big", detail: `file_too_large: audio limit is ${ceiling} bytes` };
|
|
110
|
+
}
|
|
111
|
+
// Big audio is never read into memory here; the upload streams it from disk.
|
|
112
|
+
if (size > AUDIO_INLINE_MAX_BYTES) {
|
|
113
|
+
return { ok: true, bytes: Buffer.alloc(0), fileName, extension: extension || "none", size, direct: true };
|
|
114
|
+
}
|
|
97
115
|
}
|
|
98
|
-
if (size > NOTE_FILE_MAX_BYTES) {
|
|
116
|
+
else if (size > NOTE_FILE_MAX_BYTES) {
|
|
99
117
|
return { ok: false, refusal: "file_too_big", detail: `byte_size_${size}` };
|
|
100
118
|
}
|
|
101
119
|
let bytes;
|
|
@@ -105,7 +123,7 @@ export async function readNoteFile(filePath) {
|
|
|
105
123
|
catch (error) {
|
|
106
124
|
return { ok: false, refusal: "file_unreadable", detail: errorMessage(error) };
|
|
107
125
|
}
|
|
108
|
-
return { ok: true, bytes, fileName, extension: extension || "none" };
|
|
126
|
+
return { ok: true, bytes, fileName, extension: extension || "none", size, direct: false };
|
|
109
127
|
}
|
|
110
128
|
/**
|
|
111
129
|
* Decodes note/env bytes to text the way an editor would, not the way UTF-8
|
|
@@ -129,6 +129,12 @@ export async function showNote(command, door) {
|
|
|
129
129
|
writeLine(door.io.stdout, `Invited: ${note.participants.join(", ")}`);
|
|
130
130
|
}
|
|
131
131
|
writeLine(door.io.stdout, note.visibility);
|
|
132
|
+
// A recording is transcribed in the background: say where that stands, and why
|
|
133
|
+
// when it has stopped, rather than letting the placeholder body read as the note.
|
|
134
|
+
if (body.audio?.transcription_status) {
|
|
135
|
+
const detail = body.audio.transcription_detail ? ` ${body.audio.transcription_detail}` : "";
|
|
136
|
+
writeLine(door.io.stdout, `Transcription: ${body.audio.transcription_status}.${detail}`);
|
|
137
|
+
}
|
|
132
138
|
writeLine(door.io.stdout, "");
|
|
133
139
|
writeLine(door.io.stdout, note.content.replace(/^In the room:/gm, "Invited:"));
|
|
134
140
|
return 0;
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { isInteractiveStdin, readPipedText, writeLine, askYesNo } from "./cli-io.js";
|
|
7
7
|
import { NOTE_SLOW_UPLOAD_BYTES, noteAudioMime, decodeTextBytes, noteFileRefusalSentence, readNoteFile, } from "./notes-file.js";
|
|
8
8
|
import { ask, emit, fail, sayUpload, errorText, TAG, READ_DEADLINE_MS } from "./notes-door.js";
|
|
9
|
+
import { uploadAudioDirect } from "./notes-audio-direct.js";
|
|
9
10
|
/**
|
|
10
11
|
* The upload route's own ceiling is `maxDuration = 300` — reading a note is one
|
|
11
12
|
* model call over the whole file. The client waits slightly longer so the
|
|
@@ -20,7 +21,7 @@ export async function uploadNotes(command, door) {
|
|
|
20
21
|
for (const filePath of paths) {
|
|
21
22
|
// Read and screen locally BEFORE any network call — a refusal here never
|
|
22
23
|
// reaches the dashboard, same discipline as `cockpit jarvis --image`.
|
|
23
|
-
const read = await readNoteFile(filePath);
|
|
24
|
+
const read = await readNoteFile(filePath, { allowDirectAudio: true });
|
|
24
25
|
if (!read.ok) {
|
|
25
26
|
const sentence = noteFileRefusalSentence(read.refusal, filePath);
|
|
26
27
|
writeLine(door.io.stderr, `${TAG} file refused ${JSON.stringify({ reason: read.refusal, detail: read.detail })}`);
|
|
@@ -30,24 +31,36 @@ export async function uploadNotes(command, door) {
|
|
|
30
31
|
worstExit = 1;
|
|
31
32
|
continue;
|
|
32
33
|
}
|
|
33
|
-
if (read.bytes.byteLength >= NOTE_SLOW_UPLOAD_BYTES) {
|
|
34
|
+
if (!read.direct && read.bytes.byteLength >= NOTE_SLOW_UPLOAD_BYTES) {
|
|
34
35
|
// The route reads the whole note with one model call and may take
|
|
35
36
|
// minutes. A still cursor reads as a hang, so say what is happening.
|
|
36
37
|
writeLine(door.io.stderr, `Reading ${read.fileName} (${Math.round(read.bytes.byteLength / 1024)} KB). This can take a couple of minutes.`);
|
|
37
38
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
39
|
+
// A whole-meeting recording (over 4 MB) goes straight to storage and is
|
|
40
|
+
// transcribed in the background (BLI-12963); everything else keeps the
|
|
41
|
+
// upload route.
|
|
42
|
+
let answer;
|
|
43
|
+
if (read.direct) {
|
|
44
|
+
const direct = await uploadAudioDirect(door, {
|
|
45
|
+
filePath, fileName: read.fileName, size: read.size, exclude: command.exclude, folder: command.folder,
|
|
46
|
+
});
|
|
47
|
+
answer = direct.ok ? { ok: true, body: direct.body } : { ok: false, reason: direct.reason, detail: direct.detail };
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
const form = new FormData();
|
|
51
|
+
form.set("file", new File([new Uint8Array(read.bytes)], read.fileName, { type: noteAudioMime(read.fileName) ?? "" }));
|
|
52
|
+
if (command.exclude)
|
|
53
|
+
form.set("exclusions", command.exclude);
|
|
54
|
+
if (command.folder)
|
|
55
|
+
form.set("folder_path", command.folder);
|
|
56
|
+
answer = await ask(door, {
|
|
57
|
+
path: "/api/notes/upload",
|
|
58
|
+
method: "POST",
|
|
59
|
+
label: "notes upload",
|
|
60
|
+
timeoutMs: UPLOAD_DEADLINE_MS,
|
|
61
|
+
body: form,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
51
64
|
if (!answer.ok) {
|
|
52
65
|
writeLine(door.io.stderr, `${TAG} upload failed ${JSON.stringify({ reason: answer.reason })}`);
|
|
53
66
|
if (!door.json)
|
|
@@ -58,7 +71,7 @@ export async function uploadNotes(command, door) {
|
|
|
58
71
|
}
|
|
59
72
|
const body = answer.body;
|
|
60
73
|
if (command.wait && body.noteId && body.transcription?.status === "pending") {
|
|
61
|
-
body.transcription = await waitForTranscription(door, body.noteId);
|
|
74
|
+
body.transcription = await waitForTranscription(door, body.noteId, read.direct);
|
|
62
75
|
}
|
|
63
76
|
if (body.transcription?.status === "failed")
|
|
64
77
|
worstExit = 1;
|
|
@@ -73,7 +86,8 @@ export async function uploadNotes(command, door) {
|
|
|
73
86
|
stored: body.stored === true,
|
|
74
87
|
note_id: body.noteId ?? null,
|
|
75
88
|
scope: body.scope ?? null,
|
|
76
|
-
byte_size: read.
|
|
89
|
+
byte_size: read.size,
|
|
90
|
+
direct: read.direct,
|
|
77
91
|
extension: read.extension,
|
|
78
92
|
})}`);
|
|
79
93
|
}
|
|
@@ -257,22 +271,38 @@ export async function moveNote(command, door) {
|
|
|
257
271
|
writeLine(door.io.stdout, line);
|
|
258
272
|
return body.ok === true ? 0 : 1;
|
|
259
273
|
}
|
|
260
|
-
/**
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
274
|
+
/**
|
|
275
|
+
* Bounded polling of the same note read used by the browser and MCP. A small
|
|
276
|
+
* file is transcribed inside the upload request's own budget, so five and a
|
|
277
|
+
* half minutes is plenty; a whole-meeting recording is transcribed by the
|
|
278
|
+
* background job, which can take an hour, so `--wait` there polls for up to
|
|
279
|
+
* four hours and prints each new progress line.
|
|
280
|
+
*/
|
|
281
|
+
async function waitForTranscription(door, noteId, background = false) {
|
|
282
|
+
const budgetMs = background ? 4 * 60 * 60 * 1000 : 330_000;
|
|
283
|
+
const intervalMs = background ? 10_000 : 3000;
|
|
284
|
+
const deadline = Date.now() + budgetMs;
|
|
285
|
+
let lastDetail = "";
|
|
286
|
+
while (Date.now() < deadline) {
|
|
264
287
|
const answer = await ask(door, { path: `/api/notes/library/${encodeURIComponent(noteId)}`,
|
|
265
288
|
method: "GET", label: "notes transcription", timeoutMs: Math.min(10_000, deadline - Date.now()) });
|
|
266
289
|
if (!answer.ok)
|
|
267
290
|
return { status: "failed", reason: answer.reason };
|
|
268
|
-
const
|
|
291
|
+
const audio = answer.body.audio;
|
|
292
|
+
const status = audio?.transcription_status;
|
|
269
293
|
if (status === "done")
|
|
270
294
|
return { status: "done" };
|
|
271
295
|
if (status?.startsWith("failed:"))
|
|
272
296
|
return { status: "failed", reason: status.slice(7) };
|
|
273
297
|
if (status !== "pending")
|
|
274
298
|
return { status: "failed", reason: "transcription_status_unavailable" };
|
|
275
|
-
|
|
299
|
+
if (audio?.transcription_detail && audio.transcription_detail !== lastDetail) {
|
|
300
|
+
lastDetail = audio.transcription_detail;
|
|
301
|
+
if (!door.json)
|
|
302
|
+
writeLine(door.io.stderr, lastDetail);
|
|
303
|
+
}
|
|
304
|
+
const pause = Math.min(intervalMs, Math.max(0, deadline - Date.now()));
|
|
305
|
+
await (door.io.sleep ? door.io.sleep(pause) : new Promise((resolve) => setTimeout(resolve, pause)));
|
|
276
306
|
}
|
|
277
307
|
return { status: "failed", reason: "transcription_wait_timeout" };
|
|
278
308
|
}
|
|
@@ -21,7 +21,7 @@ export async function runCockpitCli(argv, io) {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
if (command === "--version" || command === "-V" || command === "version") {
|
|
24
|
-
writeLine(io?.stdout ?? process.stdout, "0.2.
|
|
24
|
+
writeLine(io?.stdout ?? process.stdout, "0.2.186");
|
|
25
25
|
return 0;
|
|
26
26
|
}
|
|
27
27
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bli-cockpit/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.186",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"test": "node dist/cli.js --help && node ../../scripts/assert-public-cli-routing.mjs && node ../../scripts/assert-public-cli-verb-help.mjs && node ../../scripts/assert-public-cli-surface-doors.mjs && node ../../scripts/assert-public-cli-runtime-files.mjs && node ../../scripts/assert-public-cli-exit-contract.mjs && node ../../scripts/assert-public-cli-no-fleet-posts.mjs && node ../../scripts/assert-public-cli-doctor-json.mjs && node ../../scripts/assert-public-package-pack.mjs --workspace=@bli-cockpit/cli"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@bli-cockpit/memory-mcp": "0.1.
|
|
31
|
-
"@bli-cockpit/mcp": "0.1.
|
|
30
|
+
"@bli-cockpit/memory-mcp": "0.1.56",
|
|
31
|
+
"@bli-cockpit/mcp": "0.1.99",
|
|
32
32
|
"@bli-cockpit/telemetry-core": "0.1.66"
|
|
33
33
|
},
|
|
34
34
|
"repository": {
|