@openagents-org/agent-launcher 0.1.2 → 0.1.3
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/package.json +1 -1
- package/src/tui.js +159 -337
package/package.json
CHANGED
package/src/tui.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Interactive TUI dashboard for OpenAgents — `openagents` or `openagents tui`
|
|
3
|
-
*
|
|
4
|
-
* Ported from Python Textual TUI (cli_tui.py). Uses blessed for terminal UI.
|
|
5
3
|
*/
|
|
6
4
|
|
|
7
5
|
'use strict';
|
|
@@ -13,8 +11,6 @@ const fs = require('fs');
|
|
|
13
11
|
const { spawn } = require('child_process');
|
|
14
12
|
const { getExtraBinDirs } = require('./paths');
|
|
15
13
|
|
|
16
|
-
// ── Helpers ──────────────────────────────────────────────────────────────
|
|
17
|
-
|
|
18
14
|
const IS_WINDOWS = process.platform === 'win32';
|
|
19
15
|
|
|
20
16
|
function getConnector() {
|
|
@@ -28,7 +24,6 @@ function loadAgentRows(connector) {
|
|
|
28
24
|
const status = connector.getDaemonStatus() || {};
|
|
29
25
|
const agentStatuses = status.agents || {};
|
|
30
26
|
const pid = connector.getDaemonPid();
|
|
31
|
-
|
|
32
27
|
return agents.map(agent => {
|
|
33
28
|
const info = agentStatuses[agent.name] || {};
|
|
34
29
|
const state = pid ? (info.state || 'stopped') : 'stopped';
|
|
@@ -36,182 +31,124 @@ function loadAgentRows(connector) {
|
|
|
36
31
|
if (agent.network) {
|
|
37
32
|
const nets = config.networks || [];
|
|
38
33
|
const net = nets.find(n => n.slug === agent.network || n.id === agent.network);
|
|
39
|
-
|
|
40
|
-
workspace = `${net.slug || net.id} (${net.name || ''})`;
|
|
41
|
-
} else {
|
|
42
|
-
workspace = agent.network;
|
|
43
|
-
}
|
|
34
|
+
workspace = net ? `${net.slug || net.id} (${net.name || ''})` : agent.network;
|
|
44
35
|
}
|
|
45
|
-
return { name: agent.name, type: agent.type, state, workspace
|
|
36
|
+
return { name: agent.name, type: agent.type, state, workspace };
|
|
46
37
|
});
|
|
47
38
|
}
|
|
48
39
|
|
|
49
40
|
function loadCatalog(connector) {
|
|
50
41
|
const registry = connector.getRegistry();
|
|
51
|
-
|
|
52
|
-
return entries.map(e => {
|
|
42
|
+
return registry.list().map(e => {
|
|
53
43
|
let installed = false;
|
|
54
|
-
try {
|
|
55
|
-
const { whichBinary } = require('./paths');
|
|
56
|
-
installed = !!whichBinary(e.install?.binary || e.name);
|
|
57
|
-
} catch {}
|
|
44
|
+
try { const { whichBinary } = require('./paths'); installed = !!whichBinary(e.install?.binary || e.name); } catch {}
|
|
58
45
|
if (!installed) {
|
|
59
46
|
try {
|
|
60
|
-
const
|
|
61
|
-
if (fs.existsSync(
|
|
62
|
-
const markers = JSON.parse(fs.readFileSync(markerFile, 'utf-8'));
|
|
63
|
-
installed = !!markers[e.name];
|
|
64
|
-
}
|
|
47
|
+
const f = path.join(connector.config?.configDir || '', 'installed_agents.json');
|
|
48
|
+
if (fs.existsSync(f)) installed = !!JSON.parse(fs.readFileSync(f, 'utf-8'))[e.name];
|
|
65
49
|
} catch {}
|
|
66
50
|
}
|
|
67
|
-
return {
|
|
68
|
-
name: e.name,
|
|
69
|
-
label: e.label || e.name,
|
|
70
|
-
description: e.description || '',
|
|
71
|
-
installed,
|
|
72
|
-
};
|
|
51
|
+
return { name: e.name, label: e.label || e.name, description: e.description || '', installed };
|
|
73
52
|
});
|
|
74
53
|
}
|
|
75
54
|
|
|
76
|
-
const STATE_STYLES = {
|
|
77
|
-
online: { sym: '●', color: 'green', label: 'running' },
|
|
78
|
-
running: { sym: '●', color: 'green', label: 'running' },
|
|
79
|
-
starting: { sym: '◐', color: 'yellow', label: 'starting' },
|
|
80
|
-
reconnecting: { sym: '◐', color: 'yellow', label: 'reconnecting' },
|
|
81
|
-
stopped: { sym: '○', color: 'gray', label: 'stopped' },
|
|
82
|
-
error: { sym: '✗', color: 'red', label: 'error' },
|
|
83
|
-
'not configured': { sym: '○', color: 'gray', label: 'not configured' },
|
|
84
|
-
};
|
|
85
|
-
|
|
86
55
|
// ── Main TUI ─────────────────────────────────────────────────────────────
|
|
87
56
|
|
|
88
57
|
function createTUI() {
|
|
89
|
-
const screen = blessed.screen({
|
|
90
|
-
smartCSR: true,
|
|
91
|
-
title: 'OpenAgents',
|
|
92
|
-
fullUnicode: true,
|
|
93
|
-
});
|
|
94
|
-
|
|
58
|
+
const screen = blessed.screen({ smartCSR: true, title: 'OpenAgents', fullUnicode: true });
|
|
95
59
|
const connector = getConnector();
|
|
96
60
|
let pkg;
|
|
97
61
|
try { pkg = require('../package.json'); } catch { pkg = { version: '?' }; }
|
|
98
62
|
|
|
99
|
-
// ──
|
|
100
|
-
|
|
101
|
-
// Header bar
|
|
63
|
+
// ── Header ──
|
|
102
64
|
const header = blessed.box({
|
|
103
65
|
top: 0, left: 0, width: '100%', height: 1,
|
|
104
|
-
tags: true,
|
|
105
66
|
style: { bg: 'blue', fg: 'white', bold: true },
|
|
106
67
|
});
|
|
107
68
|
|
|
108
|
-
// Title
|
|
69
|
+
// ── Title ──
|
|
109
70
|
const titleBox = blessed.box({
|
|
110
|
-
top: 1, left: 0, width: '100%', height:
|
|
111
|
-
|
|
112
|
-
|
|
71
|
+
top: 1, left: 0, width: '100%', height: 2,
|
|
72
|
+
content: ` OpenAgents v${pkg.version}`,
|
|
73
|
+
style: { bold: true },
|
|
113
74
|
});
|
|
114
75
|
|
|
115
|
-
// Column
|
|
76
|
+
// ── Column Headers ──
|
|
116
77
|
const colHeaders = blessed.box({
|
|
117
|
-
top:
|
|
118
|
-
tags: true,
|
|
78
|
+
top: 3, left: 0, width: '100%', height: 1,
|
|
119
79
|
style: { bg: 'white', fg: 'black' },
|
|
120
80
|
content: ` ${'NAME'.padEnd(22)} ${'TYPE'.padEnd(14)} ${'STATUS'.padEnd(14)} WORKSPACE`,
|
|
121
81
|
});
|
|
122
82
|
|
|
123
|
-
// Agent
|
|
83
|
+
// ── Agent List ──
|
|
124
84
|
const agentList = blessed.list({
|
|
125
|
-
top:
|
|
126
|
-
|
|
127
|
-
border: { type: 'line', left: false, right: false, top: false },
|
|
85
|
+
top: 4, left: 0, width: '100%', height: '50%-1',
|
|
86
|
+
keys: true, vi: true, mouse: true,
|
|
128
87
|
style: {
|
|
129
|
-
selected: { bg: 'blue', fg: 'white'
|
|
88
|
+
selected: { bg: 'blue', fg: 'white' },
|
|
130
89
|
item: { fg: 'white' },
|
|
131
|
-
border: { fg: 'gray' },
|
|
132
90
|
},
|
|
133
91
|
});
|
|
134
92
|
|
|
135
|
-
//
|
|
136
|
-
const separator = blessed.line({
|
|
137
|
-
top: '50%+3', left: 0, width: '100%',
|
|
138
|
-
orientation: 'horizontal',
|
|
139
|
-
style: { fg: 'gray' },
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
// Activity log
|
|
93
|
+
// ── Activity Section ──
|
|
143
94
|
const logLabel = blessed.box({
|
|
144
|
-
top: '50%+
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
style: { fg: 'white' },
|
|
95
|
+
top: '50%+3', left: 0, width: '100%', height: 1,
|
|
96
|
+
content: ' ACTIVITY',
|
|
97
|
+
style: { bg: 'white', fg: 'black' },
|
|
148
98
|
});
|
|
149
99
|
|
|
150
100
|
const logContent = blessed.log({
|
|
151
|
-
top: '50%+
|
|
152
|
-
|
|
153
|
-
scrollable: true,
|
|
154
|
-
scrollOnInput: true,
|
|
101
|
+
top: '50%+4', left: 0, width: '100%', height: '50%-7',
|
|
102
|
+
scrollable: true, scrollOnInput: true,
|
|
155
103
|
padding: { left: 2 },
|
|
156
|
-
style: { fg: '
|
|
104
|
+
style: { fg: 'white' },
|
|
157
105
|
});
|
|
158
106
|
|
|
159
|
-
// Footer
|
|
107
|
+
// ── Footer ──
|
|
160
108
|
const footer = blessed.box({
|
|
161
109
|
bottom: 0, left: 0, width: '100%', height: 1,
|
|
162
|
-
tags: true,
|
|
163
110
|
style: { bg: 'blue', fg: 'white' },
|
|
111
|
+
content: ' i Install n New s Start x Stop c Connect u Daemon r Refresh q Quit',
|
|
164
112
|
});
|
|
165
113
|
|
|
166
114
|
screen.append(header);
|
|
167
115
|
screen.append(titleBox);
|
|
168
116
|
screen.append(colHeaders);
|
|
169
117
|
screen.append(agentList);
|
|
170
|
-
screen.append(separator);
|
|
171
118
|
screen.append(logLabel);
|
|
172
119
|
screen.append(logContent);
|
|
173
120
|
screen.append(footer);
|
|
174
121
|
|
|
175
|
-
// ── State ──
|
|
176
|
-
|
|
177
122
|
let agentRows = [];
|
|
178
123
|
let currentView = 'main';
|
|
179
124
|
|
|
180
125
|
function log(msg) {
|
|
181
126
|
const ts = new Date().toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
182
|
-
logContent.log(
|
|
127
|
+
logContent.log(`${ts} ${msg}`);
|
|
183
128
|
screen.render();
|
|
184
129
|
}
|
|
185
130
|
|
|
186
|
-
// ── Refresh ──
|
|
187
|
-
|
|
188
131
|
function refreshAgentTable() {
|
|
189
|
-
try {
|
|
190
|
-
agentRows = loadAgentRows(connector);
|
|
191
|
-
} catch { agentRows = []; }
|
|
132
|
+
try { agentRows = loadAgentRows(connector); } catch { agentRows = []; }
|
|
192
133
|
|
|
193
134
|
const items = agentRows.length ? agentRows.map(r => {
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
135
|
+
const sym = r.state === 'running' || r.state === 'online' ? '\u25CF' :
|
|
136
|
+
r.state === 'error' ? '\u2717' : '\u25CB';
|
|
137
|
+
const ws = r.workspace || '-';
|
|
138
|
+
return ` ${sym} ${r.name.padEnd(20)} ${r.type.padEnd(14)} ${r.state.padEnd(14)} ${ws}`;
|
|
139
|
+
}) : [' No agents configured. Press i to install, n to create.'];
|
|
198
140
|
|
|
199
141
|
agentList.setItems(items);
|
|
200
142
|
updateHeader();
|
|
201
|
-
updateFooter();
|
|
202
143
|
screen.render();
|
|
203
144
|
}
|
|
204
145
|
|
|
205
146
|
function updateHeader() {
|
|
206
147
|
const pid = connector.getDaemonPid();
|
|
207
|
-
const dot = pid ? '
|
|
148
|
+
const dot = pid ? '\u25CF' : '\u25CB';
|
|
208
149
|
const state = pid ? 'Daemon running' : 'Daemon idle';
|
|
209
150
|
const count = agentRows.length;
|
|
210
|
-
header.setContent(` ${dot} ${state}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function updateFooter() {
|
|
214
|
-
footer.setContent(' {bold}i{/bold} Install {bold}n{/bold} New {bold}s{/bold} Start {bold}x{/bold} Stop {bold}c{/bold} Connect {bold}u{/bold} Daemon {bold}r{/bold} Refresh {bold}q{/bold} Quit');
|
|
151
|
+
header.setContent(` ${dot} ${state} | ${count} agent${count !== 1 ? 's' : ''} configured`);
|
|
215
152
|
}
|
|
216
153
|
|
|
217
154
|
// ── Install Screen ──
|
|
@@ -219,275 +156,199 @@ function createTUI() {
|
|
|
219
156
|
function showInstallScreen() {
|
|
220
157
|
currentView = 'install';
|
|
221
158
|
let catalog;
|
|
222
|
-
try {
|
|
223
|
-
catalog = loadCatalog(connector);
|
|
224
|
-
} catch (e) {
|
|
225
|
-
log(`{red-fg}Error loading catalog: ${e.message}{/red-fg}`);
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
159
|
+
try { catalog = loadCatalog(connector); } catch (e) { log('Error: ' + e.message); return; }
|
|
228
160
|
|
|
229
|
-
const
|
|
230
|
-
top: 0, left: 0, width: '100%', height: '100%',
|
|
231
|
-
tags: true,
|
|
232
|
-
});
|
|
161
|
+
const box = blessed.box({ top: 0, left: 0, width: '100%', height: '100%' });
|
|
233
162
|
|
|
234
|
-
// Header
|
|
235
|
-
const installHeader = blessed.box({
|
|
236
|
-
parent: installBox,
|
|
237
|
-
top: 0, left: 0, width: '100%', height: 1,
|
|
238
|
-
tags: true,
|
|
239
|
-
style: { bg: 'blue', fg: 'white', bold: true },
|
|
240
|
-
content: ' Install Agent Runtimes',
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
// Subtitle
|
|
244
163
|
blessed.box({
|
|
245
|
-
parent:
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
content: '\n {gray-fg}Select a runtime and press Enter to install or update.{/gray-fg}',
|
|
164
|
+
parent: box, top: 0, left: 0, width: '100%', height: 1,
|
|
165
|
+
style: { bg: 'blue', fg: 'white', bold: true },
|
|
166
|
+
content: ' Install Agent Runtimes (Enter = install, Esc = back)',
|
|
249
167
|
});
|
|
250
168
|
|
|
251
|
-
// Column headers
|
|
252
169
|
blessed.box({
|
|
253
|
-
parent:
|
|
254
|
-
top: 3, left: 0, width: '100%', height: 1,
|
|
255
|
-
tags: true,
|
|
170
|
+
parent: box, top: 1, left: 0, width: '100%', height: 1,
|
|
256
171
|
style: { bg: 'white', fg: 'black' },
|
|
257
172
|
content: ` ${'AGENT'.padEnd(25)} ${'STATUS'.padEnd(16)} DESCRIPTION`,
|
|
258
173
|
});
|
|
259
174
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
top: 4, left: 0, width: '100%', height: '100%-7',
|
|
264
|
-
tags: true, keys: true, vi: true, mouse: true,
|
|
175
|
+
const list = blessed.list({
|
|
176
|
+
parent: box, top: 2, left: 0, width: '100%', height: '100%-4',
|
|
177
|
+
keys: true, vi: true, mouse: true,
|
|
265
178
|
style: {
|
|
266
|
-
selected: { bg: 'blue', fg: 'white'
|
|
179
|
+
selected: { bg: 'blue', fg: 'white' },
|
|
267
180
|
item: { fg: 'white' },
|
|
268
181
|
},
|
|
269
182
|
});
|
|
270
183
|
|
|
271
|
-
// Status bar at bottom
|
|
272
184
|
const statusBar = blessed.box({
|
|
273
|
-
parent:
|
|
274
|
-
bottom: 1, left: 0, width: '100%', height: 1,
|
|
275
|
-
tags: true,
|
|
276
|
-
content: '',
|
|
185
|
+
parent: box, bottom: 1, left: 0, width: '100%', height: 1,
|
|
277
186
|
});
|
|
278
187
|
|
|
279
|
-
// Footer
|
|
280
188
|
blessed.box({
|
|
281
|
-
parent:
|
|
282
|
-
bottom: 0, left: 0, width: '100%', height: 1,
|
|
283
|
-
tags: true,
|
|
189
|
+
parent: box, bottom: 0, left: 0, width: '100%', height: 1,
|
|
284
190
|
style: { bg: 'blue', fg: 'white' },
|
|
285
|
-
content: '
|
|
191
|
+
content: ' Enter Install/Update Esc Back',
|
|
286
192
|
});
|
|
287
193
|
|
|
288
|
-
function
|
|
289
|
-
|
|
290
|
-
const st = e.installed
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
return ` ${e.label.padEnd(25)} ${st} ${desc}`;
|
|
295
|
-
});
|
|
296
|
-
installList.setItems(items);
|
|
194
|
+
function renderList() {
|
|
195
|
+
list.setItems(catalog.map(e => {
|
|
196
|
+
const st = e.installed ? '\u25CF installed' : '\u25CB available';
|
|
197
|
+
const desc = e.description ? e.description.substring(0, 40) : '';
|
|
198
|
+
return ` ${e.label.padEnd(25)} ${st.padEnd(16)} ${desc}`;
|
|
199
|
+
}));
|
|
297
200
|
}
|
|
201
|
+
renderList();
|
|
202
|
+
list.focus();
|
|
298
203
|
|
|
299
|
-
|
|
300
|
-
installList.focus();
|
|
301
|
-
|
|
302
|
-
installList.on('select', (_item, idx) => {
|
|
204
|
+
list.on('select', (_item, idx) => {
|
|
303
205
|
const entry = catalog[idx];
|
|
304
206
|
if (!entry) return;
|
|
305
|
-
|
|
306
207
|
const verb = entry.installed ? 'Update' : 'Install';
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
top: 'center', left: 'center',
|
|
310
|
-
width:
|
|
208
|
+
|
|
209
|
+
const dialog = blessed.box({
|
|
210
|
+
parent: box, top: 'center', left: 'center',
|
|
211
|
+
width: 50, height: 5,
|
|
311
212
|
border: { type: 'line' },
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
style: { bg: 'black', fg: 'white', border: { fg: 'cyan' }, label: { fg: 'cyan', bold: true } },
|
|
213
|
+
style: { border: { fg: 'cyan' } },
|
|
214
|
+
content: `\n ${verb} ${entry.label}? (y = yes, n = no)`,
|
|
315
215
|
});
|
|
216
|
+
screen.render();
|
|
316
217
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
218
|
+
const onKey = (ch) => {
|
|
219
|
+
screen.unkey(['y', 'n', 'escape'], onKey);
|
|
220
|
+
dialog.destroy();
|
|
221
|
+
if (ch === 'y') {
|
|
222
|
+
doInstall(entry, statusBar, list, catalog, renderList);
|
|
223
|
+
} else {
|
|
224
|
+
list.focus();
|
|
225
|
+
}
|
|
226
|
+
screen.render();
|
|
227
|
+
};
|
|
228
|
+
screen.key(['y', 'n', 'escape'], onKey);
|
|
322
229
|
});
|
|
323
230
|
|
|
324
|
-
|
|
325
|
-
screen.remove(
|
|
326
|
-
|
|
231
|
+
list.key('escape', () => {
|
|
232
|
+
screen.remove(box);
|
|
233
|
+
box.destroy();
|
|
327
234
|
currentView = 'main';
|
|
328
235
|
agentList.focus();
|
|
329
236
|
refreshAgentTable();
|
|
330
237
|
});
|
|
331
238
|
|
|
332
|
-
screen.append(
|
|
333
|
-
|
|
239
|
+
screen.append(box);
|
|
240
|
+
list.focus();
|
|
334
241
|
screen.render();
|
|
335
242
|
}
|
|
336
243
|
|
|
337
|
-
function doInstall(entry, statusBar,
|
|
338
|
-
statusBar.setContent(`
|
|
244
|
+
function doInstall(entry, statusBar, list, catalog, renderList) {
|
|
245
|
+
statusBar.setContent(` Installing ${entry.name}...`);
|
|
339
246
|
screen.render();
|
|
340
247
|
|
|
341
248
|
const installer = connector.getInstaller();
|
|
342
|
-
const
|
|
343
|
-
if (!
|
|
344
|
-
statusBar.setContent(`
|
|
249
|
+
const cmd = installer._resolveInstallCommand(entry.name);
|
|
250
|
+
if (!cmd) {
|
|
251
|
+
statusBar.setContent(` Error: No install command for ${entry.name}`);
|
|
345
252
|
screen.render();
|
|
346
|
-
|
|
253
|
+
list.focus();
|
|
347
254
|
return;
|
|
348
255
|
}
|
|
349
256
|
|
|
350
|
-
log(
|
|
351
|
-
statusBar.setContent(
|
|
257
|
+
log('$ ' + cmd);
|
|
258
|
+
statusBar.setContent(' Running: ' + cmd.substring(0, 70));
|
|
352
259
|
screen.render();
|
|
353
260
|
|
|
354
|
-
const env = { ...process.env };
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
const extraDirs = getExtraBinDirs();
|
|
359
|
-
if (extraDirs.length) {
|
|
360
|
-
const sep = IS_WINDOWS ? ';' : ':';
|
|
361
|
-
env.PATH = extraDirs.join(sep) + sep + (env.PATH || '');
|
|
261
|
+
const env = { ...process.env, npm_config_yes: 'true', CI: '1' };
|
|
262
|
+
const extra = getExtraBinDirs();
|
|
263
|
+
if (extra.length) {
|
|
264
|
+
env.PATH = extra.join(IS_WINDOWS ? ';' : ':') + (IS_WINDOWS ? ';' : ':') + (env.PATH || '');
|
|
362
265
|
}
|
|
363
266
|
|
|
364
|
-
const proc = spawn(
|
|
365
|
-
|
|
366
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
367
|
-
});
|
|
267
|
+
const proc = spawn(cmd, [], { shell: true, env, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
268
|
+
let lines = 0;
|
|
368
269
|
|
|
369
|
-
let lastLine = '';
|
|
370
|
-
let lineCount = 0;
|
|
371
270
|
const onData = (data) => {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
statusBar.setContent(` {gray-fg}[${lineCount} lines] ${lastLine.substring(0, 70)}{/gray-fg}`);
|
|
271
|
+
data.toString().split('\n').filter(l => l.trim()).forEach(line => {
|
|
272
|
+
lines++;
|
|
273
|
+
const clean = line.trim().substring(0, 90);
|
|
274
|
+
log(' ' + clean);
|
|
275
|
+
statusBar.setContent(` [${lines}] ${clean.substring(0, 70)}`);
|
|
378
276
|
screen.render();
|
|
379
|
-
}
|
|
277
|
+
});
|
|
380
278
|
};
|
|
381
279
|
proc.stdout.on('data', onData);
|
|
382
280
|
proc.stderr.on('data', onData);
|
|
383
281
|
|
|
384
282
|
proc.on('close', (code) => {
|
|
385
283
|
if (code === 0) {
|
|
386
|
-
statusBar.setContent(`
|
|
387
|
-
|
|
388
|
-
|
|
284
|
+
statusBar.setContent(` Done! ${entry.name} installed successfully.`);
|
|
285
|
+
statusBar.style.fg = 'green';
|
|
286
|
+
log(entry.name + ' installed successfully');
|
|
389
287
|
try {
|
|
390
|
-
const
|
|
391
|
-
let
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
fs.writeFileSync(markerFile, JSON.stringify(markers, null, 2));
|
|
395
|
-
} catch {}
|
|
396
|
-
// Refresh
|
|
397
|
-
try {
|
|
398
|
-
const newCatalog = loadCatalog(connector);
|
|
399
|
-
for (let i = 0; i < catalog.length; i++) {
|
|
400
|
-
const updated = newCatalog.find(c => c.name === catalog[i].name);
|
|
401
|
-
if (updated) catalog[i] = updated;
|
|
402
|
-
}
|
|
403
|
-
// Also mark the just-installed one
|
|
404
|
-
const idx = catalog.findIndex(c => c.name === entry.name);
|
|
405
|
-
if (idx >= 0) catalog[idx].installed = true;
|
|
406
|
-
renderCatalog();
|
|
288
|
+
const f = path.join(connector.config?.configDir || '', 'installed_agents.json');
|
|
289
|
+
let m = {}; try { m = JSON.parse(fs.readFileSync(f, 'utf-8')); } catch {}
|
|
290
|
+
m[entry.name] = { installed_at: new Date().toISOString() };
|
|
291
|
+
fs.writeFileSync(f, JSON.stringify(m, null, 2));
|
|
407
292
|
} catch {}
|
|
293
|
+
const idx = catalog.findIndex(c => c.name === entry.name);
|
|
294
|
+
if (idx >= 0) catalog[idx].installed = true;
|
|
295
|
+
renderList();
|
|
408
296
|
} else {
|
|
409
|
-
statusBar.setContent(`
|
|
410
|
-
|
|
297
|
+
statusBar.setContent(` Failed (exit ${code})`);
|
|
298
|
+
statusBar.style.fg = 'red';
|
|
299
|
+
log(entry.name + ' install failed (exit ' + code + ')');
|
|
411
300
|
}
|
|
412
|
-
|
|
301
|
+
setTimeout(() => { statusBar.style.fg = 'white'; }, 5000);
|
|
302
|
+
list.focus();
|
|
413
303
|
screen.render();
|
|
414
304
|
});
|
|
415
305
|
}
|
|
416
306
|
|
|
417
|
-
// ── New Agent
|
|
307
|
+
// ── New Agent ──
|
|
418
308
|
|
|
419
309
|
function showNewAgentDialog() {
|
|
420
310
|
const dialog = blessed.box({
|
|
421
|
-
top: 'center', left: 'center',
|
|
422
|
-
width: 60, height: 16,
|
|
311
|
+
top: 'center', left: 'center', width: 56, height: 13,
|
|
423
312
|
border: { type: 'line' },
|
|
424
|
-
|
|
425
|
-
label: '
|
|
426
|
-
style: { border: { fg: 'cyan' }, label: { fg: 'cyan' } },
|
|
313
|
+
style: { border: { fg: 'cyan' } },
|
|
314
|
+
label: ' Create Agent ',
|
|
427
315
|
});
|
|
428
316
|
|
|
429
|
-
blessed.text({ parent: dialog, top: 1, left: 2,
|
|
317
|
+
blessed.text({ parent: dialog, top: 1, left: 2, content: 'Name:' });
|
|
430
318
|
const nameInput = blessed.textbox({
|
|
431
|
-
parent: dialog, top: 2, left: 2, width:
|
|
432
|
-
border: { type: 'line' },
|
|
433
|
-
|
|
434
|
-
style: { border: { fg: 'gray' }, focus: { border: { fg: 'cyan' } } },
|
|
319
|
+
parent: dialog, top: 2, left: 2, width: 40, height: 3,
|
|
320
|
+
border: { type: 'line' }, inputOnFocus: true,
|
|
321
|
+
style: { focus: { border: { fg: 'cyan' } } },
|
|
435
322
|
});
|
|
436
323
|
|
|
437
|
-
blessed.text({ parent: dialog, top: 5, left: 2,
|
|
324
|
+
blessed.text({ parent: dialog, top: 5, left: 2, content: 'Type: (openclaw, claude, codex, aider, goose)' });
|
|
438
325
|
const typeInput = blessed.textbox({
|
|
439
|
-
parent: dialog, top: 6, left: 2, width:
|
|
440
|
-
border: { type: 'line' },
|
|
441
|
-
|
|
442
|
-
value: 'openclaw',
|
|
443
|
-
style: { border: { fg: 'gray' }, focus: { border: { fg: 'cyan' } } },
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
const statusLabel = blessed.text({ parent: dialog, top: 10, left: 2, tags: true, content: '' });
|
|
447
|
-
|
|
448
|
-
const createBtn = blessed.button({
|
|
449
|
-
parent: dialog, top: 12, left: 2, width: 14, height: 1,
|
|
450
|
-
content: ' Create ', tags: true, mouse: true,
|
|
451
|
-
style: { bg: 'blue', fg: 'white', focus: { bg: 'cyan' }, hover: { bg: 'cyan' } },
|
|
326
|
+
parent: dialog, top: 6, left: 2, width: 40, height: 3,
|
|
327
|
+
border: { type: 'line' }, inputOnFocus: true, value: 'openclaw',
|
|
328
|
+
style: { focus: { border: { fg: 'cyan' } } },
|
|
452
329
|
});
|
|
453
330
|
|
|
454
|
-
const
|
|
455
|
-
parent: dialog, top: 12, left: 18, width: 14, height: 1,
|
|
456
|
-
content: ' Cancel ', tags: true, mouse: true,
|
|
457
|
-
style: { bg: 'gray', fg: 'white', focus: { bg: 'red' }, hover: { bg: 'red' } },
|
|
458
|
-
});
|
|
331
|
+
const msg = blessed.text({ parent: dialog, top: 10, left: 2, content: '' });
|
|
459
332
|
|
|
460
|
-
|
|
333
|
+
const doCreate = () => {
|
|
461
334
|
const name = nameInput.getValue().trim();
|
|
462
335
|
const type = typeInput.getValue().trim();
|
|
463
|
-
if (!name) {
|
|
464
|
-
if (!type) {
|
|
336
|
+
if (!name) { msg.setContent('Name is required'); screen.render(); return; }
|
|
337
|
+
if (!type) { msg.setContent('Type is required'); screen.render(); return; }
|
|
465
338
|
try {
|
|
466
339
|
connector.createAgent(name, type);
|
|
467
|
-
log(
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
} catch (e) {
|
|
473
|
-
statusLabel.setContent(`{red-fg}${e.message}{/red-fg}`);
|
|
474
|
-
screen.render();
|
|
475
|
-
}
|
|
476
|
-
}
|
|
340
|
+
log('Agent ' + name + ' (' + type + ') created');
|
|
341
|
+
} catch (e) { msg.setContent(e.message); screen.render(); return; }
|
|
342
|
+
screen.remove(dialog); dialog.destroy();
|
|
343
|
+
agentList.focus(); refreshAgentTable();
|
|
344
|
+
};
|
|
477
345
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
screen.remove(dialog);
|
|
481
|
-
dialog.destroy();
|
|
482
|
-
agentList.focus();
|
|
483
|
-
screen.render();
|
|
484
|
-
});
|
|
346
|
+
nameInput.key('enter', () => typeInput.focus());
|
|
347
|
+
typeInput.key('enter', doCreate);
|
|
485
348
|
|
|
486
349
|
dialog.key('escape', () => {
|
|
487
|
-
screen.remove(dialog);
|
|
488
|
-
|
|
489
|
-
agentList.focus();
|
|
490
|
-
screen.render();
|
|
350
|
+
screen.remove(dialog); dialog.destroy();
|
|
351
|
+
agentList.focus(); screen.render();
|
|
491
352
|
});
|
|
492
353
|
|
|
493
354
|
screen.append(dialog);
|
|
@@ -495,93 +356,54 @@ function createTUI() {
|
|
|
495
356
|
screen.render();
|
|
496
357
|
}
|
|
497
358
|
|
|
498
|
-
// ──
|
|
359
|
+
// ── Keys ──
|
|
499
360
|
|
|
500
361
|
screen.key('q', () => { if (currentView === 'main') process.exit(0); });
|
|
501
362
|
screen.key('C-c', () => process.exit(0));
|
|
502
|
-
|
|
503
363
|
screen.key('i', () => { if (currentView === 'main') showInstallScreen(); });
|
|
504
364
|
screen.key('n', () => { if (currentView === 'main') showNewAgentDialog(); });
|
|
505
|
-
|
|
506
|
-
screen.key('r', () => {
|
|
507
|
-
if (currentView === 'main') {
|
|
508
|
-
refreshAgentTable();
|
|
509
|
-
log('Refreshed');
|
|
510
|
-
}
|
|
511
|
-
});
|
|
365
|
+
screen.key('r', () => { if (currentView === 'main') { refreshAgentTable(); log('Refreshed'); } });
|
|
512
366
|
|
|
513
367
|
screen.key('s', () => {
|
|
514
|
-
if (currentView !== 'main') return;
|
|
515
|
-
const
|
|
516
|
-
|
|
517
|
-
if (!agent) { log('{yellow-fg}No agent selected{/yellow-fg}'); return; }
|
|
518
|
-
try {
|
|
519
|
-
connector.startAgent(agent.name);
|
|
520
|
-
log(`Starting ${agent.name}...`);
|
|
521
|
-
} catch (e) { log(`{red-fg}Error: ${e.message}{/red-fg}`); }
|
|
368
|
+
if (currentView !== 'main' || !agentRows[agentList.selected]) return;
|
|
369
|
+
const a = agentRows[agentList.selected];
|
|
370
|
+
try { connector.startAgent(a.name); log('Starting ' + a.name + '...'); } catch (e) { log('Error: ' + e.message); }
|
|
522
371
|
setTimeout(refreshAgentTable, 2000);
|
|
523
372
|
});
|
|
524
373
|
|
|
525
374
|
screen.key('x', () => {
|
|
526
|
-
if (currentView !== 'main') return;
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
if (!agent) { log('{yellow-fg}No agent selected{/yellow-fg}'); return; }
|
|
530
|
-
try {
|
|
531
|
-
connector.stopAgent(agent.name);
|
|
532
|
-
log(`Stopped ${agent.name}`);
|
|
533
|
-
} catch (e) { log(`{red-fg}Error: ${e.message}{/red-fg}`); }
|
|
375
|
+
if (currentView !== 'main' || !agentRows[agentList.selected]) return;
|
|
376
|
+
const a = agentRows[agentList.selected];
|
|
377
|
+
try { connector.stopAgent(a.name); log('Stopped ' + a.name); } catch (e) { log('Error: ' + e.message); }
|
|
534
378
|
setTimeout(refreshAgentTable, 1000);
|
|
535
379
|
});
|
|
536
380
|
|
|
537
381
|
screen.key('u', () => {
|
|
538
382
|
if (currentView !== 'main') return;
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
connector.stopDaemon();
|
|
542
|
-
log('Daemon stopped');
|
|
543
|
-
} else {
|
|
544
|
-
connector.startDaemon();
|
|
545
|
-
log('Daemon starting...');
|
|
546
|
-
}
|
|
383
|
+
if (connector.getDaemonPid()) { connector.stopDaemon(); log('Daemon stopped'); }
|
|
384
|
+
else { connector.startDaemon(); log('Daemon starting...'); }
|
|
547
385
|
setTimeout(refreshAgentTable, 2000);
|
|
548
386
|
});
|
|
549
387
|
|
|
550
388
|
screen.key('c', () => {
|
|
551
|
-
if (currentView !== 'main') return;
|
|
552
|
-
|
|
553
|
-
const agent = agentRows[idx];
|
|
554
|
-
if (!agent) { log('{yellow-fg}No agent selected{/yellow-fg}'); return; }
|
|
555
|
-
log(`{gray-fg}Use: openagents connect ${agent.name} <workspace-token>{/gray-fg}`);
|
|
389
|
+
if (currentView !== 'main' || !agentRows[agentList.selected]) return;
|
|
390
|
+
log('Use: openagents connect ' + agentRows[agentList.selected].name + ' <token>');
|
|
556
391
|
});
|
|
557
392
|
|
|
558
393
|
screen.key('d', () => {
|
|
559
|
-
if (currentView !== 'main') return;
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
if (!agent) { log('{yellow-fg}No agent selected{/yellow-fg}'); return; }
|
|
563
|
-
try {
|
|
564
|
-
connector.disconnectAgent(agent.name);
|
|
565
|
-
log(`Disconnected ${agent.name}`);
|
|
566
|
-
} catch (e) { log(`{red-fg}Error: ${e.message}{/red-fg}`); }
|
|
394
|
+
if (currentView !== 'main' || !agentRows[agentList.selected]) return;
|
|
395
|
+
const a = agentRows[agentList.selected];
|
|
396
|
+
try { connector.disconnectAgent(a.name); log('Disconnected ' + a.name); } catch (e) { log('Error: ' + e.message); }
|
|
567
397
|
refreshAgentTable();
|
|
568
398
|
});
|
|
569
399
|
|
|
570
400
|
// ── Init ──
|
|
571
|
-
|
|
572
401
|
agentList.focus();
|
|
573
402
|
refreshAgentTable();
|
|
574
|
-
log('Welcome to
|
|
575
|
-
|
|
403
|
+
log('Welcome to OpenAgents. Press i to install agents, n to create one.');
|
|
576
404
|
setInterval(refreshAgentTable, 5000);
|
|
577
405
|
screen.render();
|
|
578
|
-
return screen;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
// ── Entry point ──────────────────────────────────────────────────────────
|
|
582
|
-
|
|
583
|
-
function run() {
|
|
584
|
-
createTUI();
|
|
585
406
|
}
|
|
586
407
|
|
|
408
|
+
function run() { createTUI(); }
|
|
587
409
|
module.exports = { run };
|