@openagents-org/agent-launcher 0.1.2 → 0.1.4
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 +150 -358
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() {
|
|
@@ -23,12 +19,11 @@ function getConnector() {
|
|
|
23
19
|
}
|
|
24
20
|
|
|
25
21
|
function loadAgentRows(connector) {
|
|
26
|
-
const config = connector.
|
|
22
|
+
const config = connector.config.load();
|
|
27
23
|
const agents = config.agents || [];
|
|
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
|
-
const
|
|
51
|
-
const entries = registry.list();
|
|
41
|
+
const entries = connector.registry.getCatalogSync();
|
|
52
42
|
return entries.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._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,169 @@ 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();
|
|
247
|
+
log('Installing ' + entry.name + '...');
|
|
340
248
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
if (!installCmd) {
|
|
344
|
-
statusBar.setContent(` {red-fg}✗ No install command for ${entry.name}{/red-fg}`);
|
|
345
|
-
screen.render();
|
|
346
|
-
installList.focus();
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
log(`{cyan-fg}$ ${installCmd}{/cyan-fg}`);
|
|
351
|
-
statusBar.setContent(` {gray-fg}$ ${installCmd.substring(0, 80)}{/gray-fg}`);
|
|
352
|
-
screen.render();
|
|
353
|
-
|
|
354
|
-
const env = { ...process.env };
|
|
355
|
-
env.npm_config_yes = 'true';
|
|
356
|
-
env.CI = '1';
|
|
357
|
-
|
|
358
|
-
const extraDirs = getExtraBinDirs();
|
|
359
|
-
if (extraDirs.length) {
|
|
360
|
-
const sep = IS_WINDOWS ? ';' : ':';
|
|
361
|
-
env.PATH = extraDirs.join(sep) + sep + (env.PATH || '');
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
const proc = spawn(installCmd, [], {
|
|
365
|
-
shell: true, env,
|
|
366
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
let lastLine = '';
|
|
370
|
-
let lineCount = 0;
|
|
371
|
-
const onData = (data) => {
|
|
372
|
-
const lines = data.toString().split('\n').filter(l => l.trim());
|
|
249
|
+
connector.installer.installStreaming(entry.name, (chunk) => {
|
|
250
|
+
const lines = chunk.split('\n').filter(l => l.trim());
|
|
373
251
|
for (const line of lines) {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
statusBar.setContent(` {gray-fg}[${lineCount} lines] ${lastLine.substring(0, 70)}{/gray-fg}`);
|
|
252
|
+
const clean = line.trim().substring(0, 90);
|
|
253
|
+
log(' ' + clean);
|
|
254
|
+
statusBar.setContent(' ' + clean.substring(0, 70));
|
|
378
255
|
screen.render();
|
|
379
256
|
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
if (
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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();
|
|
407
|
-
} catch {}
|
|
408
|
-
} else {
|
|
409
|
-
statusBar.setContent(` {red-fg}✗ Failed (exit ${code}): ${lastLine.substring(0, 60)}{/red-fg}`);
|
|
410
|
-
log(`{red-fg}✗ ${entry.name} install failed (exit ${code}){/red-fg}`);
|
|
411
|
-
}
|
|
412
|
-
installList.focus();
|
|
257
|
+
}).then(() => {
|
|
258
|
+
statusBar.setContent(` Done! ${entry.name} installed successfully.`);
|
|
259
|
+
statusBar.style.fg = 'green';
|
|
260
|
+
log(entry.name + ' installed successfully');
|
|
261
|
+
const idx = catalog.findIndex(c => c.name === entry.name);
|
|
262
|
+
if (idx >= 0) catalog[idx].installed = true;
|
|
263
|
+
renderList();
|
|
264
|
+
setTimeout(() => { statusBar.style.fg = 'white'; }, 5000);
|
|
265
|
+
list.focus();
|
|
266
|
+
screen.render();
|
|
267
|
+
}).catch((e) => {
|
|
268
|
+
statusBar.setContent(` Failed: ${e.message.substring(0, 60)}`);
|
|
269
|
+
statusBar.style.fg = 'red';
|
|
270
|
+
log('Install failed: ' + e.message);
|
|
271
|
+
setTimeout(() => { statusBar.style.fg = 'white'; }, 5000);
|
|
272
|
+
list.focus();
|
|
413
273
|
screen.render();
|
|
414
274
|
});
|
|
415
275
|
}
|
|
416
276
|
|
|
417
|
-
// ── New Agent
|
|
277
|
+
// ── New Agent ──
|
|
418
278
|
|
|
419
279
|
function showNewAgentDialog() {
|
|
420
280
|
const dialog = blessed.box({
|
|
421
|
-
top: 'center', left: 'center',
|
|
422
|
-
width: 60, height: 16,
|
|
281
|
+
top: 'center', left: 'center', width: 56, height: 13,
|
|
423
282
|
border: { type: 'line' },
|
|
424
|
-
|
|
425
|
-
label: '
|
|
426
|
-
style: { border: { fg: 'cyan' }, label: { fg: 'cyan' } },
|
|
283
|
+
style: { border: { fg: 'cyan' } },
|
|
284
|
+
label: ' Create Agent ',
|
|
427
285
|
});
|
|
428
286
|
|
|
429
|
-
blessed.text({ parent: dialog, top: 1, left: 2,
|
|
287
|
+
blessed.text({ parent: dialog, top: 1, left: 2, content: 'Name:' });
|
|
430
288
|
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' } } },
|
|
289
|
+
parent: dialog, top: 2, left: 2, width: 40, height: 3,
|
|
290
|
+
border: { type: 'line' }, inputOnFocus: true,
|
|
291
|
+
style: { focus: { border: { fg: 'cyan' } } },
|
|
435
292
|
});
|
|
436
293
|
|
|
437
|
-
blessed.text({ parent: dialog, top: 5, left: 2,
|
|
294
|
+
blessed.text({ parent: dialog, top: 5, left: 2, content: 'Type: (openclaw, claude, codex, aider, goose)' });
|
|
438
295
|
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' } },
|
|
296
|
+
parent: dialog, top: 6, left: 2, width: 40, height: 3,
|
|
297
|
+
border: { type: 'line' }, inputOnFocus: true, value: 'openclaw',
|
|
298
|
+
style: { focus: { border: { fg: 'cyan' } } },
|
|
452
299
|
});
|
|
453
300
|
|
|
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
|
-
});
|
|
301
|
+
const msg = blessed.text({ parent: dialog, top: 10, left: 2, content: '' });
|
|
459
302
|
|
|
460
|
-
|
|
303
|
+
const doCreate = () => {
|
|
461
304
|
const name = nameInput.getValue().trim();
|
|
462
305
|
const type = typeInput.getValue().trim();
|
|
463
|
-
if (!name) {
|
|
464
|
-
if (!type) {
|
|
306
|
+
if (!name) { msg.setContent('Name is required'); screen.render(); return; }
|
|
307
|
+
if (!type) { msg.setContent('Type is required'); screen.render(); return; }
|
|
465
308
|
try {
|
|
466
|
-
connector.
|
|
467
|
-
log(
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
} catch (e) {
|
|
473
|
-
statusLabel.setContent(`{red-fg}${e.message}{/red-fg}`);
|
|
474
|
-
screen.render();
|
|
475
|
-
}
|
|
476
|
-
}
|
|
309
|
+
connector.addAgent({ name, type });
|
|
310
|
+
log('Agent ' + name + ' (' + type + ') created');
|
|
311
|
+
} catch (e) { msg.setContent(e.message); screen.render(); return; }
|
|
312
|
+
screen.remove(dialog); dialog.destroy();
|
|
313
|
+
agentList.focus(); refreshAgentTable();
|
|
314
|
+
};
|
|
477
315
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
screen.remove(dialog);
|
|
481
|
-
dialog.destroy();
|
|
482
|
-
agentList.focus();
|
|
483
|
-
screen.render();
|
|
484
|
-
});
|
|
316
|
+
nameInput.key('enter', () => typeInput.focus());
|
|
317
|
+
typeInput.key('enter', doCreate);
|
|
485
318
|
|
|
486
319
|
dialog.key('escape', () => {
|
|
487
|
-
screen.remove(dialog);
|
|
488
|
-
|
|
489
|
-
agentList.focus();
|
|
490
|
-
screen.render();
|
|
320
|
+
screen.remove(dialog); dialog.destroy();
|
|
321
|
+
agentList.focus(); screen.render();
|
|
491
322
|
});
|
|
492
323
|
|
|
493
324
|
screen.append(dialog);
|
|
@@ -495,93 +326,54 @@ function createTUI() {
|
|
|
495
326
|
screen.render();
|
|
496
327
|
}
|
|
497
328
|
|
|
498
|
-
// ──
|
|
329
|
+
// ── Keys ──
|
|
499
330
|
|
|
500
331
|
screen.key('q', () => { if (currentView === 'main') process.exit(0); });
|
|
501
332
|
screen.key('C-c', () => process.exit(0));
|
|
502
|
-
|
|
503
333
|
screen.key('i', () => { if (currentView === 'main') showInstallScreen(); });
|
|
504
334
|
screen.key('n', () => { if (currentView === 'main') showNewAgentDialog(); });
|
|
505
|
-
|
|
506
|
-
screen.key('r', () => {
|
|
507
|
-
if (currentView === 'main') {
|
|
508
|
-
refreshAgentTable();
|
|
509
|
-
log('Refreshed');
|
|
510
|
-
}
|
|
511
|
-
});
|
|
335
|
+
screen.key('r', () => { if (currentView === 'main') { refreshAgentTable(); log('Refreshed'); } });
|
|
512
336
|
|
|
513
337
|
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}`); }
|
|
338
|
+
if (currentView !== 'main' || !agentRows[agentList.selected]) return;
|
|
339
|
+
const a = agentRows[agentList.selected];
|
|
340
|
+
try { connector.sendDaemonCommand('start:' + a.name); log('Starting ' + a.name + '...'); } catch (e) { log('Error: ' + e.message); }
|
|
522
341
|
setTimeout(refreshAgentTable, 2000);
|
|
523
342
|
});
|
|
524
343
|
|
|
525
344
|
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}`); }
|
|
345
|
+
if (currentView !== 'main' || !agentRows[agentList.selected]) return;
|
|
346
|
+
const a = agentRows[agentList.selected];
|
|
347
|
+
try { connector.sendDaemonCommand('stop:' + a.name); log('Stopped ' + a.name); } catch (e) { log('Error: ' + e.message); }
|
|
534
348
|
setTimeout(refreshAgentTable, 1000);
|
|
535
349
|
});
|
|
536
350
|
|
|
537
351
|
screen.key('u', () => {
|
|
538
352
|
if (currentView !== 'main') return;
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
connector.stopDaemon();
|
|
542
|
-
log('Daemon stopped');
|
|
543
|
-
} else {
|
|
544
|
-
connector.startDaemon();
|
|
545
|
-
log('Daemon starting...');
|
|
546
|
-
}
|
|
353
|
+
if (connector.getDaemonPid()) { connector.stopDaemon(); log('Daemon stopped'); }
|
|
354
|
+
else { connector.startDaemon(); log('Daemon starting...'); }
|
|
547
355
|
setTimeout(refreshAgentTable, 2000);
|
|
548
356
|
});
|
|
549
357
|
|
|
550
358
|
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}`);
|
|
359
|
+
if (currentView !== 'main' || !agentRows[agentList.selected]) return;
|
|
360
|
+
log('Use: openagents connect ' + agentRows[agentList.selected].name + ' <token>');
|
|
556
361
|
});
|
|
557
362
|
|
|
558
363
|
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}`); }
|
|
364
|
+
if (currentView !== 'main' || !agentRows[agentList.selected]) return;
|
|
365
|
+
const a = agentRows[agentList.selected];
|
|
366
|
+
try { connector.disconnectWorkspace(a.name); log('Disconnected ' + a.name); } catch (e) { log('Error: ' + e.message); }
|
|
567
367
|
refreshAgentTable();
|
|
568
368
|
});
|
|
569
369
|
|
|
570
370
|
// ── Init ──
|
|
571
|
-
|
|
572
371
|
agentList.focus();
|
|
573
372
|
refreshAgentTable();
|
|
574
|
-
log('Welcome to
|
|
575
|
-
|
|
373
|
+
log('Welcome to OpenAgents. Press i to install agents, n to create one.');
|
|
576
374
|
setInterval(refreshAgentTable, 5000);
|
|
577
375
|
screen.render();
|
|
578
|
-
return screen;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
// ── Entry point ──────────────────────────────────────────────────────────
|
|
582
|
-
|
|
583
|
-
function run() {
|
|
584
|
-
createTUI();
|
|
585
376
|
}
|
|
586
377
|
|
|
378
|
+
function run() { createTUI(); }
|
|
587
379
|
module.exports = { run };
|