aegiscode 5.2.32 → 6.0.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 +123 -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 +543 -0
- package/src/art.js +45 -0
- package/src/commands.js +163 -0
- package/src/deps.js +65 -0
- package/src/format.js +54 -0
- package/src/render.js +281 -0
- package/src/screen.js +177 -0
- package/src/theme.js +120 -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/sharp-stub/index.js +0 -9
- package/scripts/sharp-stub/package.json +0 -5
package/src/screen.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Terminal plumbing: cell-accurate width maths, wrapping, and the ephemeral
|
|
5
|
+
* "live" region the CLI redraws while a call is in flight.
|
|
6
|
+
*
|
|
7
|
+
* Design note — this CLI is a LINEAR transcript, not a full-screen alt-buffer
|
|
8
|
+
* TUI (which is what Claude Code and aegiscodex-dev are). Finished turns are
|
|
9
|
+
* written once to scrollback, so output stays selectable, pipeable and
|
|
10
|
+
* scrollback-searchable; only the one live status/spinner line is redrawn. That
|
|
11
|
+
* is a deliberate product difference, and it is also why there is no alt-screen
|
|
12
|
+
* or mouse handling here.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const ANSI_RE = /\x1b\[[0-9;]*[A-Za-z]/g;
|
|
16
|
+
|
|
17
|
+
/** Combining marks and variation selectors occupy no cell. */
|
|
18
|
+
const RE_ZERO = /[\u0300-\u036f\ufe00-\ufe0f\u200b-\u200f]/;
|
|
19
|
+
/** Wide (CJK/fullwidth/emoji) ranges occupy two cells. */
|
|
20
|
+
const RE_WIDE =
|
|
21
|
+
/[\u1100-\u115f\u2e80-\u303e\u3041-\u33ff\u3400-\u4dbf\u4e00-\u9fff\ua000-\ua4cf\uac00-\ud7a3\uf900-\ufaff\ufe30-\ufe6f\uff00-\uff60\uffe0-\uffe6]|[\u{1f300}-\u{1faff}]|[\u{1f000}-\u{1f2ff}]/u;
|
|
22
|
+
|
|
23
|
+
function stripAnsi(s) {
|
|
24
|
+
return String(s).replace(ANSI_RE, '');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Display width of a string in terminal cells. */
|
|
28
|
+
function w(s) {
|
|
29
|
+
let n = 0;
|
|
30
|
+
for (const ch of stripAnsi(s)) {
|
|
31
|
+
if (RE_ZERO.test(ch)) continue;
|
|
32
|
+
n += RE_WIDE.test(ch) ? 2 : 1;
|
|
33
|
+
}
|
|
34
|
+
return n;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Truncate to `width` cells without cutting a wide/combining codepoint. */
|
|
38
|
+
function clip(s, width) {
|
|
39
|
+
if (w(s) <= width) return s;
|
|
40
|
+
let out = '';
|
|
41
|
+
let used = 0;
|
|
42
|
+
for (const ch of s) {
|
|
43
|
+
const cw = RE_ZERO.test(ch) ? 0 : RE_WIDE.test(ch) ? 2 : 1;
|
|
44
|
+
if (used + cw > width) break;
|
|
45
|
+
out += ch;
|
|
46
|
+
used += cw;
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Pad (or clip) to exactly `width` cells. */
|
|
52
|
+
function pad(s, width) {
|
|
53
|
+
const t = clip(s, width);
|
|
54
|
+
return t + ' '.repeat(Math.max(0, width - w(t)));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Right-align inside `width`. */
|
|
58
|
+
function padStart(s, width) {
|
|
59
|
+
const t = clip(s, width);
|
|
60
|
+
return ' '.repeat(Math.max(0, width - w(t))) + t;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Word-wrap one logical line to `width` cells, preserving words and never
|
|
65
|
+
* breaking mid-word unless the word alone exceeds the width.
|
|
66
|
+
*/
|
|
67
|
+
function wrapLine(line, width, indent = '') {
|
|
68
|
+
const limit = Math.max(8, width);
|
|
69
|
+
if (w(line) <= limit) return [line];
|
|
70
|
+
const words = line.split(' ');
|
|
71
|
+
const out = [];
|
|
72
|
+
let cur = '';
|
|
73
|
+
for (const word of words) {
|
|
74
|
+
const piece = cur ? `${cur} ${word}` : word;
|
|
75
|
+
if (w(piece) <= limit) {
|
|
76
|
+
cur = piece;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (cur) out.push(cur);
|
|
80
|
+
if (w(word) <= limit) {
|
|
81
|
+
cur = word;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
// A single token longer than the line: hard-split on cells.
|
|
85
|
+
let rest = word;
|
|
86
|
+
while (w(rest) > limit) {
|
|
87
|
+
const head = clip(rest, limit);
|
|
88
|
+
out.push(head);
|
|
89
|
+
rest = rest.slice(head.length);
|
|
90
|
+
}
|
|
91
|
+
cur = rest;
|
|
92
|
+
}
|
|
93
|
+
if (cur || !out.length) out.push(cur);
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Wrap a block of text (honours existing newlines), applying `indent` to
|
|
98
|
+
* continuation lines only when `hang` is true. */
|
|
99
|
+
function wrapBlock(text, width, indent = '', hang = false) {
|
|
100
|
+
const lines = [];
|
|
101
|
+
for (const raw of String(text).split('\n')) {
|
|
102
|
+
if (raw === '') {
|
|
103
|
+
lines.push('');
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const parts = wrapLine(raw, width - w(indent));
|
|
107
|
+
parts.forEach((p, i) => lines.push(i === 0 || !hang ? indent + p : indent + p));
|
|
108
|
+
}
|
|
109
|
+
return lines;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// --- cursor / screen control ------------------------------------------------
|
|
113
|
+
|
|
114
|
+
const ESC = {
|
|
115
|
+
hideCursor: '\x1b[?25l',
|
|
116
|
+
showCursor: '\x1b[?25h',
|
|
117
|
+
clearToEnd: '\x1b[0K',
|
|
118
|
+
clearLine: '\x1b[2K',
|
|
119
|
+
clearScreen: '\x1b[2J\x1b[H',
|
|
120
|
+
up: (n) => (n > 0 ? `\x1b[${n}A` : ''),
|
|
121
|
+
down: (n) => (n > 0 ? `\x1b[${n}B` : ''),
|
|
122
|
+
col: (n) => `\x1b[${n}G`,
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
function termWidth(fallback = 80) {
|
|
126
|
+
return Math.max(20, (process.stdout && process.stdout.columns) || fallback);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* A block of lines at the bottom of the transcript that can be redrawn in
|
|
131
|
+
* place. Every write goes through here, so the escape arithmetic lives in one
|
|
132
|
+
* place: `update()` erases the previous block, prints the new one, and keeps
|
|
133
|
+
* the cursor below it.
|
|
134
|
+
*/
|
|
135
|
+
class LiveRegion {
|
|
136
|
+
constructor(out = process.stdout) {
|
|
137
|
+
this.out = out;
|
|
138
|
+
this.rows = 0;
|
|
139
|
+
this.active = false;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
update(lines) {
|
|
143
|
+
const body = lines.map((l) => l + ESC.clearToEnd).join('\n');
|
|
144
|
+
if (!this.active) {
|
|
145
|
+
this.out.write(body);
|
|
146
|
+
this.active = true;
|
|
147
|
+
} else {
|
|
148
|
+
// Back to the first row of the old block, rewrite, clear the tail.
|
|
149
|
+
this.out.write(ESC.col(1) + ESC.up(this.rows) + body + '\n' + ESC.col(1) + ESC.up(1));
|
|
150
|
+
}
|
|
151
|
+
this.rows = lines.length;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Erase the block and return the cursor to its start, so the caller can
|
|
155
|
+
* print the durable version of the same content. */
|
|
156
|
+
clear() {
|
|
157
|
+
if (!this.active) return;
|
|
158
|
+
this.out.write(ESC.col(1) + ESC.clearLine);
|
|
159
|
+
for (let i = 1; i < this.rows; i++) this.out.write(`\n${ESC.clearLine}`);
|
|
160
|
+
this.out.write(ESC.up(this.rows - 1) + ESC.col(1));
|
|
161
|
+
this.rows = 0;
|
|
162
|
+
this.active = false;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
module.exports = {
|
|
167
|
+
w,
|
|
168
|
+
clip,
|
|
169
|
+
pad,
|
|
170
|
+
padStart,
|
|
171
|
+
stripAnsi,
|
|
172
|
+
wrapLine,
|
|
173
|
+
wrapBlock,
|
|
174
|
+
EC: ESC,
|
|
175
|
+
LiveRegion,
|
|
176
|
+
termWidth,
|
|
177
|
+
};
|
package/src/theme.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AEGIS terminal theme — "Signal".
|
|
5
|
+
*
|
|
6
|
+
* This CLI is deliberately NOT a Claude Code look-alike. The palette below is
|
|
7
|
+
* the whole point of the file: violet/cyan on ink, not the warm gold/coral
|
|
8
|
+
* scheme every Claude Code derivative ships. `test/cli-identity.test.mjs` pins
|
|
9
|
+
* that divergence — it fails if any of Claude Code's exact RGB triples, or its
|
|
10
|
+
* prompt/spinner glyphs, reappear here.
|
|
11
|
+
*
|
|
12
|
+
* Zero dependencies: SGR escapes are hand-built, like the rest of this repo.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const RESET = '\x1b[0m';
|
|
16
|
+
const BOLD = '\x1b[1m';
|
|
17
|
+
const DIM = '\x1b[2m';
|
|
18
|
+
const ITALIC = '\x1b[3m';
|
|
19
|
+
const UNDER = '\x1b[4m';
|
|
20
|
+
const BOLD_OFF = '\x1b[22m';
|
|
21
|
+
const ITALIC_OFF = '\x1b[23m';
|
|
22
|
+
const UNDER_OFF = '\x1b[24m';
|
|
23
|
+
const RESET_FG = '\x1b[39m';
|
|
24
|
+
const RESET_BG = '\x1b[49m';
|
|
25
|
+
|
|
26
|
+
const RGB = (r, g, b) => `\x1b[38;2;${r};${g};${b}m`;
|
|
27
|
+
const BG = (r, g, b) => `\x1b[48;2;${r};${g};${b}m`;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Dark theme (default). Every value is a truecolor triple; the palette leans
|
|
31
|
+
* cool, and `ink`/`panel` are blue-black rather than neutral grey so the
|
|
32
|
+
* transcript reads as a different product at a glance.
|
|
33
|
+
*/
|
|
34
|
+
const SIGNAL = {
|
|
35
|
+
plasma: [124, 92, 255], // brand accent — sigil, prompts, active states
|
|
36
|
+
beam: [34, 211, 238], // secondary — structure, model ids, code
|
|
37
|
+
pulse: [52, 211, 153], // success
|
|
38
|
+
alert: [251, 113, 133], // warning
|
|
39
|
+
fault: [255, 99, 99], // error
|
|
40
|
+
muted: [148, 163, 184], // secondary text
|
|
41
|
+
dim: [71, 85, 105], // rails, rules, disabled
|
|
42
|
+
text: [226, 232, 240], // body
|
|
43
|
+
ink: [10, 12, 18], // deepest background (status bar, inverse)
|
|
44
|
+
panel: [22, 25, 34], // raised surface (banner box)
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/** Light theme — same roles, daylight values. Never a Claude Code palette. */
|
|
48
|
+
const SIGNAL_LIGHT = {
|
|
49
|
+
plasma: [79, 55, 200],
|
|
50
|
+
beam: [8, 122, 145],
|
|
51
|
+
pulse: [5, 122, 85],
|
|
52
|
+
alert: [190, 55, 90],
|
|
53
|
+
fault: [185, 28, 28],
|
|
54
|
+
muted: [71, 85, 105],
|
|
55
|
+
dim: [148, 163, 184],
|
|
56
|
+
text: [15, 23, 42],
|
|
57
|
+
ink: [248, 250, 252],
|
|
58
|
+
panel: [241, 245, 249],
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Glyphs. Chosen to share no character with Claude Code's prompt (`❯`),
|
|
63
|
+
* spinner (`✢ · ✻ * ✽ ✶`), block cursor (`●`), hook (`⎿`) or quote bar (`▎`).
|
|
64
|
+
*/
|
|
65
|
+
const GLYPH = {
|
|
66
|
+
prompt: '»', // input prompt
|
|
67
|
+
sigil: '⬢', // AEGIS mark / assistant turns
|
|
68
|
+
rail: '┃', // heavy vertical gutter, both transcript roles
|
|
69
|
+
railEnd: '┣', // gutter corner where a turn's meta line attaches
|
|
70
|
+
spend: '∅', // money readout
|
|
71
|
+
ok: '✓',
|
|
72
|
+
err: '✗',
|
|
73
|
+
warn: '!',
|
|
74
|
+
bullet: '∙', // U+2219, not Claude's U+00B7
|
|
75
|
+
pointer: '▶', // selected row
|
|
76
|
+
divider: '─',
|
|
77
|
+
rule: '━',
|
|
78
|
+
spin: ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█', '▇', '▆', '▅', '▄', '▃', '▂'],
|
|
79
|
+
box: { tl: '┏', tr: '┓', bl: '┗', br: '┛', h: '━', v: '┃' }, // heavy, not rounded
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/** Verbs for the working line — plain, no Claude Code whimsy. */
|
|
83
|
+
const VERBS = [
|
|
84
|
+
'Consulting',
|
|
85
|
+
'Routing',
|
|
86
|
+
'Pooling',
|
|
87
|
+
'Reasoning',
|
|
88
|
+
'Drafting',
|
|
89
|
+
'Checking',
|
|
90
|
+
'Settling',
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
const THEMES = { dark: SIGNAL, light: SIGNAL_LIGHT };
|
|
94
|
+
|
|
95
|
+
/** The palette object for a context, per the rule that colours are always
|
|
96
|
+
* derived from one of the two theme objects — never typed inline. */
|
|
97
|
+
function themeOf(ctx) {
|
|
98
|
+
return ctx && ctx.light ? SIGNAL_LIGHT : SIGNAL;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
module.exports = {
|
|
102
|
+
RGB,
|
|
103
|
+
BG,
|
|
104
|
+
RESET,
|
|
105
|
+
BOLD,
|
|
106
|
+
DIM,
|
|
107
|
+
ITALIC,
|
|
108
|
+
UNDER,
|
|
109
|
+
BOLD_OFF,
|
|
110
|
+
ITALIC_OFF,
|
|
111
|
+
UNDER_OFF,
|
|
112
|
+
RESET_FG,
|
|
113
|
+
RESET_BG,
|
|
114
|
+
SIGNAL,
|
|
115
|
+
SIGNAL_LIGHT,
|
|
116
|
+
THEMES,
|
|
117
|
+
GLYPH,
|
|
118
|
+
VERBS,
|
|
119
|
+
themeOf,
|
|
120
|
+
};
|