@addai/node 0.4.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 +96 -0
- package/dist/agent-auth.d.ts +9 -0
- package/dist/agent-auth.js +96 -0
- package/dist/attachments.d.ts +20 -0
- package/dist/attachments.js +164 -0
- package/dist/capabilities.d.ts +64 -0
- package/dist/capabilities.js +406 -0
- package/dist/claude-binary.d.ts +16 -0
- package/dist/claude-binary.js +145 -0
- package/dist/claude-config.d.ts +11 -0
- package/dist/claude-config.js +144 -0
- package/dist/claude-print.d.ts +35 -0
- package/dist/claude-print.js +312 -0
- package/dist/claude-spawn.d.ts +40 -0
- package/dist/claude-spawn.js +220 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +137 -0
- package/dist/codex-binary.d.ts +4 -0
- package/dist/codex-binary.js +70 -0
- package/dist/codex-spawn.d.ts +47 -0
- package/dist/codex-spawn.js +320 -0
- package/dist/command-runner.d.ts +9 -0
- package/dist/command-runner.js +592 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.js +38 -0
- package/dist/control-server.d.ts +5 -0
- package/dist/control-server.js +292 -0
- package/dist/diskguard.d.ts +96 -0
- package/dist/diskguard.js +435 -0
- package/dist/error-classify.d.ts +20 -0
- package/dist/error-classify.js +159 -0
- package/dist/events.d.ts +26 -0
- package/dist/events.js +97 -0
- package/dist/gemini-binary.d.ts +4 -0
- package/dist/gemini-binary.js +64 -0
- package/dist/gemini-spawn.d.ts +52 -0
- package/dist/gemini-spawn.js +369 -0
- package/dist/git-identity.d.ts +18 -0
- package/dist/git-identity.js +104 -0
- package/dist/grok-binary.d.ts +4 -0
- package/dist/grok-binary.js +70 -0
- package/dist/grok-spawn.d.ts +41 -0
- package/dist/grok-spawn.js +495 -0
- package/dist/guardrails.d.ts +32 -0
- package/dist/guardrails.js +125 -0
- package/dist/hang-watchdog.d.ts +17 -0
- package/dist/hang-watchdog.js +48 -0
- package/dist/harness-registry.d.ts +55 -0
- package/dist/harness-registry.js +180 -0
- package/dist/heartbeat.d.ts +13 -0
- package/dist/heartbeat.js +95 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +440 -0
- package/dist/install.d.ts +63 -0
- package/dist/install.js +467 -0
- package/dist/jsonl-tail.d.ts +20 -0
- package/dist/jsonl-tail.js +192 -0
- package/dist/kimi-binary.d.ts +4 -0
- package/dist/kimi-binary.js +68 -0
- package/dist/kimi-spawn.d.ts +47 -0
- package/dist/kimi-spawn.js +319 -0
- package/dist/lockfile.d.ts +30 -0
- package/dist/lockfile.js +169 -0
- package/dist/mcp-config.d.ts +23 -0
- package/dist/mcp-config.js +133 -0
- package/dist/memory-capture.d.ts +20 -0
- package/dist/memory-capture.js +45 -0
- package/dist/memory-pack.d.ts +34 -0
- package/dist/memory-pack.js +171 -0
- package/dist/model-filter.d.ts +4 -0
- package/dist/model-filter.js +34 -0
- package/dist/node-health.d.ts +18 -0
- package/dist/node-health.js +74 -0
- package/dist/pairing.d.ts +20 -0
- package/dist/pairing.js +129 -0
- package/dist/paths.d.ts +9 -0
- package/dist/paths.js +48 -0
- package/dist/precompact-hook.d.ts +27 -0
- package/dist/precompact-hook.js +136 -0
- package/dist/projects.d.ts +25 -0
- package/dist/projects.js +255 -0
- package/dist/prompt-keystrokes.d.ts +3 -0
- package/dist/prompt-keystrokes.js +56 -0
- package/dist/pty-helper.d.ts +26 -0
- package/dist/pty-helper.js +156 -0
- package/dist/request-pump.d.ts +12 -0
- package/dist/request-pump.js +249 -0
- package/dist/run-retry.d.ts +28 -0
- package/dist/run-retry.js +81 -0
- package/dist/self-update.d.ts +75 -0
- package/dist/self-update.js +192 -0
- package/dist/session-runner.d.ts +130 -0
- package/dist/session-runner.js +2271 -0
- package/dist/sleep-detector.d.ts +11 -0
- package/dist/sleep-detector.js +32 -0
- package/dist/state-migrate.d.ts +8 -0
- package/dist/state-migrate.js +91 -0
- package/dist/store.d.ts +5 -0
- package/dist/store.js +82 -0
- package/dist/supabase-client.d.ts +15 -0
- package/dist/supabase-client.js +119 -0
- package/dist/time-context.d.ts +10 -0
- package/dist/time-context.js +71 -0
- package/dist/tokens.d.ts +4 -0
- package/dist/tokens.js +33 -0
- package/dist/tui/app.d.ts +35 -0
- package/dist/tui/app.js +82 -0
- package/dist/tui/console-capture.d.ts +13 -0
- package/dist/tui/console-capture.js +62 -0
- package/dist/tui/dashboard.d.ts +42 -0
- package/dist/tui/dashboard.js +205 -0
- package/dist/tui/data.d.ts +57 -0
- package/dist/tui/data.js +62 -0
- package/dist/tui/harnesses.d.ts +16 -0
- package/dist/tui/harnesses.js +354 -0
- package/dist/tui/render.d.ts +21 -0
- package/dist/tui/render.js +116 -0
- package/dist/tui/requests.d.ts +18 -0
- package/dist/tui/requests.js +127 -0
- package/dist/tui/run.d.ts +13 -0
- package/dist/tui/run.js +178 -0
- package/dist/tui/transcript.d.ts +39 -0
- package/dist/tui/transcript.js +234 -0
- package/dist/tui.d.ts +1 -0
- package/dist/tui.js +314 -0
- package/dist/types.d.ts +65 -0
- package/dist/types.js +3 -0
- package/dist/win.d.ts +67 -0
- package/dist/win.js +317 -0
- package/package.json +60 -0
- package/scripts/fix-pty-helper.js +28 -0
- package/scripts/precompact-capture.js +292 -0
- package/scripts/smoke-test.sh +74 -0
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// `entities-runtime harnesses` — interactive harness manager in the
|
|
3
|
+
// Claude Code terminal idiom: alt-screen, rounded panels, dim chrome,
|
|
4
|
+
// ❯ selection, braille spinner, ⏺ status dots. Hand-rolled ANSI — the
|
|
5
|
+
// daemon deliberately has no TUI framework dependency.
|
|
6
|
+
//
|
|
7
|
+
// Local actions hand the real terminal to the harness CLI itself (there
|
|
8
|
+
// is nothing to bridge when you're already at the machine): install
|
|
9
|
+
// streams npm output into a panel; login leaves the alt screen, runs the
|
|
10
|
+
// CLI's own login flow attached to your TTY, then re-probes on return.
|
|
11
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
14
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
15
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
16
|
+
}
|
|
17
|
+
Object.defineProperty(o, k2, desc);
|
|
18
|
+
}) : (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
o[k2] = m[k];
|
|
21
|
+
}));
|
|
22
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
23
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
24
|
+
}) : function(o, v) {
|
|
25
|
+
o["default"] = v;
|
|
26
|
+
});
|
|
27
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
28
|
+
var ownKeys = function(o) {
|
|
29
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
30
|
+
var ar = [];
|
|
31
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
32
|
+
return ar;
|
|
33
|
+
};
|
|
34
|
+
return ownKeys(o);
|
|
35
|
+
};
|
|
36
|
+
return function (mod) {
|
|
37
|
+
if (mod && mod.__esModule) return mod;
|
|
38
|
+
var result = {};
|
|
39
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
40
|
+
__setModuleDefault(result, mod);
|
|
41
|
+
return result;
|
|
42
|
+
};
|
|
43
|
+
})();
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.runHarnessesTui = runHarnessesTui;
|
|
46
|
+
const child_process_1 = require("child_process");
|
|
47
|
+
const readline = __importStar(require("readline"));
|
|
48
|
+
const capabilities_1 = require("../capabilities");
|
|
49
|
+
const win_1 = require("../win");
|
|
50
|
+
const fs = __importStar(require("fs"));
|
|
51
|
+
const os = __importStar(require("os"));
|
|
52
|
+
const path = __importStar(require("path"));
|
|
53
|
+
const harness_registry_1 = require("../harness-registry");
|
|
54
|
+
const render_1 = require("./render");
|
|
55
|
+
/* ── ansi helpers ────────────────────────────────────────────────────── */
|
|
56
|
+
const ESC = '\x1b[';
|
|
57
|
+
const dim = (s) => `${ESC}2m${s}${ESC}22m`;
|
|
58
|
+
const bold = (s) => `${ESC}1m${s}${ESC}22m`;
|
|
59
|
+
const fg = (n, s) => `${ESC}38;5;${n}m${s}${ESC}39m`;
|
|
60
|
+
const green = (s) => fg(114, s);
|
|
61
|
+
const yellow = (s) => fg(179, s);
|
|
62
|
+
const grey = (s) => fg(244, s);
|
|
63
|
+
const cyan = (s) => fg(80, s);
|
|
64
|
+
const altOn = () => process.stdout.write(`${ESC}?1049h${ESC}?25l`);
|
|
65
|
+
const altOff = () => process.stdout.write(`${ESC}?1049l${ESC}?25h`);
|
|
66
|
+
const home = () => process.stdout.write(`${ESC}H${ESC}2J`);
|
|
67
|
+
const SPIN = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
68
|
+
const width = () => Math.min(process.stdout.columns || 100, 110);
|
|
69
|
+
function panel(title, lines) {
|
|
70
|
+
const w = width() - 2;
|
|
71
|
+
const strip = (s) => s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
72
|
+
const top = `${dim('╭─')} ${bold(title)} ${dim('─'.repeat(Math.max(1, w - strip(title).length - 4)) + '╮')}`;
|
|
73
|
+
const body = lines.map(raw => {
|
|
74
|
+
// Rows are wider than a narrow terminal: without a cut they run past
|
|
75
|
+
// the right border and the panel frame comes apart.
|
|
76
|
+
const l = (0, render_1.truncate)(raw, w - 2);
|
|
77
|
+
const pad = Math.max(0, w - 2 - strip(l).length);
|
|
78
|
+
return `${dim('│')} ${l}${' '.repeat(pad)} ${dim('│')}`;
|
|
79
|
+
});
|
|
80
|
+
const bottom = dim(`╰${'─'.repeat(w)}╯`);
|
|
81
|
+
return [top, ...body, bottom].join('\n');
|
|
82
|
+
}
|
|
83
|
+
function dot(p) {
|
|
84
|
+
if (!p?.available)
|
|
85
|
+
return grey('⏺');
|
|
86
|
+
return p.authed ? green('⏺') : yellow('⏺');
|
|
87
|
+
}
|
|
88
|
+
function actionFor(id, p) {
|
|
89
|
+
if (!p?.available)
|
|
90
|
+
return (0, harness_registry_1.isInstallable)(id) ? 'install' : 'how to install';
|
|
91
|
+
if (!p.authed)
|
|
92
|
+
return 'log in';
|
|
93
|
+
return harness_registry_1.HARNESSES[id].logout ? 'log out' : 'ok';
|
|
94
|
+
}
|
|
95
|
+
function row(id, p, selected) {
|
|
96
|
+
const spec = harness_registry_1.HARNESSES[id];
|
|
97
|
+
// Budget: the action hint on the right is the point of the row, so the
|
|
98
|
+
// wide informational columns give up space before it does. At 100 cols
|
|
99
|
+
// the old widths pushed the action past the border and it was cut.
|
|
100
|
+
const name = spec.label.padEnd(13);
|
|
101
|
+
const ver = (p?.version ? `v${p.version}` : '—').padEnd(11).slice(0, 11);
|
|
102
|
+
const who = (p?.authed
|
|
103
|
+
? `${p.account ?? p.accountKind ?? 'logged in'}${p.plan ? ` · ${p.plan}` : ''}`
|
|
104
|
+
: p?.available ? 'not logged in' : 'not installed').padEnd(24).slice(0, 24);
|
|
105
|
+
const models = `${p?.models?.length ?? 0} models`.padEnd(9);
|
|
106
|
+
const efforts = spec.efforts.length ? spec.efforts.join('/') : '—';
|
|
107
|
+
const act = actionFor(id, p);
|
|
108
|
+
const actTxt = act === 'ok' ? green('✓') : act === 'log out' ? dim('↵ log out') : cyan(`↵ ${act}`);
|
|
109
|
+
const line = `${dot(p)} ${name} ${dim(ver)} ${who} ${dim(models)} ${dim(efforts.padEnd(17).slice(0, 17))} ${actTxt}`;
|
|
110
|
+
return selected ? `${cyan('❯')} ${bold(line)}` : ` ${line}`;
|
|
111
|
+
}
|
|
112
|
+
function render(st) {
|
|
113
|
+
home();
|
|
114
|
+
const lines = [];
|
|
115
|
+
if (!st.caps) {
|
|
116
|
+
lines.push(`${cyan(SPIN[st.spin % SPIN.length])} probing installed harnesses…`);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
lines.push(dim(' harness version account models efforts action'));
|
|
120
|
+
harness_registry_1.HARNESS_IDS.forEach((id, i) => lines.push(row(id, st.caps[id], i === st.sel)));
|
|
121
|
+
}
|
|
122
|
+
if (st.busy)
|
|
123
|
+
lines.push('', `${cyan(SPIN[st.spin % SPIN.length])} ${st.busy}`);
|
|
124
|
+
if (st.log.length) {
|
|
125
|
+
lines.push('');
|
|
126
|
+
st.log.slice(-8).forEach(l => lines.push(dim(l.slice(0, width() - 8))));
|
|
127
|
+
}
|
|
128
|
+
if (st.note)
|
|
129
|
+
lines.push('', st.note);
|
|
130
|
+
process.stdout.write(panel('Harnesses', lines) + '\n');
|
|
131
|
+
process.stdout.write(dim(' ↑/↓ move · ↵ install / log in · r refresh · q quit\n'));
|
|
132
|
+
}
|
|
133
|
+
/* ── actions ─────────────────────────────────────────────────────────── */
|
|
134
|
+
async function installSelected(st, id, redraw) {
|
|
135
|
+
const spec = harness_registry_1.HARNESSES[id];
|
|
136
|
+
if (!(0, harness_registry_1.isInstallable)(id)) {
|
|
137
|
+
st.note = yellow(`${spec.label}: ${spec.login.instructions}`);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
st.busy = `installing ${spec.label} (npm i -g ${spec.npmPackage})`;
|
|
141
|
+
st.log = [];
|
|
142
|
+
redraw();
|
|
143
|
+
const inv = (0, win_1.resolveCliInvocation)('npm', ['install', '-g', spec.npmPackage]);
|
|
144
|
+
await new Promise(resolve => {
|
|
145
|
+
const child = (0, child_process_1.spawn)(inv.file, inv.args, { env: process.env });
|
|
146
|
+
const onChunk = (b) => {
|
|
147
|
+
st.log.push(...b.toString('utf8').split('\n').filter(Boolean));
|
|
148
|
+
redraw();
|
|
149
|
+
};
|
|
150
|
+
child.stdout?.on('data', onChunk);
|
|
151
|
+
child.stderr?.on('data', onChunk);
|
|
152
|
+
child.on('exit', code => {
|
|
153
|
+
st.busy = null;
|
|
154
|
+
st.note = code === 0 ? green(`✓ ${spec.label} installed`) : fg(203, `✗ npm exited ${code}`);
|
|
155
|
+
resolve();
|
|
156
|
+
});
|
|
157
|
+
child.on('error', err => { st.busy = null; st.note = fg(203, `✗ ${err.message}`); resolve(); });
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
function logoutSelected(id) {
|
|
161
|
+
const spec = harness_registry_1.HARNESSES[id];
|
|
162
|
+
if (!spec.logout)
|
|
163
|
+
return `${spec.label} can only be logged out on the device`;
|
|
164
|
+
if (spec.logout.cmd) {
|
|
165
|
+
const bin = spec.findBinary();
|
|
166
|
+
if (!bin)
|
|
167
|
+
return `${spec.label} is not installed`;
|
|
168
|
+
const inv = (0, win_1.resolveCliInvocation)(bin, spec.logout.cmd);
|
|
169
|
+
const res = (0, child_process_1.spawnSync)(inv.file, inv.args, { stdio: 'ignore', env: process.env, timeout: 60_000 });
|
|
170
|
+
if (res.status !== 0)
|
|
171
|
+
return `logout exited with code ${res.status ?? '?'}`;
|
|
172
|
+
}
|
|
173
|
+
for (const rel of spec.logout.files ?? []) {
|
|
174
|
+
try {
|
|
175
|
+
fs.rmSync(path.join(os.homedir(), rel), { force: true });
|
|
176
|
+
}
|
|
177
|
+
catch { /* best effort */ }
|
|
178
|
+
}
|
|
179
|
+
for (const [rel, varName] of spec.logout.envStrip ?? []) {
|
|
180
|
+
const envPath = path.join(os.homedir(), rel);
|
|
181
|
+
try {
|
|
182
|
+
const body = fs.readFileSync(envPath, 'utf8');
|
|
183
|
+
fs.writeFileSync(envPath, body.split('\n').filter(l => !l.startsWith(`${varName}=`)).join('\n'), { mode: 0o600 });
|
|
184
|
+
}
|
|
185
|
+
catch { /* nothing to strip */ }
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
function loginSelected(id) {
|
|
190
|
+
const spec = harness_registry_1.HARNESSES[id];
|
|
191
|
+
const bin = spec.findBinary();
|
|
192
|
+
if (!bin)
|
|
193
|
+
return `${spec.label} is not installed`;
|
|
194
|
+
// Hand the real terminal to the CLI — leave the alt screen first.
|
|
195
|
+
altOff();
|
|
196
|
+
console.log(cyan(`\n— ${spec.label} login —`));
|
|
197
|
+
console.log(dim(spec.login.instructions) + '\n');
|
|
198
|
+
const args = spec.login.strategy === 'pty-url-code' ? (spec.login.loginArgs ?? []) : [];
|
|
199
|
+
const inv = (0, win_1.resolveCliInvocation)(bin, args);
|
|
200
|
+
const res = (0, child_process_1.spawnSync)(inv.file, inv.args, { stdio: 'inherit', env: process.env });
|
|
201
|
+
altOn();
|
|
202
|
+
return res.status === 0 ? null : `login exited with code ${res.status ?? '?'}`;
|
|
203
|
+
}
|
|
204
|
+
/* ── entry ───────────────────────────────────────────────────────────── */
|
|
205
|
+
async function plainStatus() {
|
|
206
|
+
const caps = await (0, capabilities_1.probeCapabilities)();
|
|
207
|
+
for (const id of harness_registry_1.HARNESS_IDS) {
|
|
208
|
+
const p = caps[id];
|
|
209
|
+
const state = !p?.available ? 'not installed' : p.authed ? `authed (${p.account ?? p.accountKind ?? '?'})` : 'not logged in';
|
|
210
|
+
console.log(`${id.padEnd(8)} ${(p?.version ?? '—').padEnd(12)} ${state}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Run the harness manager.
|
|
215
|
+
*
|
|
216
|
+
* The returned promise resolves when the USER LEAVES the screen — not when
|
|
217
|
+
* the first capability probe finishes. Resolving early is what made the
|
|
218
|
+
* embedded case flicker: the dashboard re-attached its own keypress handler
|
|
219
|
+
* and repainted while this screen was still live, and the two fought over
|
|
220
|
+
* the terminal.
|
|
221
|
+
*
|
|
222
|
+
* `embedded` distinguishes `ainode harnesses` (q exits the process) from
|
|
223
|
+
* the dashboard's `h` (q hands control back to the caller). Ctrl-C always
|
|
224
|
+
* ends the process.
|
|
225
|
+
*/
|
|
226
|
+
async function runHarnessesTui(opts = {}) {
|
|
227
|
+
if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
228
|
+
await plainStatus();
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const st = { caps: null, sel: 0, spin: 0, busy: null, log: [], note: null, confirmLogout: null };
|
|
232
|
+
const redraw = () => render(st);
|
|
233
|
+
altOn();
|
|
234
|
+
readline.emitKeypressEvents(process.stdin);
|
|
235
|
+
process.stdin.setRawMode(true);
|
|
236
|
+
process.stdin.resume();
|
|
237
|
+
let done = null;
|
|
238
|
+
const spinTimer = setInterval(() => { st.spin++; if (st.busy || !st.caps)
|
|
239
|
+
redraw(); }, 90);
|
|
240
|
+
const detach = () => {
|
|
241
|
+
clearInterval(spinTimer);
|
|
242
|
+
process.stdin.off('keypress', onKeypress);
|
|
243
|
+
};
|
|
244
|
+
const quit = (hard) => {
|
|
245
|
+
detach();
|
|
246
|
+
if (opts.embedded && !hard) {
|
|
247
|
+
done?.();
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
process.stdin.setRawMode(false);
|
|
251
|
+
process.stdin.pause();
|
|
252
|
+
altOff();
|
|
253
|
+
process.exit(0);
|
|
254
|
+
};
|
|
255
|
+
const reprobe = async () => {
|
|
256
|
+
st.caps = null;
|
|
257
|
+
redraw();
|
|
258
|
+
st.caps = await (0, capabilities_1.probeCapabilities)();
|
|
259
|
+
redraw();
|
|
260
|
+
};
|
|
261
|
+
let acting = false;
|
|
262
|
+
function onKeypress(_str, key) {
|
|
263
|
+
void (async () => {
|
|
264
|
+
if (!key || acting)
|
|
265
|
+
return;
|
|
266
|
+
if (key.ctrl && key.name === 'c') {
|
|
267
|
+
quit(true);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (key.name === 'q') {
|
|
271
|
+
quit(false);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
if (!st.caps)
|
|
275
|
+
return;
|
|
276
|
+
if (st.confirmLogout) {
|
|
277
|
+
const target = st.confirmLogout;
|
|
278
|
+
st.confirmLogout = null;
|
|
279
|
+
if (key.name === 'y') {
|
|
280
|
+
acting = true;
|
|
281
|
+
try {
|
|
282
|
+
const err = logoutSelected(target);
|
|
283
|
+
st.note = err ? fg(203, `✗ ${err}`) : green(`✓ ${harness_registry_1.HARNESSES[target].label} logged out`);
|
|
284
|
+
await reprobe();
|
|
285
|
+
}
|
|
286
|
+
finally {
|
|
287
|
+
acting = false;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
st.note = dim('logout cancelled');
|
|
292
|
+
redraw();
|
|
293
|
+
}
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (key.name === 'up' || key.name === 'k') {
|
|
297
|
+
st.sel = (st.sel + harness_registry_1.HARNESS_IDS.length - 1) % harness_registry_1.HARNESS_IDS.length;
|
|
298
|
+
redraw();
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (key.name === 'down' || key.name === 'j') {
|
|
302
|
+
st.sel = (st.sel + 1) % harness_registry_1.HARNESS_IDS.length;
|
|
303
|
+
redraw();
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (key.name === 'r') {
|
|
307
|
+
await reprobe();
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (key.name !== 'return')
|
|
311
|
+
return;
|
|
312
|
+
const id = harness_registry_1.HARNESS_IDS[st.sel];
|
|
313
|
+
const p = st.caps[id];
|
|
314
|
+
acting = true;
|
|
315
|
+
try {
|
|
316
|
+
if (!p?.available) {
|
|
317
|
+
await installSelected(st, id, redraw);
|
|
318
|
+
await reprobe();
|
|
319
|
+
}
|
|
320
|
+
else if (!p.authed) {
|
|
321
|
+
const err = loginSelected(id);
|
|
322
|
+
st.note = err ? fg(203, `✗ ${err}`) : green('✓ login flow finished — verifying…');
|
|
323
|
+
await reprobe();
|
|
324
|
+
const now = st.caps?.[id];
|
|
325
|
+
if (now?.authed)
|
|
326
|
+
st.note = green(`✓ ${harness_registry_1.HARNESSES[id].label} logged in as ${now.account ?? now.accountKind ?? '?'}`);
|
|
327
|
+
else if (!err)
|
|
328
|
+
st.note = yellow(`${harness_registry_1.HARNESSES[id].label} still reports unauthenticated`);
|
|
329
|
+
redraw();
|
|
330
|
+
}
|
|
331
|
+
else if (harness_registry_1.HARNESSES[id].logout) {
|
|
332
|
+
st.confirmLogout = id;
|
|
333
|
+
st.note = yellow(`log out of ${harness_registry_1.HARNESSES[id].label}? press y to confirm`);
|
|
334
|
+
redraw();
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
st.note = green(`${harness_registry_1.HARNESSES[id].label} is installed and logged in.`);
|
|
338
|
+
redraw();
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
finally {
|
|
342
|
+
acting = false;
|
|
343
|
+
}
|
|
344
|
+
})();
|
|
345
|
+
}
|
|
346
|
+
// The exit promise is created BEFORE the first probe, not after it.
|
|
347
|
+
// Probing five CLIs takes seconds; if `q` arrives during that window and
|
|
348
|
+
// `done` isn't wired yet, quit() detaches the key listener and resolves
|
|
349
|
+
// nothing — the screen wedges with no way out.
|
|
350
|
+
const exited = new Promise(resolve => { done = resolve; });
|
|
351
|
+
process.stdin.on('keypress', onKeypress);
|
|
352
|
+
void reprobe();
|
|
353
|
+
await exited;
|
|
354
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare const SPIN: string[];
|
|
2
|
+
export declare function stripAnsi(s: string): string;
|
|
3
|
+
export declare function visibleWidth(s: string): number;
|
|
4
|
+
/** Cut to `max` VISIBLE columns, ellipsis included in the budget. */
|
|
5
|
+
export declare function truncate(s: string, max: number): string;
|
|
6
|
+
export declare function padEndVisible(s: string, width: number): string;
|
|
7
|
+
export declare const dim: (s: string) => string;
|
|
8
|
+
export declare const bold: (s: string) => string;
|
|
9
|
+
export declare const green: (s: string) => string;
|
|
10
|
+
export declare const yellow: (s: string) => string;
|
|
11
|
+
export declare const grey: (s: string) => string;
|
|
12
|
+
export declare const cyan: (s: string) => string;
|
|
13
|
+
export declare const red: (s: string) => string;
|
|
14
|
+
export declare function termWidth(): number;
|
|
15
|
+
/** A rounded panel. Every returned line has exactly `width` visible columns. */
|
|
16
|
+
export declare function panel(title: string, lines: string[], width: number): string[];
|
|
17
|
+
export declare function fmtRelative(iso: string | null | undefined, now?: number): string;
|
|
18
|
+
export declare function fmtDuration(ms: number | null): string;
|
|
19
|
+
export declare const altOn: () => void;
|
|
20
|
+
export declare const altOff: () => void;
|
|
21
|
+
export declare const home: () => void;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ANSI primitives for the +Ai Node console UI. Hand-rolled on purpose:
|
|
3
|
+
// the daemon ships two dependencies and a TUI framework is not going to
|
|
4
|
+
// be the third.
|
|
5
|
+
//
|
|
6
|
+
// Everything here is pure except the three cursor/screen writers at the
|
|
7
|
+
// bottom, which is what lets the screens be unit-tested without a TTY.
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.home = exports.altOff = exports.altOn = exports.red = exports.cyan = exports.grey = exports.yellow = exports.green = exports.bold = exports.dim = exports.SPIN = void 0;
|
|
10
|
+
exports.stripAnsi = stripAnsi;
|
|
11
|
+
exports.visibleWidth = visibleWidth;
|
|
12
|
+
exports.truncate = truncate;
|
|
13
|
+
exports.padEndVisible = padEndVisible;
|
|
14
|
+
exports.termWidth = termWidth;
|
|
15
|
+
exports.panel = panel;
|
|
16
|
+
exports.fmtRelative = fmtRelative;
|
|
17
|
+
exports.fmtDuration = fmtDuration;
|
|
18
|
+
const E = '\x1b[';
|
|
19
|
+
exports.SPIN = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
20
|
+
/** Not global: a /g regex carries lastIndex between calls and would make
|
|
21
|
+
* repeated .test() calls alternate true/false. */
|
|
22
|
+
const ANSI_RE = /\x1b\[[0-9;]*m/;
|
|
23
|
+
const ANSI_RE_G = /\x1b\[[0-9;]*m/g;
|
|
24
|
+
function stripAnsi(s) {
|
|
25
|
+
return s.replace(ANSI_RE_G, '');
|
|
26
|
+
}
|
|
27
|
+
function visibleWidth(s) {
|
|
28
|
+
return stripAnsi(s).length;
|
|
29
|
+
}
|
|
30
|
+
/** Cut to `max` VISIBLE columns, ellipsis included in the budget. */
|
|
31
|
+
function truncate(s, max) {
|
|
32
|
+
if (max <= 0)
|
|
33
|
+
return '';
|
|
34
|
+
if (visibleWidth(s) <= max)
|
|
35
|
+
return s;
|
|
36
|
+
const coloured = ANSI_RE.test(s);
|
|
37
|
+
let out = '';
|
|
38
|
+
let seen = 0;
|
|
39
|
+
const budget = max - 1;
|
|
40
|
+
for (let i = 0; i < s.length; i++) {
|
|
41
|
+
if (s[i] === '\x1b') {
|
|
42
|
+
const m = ANSI_RE.exec(s.slice(i));
|
|
43
|
+
if (m && m.index === 0) {
|
|
44
|
+
out += m[0];
|
|
45
|
+
i += m[0].length - 1;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (seen >= budget)
|
|
50
|
+
break;
|
|
51
|
+
out += s[i];
|
|
52
|
+
seen++;
|
|
53
|
+
}
|
|
54
|
+
// The reset only matters when we cut mid-colour; adding it unconditionally
|
|
55
|
+
// would make truncate('abcdefgh', 4) !== 'abc…' for plain text.
|
|
56
|
+
return coloured ? `${out}${E}0m…` : `${out}…`;
|
|
57
|
+
}
|
|
58
|
+
function padEndVisible(s, width) {
|
|
59
|
+
const pad = Math.max(0, width - visibleWidth(s));
|
|
60
|
+
return s + ' '.repeat(pad);
|
|
61
|
+
}
|
|
62
|
+
const wrapCode = (on, off) => (s) => `${E}${on}${s}${E}${off}`;
|
|
63
|
+
exports.dim = wrapCode('2m', '22m');
|
|
64
|
+
exports.bold = wrapCode('1m', '22m');
|
|
65
|
+
const fg = (n) => (s) => `${E}38;5;${n}m${s}${E}39m`;
|
|
66
|
+
exports.green = fg(114);
|
|
67
|
+
exports.yellow = fg(179);
|
|
68
|
+
exports.grey = fg(244);
|
|
69
|
+
exports.cyan = fg(80);
|
|
70
|
+
exports.red = fg(203);
|
|
71
|
+
function termWidth() {
|
|
72
|
+
return Math.min(process.stdout.columns || 100, 110);
|
|
73
|
+
}
|
|
74
|
+
/** A rounded panel. Every returned line has exactly `width` visible columns. */
|
|
75
|
+
function panel(title, lines, width) {
|
|
76
|
+
const inner = width - 4;
|
|
77
|
+
// "╭─ " + title + " " + rule + "╮"
|
|
78
|
+
const ruleLen = Math.max(1, width - visibleWidth(title) - 5);
|
|
79
|
+
const top = `${(0, exports.dim)('╭─')} ${(0, exports.bold)(title)} ${(0, exports.dim)('─'.repeat(ruleLen))}${(0, exports.dim)('╮')}`;
|
|
80
|
+
const body = lines.map(l => `${(0, exports.dim)('│')} ${padEndVisible(truncate(l, inner), inner)} ${(0, exports.dim)('│')}`);
|
|
81
|
+
const bottom = (0, exports.dim)(`╰${'─'.repeat(width - 2)}╯`);
|
|
82
|
+
return [padEndVisible(truncate(top, width), width), ...body, bottom];
|
|
83
|
+
}
|
|
84
|
+
function fmtRelative(iso, now = Date.now()) {
|
|
85
|
+
if (!iso)
|
|
86
|
+
return 'never';
|
|
87
|
+
const s = Math.floor((now - new Date(iso).getTime()) / 1000);
|
|
88
|
+
if (s < 60)
|
|
89
|
+
return s <= 1 ? 'just now' : `${s}s ago`;
|
|
90
|
+
const min = Math.floor(s / 60);
|
|
91
|
+
if (min < 60)
|
|
92
|
+
return `${min}m ago`;
|
|
93
|
+
const hr = Math.floor(min / 60);
|
|
94
|
+
if (hr < 24)
|
|
95
|
+
return `${hr}h ago`;
|
|
96
|
+
return `${Math.floor(hr / 24)}d ago`;
|
|
97
|
+
}
|
|
98
|
+
function fmtDuration(ms) {
|
|
99
|
+
if (ms == null)
|
|
100
|
+
return '—';
|
|
101
|
+
if (ms < 1000)
|
|
102
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
103
|
+
const s = Math.floor(ms / 1000);
|
|
104
|
+
if (s < 60)
|
|
105
|
+
return `${s}s`;
|
|
106
|
+
const m = Math.floor(s / 60);
|
|
107
|
+
if (m < 60)
|
|
108
|
+
return `${m}m${String(s % 60).padStart(2, '0')}s`;
|
|
109
|
+
return `${Math.floor(m / 60)}h${String(m % 60).padStart(2, '0')}m`;
|
|
110
|
+
}
|
|
111
|
+
const altOn = () => { process.stdout.write(`${E}?1049h${E}?25l`); };
|
|
112
|
+
exports.altOn = altOn;
|
|
113
|
+
const altOff = () => { process.stdout.write(`${E}?1049l${E}?25h`); };
|
|
114
|
+
exports.altOff = altOff;
|
|
115
|
+
const home = () => { process.stdout.write(`${E}H${E}2J`); };
|
|
116
|
+
exports.home = home;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { DataLayer, RequestRow } from './data';
|
|
2
|
+
import type { AppHost, Screen } from './app';
|
|
3
|
+
export interface RequestsState {
|
|
4
|
+
rows: RequestRow[];
|
|
5
|
+
sel: number;
|
|
6
|
+
filter: string;
|
|
7
|
+
filtering: boolean;
|
|
8
|
+
loading: boolean;
|
|
9
|
+
exhausted: boolean;
|
|
10
|
+
now: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function filterRows(rows: RequestRow[], filter: string): RequestRow[];
|
|
13
|
+
export declare function renderRequests(st: RequestsState, width: number): string[];
|
|
14
|
+
export declare function createRequestsScreen(deps: {
|
|
15
|
+
data: DataLayer;
|
|
16
|
+
host: AppHost;
|
|
17
|
+
openTranscript(r: RequestRow): void;
|
|
18
|
+
}): Screen;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Full request history for this node: cursor-paged, filterable.
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.filterRows = filterRows;
|
|
5
|
+
exports.renderRequests = renderRequests;
|
|
6
|
+
exports.createRequestsScreen = createRequestsScreen;
|
|
7
|
+
const render_1 = require("./render");
|
|
8
|
+
const dashboard_1 = require("./dashboard");
|
|
9
|
+
const PAGE = 50;
|
|
10
|
+
function filterRows(rows, filter) {
|
|
11
|
+
const f = filter.trim().toLowerCase();
|
|
12
|
+
if (!f)
|
|
13
|
+
return rows;
|
|
14
|
+
return rows.filter(r => (r.entity_name ?? '').toLowerCase().includes(f) ||
|
|
15
|
+
(r.status ?? '').toLowerCase().includes(f) ||
|
|
16
|
+
(r.agent ?? '').toLowerCase().includes(f) ||
|
|
17
|
+
(r.error_code ?? '').toLowerCase().includes(f));
|
|
18
|
+
}
|
|
19
|
+
function renderRequests(st, width) {
|
|
20
|
+
const shown = filterRows(st.rows, st.filter);
|
|
21
|
+
const out = [];
|
|
22
|
+
out.push(` ${(0, render_1.bold)('ALL REQUESTS')} ${(0, render_1.dim)(`${shown.length} shown`)}`);
|
|
23
|
+
out.push('');
|
|
24
|
+
if (shown.length === 0) {
|
|
25
|
+
out.push((0, render_1.dim)(st.filter ? ' no requests match that filter' : ' no requests yet'));
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
shown.forEach((r, i) => out.push((0, dashboard_1.requestLine)(r, st.now, i === st.sel, width)));
|
|
29
|
+
}
|
|
30
|
+
out.push('');
|
|
31
|
+
if (st.loading)
|
|
32
|
+
out.push((0, render_1.dim)(' loading more…'));
|
|
33
|
+
else if (st.exhausted)
|
|
34
|
+
out.push((0, render_1.dim)(' end of history'));
|
|
35
|
+
else
|
|
36
|
+
out.push((0, render_1.dim)(' ↓ at the bottom loads more'));
|
|
37
|
+
out.push(st.filtering
|
|
38
|
+
? ` ${(0, render_1.cyan)('/')}${st.filter}${(0, render_1.cyan)('▏')}`
|
|
39
|
+
: (0, render_1.dim)(' ↑↓ move ⏎ transcript / filter q back'));
|
|
40
|
+
return out.map(l => (0, render_1.truncate)(l, width));
|
|
41
|
+
}
|
|
42
|
+
function createRequestsScreen(deps) {
|
|
43
|
+
const st = {
|
|
44
|
+
rows: [], sel: 0, filter: '', filtering: false,
|
|
45
|
+
loading: false, exhausted: false, now: Date.now(),
|
|
46
|
+
};
|
|
47
|
+
async function loadMore() {
|
|
48
|
+
if (st.loading || st.exhausted)
|
|
49
|
+
return;
|
|
50
|
+
st.loading = true;
|
|
51
|
+
deps.host.redraw();
|
|
52
|
+
try {
|
|
53
|
+
const before = st.rows.length ? st.rows[st.rows.length - 1].created_at : null;
|
|
54
|
+
const page = await deps.data.requests({ limit: PAGE, before });
|
|
55
|
+
if (page.length < PAGE)
|
|
56
|
+
st.exhausted = true;
|
|
57
|
+
// The cursor is exclusive (created_at < before), but identical
|
|
58
|
+
// timestamps are possible — drop any id we already hold.
|
|
59
|
+
const seen = new Set(st.rows.map(r => r.id));
|
|
60
|
+
st.rows = [...st.rows, ...page.filter(r => !seen.has(r.id))];
|
|
61
|
+
st.now = Date.now();
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
st.loading = false;
|
|
65
|
+
deps.host.redraw();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
id: 'requests',
|
|
70
|
+
title: 'All requests',
|
|
71
|
+
render: (width) => renderRequests(st, width),
|
|
72
|
+
pollMs: () => 5000,
|
|
73
|
+
capturesKeys: () => st.filtering,
|
|
74
|
+
async poll() {
|
|
75
|
+
st.now = Date.now();
|
|
76
|
+
if (st.rows.length === 0)
|
|
77
|
+
await loadMore();
|
|
78
|
+
deps.host.redraw();
|
|
79
|
+
},
|
|
80
|
+
async onKey(key) {
|
|
81
|
+
if (st.filtering) {
|
|
82
|
+
if (key.name === 'return' || key.name === 'escape') {
|
|
83
|
+
st.filtering = false;
|
|
84
|
+
deps.host.redraw();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (key.name === 'backspace') {
|
|
88
|
+
st.filter = st.filter.slice(0, -1);
|
|
89
|
+
deps.host.redraw();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
// `sequence` carries the literal character; `name` is absent for
|
|
93
|
+
// punctuation and uppercase letters arrive shifted.
|
|
94
|
+
const ch = key.sequence ?? key.name ?? '';
|
|
95
|
+
if (ch.length === 1 && ch >= ' ') {
|
|
96
|
+
st.filter += ch;
|
|
97
|
+
deps.host.redraw();
|
|
98
|
+
}
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const shown = filterRows(st.rows, st.filter);
|
|
102
|
+
if (key.name === 'up' || key.name === 'k') {
|
|
103
|
+
st.sel = Math.max(0, st.sel - 1);
|
|
104
|
+
deps.host.redraw();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (key.name === 'down' || key.name === 'j') {
|
|
108
|
+
st.sel = Math.min(Math.max(0, shown.length - 1), st.sel + 1);
|
|
109
|
+
if (st.sel >= shown.length - 1)
|
|
110
|
+
await loadMore();
|
|
111
|
+
deps.host.redraw();
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (key.name === '/' || key.sequence === '/') {
|
|
115
|
+
st.filtering = true;
|
|
116
|
+
st.filter = '';
|
|
117
|
+
st.sel = 0;
|
|
118
|
+
deps.host.redraw();
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (key.name === 'return' && shown[st.sel]) {
|
|
122
|
+
deps.openTranscript(shown[st.sel]);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { FEED_ROWS } from './dashboard';
|
|
2
|
+
export declare function shouldRenderTui(env: {
|
|
3
|
+
isTTY: boolean;
|
|
4
|
+
stdinTTY: boolean;
|
|
5
|
+
noTui: boolean;
|
|
6
|
+
}): boolean;
|
|
7
|
+
export declare function runDashboard(opts: {
|
|
8
|
+
viewerMode: boolean;
|
|
9
|
+
pid: number | null;
|
|
10
|
+
startedAt: number | null;
|
|
11
|
+
version: string;
|
|
12
|
+
}): Promise<void>;
|
|
13
|
+
export { FEED_ROWS };
|