baesian 0.0.0-stage → 1.0.1
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/LICENSE.md +105 -0
- package/README.md +217 -2
- package/dist/agents.js +249 -0
- package/dist/api.js +13 -0
- package/dist/bin.js +14 -0
- package/dist/ci.js +82 -0
- package/dist/cite.js +66 -0
- package/dist/cli.js +831 -0
- package/dist/client/assets/alegreya-cyrillic-ext-wght-italic-DzWRXZb0.woff2 +0 -0
- package/dist/client/assets/alegreya-cyrillic-ext-wght-normal-CN0-BfWD.woff2 +0 -0
- package/dist/client/assets/alegreya-cyrillic-wght-italic-Dhrv2xfk.woff2 +0 -0
- package/dist/client/assets/alegreya-cyrillic-wght-normal-CU5lb38f.woff2 +0 -0
- package/dist/client/assets/alegreya-greek-ext-wght-italic-CoUI9EEu.woff2 +0 -0
- package/dist/client/assets/alegreya-greek-ext-wght-normal-C8PPRmwJ.woff2 +0 -0
- package/dist/client/assets/alegreya-greek-wght-italic-CVt9-Jr4.woff2 +0 -0
- package/dist/client/assets/alegreya-greek-wght-normal-BPx8yWcZ.woff2 +0 -0
- package/dist/client/assets/alegreya-latin-ext-wght-italic-BIuo_2sR.woff2 +0 -0
- package/dist/client/assets/alegreya-latin-ext-wght-normal-o8axeAGu.woff2 +0 -0
- package/dist/client/assets/alegreya-latin-wght-italic-Bxkcgmge.woff2 +0 -0
- package/dist/client/assets/alegreya-latin-wght-normal-BG9rTJo4.woff2 +0 -0
- package/dist/client/assets/alegreya-vietnamese-wght-italic-BAPuWgnQ.woff2 +0 -0
- package/dist/client/assets/alegreya-vietnamese-wght-normal-DOV9JYup.woff2 +0 -0
- package/dist/client/assets/archivo-latin-ext-wdth-normal-7khWdh9v.woff2 +0 -0
- package/dist/client/assets/archivo-latin-wdth-normal-DY7AcnAa.woff2 +0 -0
- package/dist/client/assets/archivo-vietnamese-wdth-normal-rJmnGBSt.woff2 +0 -0
- package/dist/client/assets/index-9yf70Vqv.css +1 -0
- package/dist/client/assets/index-Dwg9e0YR.js +15 -0
- package/dist/client/index.html +15 -0
- package/dist/facts.js +278 -0
- package/dist/folder-storage.js +71 -0
- package/dist/frontmatter.js +38 -0
- package/dist/gate-paths.js +77 -0
- package/dist/gate.js +237 -0
- package/dist/git.js +100 -0
- package/dist/globs.js +120 -0
- package/dist/hookblock.js +146 -0
- package/dist/hooks.js +911 -0
- package/dist/ids.js +23 -0
- package/dist/login.js +301 -0
- package/dist/mcp.js +303 -0
- package/dist/merges.js +849 -0
- package/dist/modes.js +245 -0
- package/dist/notify.js +91 -0
- package/dist/ops.js +381 -0
- package/dist/passkey.js +86 -0
- package/dist/personal.js +163 -0
- package/dist/practice-answers.js +42 -0
- package/dist/proposals.js +197 -0
- package/dist/quote.js +28 -0
- package/dist/report.js +261 -0
- package/dist/routing.js +411 -0
- package/dist/schema.js +1178 -0
- package/dist/server.js +514 -0
- package/dist/similarity.js +45 -0
- package/dist/sshsig.js +159 -0
- package/dist/state.js +135 -0
- package/dist/storage.js +61 -0
- package/dist/store.js +2259 -0
- package/dist/style.js +408 -0
- package/dist/sync.js +797 -0
- package/dist/trailers.js +121 -0
- package/dist/trust.js +222 -0
- package/dist/usage-fields.js +161 -0
- package/dist/vault.js +849 -0
- package/dist/verify.js +443 -0
- package/dist/version.js +70 -0
- package/dist/wait.js +449 -0
- package/dist/wikilinks.js +25 -0
- package/package.json +71 -4
package/dist/style.js
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import * as util from "node:util";
|
|
2
|
+
import { segments, unmark } from "./cite.js";
|
|
3
|
+
// ---------------------------------------------------------------- pieces
|
|
4
|
+
const role = (r) => (...text) => ({ role: r, text });
|
|
5
|
+
export const ok = role("ok");
|
|
6
|
+
export const attention = role("attention");
|
|
7
|
+
export const failure = role("failure");
|
|
8
|
+
export const hard = role("hard");
|
|
9
|
+
export const strong = role("strong");
|
|
10
|
+
export const dim = role("dim");
|
|
11
|
+
/** A card's ID, a rule's, a commit's: bold on a terminal. */
|
|
12
|
+
export const id = role("id");
|
|
13
|
+
/** A command in prose: `baesian init`. */
|
|
14
|
+
export const cmd = (code) => ({ code });
|
|
15
|
+
/** Text that never breaks and is never styled. */
|
|
16
|
+
export const lit = (literal) => ({ literal });
|
|
17
|
+
export const HANG = { hang: true };
|
|
18
|
+
/** A level, coloured as the client colours its stakes: hard in magenta. */
|
|
19
|
+
export const level = (l) => (l === "hard" ? hard(l) : l);
|
|
20
|
+
/** A card's status, coloured by what it asks of the agent. */
|
|
21
|
+
export function status(s) {
|
|
22
|
+
if (s === "resolved")
|
|
23
|
+
return ok(s);
|
|
24
|
+
if (s === "returned" || s === "deferred")
|
|
25
|
+
return attention(s);
|
|
26
|
+
if (s === "withdrawn" || s === "superseded" || s === "held")
|
|
27
|
+
return dim(s);
|
|
28
|
+
return s;
|
|
29
|
+
}
|
|
30
|
+
// ---------------------------------------------------------------- lines
|
|
31
|
+
const line = (kind, indent, pieces, mark) => ({ kind, indent, pieces, ...(mark ? { mark } : {}) });
|
|
32
|
+
/** A headline: what happened. `mark` is its symbol on a terminal; plain text has none. */
|
|
33
|
+
export const head = (mark, ...pieces) => line("head", 0, pieces, mark);
|
|
34
|
+
/** An error's headline. */
|
|
35
|
+
export const failed = (...pieces) => head("failed", ...pieces);
|
|
36
|
+
/** Prose, or anything else: wraps on a terminal, under where it starts. */
|
|
37
|
+
export const text = (...pieces) => line("text", 0, pieces);
|
|
38
|
+
/** A next step: the command first, so the line pastes, and why as a shell comment. */
|
|
39
|
+
export const next = (command, why) => line("next", 0, [{ code: command, alone: true }, ...(why ? [" ", dim(`# ${why}`)] : [])]);
|
|
40
|
+
/** A next step in words, when it isn't one command. */
|
|
41
|
+
export const nextSay = (...pieces) => line("next", 0, pieces);
|
|
42
|
+
export const note = (...pieces) => line("note", 0, pieces);
|
|
43
|
+
export const warning = (...pieces) => line("warning", 0, pieces);
|
|
44
|
+
/** A line to copy as it is (JSON, YAML, a command): never wrapped or styled. */
|
|
45
|
+
export const literal = (text) => line("literal", 0, [lit(text)]);
|
|
46
|
+
/** A trailer to paste into a commit message: at the start of its line, never wrapped, styled or prefixed. */
|
|
47
|
+
export const trailer = (text) => line("trailer", 0, [lit(text)]);
|
|
48
|
+
export const blank = line("blank", 0, []);
|
|
49
|
+
export function indent(n, lines) {
|
|
50
|
+
const one = (l) => (l.kind === "trailer" || l.kind === "blank" ? l : { ...l, indent: l.indent + n });
|
|
51
|
+
return Array.isArray(lines) ? lines.map(one) : one(lines);
|
|
52
|
+
}
|
|
53
|
+
/** Fields under a headline: labels padded to the longest, values to the longest that has an explanation after it. */
|
|
54
|
+
export function fields(rows, at = 2) {
|
|
55
|
+
const rs = rows.filter((r) => Boolean(r));
|
|
56
|
+
if (!rs.length)
|
|
57
|
+
return [];
|
|
58
|
+
const labelWidth = Math.max(...rs.map((r) => r.label.length + 1)) + 2;
|
|
59
|
+
const both = rs.filter((r) => r.value !== undefined && r.why !== undefined);
|
|
60
|
+
const valueWidth = both.length ? Math.max(...both.map((r) => width(r.value))) + 2 : 0;
|
|
61
|
+
return rs.map((r) => {
|
|
62
|
+
const pieces = [strong(`${r.label}:`), " ".repeat(labelWidth - r.label.length - 1)];
|
|
63
|
+
if (r.value !== undefined && r.why !== undefined)
|
|
64
|
+
pieces.push(r.value, " ".repeat(valueWidth - width(r.value)), HANG, dim(r.why));
|
|
65
|
+
else if (r.value !== undefined)
|
|
66
|
+
pieces.push(HANG, r.value);
|
|
67
|
+
else if (r.why !== undefined)
|
|
68
|
+
pieces.push(HANG, dim(r.why));
|
|
69
|
+
return line("text", at, pieces);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/** Rows with a header: each column padded to its widest, two spaces between, the last left as it is. */
|
|
73
|
+
export function table(header, rows, at = 2) {
|
|
74
|
+
const all = header ? [header.map((h) => dim(h)), ...rows] : [...rows];
|
|
75
|
+
if (!all.length)
|
|
76
|
+
return [];
|
|
77
|
+
const n = Math.max(...all.map((r) => r.length));
|
|
78
|
+
const widths = Array.from({ length: n }, (_, c) => Math.max(...all.map((r) => (c < r.length - 1 ? width(r[c]) : 0))));
|
|
79
|
+
return all.map((r) => line("text", at, r.flatMap((cell, c) => (c === r.length - 1 ? [HANG, cell] : [cell, " ".repeat(widths[c] - width(cell) + 2)]))));
|
|
80
|
+
}
|
|
81
|
+
export const PLAIN = { color: false, verbose: false };
|
|
82
|
+
const SYMBOLS = {
|
|
83
|
+
unicode: { done: "✓", failed: "✗", attention: "!", next: "→" },
|
|
84
|
+
ascii: { done: "ok", failed: "x", attention: "!", next: "->" },
|
|
85
|
+
};
|
|
86
|
+
const MARK_STYLE = { done: { fg: "green" }, failed: { fg: "red" }, attention: { fg: "yellow", bold: true }, next: { dim: true } };
|
|
87
|
+
const ROLE_STYLE = {
|
|
88
|
+
ok: { fg: "green" },
|
|
89
|
+
attention: { fg: "yellow" },
|
|
90
|
+
failure: { fg: "red" },
|
|
91
|
+
hard: { fg: "magenta" },
|
|
92
|
+
command: { fg: "cyan" },
|
|
93
|
+
id: { bold: true },
|
|
94
|
+
strong: { bold: true },
|
|
95
|
+
dim: { dim: true },
|
|
96
|
+
};
|
|
97
|
+
const merge = (a, b) => ({ ...a, ...b, bold: Boolean(a.bold || b.bold), dim: Boolean(a.dim || b.dim) });
|
|
98
|
+
/** Control characters made visible, so nothing printed can carry a terminal's escape codes. Newlines stay. */
|
|
99
|
+
const sanitize = (s) => s.replace(/[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
100
|
+
/** A piece as plain text reads it, without citations: for measuring columns. */
|
|
101
|
+
function plainPiece(p, verbose = false) {
|
|
102
|
+
if (typeof p === "string")
|
|
103
|
+
return sanitize(unmark(p, verbose));
|
|
104
|
+
if (Array.isArray(p))
|
|
105
|
+
return p.map((x) => plainPiece(x, verbose)).join("");
|
|
106
|
+
if ("role" in p)
|
|
107
|
+
return p.text.map((x) => plainPiece(x, verbose)).join("");
|
|
108
|
+
if ("code" in p)
|
|
109
|
+
return p.alone ? sanitize(p.code) : `\`${sanitize(p.code)}\``;
|
|
110
|
+
if ("literal" in p)
|
|
111
|
+
return sanitize(unmark(p.literal, verbose));
|
|
112
|
+
return "";
|
|
113
|
+
}
|
|
114
|
+
/** How wide a piece is in plain text. */
|
|
115
|
+
export const width = (p) => plainPiece(p).length;
|
|
116
|
+
const PLAIN_PREFIX = { next: "next: ", note: "note: ", warning: "warning: " };
|
|
117
|
+
/** A line's cells, and where it hangs when it wraps. */
|
|
118
|
+
function cellsOf(l, how) {
|
|
119
|
+
const cells = [];
|
|
120
|
+
let hang;
|
|
121
|
+
const push = (s, style, brk) => {
|
|
122
|
+
for (const ch of s)
|
|
123
|
+
cells.push(ch === " " && brk ? { ch, style, brk } : { ch, style });
|
|
124
|
+
};
|
|
125
|
+
for (let i = 0; i < l.indent; i++)
|
|
126
|
+
cells.push({ ch: " ", style: {} });
|
|
127
|
+
const styled = how.symbols !== undefined;
|
|
128
|
+
const base = l.kind === "note" ? { dim: true } : {};
|
|
129
|
+
if (styled && l.kind === "head" && l.mark) {
|
|
130
|
+
push(SYMBOLS[how.symbols][l.mark], MARK_STYLE[l.mark]);
|
|
131
|
+
push(" ", {});
|
|
132
|
+
}
|
|
133
|
+
else if (styled && l.kind === "next") {
|
|
134
|
+
push(SYMBOLS[how.symbols].next, MARK_STYLE.next);
|
|
135
|
+
push(" ", {});
|
|
136
|
+
}
|
|
137
|
+
else if (styled && l.kind === "warning") {
|
|
138
|
+
push(SYMBOLS[how.symbols].attention, MARK_STYLE.attention);
|
|
139
|
+
push(" ", {});
|
|
140
|
+
push("warning: ", { fg: "yellow" });
|
|
141
|
+
}
|
|
142
|
+
else if (PLAIN_PREFIX[l.kind])
|
|
143
|
+
push(PLAIN_PREFIX[l.kind], base);
|
|
144
|
+
const lead = cells.length;
|
|
145
|
+
const walk = (p, style) => {
|
|
146
|
+
if (typeof p === "string") {
|
|
147
|
+
for (const seg of segments(p)) {
|
|
148
|
+
if (seg.cite && !how.verbose)
|
|
149
|
+
continue;
|
|
150
|
+
push(sanitize(seg.text), seg.cite ? merge(style, { dim: true }) : style, 1);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
else if (Array.isArray(p))
|
|
154
|
+
for (const x of p)
|
|
155
|
+
walk(x, style);
|
|
156
|
+
else if ("role" in p)
|
|
157
|
+
for (const x of p.text)
|
|
158
|
+
walk(x, styled ? merge(style, ROLE_STYLE[p.role]) : style);
|
|
159
|
+
else if ("code" in p) {
|
|
160
|
+
const code = sanitize(p.code);
|
|
161
|
+
const s = styled ? merge(style, ROLE_STYLE.command) : style;
|
|
162
|
+
if (!p.alone) {
|
|
163
|
+
// In plain text a command in prose is in backticks; on a terminal its colour marks it instead.
|
|
164
|
+
push(styled ? code : `\`${code}\``, s);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
// A command alone may break only before a flag or `&&`, outside quotes, with a continuation.
|
|
168
|
+
let quote;
|
|
169
|
+
for (let i = 0; i < code.length; i++) {
|
|
170
|
+
const ch = code[i];
|
|
171
|
+
if (quote) {
|
|
172
|
+
if (ch === quote)
|
|
173
|
+
quote = undefined;
|
|
174
|
+
}
|
|
175
|
+
else if (ch === '"' || ch === "'")
|
|
176
|
+
quote = ch;
|
|
177
|
+
const before = ch === " " && !quote && (code[i + 1] === "-" || code.startsWith("&&", i + 1));
|
|
178
|
+
cells.push(before ? { ch, style: s, brk: 2 } : { ch, style: s });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
else if ("literal" in p)
|
|
182
|
+
push(sanitize(unmark(p.literal, how.verbose)), {});
|
|
183
|
+
else
|
|
184
|
+
hang = cells.length;
|
|
185
|
+
};
|
|
186
|
+
for (const p of l.pieces)
|
|
187
|
+
walk(p, base);
|
|
188
|
+
if (hang === undefined && (l.kind === "head" || l.kind === "next" || l.kind === "warning" || l.kind === "note"))
|
|
189
|
+
hang = lead;
|
|
190
|
+
return { cells, hang: hang ?? (l.kind === "text" ? l.indent : undefined) };
|
|
191
|
+
}
|
|
192
|
+
/** Wrap at `width`: prose at its spaces, under its hang; a command only before a flag or `&&`, with a `\`. */
|
|
193
|
+
function wrap(cells, hangAt, width) {
|
|
194
|
+
const out = [];
|
|
195
|
+
const pad = (n) => Array.from({ length: n }, () => ({ ch: " ", style: {} }));
|
|
196
|
+
const trimEnd = (c) => {
|
|
197
|
+
while (c.length && c.at(-1).ch === " ")
|
|
198
|
+
c.pop();
|
|
199
|
+
return c;
|
|
200
|
+
};
|
|
201
|
+
const trimStart = (c) => {
|
|
202
|
+
let i = 0;
|
|
203
|
+
while (i < c.length && c[i].ch === " ")
|
|
204
|
+
i++;
|
|
205
|
+
return c.slice(i);
|
|
206
|
+
};
|
|
207
|
+
let hang = hangAt ?? 0;
|
|
208
|
+
if (width !== undefined && hang > width * 0.6)
|
|
209
|
+
hang = Math.min(4, hang);
|
|
210
|
+
let rest = cells;
|
|
211
|
+
let start = cells.findIndex((c) => c.ch !== " ") + 1;
|
|
212
|
+
for (;;) {
|
|
213
|
+
const nl = rest.findIndex((c) => c.ch === "\n");
|
|
214
|
+
const stop = nl < 0 ? rest.length : nl;
|
|
215
|
+
// A newline in a text (an error from elsewhere) starts a line as it is; plain text breaks nowhere else.
|
|
216
|
+
const newline = () => {
|
|
217
|
+
out.push(trimEnd(rest.slice(0, nl)));
|
|
218
|
+
rest = rest.slice(nl + 1);
|
|
219
|
+
start = 1;
|
|
220
|
+
};
|
|
221
|
+
if (width === undefined || stop <= width) {
|
|
222
|
+
if (nl < 0) {
|
|
223
|
+
out.push(rest);
|
|
224
|
+
return out;
|
|
225
|
+
}
|
|
226
|
+
newline();
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
let bp = -1;
|
|
230
|
+
let cont = false;
|
|
231
|
+
for (let k = Math.max(start, 1); k < Math.min(stop, width + 1); k++) {
|
|
232
|
+
const c = rest[k];
|
|
233
|
+
if (c.brk === 1)
|
|
234
|
+
[bp, cont] = [k, false];
|
|
235
|
+
else if (c.brk === 2 && k + 2 <= width)
|
|
236
|
+
[bp, cont] = [k, true];
|
|
237
|
+
}
|
|
238
|
+
// Nothing to break at before the edge: the token runs over it, and the line breaks at the first place after.
|
|
239
|
+
if (bp < 0) {
|
|
240
|
+
for (let k = Math.max(start, 1); k < stop; k++) {
|
|
241
|
+
if (rest[k].brk) {
|
|
242
|
+
[bp, cont] = [k, rest[k].brk === 2];
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (bp < 0) {
|
|
248
|
+
if (nl < 0) {
|
|
249
|
+
out.push(rest);
|
|
250
|
+
return out;
|
|
251
|
+
}
|
|
252
|
+
newline();
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
const first = trimEnd(rest.slice(0, bp));
|
|
256
|
+
if (cont)
|
|
257
|
+
first.push({ ch: " ", style: {} }, { ch: "\\", style: { fg: "cyan" } });
|
|
258
|
+
out.push(first);
|
|
259
|
+
const by = hang + (cont ? 2 : 0);
|
|
260
|
+
rest = [...pad(by), ...trimStart(rest.slice(bp + 1))];
|
|
261
|
+
start = by + 1;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/** The SGR codes for a style, through `util.styleText`, which only paints: whether to is decided here. */
|
|
265
|
+
function paint(text, s) {
|
|
266
|
+
const formats = [];
|
|
267
|
+
if (s.fg)
|
|
268
|
+
formats.push(s.fg);
|
|
269
|
+
if (s.bold)
|
|
270
|
+
formats.push("bold");
|
|
271
|
+
if (s.dim)
|
|
272
|
+
formats.push("dim");
|
|
273
|
+
let out = text;
|
|
274
|
+
for (const f of formats)
|
|
275
|
+
out = styleText(f, out);
|
|
276
|
+
return out;
|
|
277
|
+
}
|
|
278
|
+
const SGR = { green: [32, 39], yellow: [33, 39], red: [31, 39], magenta: [35, 39], cyan: [36, 39], bold: [1, 22], dim: [2, 22] };
|
|
279
|
+
/** `util.styleText`, told not to check a stream: Node's own check exists only from 22.8, and isn't ours to make. */
|
|
280
|
+
function styleText(format, text) {
|
|
281
|
+
const st = util.styleText;
|
|
282
|
+
if (typeof st === "function")
|
|
283
|
+
return st(format, text, { validateStream: false });
|
|
284
|
+
const [on, off] = SGR[format];
|
|
285
|
+
return `\x1b[${on}m${text}\x1b[${off}m`;
|
|
286
|
+
}
|
|
287
|
+
function paintLine(cells, color) {
|
|
288
|
+
if (!color)
|
|
289
|
+
return cells.map((c) => c.ch).join("");
|
|
290
|
+
let out = "";
|
|
291
|
+
let run = "";
|
|
292
|
+
let style;
|
|
293
|
+
const same = (a, b) => a.fg === b.fg && Boolean(a.bold) === Boolean(b.bold) && Boolean(a.dim) === Boolean(b.dim);
|
|
294
|
+
const flush = () => {
|
|
295
|
+
if (run)
|
|
296
|
+
out += style && (style.fg || style.bold || style.dim) ? paint(run, style) : run;
|
|
297
|
+
run = "";
|
|
298
|
+
};
|
|
299
|
+
for (const c of cells) {
|
|
300
|
+
if (!style || !same(style, c.style)) {
|
|
301
|
+
flush();
|
|
302
|
+
style = c.style;
|
|
303
|
+
}
|
|
304
|
+
run += c.ch;
|
|
305
|
+
}
|
|
306
|
+
flush();
|
|
307
|
+
return out;
|
|
308
|
+
}
|
|
309
|
+
/** Lines as a stream gets them: plain text, or as a terminal shows them. */
|
|
310
|
+
export function render(lines, how = PLAIN) {
|
|
311
|
+
const out = [];
|
|
312
|
+
const prefix = how.prefix ?? "";
|
|
313
|
+
const prefixCells = [...prefix].map((ch) => ({ ch, style: how.symbols ? { bold: true } : {} }));
|
|
314
|
+
for (const l of lines) {
|
|
315
|
+
if (l.kind === "trailer") {
|
|
316
|
+
out.push(plainPiece(l.pieces, how.verbose));
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
if (l.kind === "blank") {
|
|
320
|
+
out.push(prefix.trimEnd());
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
const { cells, hang } = cellsOf(l, how);
|
|
324
|
+
const width = l.kind === "literal" || how.width === undefined ? undefined : Math.max(20, how.width - prefix.length);
|
|
325
|
+
// Plain text never wraps; a newline in a text (an error from elsewhere) still starts a line, prefixed.
|
|
326
|
+
for (const row of wrap(cells, hang, width))
|
|
327
|
+
out.push(paintLine([...prefixCells, ...row], how.color && how.symbols !== undefined));
|
|
328
|
+
}
|
|
329
|
+
return out;
|
|
330
|
+
}
|
|
331
|
+
/** Lines as plain text: what agents, the MCP server and the plugin read. */
|
|
332
|
+
export const plain = (lines) => render(lines, PLAIN);
|
|
333
|
+
/** Plain text, one string. */
|
|
334
|
+
export const plainText = (lines) => plain(lines).join("\n");
|
|
335
|
+
/**
|
|
336
|
+
* The variables agents' harnesses set in the commands they run, any of which means an agent reads the output: Claude
|
|
337
|
+
* Code (`CLAUDECODE`, `CLAUDE_CODE_ENTRYPOINT`, and `AI_AGENT`, which others set too), Codex (`CODEX_THREAD_ID`), Gemini
|
|
338
|
+
* CLI (`GEMINI_CLI`), Cursor's agent (`CURSOR_AGENT`) and opencode (`OPENCODE`).
|
|
339
|
+
*/
|
|
340
|
+
export const HARNESS_VARS = ["CLAUDECODE", "CLAUDE_CODE_ENTRYPOINT", "AI_AGENT", "CODEX_THREAD_ID", "GEMINI_CLI", "CURSOR_AGENT", "OPENCODE"];
|
|
341
|
+
/** The variable that says an agent's harness runs this command, if one does. */
|
|
342
|
+
export function harnessVar(env) {
|
|
343
|
+
return HARNESS_VARS.find((v) => Boolean(env[v]?.trim()));
|
|
344
|
+
}
|
|
345
|
+
/** Whether a terminal can show ✓ ✗ →: a UTF-8 locale, or Windows Terminal; not the Linux console, or a dumb terminal. */
|
|
346
|
+
export function unicodeTerminal(env, platform = process.platform) {
|
|
347
|
+
if (env.TERM === "dumb" || env.TERM === "linux")
|
|
348
|
+
return false;
|
|
349
|
+
if (platform === "win32")
|
|
350
|
+
return Boolean(env.WT_SESSION || env.TERM_PROGRAM === "vscode" || env.ConEmuANSI === "ON");
|
|
351
|
+
const locale = env.LC_ALL || env.LC_CTYPE || env.LANG || "";
|
|
352
|
+
return /utf-?8/i.test(locale);
|
|
353
|
+
}
|
|
354
|
+
/** The widest a terminal's lines get, however wide it is. */
|
|
355
|
+
export const MAX_WIDTH = 100;
|
|
356
|
+
/**
|
|
357
|
+
* How a stream gets its output (B-qszqr). `--color` and `--no-color` decide it, whatever else is set: `--no-color` gives
|
|
358
|
+
* exactly what an agent reads. Otherwise an agent's harness gets plain text, even in a pseudo-terminal. A terminal gets
|
|
359
|
+
* symbols and wrapping, and colour unless `NO_COLOR` is set (even with `FORCE_COLOR`) or it has none (`TERM=dumb`).
|
|
360
|
+
* `FORCE_COLOR` gives colour and symbols through a pipe too, unless `NO_COLOR` is set; `FORCE_COLOR=0` turns colour off.
|
|
361
|
+
*/
|
|
362
|
+
export function renderFor(stream, env, settings = {}, platform = process.platform) {
|
|
363
|
+
const verbose = settings.verbose ?? false;
|
|
364
|
+
const tty = Boolean(stream?.isTTY);
|
|
365
|
+
const noColor = Boolean(env.NO_COLOR);
|
|
366
|
+
const force = env.FORCE_COLOR === undefined ? undefined : ["", "1", "2", "3", "true"].includes(env.FORCE_COLOR);
|
|
367
|
+
let styled;
|
|
368
|
+
let color;
|
|
369
|
+
if (settings.color === true)
|
|
370
|
+
[styled, color] = [true, true];
|
|
371
|
+
else if (settings.color === false || harnessVar(env))
|
|
372
|
+
[styled, color] = [false, false];
|
|
373
|
+
else {
|
|
374
|
+
styled = tty || (force === true && !noColor);
|
|
375
|
+
color = styled && !noColor && (force ?? (stream?.hasColors ? stream.hasColors() : env.TERM !== "dumb"));
|
|
376
|
+
}
|
|
377
|
+
if (!styled)
|
|
378
|
+
return { color: false, verbose };
|
|
379
|
+
const columns = tty && stream?.columns ? stream.columns : undefined;
|
|
380
|
+
return { color, symbols: unicodeTerminal(env, platform) ? "unicode" : "ascii", verbose, ...(columns ? { width: Math.min(columns, MAX_WIDTH) } : {}) };
|
|
381
|
+
}
|
|
382
|
+
/** The global flags in an argument list, read before Commander parses it, so its own output follows them too. */
|
|
383
|
+
export function settingsOf(argv) {
|
|
384
|
+
const out = {};
|
|
385
|
+
for (const a of argv) {
|
|
386
|
+
if (a === "--")
|
|
387
|
+
break;
|
|
388
|
+
if (a === "--color")
|
|
389
|
+
out.color = true;
|
|
390
|
+
else if (a === "--no-color")
|
|
391
|
+
out.color = false;
|
|
392
|
+
else if (a === "--verbose")
|
|
393
|
+
out.verbose = true;
|
|
394
|
+
}
|
|
395
|
+
return out;
|
|
396
|
+
}
|
|
397
|
+
/** An error's lines, for the CLI and the MCP server: its headline (citations marked), then its next steps. */
|
|
398
|
+
export function errorLines(e) {
|
|
399
|
+
if (e instanceof Error && "marked" in e && "steps" in e) {
|
|
400
|
+
const told = e;
|
|
401
|
+
const [first, ...more] = told.marked.split("\n");
|
|
402
|
+
return [failed(first), ...more.map((m) => text(m)), ...told.steps.map((s) => next(s.command, s.why))];
|
|
403
|
+
}
|
|
404
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
405
|
+
const [first, ...more] = message.split("\n");
|
|
406
|
+
return [failed(first), ...more.map((m) => text(m))];
|
|
407
|
+
}
|
|
408
|
+
//# sourceMappingURL=style.js.map
|