@addai/node 0.5.0 → 0.5.1
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/memory-capture.js +10 -1
- package/dist/tui/dashboard.d.ts +11 -0
- package/dist/tui/dashboard.js +63 -26
- package/dist/tui/harnesses.js +11 -8
- package/dist/tui/logs.js +3 -3
- package/dist/tui/request-row.d.ts +6 -0
- package/dist/tui/request-row.js +19 -8
- package/dist/tui/requests.js +4 -4
- package/dist/tui.d.ts +1 -0
- package/dist/tui.js +314 -0
- package/package.json +1 -1
package/dist/memory-capture.js
CHANGED
|
@@ -19,7 +19,16 @@ function buildCapturePayload(req, userText, assistantText, transcriptRef) {
|
|
|
19
19
|
transcript_ref: transcriptRef,
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
|
-
|
|
22
|
+
// timeoutMs default is 15000 (not 5000): the entity-memory-capture edge fn
|
|
23
|
+
// synchronously summarizes each turn via an Anthropic Haiku call on its
|
|
24
|
+
// DEFAULT path, so its server-side latency is inherently ~3-5s (measured over
|
|
25
|
+
// 24h: p50 ~3.4s, p95 ~4.75s, p99 ~6.35s). A 5s abort clipped that long tail,
|
|
26
|
+
// surfacing "operation was aborted due to timeout" and dropping those turns'
|
|
27
|
+
// captures. postCapture is fire-and-forget (void, post-terminal — see
|
|
28
|
+
// session-runner notifyCaptureIfEnabled), so a longer timeout NEVER slows a
|
|
29
|
+
// run; it only lets slow/tail captures land. 15s clears observed p99 with 2x
|
|
30
|
+
// headroom for network/TLS.
|
|
31
|
+
async function postCapture(baseUrl, serviceToken, payload, timeoutMs = 15000) {
|
|
23
32
|
try {
|
|
24
33
|
const res = await fetch(`${baseUrl}/functions/v1/entity-memory-capture`, {
|
|
25
34
|
method: 'POST',
|
package/dist/tui/dashboard.d.ts
CHANGED
|
@@ -29,6 +29,17 @@ export declare const MENU: Array<{
|
|
|
29
29
|
/** Live rows the NOW band shows before it starts counting the rest. */
|
|
30
30
|
export declare const MAX_NOW_ROWS = 6;
|
|
31
31
|
export declare function liveRequests(rows: RequestRow[]): RequestRow[];
|
|
32
|
+
/**
|
|
33
|
+
* The node's own state, as a label.
|
|
34
|
+
*
|
|
35
|
+
* A daemon is running — we either started it or the lockfile proved it — so
|
|
36
|
+
* the node IS online, whatever the server has got round to saying. Waiting
|
|
37
|
+
* for `runtime_self` to come back before admitting that meant the first
|
|
38
|
+
* second of every session claimed the node was in an unknown state.
|
|
39
|
+
*/
|
|
40
|
+
export declare function stateLabel(st: DashboardState): string;
|
|
41
|
+
/** 1204 reads as a year; 1,204 reads as a count. */
|
|
42
|
+
export declare function fmtCount(n: number | null | undefined): string;
|
|
32
43
|
export declare function nowLines(st: DashboardState, width: number, rows: number): string[];
|
|
33
44
|
export declare function renderDashboard(st: DashboardState, width: number, height: number): string[];
|
|
34
45
|
export declare function createDashboardScreen(deps: {
|
package/dist/tui/dashboard.js
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.MAX_NOW_ROWS = exports.MENU = void 0;
|
|
10
10
|
exports.liveRequests = liveRequests;
|
|
11
|
+
exports.stateLabel = stateLabel;
|
|
12
|
+
exports.fmtCount = fmtCount;
|
|
11
13
|
exports.nowLines = nowLines;
|
|
12
14
|
exports.renderDashboard = renderDashboard;
|
|
13
15
|
exports.createDashboardScreen = createDashboardScreen;
|
|
@@ -16,51 +18,86 @@ const request_row_1 = require("./request-row");
|
|
|
16
18
|
const app_1 = require("./app");
|
|
17
19
|
/** The landing screen's menu. */
|
|
18
20
|
exports.MENU = [
|
|
19
|
-
{ key: 'activity', label: 'Activity', hint: '
|
|
20
|
-
{ key: 'harnesses', label: 'Harnesses', hint: '
|
|
21
|
-
{ key: 'logs', label: 'Logs', hint: '
|
|
21
|
+
{ key: 'activity', label: 'Activity', hint: 'Every request this node has handled', shortcut: 'a' },
|
|
22
|
+
{ key: 'harnesses', label: 'Harnesses', hint: 'Install / log in agent CLIs', shortcut: 'h' },
|
|
23
|
+
{ key: 'logs', label: 'Logs', hint: 'What the daemon is saying', shortcut: 'l' },
|
|
22
24
|
];
|
|
23
25
|
/** Live rows the NOW band shows before it starts counting the rest. */
|
|
24
26
|
exports.MAX_NOW_ROWS = 6;
|
|
25
27
|
function liveRequests(rows) {
|
|
26
28
|
return rows.filter(r => request_row_1.ACTIVE.has(r.status));
|
|
27
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* The node's own state, as a label.
|
|
32
|
+
*
|
|
33
|
+
* A daemon is running — we either started it or the lockfile proved it — so
|
|
34
|
+
* the node IS online, whatever the server has got round to saying. Waiting
|
|
35
|
+
* for `runtime_self` to come back before admitting that meant the first
|
|
36
|
+
* second of every session claimed the node was in an unknown state.
|
|
37
|
+
*/
|
|
38
|
+
function stateLabel(st) {
|
|
39
|
+
const reported = st.self?.effective_status;
|
|
40
|
+
if (!reported)
|
|
41
|
+
return (0, render_1.green)('Online');
|
|
42
|
+
if (reported === 'online')
|
|
43
|
+
return (0, render_1.green)('Online');
|
|
44
|
+
return (0, render_1.yellow)(reported.charAt(0).toUpperCase() + reported.slice(1));
|
|
45
|
+
}
|
|
28
46
|
function headerLines(st) {
|
|
29
47
|
if (!st.paired) {
|
|
30
48
|
return [
|
|
31
|
-
(0, render_1.yellow)('
|
|
49
|
+
(0, render_1.yellow)('Not paired'),
|
|
32
50
|
(0, render_1.dim)('run `ainode` and follow the prompt to vault.add.ai/entity/connect/<CODE>'),
|
|
33
51
|
];
|
|
34
52
|
}
|
|
35
53
|
const self = st.self;
|
|
36
|
-
const name = self?.name ?? self?.hostname ?? '
|
|
37
|
-
const state = self?.effective_status === 'online'
|
|
38
|
-
? (0, render_1.green)('online')
|
|
39
|
-
: (0, render_1.yellow)(self?.effective_status ?? 'unknown');
|
|
54
|
+
const name = self?.name ?? self?.hostname ?? 'This node';
|
|
40
55
|
const paired = self?.created_at ? `paired ${(0, render_1.fmtRelative)(self.created_at, st.now)}` : '';
|
|
41
|
-
const first = `${(0, render_1.bold)(name)} ${(0, render_1.dim)('·')} ${
|
|
56
|
+
const first = `${(0, render_1.bold)(name)} ${(0, render_1.dim)('·')} ${stateLabel(st)}${paired ? ` ${(0, render_1.dim)('·')} ${(0, render_1.dim)(paired)}` : ''}`;
|
|
42
57
|
const up = st.startedAt ? (0, render_1.fmtDuration)(st.now - st.startedAt) : '—';
|
|
43
58
|
const daemon = st.viewerMode
|
|
44
|
-
? `${(0, render_1.cyan)('⏺')}
|
|
45
|
-
: `${(0, render_1.green)('⏺')}
|
|
46
|
-
const chip = st.offline ? ` ${(0, render_1.yellow)('⚠
|
|
59
|
+
? `${(0, render_1.cyan)('⏺')} Viewer ${(0, render_1.dim)(`pid ${st.pid ?? '?'}`)} ${(0, render_1.dim)('· another process owns this node')}`
|
|
60
|
+
: `${(0, render_1.green)('⏺')} Running ${(0, render_1.dim)(`pid ${st.pid ?? '?'}`)} ${(0, render_1.dim)(`up ${up}`)} ${(0, render_1.dim)(`${st.inflight} in flight`)}`;
|
|
61
|
+
const chip = st.offline ? ` ${(0, render_1.yellow)('⚠ Offline · retrying')}` : '';
|
|
47
62
|
return [first, daemon + chip];
|
|
48
63
|
}
|
|
64
|
+
/** 1204 reads as a year; 1,204 reads as a count. */
|
|
65
|
+
function fmtCount(n) {
|
|
66
|
+
if (n == null)
|
|
67
|
+
return '—';
|
|
68
|
+
return String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
69
|
+
}
|
|
70
|
+
/** A labelled figure, padded so the labels line up down the screen. */
|
|
71
|
+
function stat(label, value, width = 14) {
|
|
72
|
+
return (0, render_1.padEndVisible)(`${(0, render_1.dim)(label)} ${value}`, width);
|
|
73
|
+
}
|
|
49
74
|
function statsLines(st) {
|
|
50
75
|
const s = st.stats;
|
|
51
|
-
|
|
52
|
-
|
|
76
|
+
// Keep the shape while the first fetch is in flight, so the band doesn't
|
|
77
|
+
// pop into place a second after the header.
|
|
78
|
+
if (!s) {
|
|
79
|
+
return [
|
|
80
|
+
`${stat('Today', (0, render_1.dim)('—'))}${stat('24h', (0, render_1.dim)('—'), 26)}${stat('Avg', (0, render_1.dim)('—'))}`,
|
|
81
|
+
`${stat('Week', (0, render_1.dim)('—'))}${stat('Month', (0, render_1.dim)('—'), 26)}${stat('Total', (0, render_1.dim)('—'))}`,
|
|
82
|
+
];
|
|
83
|
+
}
|
|
53
84
|
const agents = Object.entries(s.by_agent)
|
|
54
85
|
.sort((a, b) => b[1] - a[1])
|
|
55
|
-
.map(([k, v]) => `${k} ${v}`)
|
|
56
|
-
.join(' · ');
|
|
57
|
-
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
|
|
86
|
+
.map(([k, v]) => `${k.charAt(0).toUpperCase()}${k.slice(1)} ${fmtCount(v)}`)
|
|
87
|
+
.join((0, render_1.dim)(' · '));
|
|
88
|
+
// The ✓/✗ figures are a rolling 24h window while "Today" is the calendar
|
|
89
|
+
// day, so the window is labelled — otherwise ✓3 next to Today 2 looks
|
|
90
|
+
// like an arithmetic bug.
|
|
91
|
+
const outcomes = [
|
|
92
|
+
(0, render_1.green)(`✓ ${fmtCount(s.succeeded_24h)}`),
|
|
93
|
+
s.failed_24h ? (0, render_1.red)(`✗ ${fmtCount(s.failed_24h)}`) : (0, render_1.dim)('✗ 0'),
|
|
94
|
+
s.canceled_24h ? (0, render_1.grey)(`⊘ ${fmtCount(s.canceled_24h)}`) : '',
|
|
95
|
+
].filter(Boolean).join(' ');
|
|
96
|
+
// Both rows share one three-column grid, so Month sits under 24h and
|
|
97
|
+
// Total under Avg rather than the two rows drifting apart.
|
|
61
98
|
return [
|
|
62
|
-
`${(0, render_1.bold)(
|
|
63
|
-
(
|
|
99
|
+
`${stat('Today', (0, render_1.bold)(fmtCount(s.today)))}${stat('24h', outcomes, 26)}${stat('Avg', (0, render_1.fmtDuration)(s.avg_duration_ms))}`,
|
|
100
|
+
`${stat('Week', fmtCount(s.week))}${stat('Month', fmtCount(s.month), 26)}${stat('Total', fmtCount(s.total))}${(0, render_1.dim)(agents)}`,
|
|
64
101
|
];
|
|
65
102
|
}
|
|
66
103
|
/** The most recent request that actually finished, for the idle line. */
|
|
@@ -69,12 +106,12 @@ function lastFinished(rows) {
|
|
|
69
106
|
}
|
|
70
107
|
function nowLines(st, width, rows) {
|
|
71
108
|
const live = liveRequests(st.recent);
|
|
72
|
-
const out = [` ${(0, render_1.bold)('
|
|
109
|
+
const out = [` ${(0, render_1.bold)('Now')}`];
|
|
73
110
|
if (live.length === 0) {
|
|
74
111
|
const last = lastFinished(st.recent);
|
|
75
112
|
out.push(last
|
|
76
|
-
? (0, render_1.dim)(`
|
|
77
|
-
: (0, render_1.dim)('
|
|
113
|
+
? (0, render_1.dim)(` Idle — last run finished ${(0, render_1.fmtRelative)(last.finished_at, st.now)}`)
|
|
114
|
+
: (0, render_1.dim)(' Idle'));
|
|
78
115
|
return out;
|
|
79
116
|
}
|
|
80
117
|
out.push((0, request_row_1.requestHeader)(width));
|
|
@@ -86,7 +123,7 @@ function nowLines(st, width, rows) {
|
|
|
86
123
|
return out;
|
|
87
124
|
}
|
|
88
125
|
function renderDashboard(st, width, height) {
|
|
89
|
-
const head = (0, render_1.panel)(`+Ai Node ${(0, render_1.dim)(
|
|
126
|
+
const head = (0, render_1.panel)(`+Ai Node ${(0, render_1.dim)(`@addai/node v${st.version}`)}`, headerLines(st), width);
|
|
90
127
|
if (!st.paired) {
|
|
91
128
|
return [...head, '', (0, app_1.footerHint)([{ keys: 'q', label: 'quit' }])].slice(0, height);
|
|
92
129
|
}
|
package/dist/tui/harnesses.js
CHANGED
|
@@ -89,10 +89,10 @@ function formatVersion(v) {
|
|
|
89
89
|
}
|
|
90
90
|
function accountLabel(p) {
|
|
91
91
|
if (!p?.available)
|
|
92
|
-
return '
|
|
92
|
+
return 'Not installed';
|
|
93
93
|
if (!p.authed)
|
|
94
|
-
return '
|
|
95
|
-
const who = p.account ?? p.accountKind ?? '
|
|
94
|
+
return 'Not logged in';
|
|
95
|
+
const who = p.account ?? p.accountKind ?? 'Logged in';
|
|
96
96
|
return p.plan ? `${who} · ${p.plan}` : who;
|
|
97
97
|
}
|
|
98
98
|
/**
|
|
@@ -120,7 +120,10 @@ function harnessRow(id, p, selected, width) {
|
|
|
120
120
|
const spec = harness_registry_1.HARNESSES[id];
|
|
121
121
|
const w = harnessColumns(width);
|
|
122
122
|
const act = actionFor(id, p);
|
|
123
|
-
|
|
123
|
+
// The verb is Title Case in the row the same way every other label is; the
|
|
124
|
+
// value actionFor returns stays lowercase because the key handler reads it.
|
|
125
|
+
const verb = act.charAt(0).toUpperCase() + act.slice(1);
|
|
126
|
+
const action = act === 'ok' ? (0, render_1.green)('✓ Ready') : act === 'log out' ? (0, render_1.dim)(`⏎ ${verb}`) : (0, render_1.cyan)(`⏎ ${verb}`);
|
|
124
127
|
const line = (0, render_1.tableRow)([
|
|
125
128
|
dot(p),
|
|
126
129
|
selected ? (0, render_1.bold)(spec.label) : spec.label,
|
|
@@ -135,14 +138,14 @@ function harnessRow(id, p, selected, width) {
|
|
|
135
138
|
function renderHarnesses(st, width, height) {
|
|
136
139
|
const out = [];
|
|
137
140
|
const authed = st.caps ? harness_registry_1.HARNESS_IDS.filter(id => st.caps?.[id]?.authed).length : 0;
|
|
138
|
-
out.push((0, app_1.heading)('
|
|
141
|
+
out.push((0, app_1.heading)('Harnesses', st.caps ? `${authed}/${harness_registry_1.HARNESS_IDS.length} ready` : 'Probing…'));
|
|
139
142
|
out.push('');
|
|
140
143
|
if (!st.caps) {
|
|
141
|
-
out.push(` ${(0, render_1.cyan)(render_1.SPIN[st.spin % render_1.SPIN.length])} ${(0, render_1.dim)('
|
|
144
|
+
out.push(` ${(0, render_1.cyan)(render_1.SPIN[st.spin % render_1.SPIN.length])} ${(0, render_1.dim)('Probing installed harnesses…')}`);
|
|
142
145
|
}
|
|
143
146
|
else {
|
|
144
147
|
const w = harnessColumns(width);
|
|
145
|
-
out.push((0, render_1.dim)(' ' + (0, render_1.tableRow)(['', '
|
|
148
|
+
out.push((0, render_1.dim)(' ' + (0, render_1.tableRow)(['', 'Harness', 'Version', 'Account', 'Models', 'Efforts', ''], w)));
|
|
146
149
|
harness_registry_1.HARNESS_IDS.forEach((id, i) => out.push(harnessRow(id, st.caps?.[id], i === st.sel, width)));
|
|
147
150
|
}
|
|
148
151
|
if (st.busy)
|
|
@@ -353,7 +356,7 @@ async function plainStatus() {
|
|
|
353
356
|
const caps = await (0, capabilities_1.probeCapabilities)();
|
|
354
357
|
for (const id of harness_registry_1.HARNESS_IDS) {
|
|
355
358
|
const p = caps[id];
|
|
356
|
-
const state = !p?.available ? '
|
|
359
|
+
const state = !p?.available ? 'Not installed' : p.authed ? `authed (${p.account ?? p.accountKind ?? '?'})` : 'Not logged in';
|
|
357
360
|
console.log(`${id.padEnd(8)} ${(p?.version ?? '—').padEnd(12)} ${state}`);
|
|
358
361
|
}
|
|
359
362
|
}
|
package/dist/tui/logs.js
CHANGED
|
@@ -40,13 +40,13 @@ function renderLogs(st, lines, width, height, emptyHint) {
|
|
|
40
40
|
const scope = st.filter
|
|
41
41
|
? `${shown.length} matching “${st.filter}”`
|
|
42
42
|
: `${shown.length} lines this session`;
|
|
43
|
-
out.push((0, app_1.heading)('
|
|
43
|
+
out.push((0, app_1.heading)('Logs', scope));
|
|
44
44
|
out.push('');
|
|
45
45
|
if (shown.length === 0) {
|
|
46
46
|
if (st.filter)
|
|
47
|
-
out.push((0, render_1.dim)('
|
|
47
|
+
out.push((0, render_1.dim)(' Nothing matches that filter'));
|
|
48
48
|
else
|
|
49
|
-
out.push((0, render_1.dim)(` ${emptyHint ?? '
|
|
49
|
+
out.push((0, render_1.dim)(` ${emptyHint ?? 'The daemon has said nothing yet'}`));
|
|
50
50
|
}
|
|
51
51
|
else {
|
|
52
52
|
const maxScroll = Math.max(0, shown.length - rows);
|
|
@@ -10,6 +10,12 @@ 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. */
|
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))
|
|
@@ -91,9 +102,9 @@ function requestColumns(width) {
|
|
|
91
102
|
// marker, status, entity, agent/mode, model, via, duration, prompt
|
|
92
103
|
return (0, render_1.layoutColumns)([
|
|
93
104
|
{ min: 1 },
|
|
94
|
-
//
|
|
105
|
+
// 'Cancelled' is the longest label; anything shorter than it clips the
|
|
95
106
|
// status, which is the one column that must never be ambiguous.
|
|
96
|
-
{ min:
|
|
107
|
+
{ min: 9 },
|
|
97
108
|
{ min: 10, grow: 1 },
|
|
98
109
|
{ min: 12 },
|
|
99
110
|
// 'haiku-4-5' is nine columns once the claude- prefix is stripped.
|
|
@@ -106,7 +117,7 @@ function requestColumns(width) {
|
|
|
106
117
|
const ALIGN = ['left', 'left', 'left', 'left', 'left', 'left', 'right', 'left'];
|
|
107
118
|
function requestHeader(width) {
|
|
108
119
|
const w = requestColumns(width);
|
|
109
|
-
return (0, render_1.dim)(' ' + (0, render_1.tableRow)(['', '
|
|
120
|
+
return (0, render_1.dim)(' ' + (0, render_1.tableRow)(['', 'Status', 'Entity', 'Agent', 'Model', 'Via', 'Dur', 'Prompt'], w, ALIGN));
|
|
110
121
|
}
|
|
111
122
|
function requestLine(r, now, selected, width, spin = 0) {
|
|
112
123
|
const w = requestColumns(width);
|
|
@@ -123,7 +134,7 @@ function requestLine(r, now, selected, width, spin = 0) {
|
|
|
123
134
|
colour(statusLabel(r.status)),
|
|
124
135
|
r.entity_name ?? '—',
|
|
125
136
|
(0, render_1.dim)(`${r.agent}/${r.mode}`),
|
|
126
|
-
(0, render_1.dim)((r.model
|
|
137
|
+
(0, render_1.dim)(shortModel(r.model)),
|
|
127
138
|
(0, render_1.dim)(shortVia(r.issued_via)),
|
|
128
139
|
(0, render_1.dim)((0, render_1.fmtDuration)(durationOf(r, now))),
|
|
129
140
|
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.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.5.
|
|
3
|
+
"version": "0.5.1",
|
|
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": [
|