@addai/node 0.5.0 → 0.6.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/dist/autostart-mac.d.ts +62 -0
- package/dist/autostart-mac.js +306 -0
- package/dist/autostart-win.d.ts +46 -0
- package/dist/autostart-win.js +290 -0
- package/dist/autostart.d.ts +81 -0
- package/dist/autostart.js +224 -0
- package/dist/capabilities.d.ts +5 -0
- package/dist/capabilities.js +2 -0
- package/dist/cli.js +87 -1
- package/dist/command-runner.js +37 -0
- package/dist/index.js +23 -10
- package/dist/memory-capture.js +10 -1
- package/dist/tui/dashboard.d.ts +25 -0
- package/dist/tui/dashboard.js +130 -27
- package/dist/tui/data.d.ts +9 -0
- package/dist/tui/harnesses.js +11 -8
- package/dist/tui/logs.js +3 -3
- package/dist/tui/request-row.d.ts +7 -1
- package/dist/tui/request-row.js +41 -16
- package/dist/tui/requests.js +4 -4
- package/dist/tui/run.js +3 -0
- package/dist/tui.d.ts +1 -0
- package/dist/tui.js +314 -0
- package/package.json +1 -1
|
@@ -10,10 +10,16 @@ export declare const CANCELLED: Set<string>;
|
|
|
10
10
|
* this column needs.
|
|
11
11
|
*/
|
|
12
12
|
export declare function shortVia(via: string | null | undefined): string;
|
|
13
|
+
/**
|
|
14
|
+
* Model ids carry a vendor prefix and a release date the column has no room
|
|
15
|
+
* for: `claude-haiku-4-5-20251001` was rendering as `haiku-4-…`, which loses
|
|
16
|
+
* the one part you were reading it for.
|
|
17
|
+
*/
|
|
18
|
+
export declare function shortModel(model: string | null | undefined): string;
|
|
13
19
|
export declare function statusLabel(status: string): string;
|
|
14
20
|
export declare function statusColour(status: string): (s: string) => string;
|
|
15
21
|
/** Live rows get a moving spinner; settled rows get a dot. */
|
|
16
|
-
export declare function marker(status: string, spin: number): string;
|
|
22
|
+
export declare function marker(status: string, spin: number, handedOff?: boolean): string;
|
|
17
23
|
export declare function durationOf(r: RequestRow, now: number): number | null;
|
|
18
24
|
/**
|
|
19
25
|
* Column widths for the request table.
|
package/dist/tui/request-row.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.CANCELLED = exports.ACTIVE = void 0;
|
|
10
10
|
exports.shortVia = shortVia;
|
|
11
|
+
exports.shortModel = shortModel;
|
|
11
12
|
exports.statusLabel = statusLabel;
|
|
12
13
|
exports.statusColour = statusColour;
|
|
13
14
|
exports.marker = marker;
|
|
@@ -48,14 +49,24 @@ function shortVia(via) {
|
|
|
48
49
|
return map[via];
|
|
49
50
|
return via.replace(/^entity_/, '').replace(/_trigger$/, '');
|
|
50
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Model ids carry a vendor prefix and a release date the column has no room
|
|
54
|
+
* for: `claude-haiku-4-5-20251001` was rendering as `haiku-4-…`, which loses
|
|
55
|
+
* the one part you were reading it for.
|
|
56
|
+
*/
|
|
57
|
+
function shortModel(model) {
|
|
58
|
+
if (!model)
|
|
59
|
+
return '—';
|
|
60
|
+
return model.replace(/^claude-/, '').replace(/-\d{8}$/, '');
|
|
61
|
+
}
|
|
51
62
|
function statusLabel(status) {
|
|
52
63
|
if (status === 'completed')
|
|
53
|
-
return '
|
|
64
|
+
return 'Done';
|
|
54
65
|
if (exports.CANCELLED.has(status))
|
|
55
|
-
return '
|
|
66
|
+
return 'Cancelled';
|
|
56
67
|
if (status === 'dead_letter')
|
|
57
|
-
return '
|
|
58
|
-
return status.toUpperCase();
|
|
68
|
+
return 'Dead';
|
|
69
|
+
return status.charAt(0).toUpperCase() + status.slice(1);
|
|
59
70
|
}
|
|
60
71
|
function statusColour(status) {
|
|
61
72
|
if (exports.ACTIVE.has(status))
|
|
@@ -67,7 +78,12 @@ function statusColour(status) {
|
|
|
67
78
|
return render_1.red;
|
|
68
79
|
}
|
|
69
80
|
/** Live rows get a moving spinner; settled rows get a dot. */
|
|
70
|
-
function marker(status, spin) {
|
|
81
|
+
function marker(status, spin, handedOff = false) {
|
|
82
|
+
// A run this node handed on didn't finish here, whatever the row's terminal
|
|
83
|
+
// status says. Marking it as an outcome would credit this machine with
|
|
84
|
+
// another machine's work.
|
|
85
|
+
if (handedOff)
|
|
86
|
+
return (0, render_1.grey)('↗');
|
|
71
87
|
const colour = statusColour(status);
|
|
72
88
|
return colour(exports.ACTIVE.has(status) ? render_1.SPIN[spin % render_1.SPIN.length] : '⏺');
|
|
73
89
|
}
|
|
@@ -91,11 +107,11 @@ function requestColumns(width) {
|
|
|
91
107
|
// marker, status, entity, agent/mode, model, via, duration, prompt
|
|
92
108
|
return (0, render_1.layoutColumns)([
|
|
93
109
|
{ min: 1 },
|
|
94
|
-
//
|
|
110
|
+
// 'Cancelled' is the longest label; anything shorter than it clips the
|
|
95
111
|
// status, which is the one column that must never be ambiguous.
|
|
96
|
-
{ min:
|
|
112
|
+
{ min: 9 },
|
|
97
113
|
{ min: 10, grow: 1 },
|
|
98
|
-
{ min:
|
|
114
|
+
{ min: 13 },
|
|
99
115
|
// 'haiku-4-5' is nine columns once the claude- prefix is stripped.
|
|
100
116
|
{ min: 9 },
|
|
101
117
|
{ min: 8 },
|
|
@@ -106,24 +122,33 @@ function requestColumns(width) {
|
|
|
106
122
|
const ALIGN = ['left', 'left', 'left', 'left', 'left', 'left', 'right', 'left'];
|
|
107
123
|
function requestHeader(width) {
|
|
108
124
|
const w = requestColumns(width);
|
|
109
|
-
return (0, render_1.dim)(' ' + (0, render_1.tableRow)(['', '
|
|
125
|
+
return (0, render_1.dim)(' ' + (0, render_1.tableRow)(['', 'Status', 'Entity', 'Agent', 'Model', 'Via', 'Dur', 'Prompt'], w, ALIGN));
|
|
110
126
|
}
|
|
111
127
|
function requestLine(r, now, selected, width, spin = 0) {
|
|
112
128
|
const w = requestColumns(width);
|
|
113
129
|
const colour = statusColour(r.status);
|
|
130
|
+
const handedOff = r.handed_off === true;
|
|
114
131
|
// A run that failed once and then succeeded on retry still carries the
|
|
115
132
|
// old error_code. Showing it on a DONE row reads as "this broke" when
|
|
116
133
|
// the work actually landed — so the code is only for rows that ended badly.
|
|
117
134
|
const endedBadly = r.status !== 'completed' && !exports.ACTIVE.has(r.status);
|
|
118
|
-
const tail =
|
|
119
|
-
|
|
120
|
-
|
|
135
|
+
const tail = handedOff
|
|
136
|
+
// Say where it went. Without this the row reads as a run that simply
|
|
137
|
+
// stopped, when in fact it continued somewhere else.
|
|
138
|
+
? (0, render_1.grey)(`→ ${r.to_runtime_name ?? 'another node'} `) + (0, render_1.dim)((r.prompt ?? '').replace(/\s+/g, ' '))
|
|
139
|
+
: (r.error_code && endedBadly
|
|
140
|
+
? (0, render_1.red)(`[${r.error_code}]`)
|
|
141
|
+
: (r.prompt ?? '').replace(/\s+/g, ' '));
|
|
142
|
+
// `agent` is what this node spawned. A run that laddered ended somewhere
|
|
143
|
+
// else, and saying so is the difference between a history and a guess.
|
|
144
|
+
const hopped = (r.ladder_hops ?? 0) > 0 && r.current_agent && r.current_agent !== r.agent;
|
|
145
|
+
const agentCell = hopped ? `${r.agent}→${r.current_agent}` : `${r.agent}/${r.mode}`;
|
|
121
146
|
const cells = [
|
|
122
|
-
marker(r.status, spin),
|
|
123
|
-
colour(statusLabel(r.status)),
|
|
147
|
+
marker(r.status, spin, handedOff),
|
|
148
|
+
handedOff ? (0, render_1.grey)('Moved') : colour(statusLabel(r.status)),
|
|
124
149
|
r.entity_name ?? '—',
|
|
125
|
-
(0, render_1.dim)(
|
|
126
|
-
(0, render_1.dim)((r.model
|
|
150
|
+
(0, render_1.dim)(agentCell),
|
|
151
|
+
(0, render_1.dim)(shortModel(r.model)),
|
|
127
152
|
(0, render_1.dim)(shortVia(r.issued_via)),
|
|
128
153
|
(0, render_1.dim)((0, render_1.fmtDuration)(durationOf(r, now))),
|
|
129
154
|
tail,
|
package/dist/tui/requests.js
CHANGED
|
@@ -41,10 +41,10 @@ function renderRequests(st, width, height) {
|
|
|
41
41
|
st.top = windowTop(st.sel, st.top, rows, shown.length);
|
|
42
42
|
const out = [];
|
|
43
43
|
const scope = st.filter ? `${shown.length} matching “${st.filter}”` : `${shown.length} loaded`;
|
|
44
|
-
out.push((0, app_1.heading)('
|
|
44
|
+
out.push((0, app_1.heading)('Activity', scope));
|
|
45
45
|
out.push('');
|
|
46
46
|
if (shown.length === 0) {
|
|
47
|
-
out.push((0, render_1.dim)(st.filter ? '
|
|
47
|
+
out.push((0, render_1.dim)(st.filter ? ' No requests match that filter' : ' No requests yet'));
|
|
48
48
|
}
|
|
49
49
|
else {
|
|
50
50
|
out.push((0, request_row_1.requestHeader)(width));
|
|
@@ -57,9 +57,9 @@ function renderRequests(st, width, height) {
|
|
|
57
57
|
while (out.length < height - 2)
|
|
58
58
|
out.push('');
|
|
59
59
|
if (st.loading)
|
|
60
|
-
out.push((0, render_1.dim)('
|
|
60
|
+
out.push((0, render_1.dim)(' Loading more…'));
|
|
61
61
|
else if (st.exhausted)
|
|
62
|
-
out.push((0, render_1.dim)(`
|
|
62
|
+
out.push((0, render_1.dim)(` End of history — ${shown.length} rows`));
|
|
63
63
|
else
|
|
64
64
|
out.push((0, render_1.dim)(' ↓ past the last row loads more'));
|
|
65
65
|
out.push(st.filtering
|
package/dist/tui/run.js
CHANGED
|
@@ -197,6 +197,9 @@ async function runDashboard(opts) {
|
|
|
197
197
|
inflight: 0, paired: (0, store_1.isPaired)(), viewerMode: opts.viewerMode,
|
|
198
198
|
offline: false, now: Date.now(), version: opts.version,
|
|
199
199
|
logCount: 0,
|
|
200
|
+
// Read on the first poll rather than here — the first frame must paint
|
|
201
|
+
// before anything touches the filesystem.
|
|
202
|
+
autostart: null, autostartNote: null,
|
|
200
203
|
};
|
|
201
204
|
const ui = createConsole((host, suspend) => {
|
|
202
205
|
const openTranscript = (r) => host.push((0, transcript_1.createTranscriptScreen)({ data, host, request: r }));
|
package/dist/tui.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runHarnessesTui(): Promise<void>;
|
package/dist/tui.js
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
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
|
+
/* ── ansi helpers ────────────────────────────────────────────────────── */
|
|
55
|
+
const ESC = '\x1b[';
|
|
56
|
+
const dim = (s) => `${ESC}2m${s}${ESC}22m`;
|
|
57
|
+
const bold = (s) => `${ESC}1m${s}${ESC}22m`;
|
|
58
|
+
const fg = (n, s) => `${ESC}38;5;${n}m${s}${ESC}39m`;
|
|
59
|
+
const green = (s) => fg(114, s);
|
|
60
|
+
const yellow = (s) => fg(179, s);
|
|
61
|
+
const grey = (s) => fg(244, s);
|
|
62
|
+
const cyan = (s) => fg(80, s);
|
|
63
|
+
const altOn = () => process.stdout.write(`${ESC}?1049h${ESC}?25l`);
|
|
64
|
+
const altOff = () => process.stdout.write(`${ESC}?1049l${ESC}?25h`);
|
|
65
|
+
const home = () => process.stdout.write(`${ESC}H${ESC}2J`);
|
|
66
|
+
const SPIN = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
67
|
+
const width = () => Math.min(process.stdout.columns || 100, 110);
|
|
68
|
+
function panel(title, lines) {
|
|
69
|
+
const w = width() - 2;
|
|
70
|
+
const strip = (s) => s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
71
|
+
const top = `${dim('╭─')} ${bold(title)} ${dim('─'.repeat(Math.max(1, w - strip(title).length - 4)) + '╮')}`;
|
|
72
|
+
const body = lines.map(l => {
|
|
73
|
+
const pad = Math.max(0, w - 2 - strip(l).length);
|
|
74
|
+
return `${dim('│')} ${l}${' '.repeat(pad)} ${dim('│')}`;
|
|
75
|
+
});
|
|
76
|
+
const bottom = dim(`╰${'─'.repeat(w)}╯`);
|
|
77
|
+
return [top, ...body, bottom].join('\n');
|
|
78
|
+
}
|
|
79
|
+
function dot(p) {
|
|
80
|
+
if (!p?.available)
|
|
81
|
+
return grey('⏺');
|
|
82
|
+
return p.authed ? green('⏺') : yellow('⏺');
|
|
83
|
+
}
|
|
84
|
+
function actionFor(id, p) {
|
|
85
|
+
if (!p?.available)
|
|
86
|
+
return (0, harness_registry_1.isInstallable)(id) ? 'install' : 'how to install';
|
|
87
|
+
if (!p.authed)
|
|
88
|
+
return 'log in';
|
|
89
|
+
return harness_registry_1.HARNESSES[id].logout ? 'log out' : 'ok';
|
|
90
|
+
}
|
|
91
|
+
function row(id, p, selected) {
|
|
92
|
+
const spec = harness_registry_1.HARNESSES[id];
|
|
93
|
+
const name = spec.label.padEnd(13);
|
|
94
|
+
const ver = (p?.version ? `v${p.version}` : '—').padEnd(12).slice(0, 12);
|
|
95
|
+
const who = (p?.authed
|
|
96
|
+
? `${p.account ?? p.accountKind ?? 'logged in'}${p.plan ? ` · ${p.plan}` : ''}`
|
|
97
|
+
: p?.available ? 'not logged in' : 'not installed').padEnd(28).slice(0, 28);
|
|
98
|
+
const models = `${p?.models?.length ?? 0} models`.padEnd(10);
|
|
99
|
+
const efforts = spec.efforts.length ? spec.efforts.join('/') : '—';
|
|
100
|
+
const act = actionFor(id, p);
|
|
101
|
+
const actTxt = act === 'ok' ? green('✓') : act === 'log out' ? dim('↵ log out') : cyan(`↵ ${act}`);
|
|
102
|
+
const line = `${dot(p)} ${name} ${dim(ver)} ${who} ${dim(models)} ${dim(efforts.padEnd(26).slice(0, 26))} ${actTxt}`;
|
|
103
|
+
return selected ? `${cyan('❯')} ${bold(line)}` : ` ${line}`;
|
|
104
|
+
}
|
|
105
|
+
function render(st) {
|
|
106
|
+
home();
|
|
107
|
+
const lines = [];
|
|
108
|
+
if (!st.caps) {
|
|
109
|
+
lines.push(`${cyan(SPIN[st.spin % SPIN.length])} probing installed harnesses…`);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
lines.push(dim(' harness version account models efforts'));
|
|
113
|
+
harness_registry_1.HARNESS_IDS.forEach((id, i) => lines.push(row(id, st.caps[id], i === st.sel)));
|
|
114
|
+
}
|
|
115
|
+
if (st.busy)
|
|
116
|
+
lines.push('', `${cyan(SPIN[st.spin % SPIN.length])} ${st.busy}`);
|
|
117
|
+
if (st.log.length) {
|
|
118
|
+
lines.push('');
|
|
119
|
+
st.log.slice(-8).forEach(l => lines.push(dim(l.slice(0, width() - 8))));
|
|
120
|
+
}
|
|
121
|
+
if (st.note)
|
|
122
|
+
lines.push('', st.note);
|
|
123
|
+
process.stdout.write(panel('Harnesses', lines) + '\n');
|
|
124
|
+
process.stdout.write(dim(' ↑/↓ move · ↵ install / log in · r refresh · q quit\n'));
|
|
125
|
+
}
|
|
126
|
+
/* ── actions ─────────────────────────────────────────────────────────── */
|
|
127
|
+
async function installSelected(st, id, redraw) {
|
|
128
|
+
const spec = harness_registry_1.HARNESSES[id];
|
|
129
|
+
if (!(0, harness_registry_1.isInstallable)(id)) {
|
|
130
|
+
st.note = yellow(`${spec.label}: ${spec.login.instructions}`);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
st.busy = `installing ${spec.label} (npm i -g ${spec.npmPackage})`;
|
|
134
|
+
st.log = [];
|
|
135
|
+
redraw();
|
|
136
|
+
const inv = (0, win_1.resolveCliInvocation)('npm', ['install', '-g', spec.npmPackage]);
|
|
137
|
+
await new Promise(resolve => {
|
|
138
|
+
const child = (0, child_process_1.spawn)(inv.file, inv.args, { env: process.env });
|
|
139
|
+
const onChunk = (b) => {
|
|
140
|
+
st.log.push(...b.toString('utf8').split('\n').filter(Boolean));
|
|
141
|
+
redraw();
|
|
142
|
+
};
|
|
143
|
+
child.stdout?.on('data', onChunk);
|
|
144
|
+
child.stderr?.on('data', onChunk);
|
|
145
|
+
child.on('exit', code => {
|
|
146
|
+
st.busy = null;
|
|
147
|
+
st.note = code === 0 ? green(`✓ ${spec.label} installed`) : fg(203, `✗ npm exited ${code}`);
|
|
148
|
+
resolve();
|
|
149
|
+
});
|
|
150
|
+
child.on('error', err => { st.busy = null; st.note = fg(203, `✗ ${err.message}`); resolve(); });
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
function logoutSelected(id) {
|
|
154
|
+
const spec = harness_registry_1.HARNESSES[id];
|
|
155
|
+
if (!spec.logout)
|
|
156
|
+
return `${spec.label} can only be logged out on the device`;
|
|
157
|
+
if (spec.logout.cmd) {
|
|
158
|
+
const bin = spec.findBinary();
|
|
159
|
+
if (!bin)
|
|
160
|
+
return `${spec.label} is not installed`;
|
|
161
|
+
const inv = (0, win_1.resolveCliInvocation)(bin, spec.logout.cmd);
|
|
162
|
+
const res = (0, child_process_1.spawnSync)(inv.file, inv.args, { stdio: 'ignore', env: process.env, timeout: 60_000 });
|
|
163
|
+
if (res.status !== 0)
|
|
164
|
+
return `logout exited with code ${res.status ?? '?'}`;
|
|
165
|
+
}
|
|
166
|
+
for (const rel of spec.logout.files ?? []) {
|
|
167
|
+
try {
|
|
168
|
+
fs.rmSync(path.join(os.homedir(), rel), { force: true });
|
|
169
|
+
}
|
|
170
|
+
catch { /* best effort */ }
|
|
171
|
+
}
|
|
172
|
+
for (const [rel, varName] of spec.logout.envStrip ?? []) {
|
|
173
|
+
const envPath = path.join(os.homedir(), rel);
|
|
174
|
+
try {
|
|
175
|
+
const body = fs.readFileSync(envPath, 'utf8');
|
|
176
|
+
fs.writeFileSync(envPath, body.split('\n').filter(l => !l.startsWith(`${varName}=`)).join('\n'), { mode: 0o600 });
|
|
177
|
+
}
|
|
178
|
+
catch { /* nothing to strip */ }
|
|
179
|
+
}
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
function loginSelected(id) {
|
|
183
|
+
const spec = harness_registry_1.HARNESSES[id];
|
|
184
|
+
const bin = spec.findBinary();
|
|
185
|
+
if (!bin)
|
|
186
|
+
return `${spec.label} is not installed`;
|
|
187
|
+
// Hand the real terminal to the CLI — leave the alt screen first.
|
|
188
|
+
altOff();
|
|
189
|
+
console.log(cyan(`\n— ${spec.label} login —`));
|
|
190
|
+
console.log(dim(spec.login.instructions) + '\n');
|
|
191
|
+
const args = spec.login.strategy === 'pty-url-code' ? (spec.login.loginArgs ?? []) : [];
|
|
192
|
+
const inv = (0, win_1.resolveCliInvocation)(bin, args);
|
|
193
|
+
const res = (0, child_process_1.spawnSync)(inv.file, inv.args, { stdio: 'inherit', env: process.env });
|
|
194
|
+
altOn();
|
|
195
|
+
return res.status === 0 ? null : `login exited with code ${res.status ?? '?'}`;
|
|
196
|
+
}
|
|
197
|
+
/* ── entry ───────────────────────────────────────────────────────────── */
|
|
198
|
+
async function plainStatus() {
|
|
199
|
+
const caps = await (0, capabilities_1.probeCapabilities)();
|
|
200
|
+
for (const id of harness_registry_1.HARNESS_IDS) {
|
|
201
|
+
const p = caps[id];
|
|
202
|
+
const state = !p?.available ? 'not installed' : p.authed ? `authed (${p.account ?? p.accountKind ?? '?'})` : 'not logged in';
|
|
203
|
+
console.log(`${id.padEnd(8)} ${(p?.version ?? '—').padEnd(12)} ${state}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
async function runHarnessesTui() {
|
|
207
|
+
if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
208
|
+
await plainStatus();
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const st = { caps: null, sel: 0, spin: 0, busy: null, log: [], note: null, confirmLogout: null };
|
|
212
|
+
const redraw = () => render(st);
|
|
213
|
+
altOn();
|
|
214
|
+
readline.emitKeypressEvents(process.stdin);
|
|
215
|
+
process.stdin.setRawMode(true);
|
|
216
|
+
process.stdin.resume();
|
|
217
|
+
const spinTimer = setInterval(() => { st.spin++; if (st.busy || !st.caps)
|
|
218
|
+
redraw(); }, 90);
|
|
219
|
+
const quit = () => {
|
|
220
|
+
clearInterval(spinTimer);
|
|
221
|
+
process.stdin.setRawMode(false);
|
|
222
|
+
process.stdin.pause();
|
|
223
|
+
altOff();
|
|
224
|
+
process.exit(0);
|
|
225
|
+
};
|
|
226
|
+
const reprobe = async () => {
|
|
227
|
+
st.caps = null;
|
|
228
|
+
redraw();
|
|
229
|
+
st.caps = await (0, capabilities_1.probeCapabilities)();
|
|
230
|
+
redraw();
|
|
231
|
+
};
|
|
232
|
+
let acting = false;
|
|
233
|
+
process.stdin.on('keypress', (_str, key) => {
|
|
234
|
+
void (async () => {
|
|
235
|
+
if (!key || acting)
|
|
236
|
+
return;
|
|
237
|
+
if (key.name === 'q' || (key.ctrl && key.name === 'c')) {
|
|
238
|
+
quit();
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (!st.caps)
|
|
242
|
+
return;
|
|
243
|
+
if (st.confirmLogout) {
|
|
244
|
+
const target = st.confirmLogout;
|
|
245
|
+
st.confirmLogout = null;
|
|
246
|
+
if (key.name === 'y') {
|
|
247
|
+
acting = true;
|
|
248
|
+
try {
|
|
249
|
+
const err = logoutSelected(target);
|
|
250
|
+
st.note = err ? fg(203, `✗ ${err}`) : green(`✓ ${harness_registry_1.HARNESSES[target].label} logged out`);
|
|
251
|
+
await reprobe();
|
|
252
|
+
}
|
|
253
|
+
finally {
|
|
254
|
+
acting = false;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
st.note = dim('logout cancelled');
|
|
259
|
+
redraw();
|
|
260
|
+
}
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (key.name === 'up' || key.name === 'k') {
|
|
264
|
+
st.sel = (st.sel + harness_registry_1.HARNESS_IDS.length - 1) % harness_registry_1.HARNESS_IDS.length;
|
|
265
|
+
redraw();
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (key.name === 'down' || key.name === 'j') {
|
|
269
|
+
st.sel = (st.sel + 1) % harness_registry_1.HARNESS_IDS.length;
|
|
270
|
+
redraw();
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (key.name === 'r') {
|
|
274
|
+
await reprobe();
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (key.name !== 'return')
|
|
278
|
+
return;
|
|
279
|
+
const id = harness_registry_1.HARNESS_IDS[st.sel];
|
|
280
|
+
const p = st.caps[id];
|
|
281
|
+
acting = true;
|
|
282
|
+
try {
|
|
283
|
+
if (!p?.available) {
|
|
284
|
+
await installSelected(st, id, redraw);
|
|
285
|
+
await reprobe();
|
|
286
|
+
}
|
|
287
|
+
else if (!p.authed) {
|
|
288
|
+
const err = loginSelected(id);
|
|
289
|
+
st.note = err ? fg(203, `✗ ${err}`) : green('✓ login flow finished — verifying…');
|
|
290
|
+
await reprobe();
|
|
291
|
+
const now = st.caps?.[id];
|
|
292
|
+
if (now?.authed)
|
|
293
|
+
st.note = green(`✓ ${harness_registry_1.HARNESSES[id].label} logged in as ${now.account ?? now.accountKind ?? '?'}`);
|
|
294
|
+
else if (!err)
|
|
295
|
+
st.note = yellow(`${harness_registry_1.HARNESSES[id].label} still reports unauthenticated`);
|
|
296
|
+
redraw();
|
|
297
|
+
}
|
|
298
|
+
else if (harness_registry_1.HARNESSES[id].logout) {
|
|
299
|
+
st.confirmLogout = id;
|
|
300
|
+
st.note = yellow(`log out of ${harness_registry_1.HARNESSES[id].label}? press y to confirm`);
|
|
301
|
+
redraw();
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
st.note = green(`${harness_registry_1.HARNESSES[id].label} is installed and logged in.`);
|
|
305
|
+
redraw();
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
finally {
|
|
309
|
+
acting = false;
|
|
310
|
+
}
|
|
311
|
+
})();
|
|
312
|
+
});
|
|
313
|
+
await reprobe();
|
|
314
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@addai/node",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Daemon that pairs a machine with your +Ai account and runs Claude / Codex / Kimi / Gemini agents on its behalf. Reachable via Supabase from Vault, Entity Studio, or any other +Ai surface.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|