aegiscode 6.1.0 → 6.2.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 +121 -78
- package/bin/aegiscode.js +9 -1
- package/package.json +3 -3
- package/scripts/predist.mjs +11 -1
- package/src/agents.js +136 -0
- package/src/app.js +522 -164
- package/src/chatflow.js +1475 -0
- package/src/checkpoint.js +85 -0
- package/src/clipboard.js +62 -0
- package/src/commands.js +1234 -150
- package/src/config.js +163 -0
- package/src/deps.js +14 -1
- package/src/devrun.js +110 -0
- package/src/engine.js +62 -0
- package/src/events.js +278 -0
- package/src/export.js +64 -0
- package/src/history.js +201 -0
- package/src/init.js +162 -0
- package/src/input.js +136 -0
- package/src/keys.js +141 -0
- package/src/panels.js +1171 -0
- package/src/permissions.js +102 -0
- package/src/render.js +33 -1
- package/src/summarize.js +90 -0
- package/src/system.js +37 -0
- package/src/tokens.js +166 -0
- package/vendor/desktop/lib/local/agents.js +102 -0
- package/vendor/desktop/lib/local/engine.js +972 -0
- package/vendor/desktop/lib/local/prompt.js +91 -0
- package/vendor/desktop/lib/local/shell.js +208 -0
- package/vendor/desktop/lib/local/tools.js +882 -0
package/src/panels.js
ADDED
|
@@ -0,0 +1,1171 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Panel builders — the bodies that slash-commands print.
|
|
5
|
+
*
|
|
6
|
+
* Ported from `aegiscodex-dev/src/panels.js` (868 lines), keeping its layouts
|
|
7
|
+
* and copy wherever the capability exists in this client. Everything here is
|
|
8
|
+
* PURE: a builder receives all of its data as arguments and returns an array of
|
|
9
|
+
* span lines (each line an array of `{ t, s, w }`, see `screen.js`). No stdout
|
|
10
|
+
* writes, no `process.exit`, no network, no filesystem reads — which is exactly
|
|
11
|
+
* what makes them assertable from a plain Node test with no TTY.
|
|
12
|
+
*
|
|
13
|
+
* Style objects come from `themeOf(ctx)` (`ctx = { light }`), never a
|
|
14
|
+
* module-scoped palette. Money is euro: `fmtEur` renders 4dp below a cent (a
|
|
15
|
+
* pooled call settles near €0.0007) and 2dp at or above it, so a real charge
|
|
16
|
+
* never reads as "€0.00". The reference printed USD; every `$` became `€`.
|
|
17
|
+
* Claude/Anthropic names became AEGIS (`aegiscode`, `aegiscloud.org`,
|
|
18
|
+
* `github.com/aegisinfo/aegiscode-plugin`) and `~/.aegiscodex` became
|
|
19
|
+
* `~/.aegiscode`.
|
|
20
|
+
*
|
|
21
|
+
* Every builder is defensive: a missing field is omitted, never rendered as
|
|
22
|
+
* `undefined`, `NaN` or `[object Object]`.
|
|
23
|
+
*
|
|
24
|
+
* ── Ported for real ──────────────────────────────────────────────────────────
|
|
25
|
+
* buildHelp buildStatus buildCost buildContext buildTokens buildAgents
|
|
26
|
+
* renderSessionsOverlay buildPermissions buildModelList buildRewindList
|
|
27
|
+
* buildOnboarding buildShellCompletion buildTerminalSetup buildPRs
|
|
28
|
+
* buildBenchmark buildWaifu buildAegisStatus buildAegisRecall buildAegisMulti
|
|
29
|
+
* buildBilling buildMemory buildMemoryTiers buildRouter buildYolo buildSkills
|
|
30
|
+
* buildMcp buildHooksStatus buildHooksList buildTroubleshooting
|
|
31
|
+
* buildReleaseNotes
|
|
32
|
+
* (plus the pure reference helpers with no missing capability)
|
|
33
|
+
* buildAegisPrint buildSkillDetail buildMemoryEmbeddings
|
|
34
|
+
*
|
|
35
|
+
* ── Honest stubs (capability absent in this client) ──────────────────────────
|
|
36
|
+
* buildDoctor — needs the local environment diagnostic suite (runDoctor),
|
|
37
|
+
* which belongs to aegiscodex-dev; /doctor is unavailable.
|
|
38
|
+
* buildBuildPanel — needs a local multi-model agent build loop this client
|
|
39
|
+
* does not host.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
const { span, getSize, lineWidth } = require('./screen.js');
|
|
43
|
+
const { themeOf, BOLD, BOLD_OFF, GLYPH } = require('./theme.js');
|
|
44
|
+
|
|
45
|
+
// ── local helpers (this file stays self-contained apart from theme/screen) ────
|
|
46
|
+
|
|
47
|
+
/** Finite number or 0 — never NaN (which would print as "NaN"). */
|
|
48
|
+
function num(v) {
|
|
49
|
+
const n = Number(v);
|
|
50
|
+
return Number.isFinite(n) ? n : 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** A safe display string: null/undefined become '', everything else String()s. */
|
|
54
|
+
function str(v) {
|
|
55
|
+
return v == null ? '' : String(v);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Group digits: 1562 -> "1,562". Matches the CLI's format.js rule. */
|
|
59
|
+
function fmtTokens(n) {
|
|
60
|
+
const v = Number(n);
|
|
61
|
+
if (!Number.isFinite(v) || v <= 0) return '0';
|
|
62
|
+
return Math.round(v)
|
|
63
|
+
.toString()
|
|
64
|
+
.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** € amount, 4dp below a cent (per-call spend) and 2dp at or above it. */
|
|
68
|
+
function fmtEur(n) {
|
|
69
|
+
const v = Number(n);
|
|
70
|
+
if (!Number.isFinite(v)) return '€?';
|
|
71
|
+
const abs = Math.abs(v);
|
|
72
|
+
return `€${abs < 0.01 ? abs.toFixed(4) : abs.toFixed(2)}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Relative-time stamp, ported verbatim from the reference's `ago()`. */
|
|
76
|
+
function ago(ts) {
|
|
77
|
+
if (!ts) return '';
|
|
78
|
+
const ms = Date.now() - new Date(String(ts)).getTime();
|
|
79
|
+
if (!(ms >= 0) || Number.isNaN(ms)) return '';
|
|
80
|
+
if (ms < 60_000) return `${Math.max(1, Math.round(ms / 1000))}s ago`;
|
|
81
|
+
if (ms < 3_600_000) return `${Math.round(ms / 60_000)}m ago`;
|
|
82
|
+
if (ms < 86_400_000) return `${Math.round(ms / 3_600_000)}h ago`;
|
|
83
|
+
return `${Math.round(ms / 86_400_000)}d ago`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** A `█`-filled progress bar as a span line (the reference's contextBar). */
|
|
87
|
+
function contextBar(used, window, t, width = 50) {
|
|
88
|
+
const pct = Math.max(0, Math.min(1, window > 0 ? num(used) / window : 0));
|
|
89
|
+
const filled = Math.round(pct * width);
|
|
90
|
+
return [
|
|
91
|
+
span('', ' '),
|
|
92
|
+
span(t.green, '█'.repeat(filled)),
|
|
93
|
+
span(t.dim, '█'.repeat(Math.max(0, width - filled))),
|
|
94
|
+
span(t.gray, ` ${Math.round(pct * 100)}% used`),
|
|
95
|
+
];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Aligned key/value rows, the reference's `k + ' '.repeat(maxK-len) + ' : '`. */
|
|
99
|
+
function kv(t, rows, { indent = ' ', keyStyle = null, valStyle = null } = {}) {
|
|
100
|
+
const maxK = rows.reduce((m, r) => Math.max(m, [...r[0]].length), 0);
|
|
101
|
+
return rows.map(([k, v]) => [
|
|
102
|
+
span(keyStyle != null ? keyStyle : t.gray, indent + k + ' '.repeat(maxK - [...k].length) + ' : '),
|
|
103
|
+
span(valStyle != null ? valStyle : t.white, str(v)),
|
|
104
|
+
]);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** The `state` argument, guaranteed to be an object. */
|
|
108
|
+
function obj(state) {
|
|
109
|
+
return state && typeof state === 'object' ? state : {};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** An object that looks like ÆGIS memory stats, from either `state.memory` or
|
|
113
|
+
* `state` itself (the reference passed the stats object directly). */
|
|
114
|
+
function statsFrom(state) {
|
|
115
|
+
const s = obj(state);
|
|
116
|
+
if (s.stats && typeof s.stats === 'object') return s.stats;
|
|
117
|
+
if (s.memory && typeof s.memory === 'object' && (s.memory.total != null || s.memory.tiers || s.memory.sessions != null || s.memory.roles)) {
|
|
118
|
+
return s.memory;
|
|
119
|
+
}
|
|
120
|
+
if (s.total != null || s.tiers || s.roles) return s;
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Category labels, copied from the reference registry's CATEGORIES table. */
|
|
125
|
+
const CATEGORY_LABELS = {
|
|
126
|
+
session: 'Session & context',
|
|
127
|
+
workspace: 'Workspace',
|
|
128
|
+
model: 'Model & behavior',
|
|
129
|
+
data: 'Data',
|
|
130
|
+
auth: 'Auth',
|
|
131
|
+
support: 'Support',
|
|
132
|
+
fun: 'Fun',
|
|
133
|
+
aegis: 'Aegis plugin',
|
|
134
|
+
custom: 'Custom',
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
function categoryLabel(cmd) {
|
|
138
|
+
if (cmd && cmd.categoryLabel) return String(cmd.categoryLabel);
|
|
139
|
+
const id = str(cmd && cmd.category);
|
|
140
|
+
if (CATEGORY_LABELS[id]) return CATEGORY_LABELS[id];
|
|
141
|
+
return id ? id.charAt(0).toUpperCase() + id.slice(1) : 'Commands';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ── /help ────────────────────────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
/** @param {Array} commands [{name,aliases,desc,hint,args,category,unavailable}] */
|
|
147
|
+
function buildHelp(commands, ctx = {}) {
|
|
148
|
+
const t = themeOf(ctx);
|
|
149
|
+
const list = Array.isArray(commands)
|
|
150
|
+
? commands
|
|
151
|
+
: Array.isArray(obj(commands).commands)
|
|
152
|
+
? commands.commands
|
|
153
|
+
: [];
|
|
154
|
+
const out = [];
|
|
155
|
+
out.push([span(t.gold + BOLD, 'Commands'), span(BOLD_OFF, '')]);
|
|
156
|
+
out.push([span(t.gray, '─'.repeat(30))]);
|
|
157
|
+
let lastCat = null;
|
|
158
|
+
for (const cmd of list) {
|
|
159
|
+
if (!cmd || !cmd.name) continue;
|
|
160
|
+
const cat = str(cmd.category);
|
|
161
|
+
if (cat !== lastCat) {
|
|
162
|
+
out.push([span('', '')]);
|
|
163
|
+
out.push([span(t.gray + BOLD, categoryLabel(cmd)), span(BOLD_OFF, '')]);
|
|
164
|
+
lastCat = cat;
|
|
165
|
+
}
|
|
166
|
+
const name = String(cmd.name);
|
|
167
|
+
const pad = ' '.repeat(Math.max(1, 16 - [...name].length));
|
|
168
|
+
const desc = str(cmd.desc != null ? cmd.desc : cmd.help);
|
|
169
|
+
const nameStyle = cmd.unavailable ? t.gray : t.white;
|
|
170
|
+
const descStyle = cmd.unavailable ? t.dim : t.gray;
|
|
171
|
+
const tail = cmd.unavailable ? ' (unavailable)' : '';
|
|
172
|
+
out.push([span(nameStyle, '/' + name), span(descStyle, pad + desc + tail)]);
|
|
173
|
+
}
|
|
174
|
+
if (!list.length) {
|
|
175
|
+
out.push([span('', '')]);
|
|
176
|
+
out.push([span(t.gray, 'No commands registered.')]);
|
|
177
|
+
}
|
|
178
|
+
out.push([span('', '')]);
|
|
179
|
+
out.push([
|
|
180
|
+
span(t.gray, 'Shortcuts: '), span(t.white, '?'), span(t.gray, ' shortcuts · '),
|
|
181
|
+
span(t.white, '/help'), span(t.gray, ' commands · '), span(t.white, '↑↓'), span(t.gray, ' history · '),
|
|
182
|
+
span(t.white, 'Ctrl-L'), span(t.gray, ' clear · '), span(t.white, 'Ctrl-D'), span(t.gray, ' exit'),
|
|
183
|
+
]);
|
|
184
|
+
return out;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ── /status ──────────────────────────────────────────────────────────────────
|
|
188
|
+
|
|
189
|
+
/** @param {object} state session state (see the module header in the task) */
|
|
190
|
+
function buildStatus(state, ctx = {}) {
|
|
191
|
+
const t = themeOf(ctx);
|
|
192
|
+
const s = obj(state);
|
|
193
|
+
const lines = [];
|
|
194
|
+
lines.push([span(t.gold + BOLD, 'Status'), span(BOLD_OFF, '')]);
|
|
195
|
+
|
|
196
|
+
const rows = [];
|
|
197
|
+
const add = (k, v) => {
|
|
198
|
+
const value = str(v);
|
|
199
|
+
if (value !== '') rows.push([k, value]);
|
|
200
|
+
};
|
|
201
|
+
add('Session ID', s.sessionId);
|
|
202
|
+
add('Working directory', s.cwd);
|
|
203
|
+
add('Home', s.home);
|
|
204
|
+
add('Model', s.model);
|
|
205
|
+
add('Effort', s.effort);
|
|
206
|
+
if (s.thinking != null) add('Thinking', s.thinking ? 'on' : 'off');
|
|
207
|
+
if (s.stream != null) add('Streaming', s.stream === false ? 'off' : 'on');
|
|
208
|
+
if (s.vim != null) add('Vim keymap', s.vim ? 'on' : 'off');
|
|
209
|
+
add('Theme', s.theme ? str(s.theme) : (ctx && ctx.light ? 'Light mode' : 'Dark mode'));
|
|
210
|
+
add('Backend', s.backend);
|
|
211
|
+
add('Base', s.base);
|
|
212
|
+
add('Plan', s.plan);
|
|
213
|
+
add('Account', s.account);
|
|
214
|
+
if (s.online != null) add('Online', s.online ? 'yes' : 'no');
|
|
215
|
+
if (s.turns != null) add('Turns', str(num(s.turns)));
|
|
216
|
+
if (s.calls != null) add('Tool calls', str(num(s.calls)));
|
|
217
|
+
if (s.version != null) add('Version', 'v' + str(s.version));
|
|
218
|
+
add('Node', process.version);
|
|
219
|
+
add('Platform', `${process.platform} ${process.arch}`);
|
|
220
|
+
|
|
221
|
+
for (const l of kv(t, rows, { keyStyle: t.gray, valStyle: t.white })) lines.push(l);
|
|
222
|
+
|
|
223
|
+
lines.push([span('', '')]);
|
|
224
|
+
const mode = s.permissions && s.permissions.mode ? str(s.permissions.mode) : 'default';
|
|
225
|
+
lines.push([span(t.gray, `Permissions: ${mode} — tool use follows your AEGIS settings.`)]);
|
|
226
|
+
return lines;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ── /cost — the session's euro tally ─────────────────────────────────────────
|
|
230
|
+
|
|
231
|
+
/** @param {object} state {tokens, costEur, balance, model, turns, calls} */
|
|
232
|
+
function buildCost(state, ctx = {}) {
|
|
233
|
+
const t = themeOf(ctx);
|
|
234
|
+
const s = obj(state);
|
|
235
|
+
const { cols } = getSize();
|
|
236
|
+
const W = Math.max(30, cols - 2);
|
|
237
|
+
const tok = s.tokens && typeof s.tokens === 'object' ? s.tokens : {};
|
|
238
|
+
const input = num(tok.input);
|
|
239
|
+
const output = num(tok.output);
|
|
240
|
+
const total = num(tok.total) || input + output;
|
|
241
|
+
const cost = num(s.costEur);
|
|
242
|
+
const model = str(s.model) || 'the pinned model';
|
|
243
|
+
const window = num(s.contextWindow) || 200000;
|
|
244
|
+
const pct = window > 0 ? Math.min(100, Math.round((total / window) * 100)) : 0;
|
|
245
|
+
|
|
246
|
+
const lines = [];
|
|
247
|
+
lines.push([span(t.gray, '─'.repeat(W))]);
|
|
248
|
+
lines.push([
|
|
249
|
+
span('', ' '), span(t.white + BOLD, 'Session'), span(BOLD_OFF, ''),
|
|
250
|
+
span(t.gray, ' Status Config Usage Stats'),
|
|
251
|
+
]);
|
|
252
|
+
lines.push([span('', ' '), span(t.gray, 'Session')]);
|
|
253
|
+
lines.push([span('', ' '), span(t.white, 'Total cost:'), span(t.gray, ' ' + ' '.repeat(Math.max(0, 14 - fmtEur(cost).length)) + fmtEur(cost))]);
|
|
254
|
+
if (s.balance != null) {
|
|
255
|
+
lines.push([span('', ' '), span(t.white, 'Balance:'), span(t.gray, ' ' + ' '.repeat(Math.max(0, 14 - fmtEur(s.balance).length)) + fmtEur(s.balance))]);
|
|
256
|
+
}
|
|
257
|
+
if (s.turns != null || s.calls != null) {
|
|
258
|
+
lines.push([span('', ' '), span(t.white, 'Turns:'), span(t.gray, ` ${num(s.turns)} turns · ${num(s.calls)} calls`)]);
|
|
259
|
+
}
|
|
260
|
+
lines.push([span('', ' '), span(t.white, ' Usage by model:')]);
|
|
261
|
+
lines.push([span('', ' '), span(t.gray, ` ${model}: ${fmtTokens(input)} input, ${fmtTokens(output)} output (${fmtEur(cost)})`)]);
|
|
262
|
+
lines.push([span('', ' ')]);
|
|
263
|
+
lines.push([span('', ' '), span(t.white + BOLD, 'Current session'), span(BOLD_OFF, '')]);
|
|
264
|
+
lines.push(contextBar(total, window, t));
|
|
265
|
+
lines.push([span('', ' ')]);
|
|
266
|
+
lines.push([span('', ' '), span(t.gray, `Session context: ${pct}% of ${fmtTokens(window)} used.`)]);
|
|
267
|
+
lines.push([span('', ' '), span(t.dim, 'Cost is metered from the AEGIS token bank; tokens are counted this session.')]);
|
|
268
|
+
return lines;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ── /context — token usage by bucket ─────────────────────────────────────────
|
|
272
|
+
|
|
273
|
+
/** @param {object} state {tokens} */
|
|
274
|
+
function buildContext(state, ctx = {}) {
|
|
275
|
+
const t = themeOf(ctx);
|
|
276
|
+
const s = obj(state);
|
|
277
|
+
const { cols } = getSize();
|
|
278
|
+
const W = Math.max(30, cols - 2);
|
|
279
|
+
const tok = s.tokens && typeof s.tokens === 'object' ? s.tokens : {};
|
|
280
|
+
const input = num(tok.input);
|
|
281
|
+
const output = num(tok.output);
|
|
282
|
+
const total = num(tok.total) || input + output;
|
|
283
|
+
const window = num(s.contextWindow) || 200000;
|
|
284
|
+
|
|
285
|
+
const lines = [];
|
|
286
|
+
lines.push([span(t.gold + BOLD, 'Context usage'), span(BOLD_OFF, '')]);
|
|
287
|
+
lines.push([span('', ' '), span(t.gray, '─'.repeat(Math.max(1, W - 2)))]);
|
|
288
|
+
const row = (label, tokens) => [
|
|
289
|
+
span('', ' '), span(t.white, label),
|
|
290
|
+
span('', ' '.repeat(Math.max(1, 16 - [...label].length))),
|
|
291
|
+
span(t.gray, fmtTokens(tokens)),
|
|
292
|
+
span('', ' '.repeat(Math.max(1, 10 - fmtTokens(tokens).length))),
|
|
293
|
+
...contextBar(tokens, window, t),
|
|
294
|
+
];
|
|
295
|
+
lines.push(row('Prompt tokens', input));
|
|
296
|
+
lines.push(row('Output tokens', output));
|
|
297
|
+
lines.push([span('', ' ')]);
|
|
298
|
+
lines.push([span('', ' '), span(t.gray, '─'.repeat(Math.max(1, W - 2)))]);
|
|
299
|
+
lines.push([
|
|
300
|
+
span('', ' '), span(t.white + BOLD, 'Total'), span('', ' '.repeat(9)),
|
|
301
|
+
span(t.gray, fmtTokens(total)),
|
|
302
|
+
span('', ' '.repeat(Math.max(1, 10 - fmtTokens(total).length))),
|
|
303
|
+
...contextBar(total, window, t),
|
|
304
|
+
]);
|
|
305
|
+
lines.push([span('', ' ')]);
|
|
306
|
+
lines.push([span('', ' '), span(t.gray, `Context window: ${fmtTokens(window)} tokens`)]);
|
|
307
|
+
return lines;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ── agents / sessions overlay ────────────────────────────────────────────────
|
|
311
|
+
|
|
312
|
+
/** @param {object} state {version, model, cwd, sessions} */
|
|
313
|
+
function renderSessionsOverlay(state, width, height, ctx = {}) {
|
|
314
|
+
const t = themeOf(ctx);
|
|
315
|
+
const s = obj(state);
|
|
316
|
+
const W = Math.max(30, num(width) ? num(width) - 4 : 76);
|
|
317
|
+
const ver = str(s.version);
|
|
318
|
+
const model = str(s.model) || 'default model';
|
|
319
|
+
const cwd = str(s.cwd).split('/').filter(Boolean).pop() || '~';
|
|
320
|
+
const sessions = Array.isArray(s.sessions) ? s.sessions.filter(Boolean) : [];
|
|
321
|
+
const awaiting = sessions.filter((x) => x.awaiting || x.needsInput).length;
|
|
322
|
+
const working = sessions.filter((x) => x.working).length;
|
|
323
|
+
const completed = sessions.filter((x) => x.completed).length;
|
|
324
|
+
|
|
325
|
+
const line = (spans) => [
|
|
326
|
+
span(t.gray, '│'), span('', ' '), ...spans,
|
|
327
|
+
span('', ' '.repeat(Math.max(0, W - 1 - lineWidth(spans)))),
|
|
328
|
+
span(t.gray, '│'),
|
|
329
|
+
];
|
|
330
|
+
const box = [];
|
|
331
|
+
box.push([span(t.coral, '╭─── Aegiscode v' + ver + ' ' + '─'.repeat(Math.max(0, W - 24)) + '╮')]);
|
|
332
|
+
box.push(line([span(t.white, model), span(t.gray, ' · '), span(t.white, cwd)]));
|
|
333
|
+
box.push(line([span(t.gray, `${awaiting} awaiting input · ${working} working · ${completed} completed`)]));
|
|
334
|
+
box.push([span(t.gray, '├' + '─'.repeat(W) + '┤')]);
|
|
335
|
+
box.push(line([span(t.white + BOLD, 'Needs input'), span(BOLD_OFF, '')]));
|
|
336
|
+
box.push(line([span(t.gray, 'Sessions that have a question or need your decision land here')]));
|
|
337
|
+
box.push(line([span(t.coral, GLYPH.bloom), span(t.gray, ' current session send a prompt to start '), span(t.gray, '—')]));
|
|
338
|
+
box.push(line([span(t.white + BOLD, 'Working'), span(BOLD_OFF, '')]));
|
|
339
|
+
box.push(line([span(t.gray, 'Sessions AEGIS is actively working on — they keep running even if you close the terminal')]));
|
|
340
|
+
box.push(line([span(t.white + BOLD, 'Completed'), span(BOLD_OFF, '')]));
|
|
341
|
+
box.push(line([span(t.gray, 'Finished sessions wait here for you to review')]));
|
|
342
|
+
box.push([span(t.gray, '╰' + '─'.repeat(W) + '╯')]);
|
|
343
|
+
box.push([span('', ' '), span(t.gray, 'Sub-agents that keep working in the background need a local agent loop;')]);
|
|
344
|
+
box.push([span('', ' '), span(t.gray, 'this build hands a bigger task to the pooled brain instead.')]);
|
|
345
|
+
return box;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** @param {object} state /agents — the sessions overlay at the terminal size. */
|
|
349
|
+
function buildAgents(state, ctx = {}) {
|
|
350
|
+
const { cols, rows } = getSize();
|
|
351
|
+
return renderSessionsOverlay(state, cols, rows, ctx);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// ── /permissions ─────────────────────────────────────────────────────────────
|
|
355
|
+
|
|
356
|
+
/** @param {object} rules {allow,deny,ask,defaultMode,_path} @param {string} [mode] */
|
|
357
|
+
function buildPermissions(rules, mode, ctx = {}) {
|
|
358
|
+
const t = themeOf(ctx);
|
|
359
|
+
const r = obj(rules);
|
|
360
|
+
const list = (v) => (Array.isArray(v) ? v : []);
|
|
361
|
+
const def = mode != null && mode !== '' ? String(mode) : (r.defaultMode != null ? String(r.defaultMode) : 'default');
|
|
362
|
+
|
|
363
|
+
const lines = [];
|
|
364
|
+
lines.push([span(t.gold + BOLD, 'Permissions'), span(BOLD_OFF, '')]);
|
|
365
|
+
lines.push([span('', ' '), span(t.gray, 'Default mode: '), span(t.white, def)]);
|
|
366
|
+
lines.push([span('', ' ')]);
|
|
367
|
+
const section = (name, patterns) => {
|
|
368
|
+
const out = [[span('', ' '), span(t.white + BOLD, name), span(BOLD_OFF, '')]];
|
|
369
|
+
if (!patterns.length) out.push([span('', ' '), span(t.gray, '(none)')]);
|
|
370
|
+
for (const p of patterns) out.push([span('', ' '), span(t.gray, str(p))]);
|
|
371
|
+
return out;
|
|
372
|
+
};
|
|
373
|
+
for (const l of section('Allow', list(r.allow))) lines.push(l);
|
|
374
|
+
for (const l of section('Deny', list(r.deny))) lines.push(l);
|
|
375
|
+
for (const l of section('Ask', list(r.ask))) lines.push(l);
|
|
376
|
+
lines.push([span('', '')]);
|
|
377
|
+
lines.push([span(t.gray, 'Usage:')]);
|
|
378
|
+
lines.push([span(t.gray, ' /permissions allow "Bash(npm run *)"')]);
|
|
379
|
+
lines.push([span(t.gray, ' /permissions deny "Edit(src/**)"')]);
|
|
380
|
+
lines.push([span(t.gray, ' /permissions ask "Read(**/*.env)"')]);
|
|
381
|
+
lines.push([span(t.gray, ' /permissions default ask · /permissions clear')]);
|
|
382
|
+
lines.push([span('', '')]);
|
|
383
|
+
lines.push([span(t.dim, `Stored in ${str(r._path) || '~/.aegiscode/permissions.json'}`)]);
|
|
384
|
+
return lines;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// ── /model list ──────────────────────────────────────────────────────────────
|
|
388
|
+
|
|
389
|
+
/** @param {string} currentId @param {Array} models [{id,name,model}] */
|
|
390
|
+
function buildModelList(currentId, models, ctx = {}) {
|
|
391
|
+
const t = themeOf(ctx);
|
|
392
|
+
const list = Array.isArray(models) ? models : [];
|
|
393
|
+
const cur0 = str(currentId);
|
|
394
|
+
const lines = [];
|
|
395
|
+
lines.push([span(t.gold + BOLD, 'Models'), span(BOLD_OFF, '')]);
|
|
396
|
+
lines.push([span(t.gray, '─'.repeat(30))]);
|
|
397
|
+
for (const m of list) {
|
|
398
|
+
if (!m) continue;
|
|
399
|
+
const idv = str(m.id) || str(m.model);
|
|
400
|
+
const cur = idv !== '' && idv === cur0;
|
|
401
|
+
const id = (cur ? '▶ ' : ' ') + idv;
|
|
402
|
+
const name = str(m.name || m.label || m.model);
|
|
403
|
+
const model = str(m.model) || '(default)';
|
|
404
|
+
lines.push([
|
|
405
|
+
span(t.white, id), span('', ' '.repeat(Math.max(1, 26 - [...id].length))),
|
|
406
|
+
span(t.gray, name), span('', ' '.repeat(Math.max(1, 22 - [...name].length))),
|
|
407
|
+
span(t.gray, model),
|
|
408
|
+
]);
|
|
409
|
+
}
|
|
410
|
+
if (!list.length) lines.push([span('', ' '), span(t.gray, '(no models configured)')]);
|
|
411
|
+
lines.push([span('', '')]);
|
|
412
|
+
lines.push([span(t.gray, 'Switch: /model <id> · Add: /model add <id> <name> <model> <baseURL> [apiKey] · Remove: /model remove <id>')]);
|
|
413
|
+
return lines;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// ── /rewind ──────────────────────────────────────────────────────────────────
|
|
417
|
+
|
|
418
|
+
/** @param {Array} items [{idx,depth,words,ts}] */
|
|
419
|
+
function buildRewindList(items, ctx = {}) {
|
|
420
|
+
const t = themeOf(ctx);
|
|
421
|
+
const list = Array.isArray(items) ? items : [];
|
|
422
|
+
const lines = [];
|
|
423
|
+
lines.push([span(t.gold + BOLD, 'Rewind'), span(BOLD_OFF, '')]);
|
|
424
|
+
lines.push([span('', ' '), span(t.gray, 'Snapshots are taken after every exchange.')]);
|
|
425
|
+
lines.push([span('', ' ')]);
|
|
426
|
+
for (const it of list) {
|
|
427
|
+
if (!it) continue;
|
|
428
|
+
const idx = str(num(it.idx));
|
|
429
|
+
lines.push([
|
|
430
|
+
span('', ' '), span(t.green, idx),
|
|
431
|
+
span('', ' '.repeat(Math.max(1, 4 - [...idx].length))),
|
|
432
|
+
span(t.white, `${num(it.depth)} messages`),
|
|
433
|
+
span(t.gray, ` · ${num(it.words)} words · ${ago(it.ts)}`),
|
|
434
|
+
]);
|
|
435
|
+
}
|
|
436
|
+
if (!list.length) lines.push([span('', ' '), span(t.gray, '(no checkpoints)')]);
|
|
437
|
+
lines.push([span('', '')]);
|
|
438
|
+
lines.push([span(t.gray, 'Usage: /rewind N to restore that checkpoint (0 = session start)')]);
|
|
439
|
+
return lines;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// ── /hooks ───────────────────────────────────────────────────────────────────
|
|
443
|
+
|
|
444
|
+
/** @param {object} stats {enabled,hooks,events,byEvent,fromPaths} */
|
|
445
|
+
function buildHooksStatus(stats, ctx = {}) {
|
|
446
|
+
const t = themeOf(ctx);
|
|
447
|
+
const st = obj(stats);
|
|
448
|
+
const events = Array.isArray(st.events) ? st.events : [];
|
|
449
|
+
const byEvent = st.byEvent && typeof st.byEvent === 'object' ? st.byEvent : {};
|
|
450
|
+
const fromPaths = Array.isArray(st.fromPaths) ? st.fromPaths : Array.isArray(st.sources) ? st.sources : [];
|
|
451
|
+
|
|
452
|
+
const lines = [];
|
|
453
|
+
lines.push([span(t.gold + BOLD, 'Hooks status'), span(BOLD_OFF, '')]);
|
|
454
|
+
const rows = [
|
|
455
|
+
['status', st.enabled ? 'enabled' : 'disabled'],
|
|
456
|
+
['hooks', str(num(st.hooks))],
|
|
457
|
+
['events', str(events.length)],
|
|
458
|
+
];
|
|
459
|
+
for (const l of kv(t, rows, { keyStyle: t.gray, valStyle: t.white })) lines.push(l);
|
|
460
|
+
if (num(st.hooks) > 0) {
|
|
461
|
+
lines.push([span('', '')]);
|
|
462
|
+
lines.push([span('', ' '), span(t.white + BOLD, 'by event'), span(BOLD_OFF, '')]);
|
|
463
|
+
for (const [event, count] of Object.entries(byEvent)) {
|
|
464
|
+
lines.push([span('', ' '), span(t.green, GLYPH.bullet), span(t.white, ` ${event}`), span(t.gray, ` ${num(count)}`)]);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
lines.push([span('', '')]);
|
|
468
|
+
lines.push([span(t.gray, '/hooks list for full config')]);
|
|
469
|
+
lines.push([span('', '')]);
|
|
470
|
+
lines.push([span(t.dim, fromPaths.length
|
|
471
|
+
? `Settings: ${fromPaths.join(', ')}`
|
|
472
|
+
: 'Settings: no settings.json with hooks found (checked ~/.aegis, .aegis, ~/.aegiscode, .aegiscode, ~/.claude)')]);
|
|
473
|
+
lines.push([span(t.gray, 'Aegiscode reads hook config but does not manage hooks — edit the files above.')]);
|
|
474
|
+
return lines;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/** @param {object} config {hooks:{event:[{name,matcher,hooks}]}} */
|
|
478
|
+
function buildHooksList(config, ctx = {}) {
|
|
479
|
+
const t = themeOf(ctx);
|
|
480
|
+
const cfg = obj(config);
|
|
481
|
+
const hooks = cfg.hooks && typeof cfg.hooks === 'object' ? cfg.hooks : {};
|
|
482
|
+
const lines = [];
|
|
483
|
+
lines.push([span(t.gold + BOLD, 'Hooks config'), span(BOLD_OFF, '')]);
|
|
484
|
+
let hasAny = false;
|
|
485
|
+
for (const [event, matchers] of Object.entries(hooks)) {
|
|
486
|
+
if (!Array.isArray(matchers) || matchers.length === 0) continue;
|
|
487
|
+
hasAny = true;
|
|
488
|
+
lines.push([span('', '')]);
|
|
489
|
+
lines.push([span('', ' '), span(t.white + BOLD, event), span(BOLD_OFF, '')]);
|
|
490
|
+
for (const matcher of matchers) {
|
|
491
|
+
if (!matcher) continue;
|
|
492
|
+
lines.push([span('', ' '), span(t.lavender, str(matcher.name) || '(unnamed)')]);
|
|
493
|
+
if (matcher.matcher) {
|
|
494
|
+
if (matcher.matcher.tools) lines.push([span('', ' - tools: '), span(t.gray, str(matcher.matcher.tools))]);
|
|
495
|
+
if (matcher.matcher.paths) lines.push([span('', ' - paths: '), span(t.gray, str(matcher.matcher.paths))]);
|
|
496
|
+
if (matcher.matcher.commands) lines.push([span('', ' - commands: '), span(t.gray, str(matcher.matcher.commands))]);
|
|
497
|
+
}
|
|
498
|
+
lines.push([span('', ` - hooks: ${Array.isArray(matcher.hooks) ? matcher.hooks.length : 0}`)]);
|
|
499
|
+
lines.push([span('', '')]);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
if (!hasAny) {
|
|
503
|
+
lines.push([span('', ' '), span(t.gray, 'no hooks configured.')]);
|
|
504
|
+
lines.push([span('', '')]);
|
|
505
|
+
lines.push([span('', ' '), span(t.gray, 'add hooks to settings.json:')]);
|
|
506
|
+
lines.push([span('', ' '), span(t.gray, '- ~/.aegis/settings.json (user)')]);
|
|
507
|
+
lines.push([span('', ' '), span(t.gray, '- .aegis/settings.json (project)')]);
|
|
508
|
+
} else {
|
|
509
|
+
lines.push([span('', '')]);
|
|
510
|
+
lines.push([span(t.gray, '/hooks status for the summary')]);
|
|
511
|
+
}
|
|
512
|
+
return lines;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// ── support panels ───────────────────────────────────────────────────────────
|
|
516
|
+
|
|
517
|
+
function buildTroubleshooting(state, ctx = {}) {
|
|
518
|
+
const t = themeOf(ctx);
|
|
519
|
+
const lines = [];
|
|
520
|
+
lines.push([span(t.gold + BOLD, 'Troubleshooting'), span(BOLD_OFF, '')]);
|
|
521
|
+
const tips = [
|
|
522
|
+
['No output / blank screen', 'Run with TERM=xterm-256color, or switch to a lighter theme (/theme light).'],
|
|
523
|
+
['Keys not responding', 'This client needs a real TTY — run it from a terminal, not a CI job.'],
|
|
524
|
+
['401 / unauthorized', 'Check the account line with /status, or set a provider key with /byok-set.'],
|
|
525
|
+
['No answers from the brain', 'Confirm the token-bank balance with /billing.'],
|
|
526
|
+
['Memory looks empty', 'Recall is per-topic — try /aegis-recall <topic> or save one with /aegis-remember <note>.'],
|
|
527
|
+
];
|
|
528
|
+
for (const [issue, fix] of tips) {
|
|
529
|
+
lines.push([span(t.green, GLYPH.bullet), span(t.white, ' ' + issue)]);
|
|
530
|
+
lines.push([span('', ' '), span(t.gray, fix)]);
|
|
531
|
+
}
|
|
532
|
+
lines.push([span('', '')]);
|
|
533
|
+
lines.push([span(t.gray, 'Run /status for account state. Help: https://github.com/aegisinfo/aegiscode-plugin')]);
|
|
534
|
+
return lines;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function buildOnboarding(state, ctx = {}) {
|
|
538
|
+
const t = themeOf(ctx);
|
|
539
|
+
const lines = [];
|
|
540
|
+
lines.push([span(t.gold + BOLD, 'Getting started'), span(BOLD_OFF, '')]);
|
|
541
|
+
const tips = [
|
|
542
|
+
['/help', 'list every command'],
|
|
543
|
+
['/', 'open the command palette'],
|
|
544
|
+
['?', 'shortcuts'],
|
|
545
|
+
['/model <id>', 'pin a model'],
|
|
546
|
+
['/theme light|dark', 'switch the colour theme'],
|
|
547
|
+
['/aegis-ask', 'ask the pooled brain a question'],
|
|
548
|
+
['/memory', 'show your AEGIS cloud memory'],
|
|
549
|
+
['Ctrl-D', 'exit'],
|
|
550
|
+
];
|
|
551
|
+
for (const [key, what] of tips) {
|
|
552
|
+
lines.push([span('', ' '), span(t.lavender, key), span('', ' '.repeat(Math.max(1, 20 - [...key].length))), span(t.gray, what)]);
|
|
553
|
+
}
|
|
554
|
+
lines.push([span('', '')]);
|
|
555
|
+
lines.push([span(t.gray, 'Try "summarize this file for me" to see the full flow.')]);
|
|
556
|
+
return lines;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function buildShellCompletion(state, ctx = {}) {
|
|
560
|
+
const t = themeOf(ctx);
|
|
561
|
+
const lines = [];
|
|
562
|
+
lines.push([span(t.gold + BOLD, 'Shell completion'), span(BOLD_OFF, '')]);
|
|
563
|
+
lines.push([span('', ' '), span(t.gray, 'Aegiscode completes /commands with Tab inside the app.')]);
|
|
564
|
+
lines.push([span('', ' '), span(t.gray, 'For shell-level completion of the aegiscode CLI itself:')]);
|
|
565
|
+
lines.push([span('', ' ')]);
|
|
566
|
+
const rows = [
|
|
567
|
+
['bash', 'eval "$(aegiscode shell-completion bash)"'],
|
|
568
|
+
['zsh', 'eval "$(aegiscode shell-completion zsh)"'],
|
|
569
|
+
['fish', 'aegiscode shell-completion fish | source'],
|
|
570
|
+
];
|
|
571
|
+
for (const [shell, cmd] of rows) {
|
|
572
|
+
lines.push([span('', ' '), span(t.white + BOLD, shell), span('', ' '.repeat(Math.max(1, 6 - shell.length))), span(t.gray, cmd)]);
|
|
573
|
+
}
|
|
574
|
+
lines.push([span('', '')]);
|
|
575
|
+
lines.push([span(t.dim, 'Add the eval line to your ~/.bashrc or ~/.zshrc to make it persist.')]);
|
|
576
|
+
return lines;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function buildTerminalSetup(state, ctx = {}) {
|
|
580
|
+
const t = themeOf(ctx);
|
|
581
|
+
const s = obj(state);
|
|
582
|
+
const env = typeof process !== 'undefined' && process.env ? process.env : {};
|
|
583
|
+
const { cols, rows } = getSize();
|
|
584
|
+
const shell = str(s.shell) || str(env.SHELL).split('/').pop();
|
|
585
|
+
const table = [
|
|
586
|
+
['TERM', str(s.term) || str(env.TERM) || '(unset)'],
|
|
587
|
+
['LANG', str(s.lang) || str(env.LANG) || '(unset)'],
|
|
588
|
+
['Shell', shell || 'unknown'],
|
|
589
|
+
['Size', `${cols}x${rows}`],
|
|
590
|
+
];
|
|
591
|
+
const lines = [];
|
|
592
|
+
lines.push([span(t.gold + BOLD, 'Terminal setup'), span(BOLD_OFF, '')]);
|
|
593
|
+
for (const l of kv(t, table, { keyStyle: t.white, valStyle: t.gray })) lines.push(l);
|
|
594
|
+
lines.push([span('', '')]);
|
|
595
|
+
lines.push([span(t.gray, 'Tips:')]);
|
|
596
|
+
lines.push([span(t.gray, ' • TERM=xterm-256color enables full color + resize handling')]);
|
|
597
|
+
lines.push([span(t.gray, ' • Set LANG (e.g. en_US.UTF-8) for correct glyphs and word wrap')]);
|
|
598
|
+
lines.push([span(t.gray, ' • Resize mid-session is handled (SIGWINCH); overlays adapt')]);
|
|
599
|
+
return lines;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function buildPRs(ghOutput, ctx = {}) {
|
|
603
|
+
const t = themeOf(ctx);
|
|
604
|
+
const lines = [];
|
|
605
|
+
lines.push([span(t.gold + BOLD, 'Open pull requests'), span(BOLD_OFF, '')]);
|
|
606
|
+
const trimmed = str(ghOutput).trim();
|
|
607
|
+
if (!trimmed) {
|
|
608
|
+
lines.push([span('', ' '), span(t.gray, 'No open PRs in this repository.')]);
|
|
609
|
+
return lines;
|
|
610
|
+
}
|
|
611
|
+
for (const l of trimmed.split('\n').slice(0, 10)) {
|
|
612
|
+
lines.push([span('', ' '), span(t.white, l)]);
|
|
613
|
+
}
|
|
614
|
+
lines.push([span('', '')]);
|
|
615
|
+
lines.push([span(t.gray, 'Review with /review · comments via /pr-comments')]);
|
|
616
|
+
return lines;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// ── /benchmark ───────────────────────────────────────────────────────────────
|
|
620
|
+
|
|
621
|
+
/** @param {Array} results [[label, ms], ...] */
|
|
622
|
+
function buildBenchmark(results, ctx = {}) {
|
|
623
|
+
const t = themeOf(ctx);
|
|
624
|
+
const list = Array.isArray(results) ? results : [];
|
|
625
|
+
const lines = [];
|
|
626
|
+
lines.push([span(t.gold + BOLD, 'Benchmark'), span(BOLD_OFF, '')]);
|
|
627
|
+
lines.push([span('', ' '), span(t.gray, 'In-app micro-benchmarks (this process, this machine):')]);
|
|
628
|
+
lines.push([span('', ' ')]);
|
|
629
|
+
for (const item of list) {
|
|
630
|
+
if (!Array.isArray(item) || item.length < 2) continue;
|
|
631
|
+
const label = str(item[0]);
|
|
632
|
+
lines.push([span('', ' '), span(t.white, label), span('', ' '.repeat(Math.max(1, 30 - [...label].length))), span(t.gray, `${num(item[1])} ms`)]);
|
|
633
|
+
}
|
|
634
|
+
if (!list.length) lines.push([span('', ' '), span(t.gray, '(no benchmarks run)')]);
|
|
635
|
+
lines.push([span('', '')]);
|
|
636
|
+
lines.push([span(t.dim, 'Spoiler: the terminal renders faster than you type.')]);
|
|
637
|
+
return lines;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// ── /waifu ───────────────────────────────────────────────────────────────────
|
|
641
|
+
|
|
642
|
+
const WAIFU_ART = [
|
|
643
|
+
' ▄▄▄▄▄▄▄▄▄▄▄▄',
|
|
644
|
+
' ▄█▓▓▓▓▓▓▓▓▓▓▓▓█▄',
|
|
645
|
+
' █▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█',
|
|
646
|
+
' █▓▓▓█▓▓▓▓▓▓▓█▓▓▓█',
|
|
647
|
+
' █▓▓█▓▓█▓▓▓█▓▓█▓▓█',
|
|
648
|
+
' █▓▓█▓█▓▓▓█▓█▓▓█',
|
|
649
|
+
' █▓▓▓█▓▓▓█▓▓▓█',
|
|
650
|
+
' ██▓▓▓▓▓▓▓▓▓▓▓▓▓██',
|
|
651
|
+
' █▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█',
|
|
652
|
+
' █▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█',
|
|
653
|
+
' █▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█',
|
|
654
|
+
' ▀█▓▓▓▓▓▓▓▓▓▓▓▓▓█▀',
|
|
655
|
+
' ▀▀▀███████▀▀▀',
|
|
656
|
+
];
|
|
657
|
+
|
|
658
|
+
function buildWaifu(ctx = {}) {
|
|
659
|
+
const t = themeOf(ctx);
|
|
660
|
+
const lines = [];
|
|
661
|
+
lines.push([span(t.gold + BOLD, 'Waifu'), span(BOLD_OFF, '')]);
|
|
662
|
+
for (const row of WAIFU_ART) lines.push([span(t.lavender, row)]);
|
|
663
|
+
lines.push([span('', '')]);
|
|
664
|
+
lines.push([span(t.gray, 'A distinguished gentleman, rendered in block characters.')]);
|
|
665
|
+
return lines;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// ── /aegis-* panels ──────────────────────────────────────────────────────────
|
|
669
|
+
|
|
670
|
+
/** @param {object} state {stats|memory, cloud, cloudNote, version, backend} */
|
|
671
|
+
function buildAegisStatus(state, ctx = {}) {
|
|
672
|
+
const t = themeOf(ctx);
|
|
673
|
+
const s = obj(state);
|
|
674
|
+
const stats = statsFrom(s);
|
|
675
|
+
const cloud = s.cloud && typeof s.cloud === 'object' ? s.cloud : null;
|
|
676
|
+
const cloudNote = str(s.cloudNote);
|
|
677
|
+
const version = str(s.version);
|
|
678
|
+
const backend = str(s.backend || s.store);
|
|
679
|
+
|
|
680
|
+
const lines = [];
|
|
681
|
+
lines.push([span(t.gold + BOLD, 'ÆGIS Status'), span(BOLD_OFF, '')]);
|
|
682
|
+
lines.push([span(t.gray, '─'.repeat(30))]);
|
|
683
|
+
const total = stats ? num(stats.total) : 0;
|
|
684
|
+
const sessions = stats ? num(stats.sessions) : 0;
|
|
685
|
+
lines.push([span(t.white, `Memory: ${total.toLocaleString()} entries across ${sessions} sessions`)]);
|
|
686
|
+
if (stats && stats.roles && (stats.roles.user != null || stats.roles.assistant != null)) {
|
|
687
|
+
const roles = stats.roles;
|
|
688
|
+
const parts = [];
|
|
689
|
+
if (roles.user != null) parts.push(`${num(roles.user)} user`);
|
|
690
|
+
if (roles.assistant != null) parts.push(`${num(roles.assistant)} assistant`);
|
|
691
|
+
if (roles.other) parts.push(`${num(roles.other)} other`);
|
|
692
|
+
if (parts.length) lines.push([span(t.gray, ` roles: ${parts.join(' · ')}`)]);
|
|
693
|
+
}
|
|
694
|
+
if (stats && stats.tiers) lines.push([span(t.gray, ` tiers: ${JSON.stringify(stats.tiers)}`)]);
|
|
695
|
+
if (backend) lines.push([span(t.gray, ` store: ${backend}`)]);
|
|
696
|
+
if (stats && stats.total != null) {
|
|
697
|
+
const withEmb = num(stats.withEmbeddings);
|
|
698
|
+
const pct = total ? Math.round((withEmb / total) * 100) : 0;
|
|
699
|
+
if (stats.embeddingsEnabled === true) {
|
|
700
|
+
const eng = (stats.embeddingModels && Object.keys(stats.embeddingModels)[0]) || 'embedding';
|
|
701
|
+
lines.push([span(t.gray, ` embed: ${eng} · ${pct}% coverage`)]);
|
|
702
|
+
} else if (stats.embeddingsEnabled === false && total) {
|
|
703
|
+
lines.push([span(t.gray, ' embed: keyword-only')]);
|
|
704
|
+
}
|
|
705
|
+
if (stats.stale) lines.push([span(t.gray, ` stale: ${num(stats.stale)} entries >90d`)]);
|
|
706
|
+
}
|
|
707
|
+
if (cloud) {
|
|
708
|
+
const key = cloud.key ? '✓ connected' : '✗ no API key';
|
|
709
|
+
const sync = cloud.sync ? 'sync on' : 'sync off';
|
|
710
|
+
lines.push([span(t.white, `Cloud: ${key}, ${sync}`)]);
|
|
711
|
+
} else if (cloudNote) {
|
|
712
|
+
lines.push([span(t.gray, `Cloud: ${cloudNote}`)]);
|
|
713
|
+
} else {
|
|
714
|
+
lines.push([span(t.gray, 'Cloud: not checked (aegis /cloud status unavailable)')]);
|
|
715
|
+
}
|
|
716
|
+
if (version) lines.push([span(t.gray, `CLI: aegis ${version}`)]);
|
|
717
|
+
return lines;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/** @param {Array} results [{content,timestamp,source,tags}] */
|
|
721
|
+
function buildAegisRecall(results, ctx = {}) {
|
|
722
|
+
const t = themeOf(ctx);
|
|
723
|
+
const list = Array.isArray(results) ? results.filter(Boolean) : [];
|
|
724
|
+
const lines = [];
|
|
725
|
+
lines.push([span(t.gold + BOLD, 'ÆGIS Memory'), span(BOLD_OFF, '')]);
|
|
726
|
+
lines.push([span(t.gray, '─'.repeat(30))]);
|
|
727
|
+
if (!list.length) {
|
|
728
|
+
lines.push([span(t.gray, 'No memory found for this topic.')]);
|
|
729
|
+
lines.push([span(t.gray, 'Nothing relevant is stored yet — /aegis-remember <note> saves one.')]);
|
|
730
|
+
return lines;
|
|
731
|
+
}
|
|
732
|
+
for (const r of list.slice(0, 10)) {
|
|
733
|
+
const first = str(r.content).split('\n')[0].slice(0, 90);
|
|
734
|
+
lines.push([span(t.white + BOLD, `• ${first}`), span(BOLD_OFF, '')]);
|
|
735
|
+
const when = r.timestamp ? str(r.timestamp).slice(0, 10) : '';
|
|
736
|
+
const src = r.source ? ` (${str(r.source)})` : '';
|
|
737
|
+
const tags = Array.isArray(r.tags) && r.tags.length ? ' · tags: ' + r.tags.join(', ') : '';
|
|
738
|
+
lines.push([span(t.gray, ` ${when}${src}${tags}`)]);
|
|
739
|
+
}
|
|
740
|
+
if (list.length > 10) lines.push([span(t.gray, `…and ${list.length - 10} more`)]);
|
|
741
|
+
return lines;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/** @param {string} task */
|
|
745
|
+
function buildAegisMulti(task, ctx = {}) {
|
|
746
|
+
const t = themeOf(ctx);
|
|
747
|
+
const subject = str(task);
|
|
748
|
+
const lines = [];
|
|
749
|
+
lines.push([span(t.gold + BOLD, 'ÆGIS Multi — compose'), span(BOLD_OFF, '')]);
|
|
750
|
+
lines.push([span(t.gray, '─'.repeat(30))]);
|
|
751
|
+
lines.push([span(t.gray, 'Paste into your aegis-cli session (the confirmation prompt works there):')]);
|
|
752
|
+
lines.push([span(t.white, ` /multi ${subject}`)]);
|
|
753
|
+
lines.push([span(t.gray, ' /multiyolo ' + subject + ' — auto-approved variant')]);
|
|
754
|
+
lines.push([span('', '')]);
|
|
755
|
+
lines.push([span(t.gray, 'Optional flags: --save-as <name> · --template <id>')]);
|
|
756
|
+
lines.push([span(t.gray, 'Add "run" to execute headless here instead: /aegis-multi <task> run')]);
|
|
757
|
+
return lines;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/** Shared renderer for --print slash results (/council, /ask, /multi run). */
|
|
761
|
+
function buildAegisPrint(title, opts = {}, ctx = {}) {
|
|
762
|
+
const t = themeOf(ctx);
|
|
763
|
+
const o = obj(opts);
|
|
764
|
+
const lines = [];
|
|
765
|
+
lines.push([span(t.gold + BOLD, str(title)), span(BOLD_OFF, '')]);
|
|
766
|
+
lines.push([span(t.gray, '─'.repeat(30))]);
|
|
767
|
+
for (const l of str(o.result).split('\n')) lines.push([span(t.white, l)]);
|
|
768
|
+
if (o.model || o.provider) {
|
|
769
|
+
lines.push([span(t.gray, ` via ${str(o.provider) || 'aegis'}${o.model ? ' · ' + str(o.model) : ''}`)]);
|
|
770
|
+
}
|
|
771
|
+
return lines;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// ── /billing ─────────────────────────────────────────────────────────────────
|
|
775
|
+
|
|
776
|
+
/** @param {object} state {balance,plan,account,costEur} */
|
|
777
|
+
function buildBilling(state, ctx = {}) {
|
|
778
|
+
const t = themeOf(ctx);
|
|
779
|
+
const s = obj(state);
|
|
780
|
+
const lines = [];
|
|
781
|
+
lines.push([span(t.gold + BOLD, 'Billing'), span(BOLD_OFF, '')]);
|
|
782
|
+
lines.push([span(t.gray, '─'.repeat(30))]);
|
|
783
|
+
const rows = [];
|
|
784
|
+
if (s.balance != null) rows.push(['Balance', fmtEur(s.balance)]);
|
|
785
|
+
if (s.plan != null) rows.push(['Plan', str(s.plan)]);
|
|
786
|
+
if (s.account != null) rows.push(['Account', str(s.account)]);
|
|
787
|
+
if (s.costEur != null) rows.push(['This session', fmtEur(s.costEur)]);
|
|
788
|
+
if (rows.length) {
|
|
789
|
+
for (const l of kv(t, rows, { indent: ' ', keyStyle: t.gray, valStyle: t.white })) lines.push(l);
|
|
790
|
+
} else {
|
|
791
|
+
lines.push([span('', ' '), span(t.gray, 'No billing data available.')]);
|
|
792
|
+
}
|
|
793
|
+
lines.push([span('', '')]);
|
|
794
|
+
lines.push([span(t.white, 'AEGIS pooled inference'), span(t.gray, ' metered against your token bank')]);
|
|
795
|
+
lines.push([span(t.gray, ' AEGIS has no subscription of its own — /cost shows this session\'s spend.')]);
|
|
796
|
+
lines.push([span(t.gray, ' Top-ups and invoices: https://aegiscloud.org/billing')]);
|
|
797
|
+
return lines;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// ── /tokens ──────────────────────────────────────────────────────────────────
|
|
801
|
+
|
|
802
|
+
/** @param {object} state {tokens, costEur} */
|
|
803
|
+
function buildTokens(state, ctx = {}) {
|
|
804
|
+
const t = themeOf(ctx);
|
|
805
|
+
const s = obj(state);
|
|
806
|
+
const { cols } = getSize();
|
|
807
|
+
const W = Math.max(24, cols - 2);
|
|
808
|
+
const tok = s.tokens && typeof s.tokens === 'object' ? s.tokens : {};
|
|
809
|
+
const input = num(tok.input);
|
|
810
|
+
const output = num(tok.output);
|
|
811
|
+
const cacheRead = num(tok.cacheRead);
|
|
812
|
+
const cacheWrite = num(tok.cacheWrite);
|
|
813
|
+
const total = num(tok.total) || input + output;
|
|
814
|
+
const cost = num(s.costEur);
|
|
815
|
+
const window = num(s.contextWindow) || 200000;
|
|
816
|
+
const pct = window > 0 ? Math.min(100, Math.round((total / window) * 100)) : 0;
|
|
817
|
+
|
|
818
|
+
const lines = [];
|
|
819
|
+
lines.push([span(t.gold + BOLD, 'Token usage'), span(BOLD_OFF, '')]);
|
|
820
|
+
lines.push([span(t.gray, '─'.repeat(Math.min(30, W)))]);
|
|
821
|
+
const max = Math.max(input, output, cacheRead, cacheWrite, 1);
|
|
822
|
+
const bar = (label, tokens) => {
|
|
823
|
+
const frac = Math.min(1, tokens / max);
|
|
824
|
+
const filled = Math.max(0, Math.round(frac * 24));
|
|
825
|
+
return [
|
|
826
|
+
span('', ' '), span(t.white, label),
|
|
827
|
+
span('', ' '.repeat(Math.max(1, 11 - [...label].length))),
|
|
828
|
+
span(t.green, '█'.repeat(filled)),
|
|
829
|
+
span(t.dim, '█'.repeat(Math.max(0, 24 - filled))),
|
|
830
|
+
span(t.gray, ` ${fmtTokens(tokens)}`),
|
|
831
|
+
];
|
|
832
|
+
};
|
|
833
|
+
lines.push(bar('input', input));
|
|
834
|
+
lines.push(bar('output', output));
|
|
835
|
+
lines.push(bar('cache read', cacheRead));
|
|
836
|
+
lines.push(bar('cache write', cacheWrite));
|
|
837
|
+
lines.push([span('', ' ')]);
|
|
838
|
+
lines.push([
|
|
839
|
+
span('', ' '), span(t.white, 'Total:'), span(t.gray, ` ${fmtTokens(total)}`),
|
|
840
|
+
span('', ' '.repeat(8)), span(t.white, 'Est. cost:'), span(t.gray, ` ${fmtEur(cost)}`),
|
|
841
|
+
]);
|
|
842
|
+
lines.push([span('', ' '), span(t.gray, `Context: ${pct}% of ${fmtTokens(window)} window`)]);
|
|
843
|
+
lines.push([span('', ' '), span(t.gray, 'Tokens are counted from this session; cost is metered in euro.')]);
|
|
844
|
+
return lines;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// ── /skills ──────────────────────────────────────────────────────────────────
|
|
848
|
+
|
|
849
|
+
/** @param {Array} skills [{source,name,namespace,description}] @param {Array} scannedDirs */
|
|
850
|
+
function buildSkills(skills, scannedDirs, ctx = {}) {
|
|
851
|
+
const t = themeOf(ctx);
|
|
852
|
+
const list = Array.isArray(skills) ? skills.filter(Boolean) : [];
|
|
853
|
+
const dirs = Array.isArray(scannedDirs) ? scannedDirs : [];
|
|
854
|
+
const lines = [];
|
|
855
|
+
lines.push([span(t.gold + BOLD, 'Skills'), span(BOLD_OFF, '')]);
|
|
856
|
+
lines.push([span(t.gray, '─'.repeat(30))]);
|
|
857
|
+
if (!list.length) {
|
|
858
|
+
lines.push([span(t.gray, 'No skills found.')]);
|
|
859
|
+
lines.push([span(t.gray, 'Skill dirs scanned:')]);
|
|
860
|
+
for (const d of dirs) lines.push([span(t.dim, ` ${str(d)}`)]);
|
|
861
|
+
lines.push([span('', '')]);
|
|
862
|
+
lines.push([span(t.gray, 'Add one: mkdir -p .aegis/skills/<name> and drop a SKILL.md inside.')]);
|
|
863
|
+
return lines;
|
|
864
|
+
}
|
|
865
|
+
let lastSource = null;
|
|
866
|
+
for (const s of list) {
|
|
867
|
+
if (s.source !== lastSource) {
|
|
868
|
+
lines.push([span(t.gray + BOLD, s.source === 'user' ? 'User' : 'Project'), span(BOLD_OFF, '')]);
|
|
869
|
+
lastSource = s.source;
|
|
870
|
+
}
|
|
871
|
+
const name = s.namespace ? `${str(s.namespace)}/${str(s.name)}` : str(s.name);
|
|
872
|
+
const desc = str(s.description) || '(no description)';
|
|
873
|
+
lines.push([span(t.white, ` ${name}`), span(t.gray, ' '.repeat(Math.max(1, 18 - [...name].length)) + desc)]);
|
|
874
|
+
}
|
|
875
|
+
lines.push([span('', '')]);
|
|
876
|
+
lines.push([span(t.gray, '/skills <name> for details · /skills refresh to rescan')]);
|
|
877
|
+
return lines;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
/** /skills <name> — one skill's frontmatter + body excerpt. */
|
|
881
|
+
function buildSkillDetail(skill, ctx = {}) {
|
|
882
|
+
const t = themeOf(ctx);
|
|
883
|
+
const s = obj(skill);
|
|
884
|
+
const name = s.namespace ? `${str(s.namespace)}/${str(s.name)}` : str(s.name);
|
|
885
|
+
const lines = [];
|
|
886
|
+
lines.push([span(t.gold + BOLD, `Skill: ${name}`), span(BOLD_OFF, '')]);
|
|
887
|
+
lines.push([span(t.gray, '─'.repeat(30))]);
|
|
888
|
+
if (s.description) lines.push([span(t.gray, str(s.description))]);
|
|
889
|
+
lines.push([span(t.dim, str(s.path))]);
|
|
890
|
+
lines.push([span('', '')]);
|
|
891
|
+
const body = str(s.content);
|
|
892
|
+
const bodyLines = body.split('\n');
|
|
893
|
+
const excerpt = bodyLines.slice(0, 8).join('\n');
|
|
894
|
+
for (const l of excerpt.split('\n')) lines.push([span(t.white, l)]);
|
|
895
|
+
if (bodyLines.length > 8) lines.push([span(t.gray, `… (${bodyLines.length} lines)`)]);
|
|
896
|
+
return lines;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// ── /mcp ─────────────────────────────────────────────────────────────────────
|
|
900
|
+
|
|
901
|
+
/** @param {Array|object} servers @param {Array} fromPaths */
|
|
902
|
+
function buildMcp(servers, fromPaths, ctx = {}) {
|
|
903
|
+
const t = themeOf(ctx);
|
|
904
|
+
const entries = Array.isArray(servers)
|
|
905
|
+
? servers.map((srv, i) => [str(srv && srv.name) || String(i), srv])
|
|
906
|
+
: servers && typeof servers === 'object'
|
|
907
|
+
? Object.entries(servers)
|
|
908
|
+
: [];
|
|
909
|
+
const paths = Array.isArray(fromPaths) ? fromPaths : [];
|
|
910
|
+
const lines = [];
|
|
911
|
+
lines.push([span(t.gold + BOLD, 'MCP servers'), span(BOLD_OFF, '')]);
|
|
912
|
+
lines.push([span(t.gray, '─'.repeat(30))]);
|
|
913
|
+
if (!entries.length) {
|
|
914
|
+
lines.push([span(t.gray, 'No MCP servers configured.')]);
|
|
915
|
+
} else {
|
|
916
|
+
for (const [name, srv] of entries) {
|
|
917
|
+
const cmd = typeof srv === 'string'
|
|
918
|
+
? srv
|
|
919
|
+
: (str(srv && srv.command) + ' ' + (Array.isArray(srv && srv.args) ? srv.args.join(' ') : '')).trim();
|
|
920
|
+
lines.push([span(t.white, ` ${str(name)}`), span(t.gray, ' '.repeat(Math.max(1, 14 - [...str(name)].length)) + cmd)]);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
lines.push([span('', '')]);
|
|
924
|
+
lines.push([span(t.gray, 'This build has no MCP runtime — servers are configuration only.')]);
|
|
925
|
+
lines.push([span(t.gray, '/mcp add <name> <command> [args…] · /mcp remove <name>')]);
|
|
926
|
+
if (paths.length) lines.push([span(t.dim, 'Sources: ' + paths.join(', '))]);
|
|
927
|
+
return lines;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
// ── /router ──────────────────────────────────────────────────────────────────
|
|
931
|
+
|
|
932
|
+
/** @param {object} state {autoRouter|router:{enabled,tiers}} */
|
|
933
|
+
function buildRouter(state, ctx = {}) {
|
|
934
|
+
const t = themeOf(ctx);
|
|
935
|
+
const s = obj(state);
|
|
936
|
+
const router = s.autoRouter && typeof s.autoRouter === 'object'
|
|
937
|
+
? s.autoRouter
|
|
938
|
+
: s.router && typeof s.router === 'object'
|
|
939
|
+
? s.router
|
|
940
|
+
: { enabled: false, tiers: {} };
|
|
941
|
+
const tiers = router.tiers && typeof router.tiers === 'object' ? router.tiers : {};
|
|
942
|
+
const lines = [];
|
|
943
|
+
lines.push([span(t.gold + BOLD, 'Model router'), span(BOLD_OFF, '')]);
|
|
944
|
+
lines.push([span(t.gray, '─'.repeat(30))]);
|
|
945
|
+
lines.push([span(t.white, `auto-router: ${router.enabled ? 'on' : 'off'}`)]);
|
|
946
|
+
lines.push([span(t.gray, ` simple ${str(tiers.simple) || '(auto)'}`)]);
|
|
947
|
+
lines.push([span(t.gray, ` medium ${str(tiers.medium) || '(auto)'}`)]);
|
|
948
|
+
lines.push([span(t.gray, ` complex ${str(tiers.complex) || '(auto)'}`)]);
|
|
949
|
+
lines.push([span('', '')]);
|
|
950
|
+
lines.push([span(t.gray, '/router on|off · /router set <simple|medium|complex> <modelId>')]);
|
|
951
|
+
lines.push([span(t.dim, 'Routing config is persisted for the real CLI; this build serves the model chosen by /model.')]);
|
|
952
|
+
return lines;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// ── /billing (provider list) + /memory ───────────────────────────────────────
|
|
956
|
+
|
|
957
|
+
/** @param {object} state {memory|stats, embedStatus, compactState, compactCfg} */
|
|
958
|
+
function buildMemory(state, ctx = {}) {
|
|
959
|
+
const t = themeOf(ctx);
|
|
960
|
+
const s = obj(state);
|
|
961
|
+
const stats = statsFrom(s);
|
|
962
|
+
const embedStatus = s.embedStatus && typeof s.embedStatus === 'object' ? s.embedStatus : null;
|
|
963
|
+
const compact = s.compactState && typeof s.compactState === 'object' ? s.compactState : (s.at ? s : null);
|
|
964
|
+
const cfg = s.compactCfg && typeof s.compactCfg === 'object' ? s.compactCfg : null;
|
|
965
|
+
|
|
966
|
+
const lines = [];
|
|
967
|
+
lines.push([span(t.gold + BOLD, 'ÆGIS Memory'), span(BOLD_OFF, '')]);
|
|
968
|
+
lines.push([span(t.gray, '─'.repeat(30))]);
|
|
969
|
+
const total = stats ? num(stats.total) : 0;
|
|
970
|
+
const sessions = stats ? num(stats.sessions) : 0;
|
|
971
|
+
lines.push([span(t.white, `${total.toLocaleString()} memories across ${sessions} sessions`)]);
|
|
972
|
+
if (stats && stats.roles && typeof stats.roles === 'object') {
|
|
973
|
+
const parts = [];
|
|
974
|
+
if (stats.roles.user != null) parts.push(`${num(stats.roles.user)} user`);
|
|
975
|
+
if (stats.roles.assistant != null) parts.push(`${num(stats.roles.assistant)} assistant`);
|
|
976
|
+
if (stats.roles.other) parts.push(`${num(stats.roles.other)} other`);
|
|
977
|
+
if (parts.length) lines.push([span(t.gray, ` roles: ${parts.join(' · ')}`)]);
|
|
978
|
+
}
|
|
979
|
+
if (stats && stats.tiers) lines.push([span(t.gray, ` tiers: ${JSON.stringify(stats.tiers)}`)]);
|
|
980
|
+
if (stats && stats.total != null) {
|
|
981
|
+
const pct = total ? Math.round((num(stats.withEmbeddings) / total) * 100) : 0;
|
|
982
|
+
if (embedStatus && embedStatus.engine) {
|
|
983
|
+
lines.push([span(t.gray, ` embed: ${str(embedStatus.engine)} · ${pct}% coverage`)]);
|
|
984
|
+
} else if (embedStatus) {
|
|
985
|
+
lines.push([span(t.gray, ' embed: keyword-only')]);
|
|
986
|
+
}
|
|
987
|
+
if (stats.stale) lines.push([span(t.gray, ` stale: ${num(stats.stale)} entries >90d`)]);
|
|
988
|
+
}
|
|
989
|
+
if (compact) {
|
|
990
|
+
lines.push([span(t.gray, compact.at ? ` last compact: ${str(compact.at).slice(0, 10)}` : ' auto-compact: never run')]);
|
|
991
|
+
}
|
|
992
|
+
if (cfg) {
|
|
993
|
+
lines.push([span(t.gray, ` auto-compact: ${cfg.enabled ? 'on' : 'off'} (min ${num(cfg.minL1)} L1 · ${num(cfg.cooldownMin)}m cooldown)`)]);
|
|
994
|
+
}
|
|
995
|
+
lines.push([span('', '')]);
|
|
996
|
+
lines.push([span(t.gray, '/memory tiers · /memory embeddings · /memory recall <q>')]);
|
|
997
|
+
lines.push([span(t.gray, '/memory compact · /memory profile · /aegis-remember <note> to save')]);
|
|
998
|
+
return lines;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
/** @param {object} state {memory|stats, pending, compactState, compactCfg} */
|
|
1002
|
+
function buildMemoryTiers(state, ctx = {}) {
|
|
1003
|
+
const t = themeOf(ctx);
|
|
1004
|
+
const s = obj(state);
|
|
1005
|
+
const stats = statsFrom(s);
|
|
1006
|
+
const pending = Array.isArray(s.pending) ? s.pending : [];
|
|
1007
|
+
const compact = s.compactState && typeof s.compactState === 'object' ? s.compactState : null;
|
|
1008
|
+
const cfg = s.compactCfg && typeof s.compactCfg === 'object' ? s.compactCfg : null;
|
|
1009
|
+
|
|
1010
|
+
const lines = [];
|
|
1011
|
+
lines.push([span(t.gold + BOLD, 'Memory Tiers'), span(BOLD_OFF, '')]);
|
|
1012
|
+
lines.push([span(t.gray, '─'.repeat(30))]);
|
|
1013
|
+
if (stats && stats.tiers) {
|
|
1014
|
+
lines.push([span(t.white, `L0 turns ${num(stats.tiers.L0)} raw session turns, volatile`)]);
|
|
1015
|
+
lines.push([span(t.white, `L1 atomic ${num(stats.tiers.L1)} facts · decisions · breakthroughs`)]);
|
|
1016
|
+
lines.push([span(t.white, `L2 scenario ${num(stats.tiers.L2)} distilled reusable context`)]);
|
|
1017
|
+
lines.push([span(t.white, `L3 profile ${num(stats.tiers.L3)} single distilled identity row`)]);
|
|
1018
|
+
}
|
|
1019
|
+
lines.push([span('', '')]);
|
|
1020
|
+
lines.push([span(t.gray, `pending L1 (auto-compact trigger): ${pending.length}`)]);
|
|
1021
|
+
if (compact && compact.at) {
|
|
1022
|
+
const sc = Array.isArray(compact.scenarios) ? compact.scenarios.length : 0;
|
|
1023
|
+
lines.push([span(t.gray, `last auto-compact: ${str(compact.at).slice(0, 16)} · ${sc} scenario${sc === 1 ? '' : 's'}`)]);
|
|
1024
|
+
} else {
|
|
1025
|
+
lines.push([span(t.gray, 'last auto-compact: never')]);
|
|
1026
|
+
}
|
|
1027
|
+
lines.push([span(t.gray, cfg
|
|
1028
|
+
? `auto-compact: ${cfg.enabled ? 'on' : 'off'} · min ${num(cfg.minL1)} L1 · ${num(cfg.cooldownMin)}m cooldown`
|
|
1029
|
+
: 'auto-compact: on · min 5 L1 · 15m cooldown')]);
|
|
1030
|
+
lines.push([span('', '')]);
|
|
1031
|
+
lines.push([span(t.gray, '/memory compact runs maintenance manually')]);
|
|
1032
|
+
return lines;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
/** /memory embeddings [status] — engine chain + vector coverage panel. */
|
|
1036
|
+
function buildMemoryEmbeddings(status, stats, ctx = {}) {
|
|
1037
|
+
const t = themeOf(ctx);
|
|
1038
|
+
const st = status && typeof status === 'object' ? status : null;
|
|
1039
|
+
const s = stats && typeof stats === 'object' ? stats : null;
|
|
1040
|
+
const lines = [];
|
|
1041
|
+
lines.push([span(t.gold + BOLD, 'Memory Embeddings'), span(BOLD_OFF, '')]);
|
|
1042
|
+
lines.push([span(t.gray, '─'.repeat(30))]);
|
|
1043
|
+
if (st && st.engine) {
|
|
1044
|
+
lines.push([span(t.white, `engine: ${str(st.engine)} · ${str(st.model)}`)]);
|
|
1045
|
+
if (st.dim) lines.push([span(t.gray, ` dim: ${num(st.dim)}`)]);
|
|
1046
|
+
} else {
|
|
1047
|
+
lines.push([span(t.white, 'engine: keyword-only (no embedder resolved)')]);
|
|
1048
|
+
if (st && st.reason) lines.push([span(t.gray, ` reason: ${str(st.reason)}`)]);
|
|
1049
|
+
}
|
|
1050
|
+
lines.push([span(t.gray, `mode: ${st ? str(st.mode) || 'auto' : 'auto'} (env AEGIS_MEMORY_EMBED or memoryConfig.embedMode)`)]);
|
|
1051
|
+
if (s && s.total != null) {
|
|
1052
|
+
const total = num(s.total);
|
|
1053
|
+
const withEmb = num(s.withEmbeddings);
|
|
1054
|
+
const pct = total ? Math.round((withEmb / total) * 100) : 0;
|
|
1055
|
+
lines.push([span(t.gray, `coverage: ${withEmb}/${total} rows (${pct}%)`)]);
|
|
1056
|
+
if (s.embeddingModels && Object.keys(s.embeddingModels).length) {
|
|
1057
|
+
const parts = Object.entries(s.embeddingModels).map(([m, n]) => `${m}: ${num(n)}`);
|
|
1058
|
+
lines.push([span(t.gray, ` models: ${parts.join(' · ')}`)]);
|
|
1059
|
+
}
|
|
1060
|
+
if (s.stale) lines.push([span(t.gray, ` stale (>90d): ${num(s.stale)}`)]);
|
|
1061
|
+
}
|
|
1062
|
+
lines.push([span('', '')]);
|
|
1063
|
+
lines.push([span(t.gray, '/memory embeddings on|off toggles the engine chain')]);
|
|
1064
|
+
return lines;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
// ── /yolo ────────────────────────────────────────────────────────────────────
|
|
1068
|
+
|
|
1069
|
+
function buildYolo(state, ctx = {}) {
|
|
1070
|
+
const t = themeOf(ctx);
|
|
1071
|
+
const lines = [];
|
|
1072
|
+
lines.push([span(t.gold + BOLD, '⚠ YOLO mode enabled'), span(BOLD_OFF, '')]);
|
|
1073
|
+
lines.push([span(t.gray, '─'.repeat(30))]);
|
|
1074
|
+
lines.push([span(t.white, 'All tool executions are auto-approved: file writes,')]);
|
|
1075
|
+
lines.push([span(t.white, 'bash commands, and network requests run without asking.')]);
|
|
1076
|
+
lines.push([span('', '')]);
|
|
1077
|
+
lines.push([span(t.gray, 'Run /yolo off to restore confirmations.')]);
|
|
1078
|
+
return lines;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
// ── /release-notes ───────────────────────────────────────────────────────────
|
|
1082
|
+
|
|
1083
|
+
function buildReleaseNotes(state, ctx = {}) {
|
|
1084
|
+
const t = themeOf(ctx);
|
|
1085
|
+
const out = [];
|
|
1086
|
+
out.push([span(t.gold + BOLD, "What's new"), span(BOLD_OFF, '')]);
|
|
1087
|
+
out.push([span(t.gray, '─'.repeat(30))]);
|
|
1088
|
+
const bullets = [
|
|
1089
|
+
'• v0.1.0 — aegiscode CLI: the aegiscode design system on the AEGIS pooled brain.',
|
|
1090
|
+
'• /help /status /cost /tokens /context /models /model /theme /stream (local).',
|
|
1091
|
+
'• /aegis-ask /aegis-status /aegis-recall /aegis-remember /memory /billing (tool-backed).',
|
|
1092
|
+
'• /byok /byok-set /byok-rm — bring your own provider keys (never echoed).',
|
|
1093
|
+
'• /tool <name> [json] — call any registry tool directly.',
|
|
1094
|
+
'• Cost and tokens are metered in euro (€4dp below a cent).',
|
|
1095
|
+
'• Config persists to ~/.aegiscode/config.json (theme/model/stream).',
|
|
1096
|
+
'• Unavailable here: /login /doctor /permissions /mcp /skills /hooks /agents',
|
|
1097
|
+
'• /resume /rewind /compact /init /export /vim /yolo /confirm — each says why.',
|
|
1098
|
+
'• Repo: github.com/aegisinfo/aegiscode-plugin',
|
|
1099
|
+
];
|
|
1100
|
+
for (const text of bullets) out.push([span(t.green, '•'), span(t.white, ' ' + text.slice(1))]);
|
|
1101
|
+
out.push([span('', '')]);
|
|
1102
|
+
out.push([span(t.gray, '/release-notes for more')]);
|
|
1103
|
+
return out;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
// ── honest stubs (capability absent in this client) ──────────────────────────
|
|
1107
|
+
|
|
1108
|
+
/** Two-line honest panel: a heading and the reason it is absent. */
|
|
1109
|
+
function stubPanel(ctx, name, reason) {
|
|
1110
|
+
const t = themeOf(ctx);
|
|
1111
|
+
return [
|
|
1112
|
+
[span(t.gold + BOLD, name), span(BOLD_OFF, '')],
|
|
1113
|
+
[span(t.gray, ` ${name} is not available in aegiscode — ${reason}`)],
|
|
1114
|
+
];
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
function buildDoctor(state, ctx = {}) {
|
|
1118
|
+
return stubPanel(ctx, 'Doctor', 'it needs the local environment diagnostic suite.');
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
function buildBuildPanel(args, ctx = {}) {
|
|
1122
|
+
const o = obj(args);
|
|
1123
|
+
const t = themeOf(ctx);
|
|
1124
|
+
return [
|
|
1125
|
+
[span(t.gold + BOLD, `⬡ Build: ${str(o.plan && o.plan.appName) || 'build'}`), span(BOLD_OFF, '')],
|
|
1126
|
+
[span(t.gray, ' Build is not available in aegiscode — it needs a local multi-model agent build loop.')],
|
|
1127
|
+
];
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
module.exports = {
|
|
1131
|
+
buildHelp,
|
|
1132
|
+
buildStatus,
|
|
1133
|
+
buildCost,
|
|
1134
|
+
buildContext,
|
|
1135
|
+
buildTokens,
|
|
1136
|
+
buildAgents,
|
|
1137
|
+
buildPermissions,
|
|
1138
|
+
buildModelList,
|
|
1139
|
+
buildRewindList,
|
|
1140
|
+
buildOnboarding,
|
|
1141
|
+
buildShellCompletion,
|
|
1142
|
+
buildTerminalSetup,
|
|
1143
|
+
buildPRs,
|
|
1144
|
+
buildBenchmark,
|
|
1145
|
+
buildWaifu,
|
|
1146
|
+
buildAegisStatus,
|
|
1147
|
+
buildAegisRecall,
|
|
1148
|
+
buildAegisMulti,
|
|
1149
|
+
buildBilling,
|
|
1150
|
+
buildMemory,
|
|
1151
|
+
buildMemoryTiers,
|
|
1152
|
+
buildRouter,
|
|
1153
|
+
buildYolo,
|
|
1154
|
+
buildSkills,
|
|
1155
|
+
buildMcp,
|
|
1156
|
+
buildHooksStatus,
|
|
1157
|
+
buildHooksList,
|
|
1158
|
+
buildTroubleshooting,
|
|
1159
|
+
buildReleaseNotes,
|
|
1160
|
+
renderSessionsOverlay,
|
|
1161
|
+
// pure reference helpers with no missing capability
|
|
1162
|
+
buildAegisPrint,
|
|
1163
|
+
buildSkillDetail,
|
|
1164
|
+
buildMemoryEmbeddings,
|
|
1165
|
+
// honest stubs
|
|
1166
|
+
buildDoctor,
|
|
1167
|
+
buildBuildPanel,
|
|
1168
|
+
// local formatting helpers (exported for the panel tests)
|
|
1169
|
+
fmtEur,
|
|
1170
|
+
fmtTokens,
|
|
1171
|
+
};
|