@bli-cockpit/cli 0.2.39 → 0.2.40
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/cli-io.js +33 -1
- package/dist/commands/correct.js +2 -13
- package/dist/commands/jarvis.js +2 -13
- package/dist/commands/local-help.js +1 -1
- package/dist/commands/notes-file.js +27 -0
- package/dist/commands/notes.js +15 -17
- package/dist/commands/public-root.js +1 -1
- package/dist/commands/settings.js +3 -2
- package/dist/commands/tower-command.js +7 -8
- package/package.json +1 -1
package/dist/commands/cli-io.js
CHANGED
|
@@ -20,7 +20,39 @@ export function writeExecOutput(io, result, options) {
|
|
|
20
20
|
writeRaw(io.stderr, result.stderr);
|
|
21
21
|
}
|
|
22
22
|
export function isInteractiveStdin(io) {
|
|
23
|
-
|
|
23
|
+
const isTTY = io.stdin.isTTY;
|
|
24
|
+
// Git Bash (mintty/MSYS) gives Node a pipe for an interactive terminal, so
|
|
25
|
+
// `isTTY` is undefined and the piped branch would wait forever on an EOF
|
|
26
|
+
// that never comes (BLI-3480). MSYSTEM is set only inside MSYS shells, so
|
|
27
|
+
// launchd / Task Scheduler / spawned runs keep the non-interactive branch.
|
|
28
|
+
if (isTTY === undefined && process.env["MSYSTEM"])
|
|
29
|
+
return true;
|
|
30
|
+
return Boolean(isTTY);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Drops one leading U+FEFF. PowerShell 5.1 redirection and Notepad both write
|
|
34
|
+
* byte-order marks; stored verbatim, a BOM makes the first line of an env blob
|
|
35
|
+
* unreachable and pollutes pasted notes (BLI-3480). One strip, never more —
|
|
36
|
+
* a BOM anywhere else is content.
|
|
37
|
+
*/
|
|
38
|
+
export function stripLeadingBom(text) {
|
|
39
|
+
return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Reads piped stdin whole as UTF-8, BOM-stripped. The one implementation
|
|
43
|
+
* behind every stdin-fed command (jarvis, correct, notes paste, settings env
|
|
44
|
+
* set) — each caller keeps its own size cap via `maxChars`.
|
|
45
|
+
*/
|
|
46
|
+
export async function readPipedText(stream, options) {
|
|
47
|
+
stream.setEncoding("utf8");
|
|
48
|
+
let text = "";
|
|
49
|
+
for await (const chunk of stream) {
|
|
50
|
+
text += chunk;
|
|
51
|
+
if (options?.maxChars !== undefined && text.length > options.maxChars) {
|
|
52
|
+
throw new Error(options.overflowMessage ?? `Piped input is limited to ${options.maxChars} characters.`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return stripLeadingBom(text);
|
|
24
56
|
}
|
|
25
57
|
/**
|
|
26
58
|
* Reads one line from stdin after writing a prompt. Shared by the onboard email
|
package/dist/commands/correct.js
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* is printed on stdout with an exit code of 0 — the correction WAS filed, with
|
|
24
24
|
* its outcome recorded. Only a failure to file at all is a non-zero exit.
|
|
25
25
|
*/
|
|
26
|
-
import { isInteractiveStdin, writeLine } from "./cli-io.js";
|
|
26
|
+
import { isInteractiveStdin, readPipedText, writeLine } from "./cli-io.js";
|
|
27
27
|
import { loadPairedSession, towerJsonRequest } from "../tower-client.js";
|
|
28
28
|
const DIM = "\x1b[2m";
|
|
29
29
|
const RESET = "\x1b[0m";
|
|
@@ -134,21 +134,10 @@ async function resolveText(command, io) {
|
|
|
134
134
|
return command.text;
|
|
135
135
|
if (isInteractiveStdin(io))
|
|
136
136
|
return null;
|
|
137
|
-
const piped = await
|
|
137
|
+
const piped = await readPipedText(io.stdin, { maxChars: MAX_CORRECTION_LENGTH, overflowMessage: `A correction is limited to ${MAX_CORRECTION_LENGTH} characters.` });
|
|
138
138
|
const trimmed = piped.trim();
|
|
139
139
|
return trimmed.length > 0 ? trimmed : null;
|
|
140
140
|
}
|
|
141
|
-
async function readAll(stream) {
|
|
142
|
-
stream.setEncoding("utf8");
|
|
143
|
-
let text = "";
|
|
144
|
-
for await (const chunk of stream) {
|
|
145
|
-
text += chunk;
|
|
146
|
-
if (text.length > MAX_CORRECTION_LENGTH) {
|
|
147
|
-
throw new Error(`A correction is limited to ${MAX_CORRECTION_LENGTH} characters.`);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
return text;
|
|
151
|
-
}
|
|
152
141
|
function writeFailure(command, io, reason, detail) {
|
|
153
142
|
if (command.json) {
|
|
154
143
|
writeLine(io.stdout, JSON.stringify({ ok: false, error: reason, detail }));
|
package/dist/commands/jarvis.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* existing paired device session, and every turn is executed by the dashboard
|
|
6
6
|
* through the same JARVIS runtime used by web chat and Slack.
|
|
7
7
|
*/
|
|
8
|
-
import { isInteractiveStdin, readLine, writeLine } from "./cli-io.js";
|
|
8
|
+
import { isInteractiveStdin, readLine, readPipedText, writeLine } from "./cli-io.js";
|
|
9
9
|
import { attachedFileRefusalSentence, readAttachedImage, } from "./jarvis-attachment.js";
|
|
10
10
|
import { loadPairedSession, towerFailureDetail, towerJsonRequest, towerRequest, } from "../tower-client.js";
|
|
11
11
|
import { readTowerTurn, streamFailureDetail, } from "../tower-stream.js";
|
|
@@ -136,7 +136,7 @@ async function resolveOneShotPrompt(command, io) {
|
|
|
136
136
|
return validatePrompt(command.prompt);
|
|
137
137
|
if (isInteractiveStdin(io))
|
|
138
138
|
return null;
|
|
139
|
-
const piped = await
|
|
139
|
+
const piped = await readPipedText(io.stdin, { maxChars: 4000, overflowMessage: "JARVIS questions are limited to 4000 characters." });
|
|
140
140
|
return validatePrompt(piped);
|
|
141
141
|
}
|
|
142
142
|
async function sendOneTurn(context, prompt, io) {
|
|
@@ -368,15 +368,4 @@ function validatePrompt(raw) {
|
|
|
368
368
|
throw new Error("JARVIS questions are limited to 4000 characters.");
|
|
369
369
|
}
|
|
370
370
|
return prompt;
|
|
371
|
-
}
|
|
372
|
-
async function readAll(stream) {
|
|
373
|
-
stream.setEncoding("utf8");
|
|
374
|
-
let text = "";
|
|
375
|
-
for await (const chunk of stream) {
|
|
376
|
-
text += chunk;
|
|
377
|
-
if (text.length > 4000) {
|
|
378
|
-
throw new Error("JARVIS questions are limited to 4000 characters.");
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
return text;
|
|
382
371
|
}
|
|
@@ -348,7 +348,7 @@ function localSubcommandHelp(command) {
|
|
|
348
348
|
"shelf [--limit <n>] — the notes YOU have put in, and what became of each.",
|
|
349
349
|
"shelves — the shelves in use, with how many notes are on each.",
|
|
350
350
|
"upload <paths...> [--exclude \"<sentence>\"] — put one or more local files in. Explicit paths only; your shell does any globbing, and a file that does not exist is named rather than skipped.",
|
|
351
|
-
"paste [--file <path>] [--name <n>] [--exclude \"<sentence>\"] — put text in
|
|
351
|
+
"paste [--file <path>] [--name <n>] [--exclude \"<sentence>\"] — put text in. --file <path> is safest (any editor encoding is decoded). Stdin works too: `pbpaste | cockpit notes paste` on macOS; on Windows use PowerShell 7 (`Get-Clipboard | cockpit notes paste`) — PowerShell 5.1 turns non-ASCII into `?` on pipes.",
|
|
352
352
|
"--exclude carries your own sentence about what to leave out, exactly as the browser's box does.",
|
|
353
353
|
"share <id> [--yes] — let everyone signed in read the statements that are safe to share. Asks first in a terminal; --yes is required without one, and --json implies --yes.",
|
|
354
354
|
"unshare <id> — take it back. Never asks: it only ever narrows who can read.",
|
|
@@ -99,4 +99,31 @@ export async function readNoteFile(filePath) {
|
|
|
99
99
|
return { ok: false, refusal: "file_unreadable", detail: errorMessage(error) };
|
|
100
100
|
}
|
|
101
101
|
return { ok: true, bytes, fileName, extension: extension || "none" };
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Decodes note/env bytes to text the way an editor would, not the way UTF-8
|
|
105
|
+
* hopes (BLI-3480). PowerShell 5.1 redirection writes UTF-16LE with a BOM and
|
|
106
|
+
* Notepad's "Unicode" save does the same; decoded blindly as UTF-8 that text
|
|
107
|
+
* becomes NUL-interleaved mojibake that passes an emptiness check and stores
|
|
108
|
+
* silently. The BOM decides the codec; a BOM-less file that still decodes to
|
|
109
|
+
* NULs is refused by name rather than stored as garbage.
|
|
110
|
+
*/
|
|
111
|
+
export function decodeTextBytes(bytes) {
|
|
112
|
+
let text;
|
|
113
|
+
if (bytes.length >= 2 && bytes[0] === 0xff && bytes[1] === 0xfe) {
|
|
114
|
+
text = bytes.subarray(2).toString("utf16le");
|
|
115
|
+
}
|
|
116
|
+
else if (bytes.length >= 2 && bytes[0] === 0xfe && bytes[1] === 0xff) {
|
|
117
|
+
text = new TextDecoder("utf-16be").decode(bytes.subarray(2));
|
|
118
|
+
}
|
|
119
|
+
else if (bytes.length >= 3 && bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf) {
|
|
120
|
+
text = bytes.subarray(3).toString("utf8");
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
text = bytes.toString("utf8");
|
|
124
|
+
}
|
|
125
|
+
if (text.includes("\u0000")) {
|
|
126
|
+
return { ok: false, reason: "undecodable_text_encoding" };
|
|
127
|
+
}
|
|
128
|
+
return { ok: true, text };
|
|
102
129
|
}
|
package/dist/commands/notes.js
CHANGED
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
* run has nobody to ask and asking it to guess is worse than the flag it
|
|
24
24
|
* already typed the command for.
|
|
25
25
|
*/
|
|
26
|
-
import { isInteractiveStdin, readLine, writeLine, yesByDefault } from "./cli-io.js";
|
|
27
|
-
import { NOTE_SLOW_UPLOAD_BYTES, noteFileRefusalSentence, readNoteFile, } from "./notes-file.js";
|
|
26
|
+
import { isInteractiveStdin, readLine, readPipedText, writeLine, yesByDefault } from "./cli-io.js";
|
|
27
|
+
import { NOTE_SLOW_UPLOAD_BYTES, decodeTextBytes, noteFileRefusalSentence, readNoteFile, } from "./notes-file.js";
|
|
28
28
|
import { loadPairedSession, towerFailureDetail, towerRequest, } from "../tower-client.js";
|
|
29
29
|
import { readResponseJson } from "../upload-http.js";
|
|
30
30
|
/**
|
|
@@ -261,15 +261,24 @@ async function pasteNote(command, door) {
|
|
|
261
261
|
writeLine(door.io.stderr, `${TAG} file refused ${JSON.stringify({ reason: read.refusal, detail: read.detail })}`);
|
|
262
262
|
return fail(door, read.refusal, sentence);
|
|
263
263
|
}
|
|
264
|
-
|
|
264
|
+
const decoded = decodeTextBytes(read.bytes);
|
|
265
|
+
if (!decoded.ok) {
|
|
266
|
+
writeLine(door.io.stderr, `${TAG} file refused ${JSON.stringify({ reason: decoded.reason })}`);
|
|
267
|
+
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.");
|
|
268
|
+
}
|
|
269
|
+
text = decoded.text;
|
|
265
270
|
}
|
|
266
271
|
else {
|
|
267
272
|
if (isInteractiveStdin(door.io)) {
|
|
268
|
-
return fail(door, "nothing_piped", "
|
|
269
|
-
+ "`
|
|
273
|
+
return fail(door, "nothing_piped", "Pass --file <path> (safest on Windows), or pipe the note in "
|
|
274
|
+
+ "(`pbpaste | cockpit notes paste` on macOS; on Windows use PowerShell 7 — "
|
|
275
|
+
+ "Windows PowerShell 5.1 turns non-ASCII into `?` on pipes).");
|
|
270
276
|
}
|
|
271
277
|
try {
|
|
272
|
-
text = await
|
|
278
|
+
text = await readPipedText(door.io.stdin, {
|
|
279
|
+
maxChars: PASTE_MAX_CHARS,
|
|
280
|
+
overflowMessage: `A pasted note is limited to ${PASTE_MAX_CHARS} characters. Save it to a file and use --file instead.`,
|
|
281
|
+
});
|
|
273
282
|
}
|
|
274
283
|
catch (error) {
|
|
275
284
|
return fail(door, "paste_too_long", errorText(error));
|
|
@@ -474,17 +483,6 @@ function sayScope(door, body) {
|
|
|
474
483
|
writeLine(door.io.stderr, `${TAG} narrowed ${JSON.stringify({ scope: body.scope ?? null, reason: body.degradedBecause })}`);
|
|
475
484
|
writeLine(door.io.stderr, body.degradedNote ?? "This answer is narrower than the browser's.");
|
|
476
485
|
}
|
|
477
|
-
async function readAll(stream) {
|
|
478
|
-
stream.setEncoding("utf8");
|
|
479
|
-
let text = "";
|
|
480
|
-
for await (const chunk of stream) {
|
|
481
|
-
text += chunk;
|
|
482
|
-
if (text.length > PASTE_MAX_CHARS) {
|
|
483
|
-
throw new Error(`A pasted note is limited to ${PASTE_MAX_CHARS} characters. Save it to a file and use --file instead.`);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
return text;
|
|
487
|
-
}
|
|
488
486
|
function errorText(error) {
|
|
489
487
|
return error instanceof Error ? error.message : String(error);
|
|
490
488
|
}
|
|
@@ -15,7 +15,7 @@ export async function runCockpitCli(argv, io) {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
if (command === "--version" || command === "-V" || command === "version") {
|
|
18
|
-
writeLine(io?.stdout ?? process.stdout, "0.2.
|
|
18
|
+
writeLine(io?.stdout ?? process.stdout, "0.2.40");
|
|
19
19
|
return 0;
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -239,8 +239,9 @@ async function setEnvBlob(command, tower, io) {
|
|
|
239
239
|
const failure = {
|
|
240
240
|
reason: "content_not_piped",
|
|
241
241
|
detail: "settings env set reads the file from stdin. Pipe it in: " +
|
|
242
|
-
"`cat <path> | cockpit settings env set --project <project> --file <name> --content-stdin
|
|
243
|
-
"(
|
|
242
|
+
"`cat <path> | cockpit settings env set --project <project> --file <name> --content-stdin`. " +
|
|
243
|
+
"On Windows use PowerShell 7 (`pwsh`): `Get-Content <path> -Raw | cockpit settings env set …` — " +
|
|
244
|
+
"Windows PowerShell 5.1 turns non-ASCII into `?` on pipes.",
|
|
244
245
|
};
|
|
245
246
|
return writeCommandFailure(io, command.json, failure);
|
|
246
247
|
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* - **stdout belongs to `--json`.** Every operational line goes to stderr, which
|
|
18
18
|
* launchd captures; a `--json` run puts exactly one object on stdout.
|
|
19
19
|
*/
|
|
20
|
-
import { writeLine } from "./cli-io.js";
|
|
20
|
+
import { readPipedText, writeLine } from "./cli-io.js";
|
|
21
21
|
import { loadPairedSession, towerFailureDetail, towerJsonRequest, } from "../tower-client.js";
|
|
22
22
|
/** Client-side ceiling. These routes are small reads and writes, not turns. */
|
|
23
23
|
const REQUEST_DEADLINE_MS = 30_000;
|
|
@@ -101,12 +101,11 @@ export function parseModelKey(key) {
|
|
|
101
101
|
}
|
|
102
102
|
return { provider: key.slice(0, separator), model: key.slice(separator + 1) };
|
|
103
103
|
}
|
|
104
|
-
/**
|
|
104
|
+
/**
|
|
105
|
+
* Reads piped stdin whole, BOM-stripped (BLI-3480 — PowerShell redirection
|
|
106
|
+
* writes one, and stored verbatim it makes the first env line unreachable).
|
|
107
|
+
* Used only for env content, which is never echoed.
|
|
108
|
+
*/
|
|
105
109
|
export async function readAllStdin(stream) {
|
|
106
|
-
stream
|
|
107
|
-
let text = "";
|
|
108
|
-
for await (const chunk of stream) {
|
|
109
|
-
text += chunk;
|
|
110
|
-
}
|
|
111
|
-
return text;
|
|
110
|
+
return readPipedText(stream);
|
|
112
111
|
}
|