aegiscode 5.2.33 → 6.1.0
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/README.md +147 -392
- package/bin/aegiscode.js +197 -0
- package/package.json +30 -83
- package/scripts/demo.mjs +92 -0
- package/scripts/predist.mjs +86 -0
- package/src/app.js +619 -0
- package/src/art.js +235 -0
- package/src/commands.js +263 -0
- package/src/deps.js +65 -0
- package/src/format.js +54 -0
- package/src/fuzzy.js +116 -0
- package/src/markdown.js +303 -0
- package/src/overlays.js +286 -0
- package/src/render.js +396 -0
- package/src/screen.js +352 -0
- package/src/theme.js +156 -0
- package/vendor/client/aegis.js +809 -0
- package/vendor/client/foreign-memory.js +666 -0
- package/vendor/desktop/renderer/usage.js +42 -0
- package/vendor/mcp/tools.js +356 -0
- package/LICENSE +0 -21
- package/bin/cli.js +0 -2362
- package/scripts/download-binary.mjs +0 -155
- package/scripts/empty-stub.mjs +0 -1
- package/scripts/ensure-node-version.mjs +0 -27
- package/scripts/install-alacritty.ps1 +0 -149
- package/scripts/install-alacritty.sh +0 -164
- package/scripts/install.sh +0 -121
- package/scripts/make-bin.mjs +0 -17
- package/scripts/release-local.sh +0 -68
- package/scripts/repro-compact.ts +0 -59
- package/scripts/repro-tokencount.ts +0 -45
- package/scripts/repro-toolcontext.ts +0 -74
- package/scripts/sharp-stub/index.js +0 -9
- package/scripts/sharp-stub/package.json +0 -5
package/src/screen.js
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Terminal plumbing: cell-accurate width maths, the span/line render model,
|
|
5
|
+
* wrapping, and the ephemeral "live" region the CLI redraws while a call is in
|
|
6
|
+
* flight.
|
|
7
|
+
*
|
|
8
|
+
* The render model is aegiscodex-dev's: a line is an array of spans
|
|
9
|
+
* `{ t: text, s: style-prefix, w: cell-width }`, built with `span()`, measured
|
|
10
|
+
* with `lineWidth()`, padded/truncated with `padLine()`, and painted with
|
|
11
|
+
* `paint()`. Two width functions exist because they have different callers:
|
|
12
|
+
* `w()` returns the display width of a *plain string* (the linear transcript
|
|
13
|
+
* and the string renderers in render.js use it), while `padLine()` does the
|
|
14
|
+
* per-codepoint maths for a span line (the overlays use it).
|
|
15
|
+
*
|
|
16
|
+
* Design note — this CLI is a LINEAR transcript, not a full-screen alt-buffer
|
|
17
|
+
* TUI (which is what Claude Code and aegiscodex-dev are). Finished turns are
|
|
18
|
+
* written once to scrollback, so output stays selectable, pipeable and
|
|
19
|
+
* scrollback-searchable; only the one live status/spinner line is redrawn. The
|
|
20
|
+
* main session loop deliberately does NOT enter the alternate screen — `--print`
|
|
21
|
+
* and piped runs must keep working. The alt-screen / mouse / bracketed-paste
|
|
22
|
+
* helpers below exist only so the overlay renderers (overlays.js) and any future
|
|
23
|
+
* modal can paint and restore cleanly; they are never called on the transcript
|
|
24
|
+
* path. Keeping the two concerns in one module (rather than two) is the same
|
|
25
|
+
* split the reference uses.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const ANSI_RE = /\x1b\[[0-9;]*[A-Za-z]/g;
|
|
29
|
+
|
|
30
|
+
// Real terminal cell width (wcwidth-style). Combining marks and variation
|
|
31
|
+
// selectors occupy no cell; CJK fullwidth forms and emoji-presentation glyphs
|
|
32
|
+
// occupy two; everything else one. A plain code-point count misaligns every
|
|
33
|
+
// width decision — padding, wrapping, cursor placement — the moment a line
|
|
34
|
+
// contains CJK or emoji, because the terminal draws those two cells wide.
|
|
35
|
+
// (Regexes copied verbatim from aegiscodex-dev/src/screen.js.)
|
|
36
|
+
const RE_ZERO = /[\u0300-\u036F\u1AB0-\u1AFF\u1DC0-\u1DFF\u20D0-\u20FF\uFE00-\uFE0F\uFE20-\uFE2F]/u;
|
|
37
|
+
const RE_WIDE = /[\u1100-\u115F\u2E80-\u303E\u3041-\u33FF\u3400-\u4DBF\u4E00-\u9FFF\uA000-\uA4CF\uA960-\uA97F\uAC00-\uD7A3\uF900-\uFAFF\uFE10-\uFE19\uFE30-\uFE4F\uFF00-\uFF60\uFFE0-\uFFE6\u{1F1E6}-\u{1F1FF}\u{1F300}-\u{1F64F}\u{1F680}-\u{1F6FF}\u{1F900}-\u{1F9FF}\u{1FA70}-\u{1FAFF}\u{20000}-\u{3FFFD}]/u;
|
|
38
|
+
/** Cell width of one code point. */
|
|
39
|
+
const cwidth = (ch) => (RE_ZERO.test(ch) ? 0 : RE_WIDE.test(ch) ? 2 : 1);
|
|
40
|
+
|
|
41
|
+
function stripAnsi(s) {
|
|
42
|
+
return String(s).replace(ANSI_RE, '');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Display width of a string in terminal cells. ANSI escapes are transparent
|
|
46
|
+
* (so a styled line measures the same as its visible text) — kept from the
|
|
47
|
+
* previous revision so callers measuring render output stay correct. */
|
|
48
|
+
function w(s) {
|
|
49
|
+
let n = 0;
|
|
50
|
+
for (const ch of stripAnsi(s)) {
|
|
51
|
+
if (RE_ZERO.test(ch)) continue;
|
|
52
|
+
n += RE_WIDE.test(ch) ? 2 : 1;
|
|
53
|
+
}
|
|
54
|
+
return n;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Truncate a plain string to `width` cells without cutting a wide/combining
|
|
58
|
+
* codepoint. */
|
|
59
|
+
function clip(s, width) {
|
|
60
|
+
if (w(s) <= width) return s;
|
|
61
|
+
let out = '';
|
|
62
|
+
let used = 0;
|
|
63
|
+
for (const ch of String(s)) {
|
|
64
|
+
const cw = cwidth(ch);
|
|
65
|
+
if (used + cw > width) break;
|
|
66
|
+
out += ch;
|
|
67
|
+
used += cw;
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Pad (or clip) a plain string to exactly `width` cells. */
|
|
73
|
+
function pad(s, width) {
|
|
74
|
+
const t = clip(s, width);
|
|
75
|
+
return t + ' '.repeat(Math.max(0, width - w(t)));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Right-align a plain string inside `width`. */
|
|
79
|
+
function padStart(s, width) {
|
|
80
|
+
const t = clip(s, width);
|
|
81
|
+
return ' '.repeat(Math.max(0, width - w(t))) + t;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ── the span/line render model ──────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
/** A styled text run. Shape { t, s, w } is the contract with overlays.js and
|
|
87
|
+
* markdown.js. */
|
|
88
|
+
function span(style, t) {
|
|
89
|
+
return { t, s: style, w: w(t) };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Total display width of a span line. */
|
|
93
|
+
const lineWidth = (line) => line.reduce((a, sp) => a + sp.w, 0);
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Pad / truncate a span line to an exact cell width. Truncation is visual only
|
|
97
|
+
* (a trailing style reset may be dropped) — the terminal clips whole glyphs, and
|
|
98
|
+
* a 2-cell glyph is never split mid-width.
|
|
99
|
+
*/
|
|
100
|
+
function padLine(line, width) {
|
|
101
|
+
let cur = 0;
|
|
102
|
+
const out = [];
|
|
103
|
+
for (const sp of line) {
|
|
104
|
+
if (cur >= width) break;
|
|
105
|
+
let t = sp.t;
|
|
106
|
+
let tw = sp.w;
|
|
107
|
+
if (cur + tw > width) {
|
|
108
|
+
// Truncate by whole code points that fit in the remaining cells — a
|
|
109
|
+
// codepoint slice (keep chars) would split a 2-cell glyph mid-width.
|
|
110
|
+
const room = width - cur;
|
|
111
|
+
let piece = '';
|
|
112
|
+
let used = 0;
|
|
113
|
+
for (const ch of [...t]) {
|
|
114
|
+
const cw = w(ch);
|
|
115
|
+
if (used + cw > room) break;
|
|
116
|
+
piece += ch;
|
|
117
|
+
used += cw;
|
|
118
|
+
}
|
|
119
|
+
t = piece;
|
|
120
|
+
tw = used;
|
|
121
|
+
}
|
|
122
|
+
out.push({ t, s: sp.s, w: tw });
|
|
123
|
+
cur += tw;
|
|
124
|
+
}
|
|
125
|
+
if (cur < width) out.push(span('', ' '.repeat(width - cur)));
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Term size, clamped so a zero-column report (CI, redirected stdout) still
|
|
130
|
+
* yields a paintable frame. */
|
|
131
|
+
const getSize = () => ({
|
|
132
|
+
cols: Math.max(20, (process.stdout && process.stdout.columns) || 80),
|
|
133
|
+
rows: Math.max(5, (process.stdout && process.stdout.rows) || 24),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// ── wrapping (plain strings; the string renderers use these) ─────────────────
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Word-wrap one logical plain line to `width` cells, preserving words and never
|
|
140
|
+
* breaking mid-word unless the word alone exceeds the width.
|
|
141
|
+
*/
|
|
142
|
+
function wrapLine(line, width, indent = '') {
|
|
143
|
+
const limit = Math.max(8, width);
|
|
144
|
+
if (w(line) <= limit) return [line];
|
|
145
|
+
const words = line.split(' ');
|
|
146
|
+
const out = [];
|
|
147
|
+
let cur = '';
|
|
148
|
+
for (const word of words) {
|
|
149
|
+
const piece = cur ? `${cur} ${word}` : word;
|
|
150
|
+
if (w(piece) <= limit) {
|
|
151
|
+
cur = piece;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (cur) out.push(cur);
|
|
155
|
+
if (w(word) <= limit) {
|
|
156
|
+
cur = word;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
// A single token longer than the line: hard-split on cells.
|
|
160
|
+
let rest = word;
|
|
161
|
+
while (w(rest) > limit) {
|
|
162
|
+
const head = clip(rest, limit);
|
|
163
|
+
out.push(head);
|
|
164
|
+
rest = rest.slice(head.length);
|
|
165
|
+
}
|
|
166
|
+
cur = rest;
|
|
167
|
+
}
|
|
168
|
+
if (cur || !out.length) out.push(cur);
|
|
169
|
+
return out;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Wrap a block of plain text (honours existing newlines), applying `indent`. */
|
|
173
|
+
function wrapBlock(text, width, indent = '', hang = false) {
|
|
174
|
+
const lines = [];
|
|
175
|
+
for (const raw of String(text).split('\n')) {
|
|
176
|
+
if (raw === '') {
|
|
177
|
+
lines.push('');
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const parts = wrapLine(raw, width - w(indent));
|
|
181
|
+
parts.forEach((p) => lines.push(indent + p));
|
|
182
|
+
}
|
|
183
|
+
return lines;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ── cursor / screen control ─────────────────────────────────────────────────
|
|
187
|
+
|
|
188
|
+
const ESC = {
|
|
189
|
+
hideCursor: '\x1b[?25l',
|
|
190
|
+
showCursor: '\x1b[?25h',
|
|
191
|
+
clearToEnd: '\x1b[0K',
|
|
192
|
+
clearLine: '\x1b[2K',
|
|
193
|
+
clearScreen: '\x1b[2J\x1b[H',
|
|
194
|
+
up: (n) => (n > 0 ? `\x1b[${n}A` : ''),
|
|
195
|
+
down: (n) => (n > 0 ? `\x1b[${n}B` : ''),
|
|
196
|
+
col: (n) => `\x1b[${n}G`,
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const hideCursor = () => process.stdout.write('\x1b[?25l');
|
|
200
|
+
const showCursor = () => process.stdout.write('\x1b[?25h');
|
|
201
|
+
const moveTo = (row, col) => process.stdout.write(`\x1b[${row};${col}H`);
|
|
202
|
+
|
|
203
|
+
function clearScreen() {
|
|
204
|
+
process.stdout.write('\x1b[2J\x1b[H');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Alternate screen buffer. Not used by the transcript (see the module note);
|
|
208
|
+
// available to overlays that need to paint and then restore the pre-launch
|
|
209
|
+
// terminal exactly. Guarded so paired enter/leave stay balanced.
|
|
210
|
+
let inAlt = false;
|
|
211
|
+
function enterAltScreen() {
|
|
212
|
+
if (inAlt) return;
|
|
213
|
+
inAlt = true;
|
|
214
|
+
process.stdout.write('\x1b[?1049h');
|
|
215
|
+
}
|
|
216
|
+
function leaveAltScreen() {
|
|
217
|
+
if (!inAlt) return;
|
|
218
|
+
inAlt = false;
|
|
219
|
+
process.stdout.write('\x1b[?1049l');
|
|
220
|
+
}
|
|
221
|
+
const isAltScreen = () => inAlt;
|
|
222
|
+
|
|
223
|
+
// Bracketed paste (DEC 2004): a paste arrives as one \x1b[200~ … \x1b[201~
|
|
224
|
+
// chunk instead of a burst of keystrokes.
|
|
225
|
+
const enableBracketedPaste = () => process.stdout.write('\x1b[?2004h');
|
|
226
|
+
const disableBracketedPaste = () => process.stdout.write('\x1b[?2004l');
|
|
227
|
+
|
|
228
|
+
// Mouse button-event tracking (DECSET 1000) + SGR coordinates (1006). Only the
|
|
229
|
+
// alt-screen/overlay path turns these on; disabled on exit so the shell we
|
|
230
|
+
// return to isn't left forwarding raw wheel bytes as input.
|
|
231
|
+
const enableMouseTracking = () => process.stdout.write('\x1b[?1000h\x1b[?1006h');
|
|
232
|
+
const disableMouseTracking = () => process.stdout.write('\x1b[?1000l\x1b[?1006l');
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Paint span lines top-aligned and clear the rest of the frame. Used by the
|
|
236
|
+
* overlay renderers; the transcript path never calls it (it stays linear).
|
|
237
|
+
*/
|
|
238
|
+
function paint(lines) {
|
|
239
|
+
const { rows, cols } = getSize();
|
|
240
|
+
let out = '\x1b[H';
|
|
241
|
+
for (let i = 0; i < rows; i++) {
|
|
242
|
+
// Reset at the top of every row: span styles are colour codes, and a colour
|
|
243
|
+
// code does NOT clear bold/italic/dim. Without this a row that ends inside a
|
|
244
|
+
// BOLD segment whose "off" span got dropped by padding would leak the
|
|
245
|
+
// attribute into every later row of the frame.
|
|
246
|
+
out += '\x1b[0m';
|
|
247
|
+
const line = lines[i];
|
|
248
|
+
if (line) {
|
|
249
|
+
const pl = padLine(line, cols);
|
|
250
|
+
for (const sp of pl) out += sp.s + sp.t;
|
|
251
|
+
out += '\x1b[0K';
|
|
252
|
+
} else {
|
|
253
|
+
out += '\x1b[0K';
|
|
254
|
+
}
|
|
255
|
+
if (i < rows - 1) out += '\n';
|
|
256
|
+
}
|
|
257
|
+
out += '\x1b[0m';
|
|
258
|
+
process.stdout.write(out);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** Repaint from `startRow` down, leaving rows above it untouched. */
|
|
262
|
+
function paintFrom(lines, startRow) {
|
|
263
|
+
const { rows, cols } = getSize();
|
|
264
|
+
let out = '';
|
|
265
|
+
for (let i = 0; i < rows - startRow; i++) {
|
|
266
|
+
const line = lines[startRow + i];
|
|
267
|
+
if (i > 0) out += '\n';
|
|
268
|
+
out += '\x1b[0K';
|
|
269
|
+
out += '\x1b[0m';
|
|
270
|
+
if (line) {
|
|
271
|
+
const pl = padLine(line, cols);
|
|
272
|
+
for (const sp of pl) out += sp.s + sp.t;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
out += '\x1b[0m';
|
|
276
|
+
process.stdout.write(out);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function termWidth(fallback = 80) {
|
|
280
|
+
return Math.max(20, (process.stdout && process.stdout.columns) || fallback);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* A block of lines at the bottom of the transcript that can be redrawn in
|
|
285
|
+
* place. Every write goes through here, so the escape arithmetic lives in one
|
|
286
|
+
* place: `update()` erases the previous block, prints the new one, and keeps
|
|
287
|
+
* the cursor below it.
|
|
288
|
+
*/
|
|
289
|
+
class LiveRegion {
|
|
290
|
+
constructor(out = process.stdout) {
|
|
291
|
+
this.out = out;
|
|
292
|
+
this.rows = 0;
|
|
293
|
+
this.active = false;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
update(lines) {
|
|
297
|
+
const body = lines.map((l) => l + ESC.clearToEnd).join('\n');
|
|
298
|
+
if (!this.active) {
|
|
299
|
+
this.out.write(body);
|
|
300
|
+
this.active = true;
|
|
301
|
+
} else {
|
|
302
|
+
// Back to the first row of the old block, rewrite, clear the tail.
|
|
303
|
+
this.out.write(ESC.col(1) + ESC.up(this.rows) + body + '\n' + ESC.col(1) + ESC.up(1));
|
|
304
|
+
}
|
|
305
|
+
this.rows = lines.length;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** Erase the block and return the cursor to its start, so the caller can
|
|
309
|
+
* print the durable version of the same content. */
|
|
310
|
+
clear() {
|
|
311
|
+
if (!this.active) return;
|
|
312
|
+
this.out.write(ESC.col(1) + ESC.clearLine);
|
|
313
|
+
for (let i = 1; i < this.rows; i++) this.out.write(`\n${ESC.clearLine}`);
|
|
314
|
+
this.out.write(ESC.up(this.rows - 1) + ESC.col(1));
|
|
315
|
+
this.rows = 0;
|
|
316
|
+
this.active = false;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
module.exports = {
|
|
321
|
+
// plain-string width helpers (kept for the string renderers + app/bin)
|
|
322
|
+
w,
|
|
323
|
+
clip,
|
|
324
|
+
pad,
|
|
325
|
+
padStart,
|
|
326
|
+
stripAnsi,
|
|
327
|
+
wrapLine,
|
|
328
|
+
wrapBlock,
|
|
329
|
+
// span/line render model (from aegiscodex-dev)
|
|
330
|
+
span,
|
|
331
|
+
lineWidth,
|
|
332
|
+
padLine,
|
|
333
|
+
paint,
|
|
334
|
+
paintFrom,
|
|
335
|
+
getSize,
|
|
336
|
+
// cursor / screen / input helpers (overlay path)
|
|
337
|
+
EC: ESC,
|
|
338
|
+
hideCursor,
|
|
339
|
+
showCursor,
|
|
340
|
+
moveTo,
|
|
341
|
+
clearScreen,
|
|
342
|
+
enterAltScreen,
|
|
343
|
+
leaveAltScreen,
|
|
344
|
+
isAltScreen,
|
|
345
|
+
enableBracketedPaste,
|
|
346
|
+
disableBracketedPaste,
|
|
347
|
+
enableMouseTracking,
|
|
348
|
+
disableMouseTracking,
|
|
349
|
+
// the live region + sizing (app/bin)
|
|
350
|
+
LiveRegion,
|
|
351
|
+
termWidth,
|
|
352
|
+
};
|
package/src/theme.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Design tokens — the aegiscodex-dev design system, adopted wholesale.
|
|
5
|
+
*
|
|
6
|
+
* Every value below is copied verbatim from `aegiscodex-dev/src/theme.js`, which
|
|
7
|
+
* extracted them from the live ANSI stream of Claude Code: gold 255,193,7 ·
|
|
8
|
+
* coral 215,119,87 · lavender 177,185,249 · blue 120,160,250 · green 78,186,101
|
|
9
|
+
* · red 220,90,90 · gray 153,153,153 · dim 80,80,80.
|
|
10
|
+
*
|
|
11
|
+
* `test/cli-conformance.test.mjs` pins these to aegiscodex-dev's source, so the
|
|
12
|
+
* CLI cannot drift away from the design it is meant to match. (The previous
|
|
13
|
+
* revision of this file was the inverse: a violet/cyan "Signal" palette with a
|
|
14
|
+
* test asserting divergence. That theme is gone.)
|
|
15
|
+
*
|
|
16
|
+
* Zero dependencies: SGR escapes are hand-built, like the rest of this repo.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const RESET = '\x1b[0m';
|
|
20
|
+
const BOLD = '\x1b[1m';
|
|
21
|
+
const DIM = '\x1b[2m';
|
|
22
|
+
const ITALIC = '\x1b[3m';
|
|
23
|
+
const UNDER = '\x1b[4m';
|
|
24
|
+
const BOLD_OFF = '\x1b[22m';
|
|
25
|
+
const ITALIC_OFF = '\x1b[23m';
|
|
26
|
+
const UNDER_OFF = '\x1b[24m';
|
|
27
|
+
const RESET_FG = '\x1b[39m';
|
|
28
|
+
const RESET_BG = '\x1b[49m';
|
|
29
|
+
|
|
30
|
+
const RGB = (r, g, b) => `\x1b[38;2;${r};${g};${b}m`;
|
|
31
|
+
const BG = (r, g, b) => `\x1b[48;2;${r};${g};${b}m`;
|
|
32
|
+
|
|
33
|
+
/** The dark palette. Role names match aegiscodex-dev exactly. */
|
|
34
|
+
const C = {
|
|
35
|
+
gold: RGB(255, 193, 7), // header line, accents, welcome frame
|
|
36
|
+
coral: RGB(215, 119, 87), // "Welcome to ..." title
|
|
37
|
+
lavender: RGB(177, 185, 249), // selection cursor ❯, links, focused option
|
|
38
|
+
blue: RGB(120, 160, 250), // selected palette row
|
|
39
|
+
green: RGB(78, 186, 101), // checkmarks, success, diff additions
|
|
40
|
+
red: RGB(220, 90, 90), // diff removals, errors
|
|
41
|
+
gray: RGB(153, 153, 153), // secondary text, menu numbers, hints
|
|
42
|
+
dim: RGB(80, 80, 80), // ╌ divider lines
|
|
43
|
+
white: RGB(255, 255, 255), // primary text, spinner glyphs
|
|
44
|
+
black: RGB(30, 30, 30), // code-block background
|
|
45
|
+
darkBg: RGB(20, 20, 20), // theme card background
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/** Light-theme approximations, true to the dark palette's hue mapping. */
|
|
49
|
+
const LIGHT = {
|
|
50
|
+
gold: RGB(180, 130, 0),
|
|
51
|
+
coral: RGB(190, 100, 70),
|
|
52
|
+
lavender: RGB(120, 130, 220),
|
|
53
|
+
blue: RGB(70, 110, 220),
|
|
54
|
+
green: RGB(60, 150, 80),
|
|
55
|
+
red: RGB(200, 60, 60),
|
|
56
|
+
gray: RGB(110, 110, 110),
|
|
57
|
+
dim: RGB(170, 170, 170),
|
|
58
|
+
white: RGB(40, 40, 40),
|
|
59
|
+
black: RGB(245, 245, 245),
|
|
60
|
+
darkBg: RGB(255, 255, 255),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Glyphs, copied from aegiscodex-dev's `GLYPH` including its platform passes.
|
|
65
|
+
* A few of these are missing from non-Linux terminal fonts (⎿ U+23BF, ⏸ U+23F8)
|
|
66
|
+
* or render as double-width emoji (✦ U+2726, ✻/✢/✽/✶), which breaks row
|
|
67
|
+
* alignment exactly like the welcome art — those platforms get single-width
|
|
68
|
+
* stand-ins instead.
|
|
69
|
+
*/
|
|
70
|
+
const GLYPH = (() => {
|
|
71
|
+
const base = {
|
|
72
|
+
cursor: '❯', // menu selection marker, prompt prefix
|
|
73
|
+
check: '✔', // selected / completed
|
|
74
|
+
divider: '╌', // menu separators
|
|
75
|
+
bullet: '·', // inline separators
|
|
76
|
+
hint: '❯', // "Try ..." suggestion marker
|
|
77
|
+
pause: '⏸', // bottom status line (manual mode)
|
|
78
|
+
star: '✦', // own-session marker, decorations
|
|
79
|
+
leftarrow: '←', // hints on the status line
|
|
80
|
+
pointer: '▸', // tips list bullets
|
|
81
|
+
hook: '⎿', // inline command / tip rows (tool commands, usage hints)
|
|
82
|
+
block: '●', // streaming cursor / assistant answer marker
|
|
83
|
+
bloom: '✻', // "done" spinner glyph (✻ Brewed for 2s)
|
|
84
|
+
spin: ['✢', '·', '✻', '*', '✽', '✶'], // working spinner
|
|
85
|
+
ellipse: '…',
|
|
86
|
+
};
|
|
87
|
+
if (process.platform === 'win32') {
|
|
88
|
+
return {
|
|
89
|
+
...base,
|
|
90
|
+
pause: '❚❚', // ⏸ U+23F8 missing from many Windows fonts
|
|
91
|
+
hook: '_|', // ⎿ U+23BF almost never present in Windows fonts
|
|
92
|
+
star: '*', // ✦ U+2726 renders wide/emoji
|
|
93
|
+
bloom: '*', // ✻ U+273B
|
|
94
|
+
spin: ['|', '/', '-', '\\', '*', '-'],
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
if (process.platform === 'darwin') {
|
|
98
|
+
return {
|
|
99
|
+
...base,
|
|
100
|
+
pause: '❚❚', // ⏸ has an emoji presentation on Apple fonts
|
|
101
|
+
star: '*', // ✦ renders as a double-width emoji in Terminal.app
|
|
102
|
+
hook: '_|', // ⎿ U+23BF not in Apple monospace fonts
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return base;
|
|
106
|
+
})();
|
|
107
|
+
|
|
108
|
+
/** Working-line verbs, verbatim from the capture-backed table. */
|
|
109
|
+
const VERBS = [
|
|
110
|
+
'Incubating',
|
|
111
|
+
'Tempering',
|
|
112
|
+
'Determining',
|
|
113
|
+
'Beboppin\'',
|
|
114
|
+
'Julienning',
|
|
115
|
+
'Inferring',
|
|
116
|
+
'Simmering',
|
|
117
|
+
'Twisting',
|
|
118
|
+
'Fermenting',
|
|
119
|
+
'Fiddle-faddling',
|
|
120
|
+
'Orbiting',
|
|
121
|
+
'Prestidigitating',
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
/** Completion verbs: "✻ Churned for 3s" after a chat turn, "✻ Worked for 3s"
|
|
125
|
+
* after one that used tools. */
|
|
126
|
+
const DONE_VERBS = ['Churned', 'Worked'];
|
|
127
|
+
|
|
128
|
+
const THEMES = { dark: C, light: LIGHT };
|
|
129
|
+
|
|
130
|
+
/** The palette object for a context. Colours are always derived from one of the
|
|
131
|
+
* two theme objects — never typed inline. */
|
|
132
|
+
function themeOf(ctx) {
|
|
133
|
+
return ctx && ctx.light ? LIGHT : C;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
module.exports = {
|
|
137
|
+
RGB,
|
|
138
|
+
BG,
|
|
139
|
+
RESET,
|
|
140
|
+
BOLD,
|
|
141
|
+
DIM,
|
|
142
|
+
ITALIC,
|
|
143
|
+
UNDER,
|
|
144
|
+
BOLD_OFF,
|
|
145
|
+
ITALIC_OFF,
|
|
146
|
+
UNDER_OFF,
|
|
147
|
+
RESET_FG,
|
|
148
|
+
RESET_BG,
|
|
149
|
+
C,
|
|
150
|
+
LIGHT,
|
|
151
|
+
THEMES,
|
|
152
|
+
GLYPH,
|
|
153
|
+
VERBS,
|
|
154
|
+
DONE_VERBS,
|
|
155
|
+
themeOf,
|
|
156
|
+
};
|