@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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/tui.js +159 -337
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openagents-org/agent-launcher",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "OpenAgents Launcher — install, configure, and run AI coding agents from your terminal",
5
5
  "main": "src/index.js",
6
6
  "bin": {
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
- if (net) {
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, role: agent.role || 'worker' };
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
- const entries = registry.list();
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 markerFile = path.join(connector.config?.configDir || '', 'installed_agents.json');
61
- if (fs.existsSync(markerFile)) {
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
- // ── Layout ──
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 area below header
69
+ // ── Title ──
109
70
  const titleBox = blessed.box({
110
- top: 1, left: 0, width: '100%', height: 3,
111
- tags: true,
112
- content: `\n {bold}OpenAgents{/bold} {gray-fg}v${pkg.version}{/gray-fg} {gray-fg}Local AI Agent Manager{/gray-fg}`,
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 headers for agent table
76
+ // ── Column Headers ──
116
77
  const colHeaders = blessed.box({
117
- top: 4, left: 0, width: '100%', height: 1,
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 list
83
+ // ── Agent List ──
124
84
  const agentList = blessed.list({
125
- top: 5, left: 0, width: '100%', height: '50%-2',
126
- tags: true, keys: true, vi: true, mouse: true,
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', bold: true },
88
+ selected: { bg: 'blue', fg: 'white' },
130
89
  item: { fg: 'white' },
131
- border: { fg: 'gray' },
132
90
  },
133
91
  });
134
92
 
135
- // Separator
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%+4', left: 0, width: '100%', height: 1,
145
- tags: true,
146
- content: ' {bold}Activity{/bold}',
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%+5', left: 0, width: '100%', height: '50%-8',
152
- tags: true,
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: 'gray' },
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(`{gray-fg}${ts}{/gray-fg} ${msg}`);
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 st = STATE_STYLES[r.state] || STATE_STYLES.stopped;
195
- const ws = r.workspace || '{gray-fg}—{/gray-fg}';
196
- return ` {${st.color}-fg}${st.sym}{/${st.color}-fg} ${r.name.padEnd(20)} ${r.type.padEnd(14)} {${st.color}-fg}${st.label.padEnd(14)}{/${st.color}-fg} ${ws}`;
197
- }) : [' {gray-fg}No agents configured. Press {bold}i{/bold} to install, {bold}n{/bold} to create.{/gray-fg}'];
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 ? '{green-fg}●{/green-fg}' : '{gray-fg}○{/gray-fg}';
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} ${count} agent${count !== 1 ? 's' : ''} configured`);
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 installBox = blessed.box({
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: installBox,
246
- top: 1, left: 0, width: '100%', height: 2,
247
- tags: true,
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: installBox,
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
- // Install list
261
- const installList = blessed.list({
262
- parent: installBox,
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', bold: true },
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: installBox,
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: installBox,
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: ' {bold}Enter{/bold} Install/Update {bold}Esc{/bold} Back',
191
+ content: ' Enter Install/Update Esc Back',
286
192
  });
287
193
 
288
- function renderCatalog() {
289
- const items = catalog.map(e => {
290
- const st = e.installed
291
- ? '{green-fg}● installed{/green-fg} '
292
- : '{yellow-fg} available{/yellow-fg} ';
293
- const desc = e.description ? `{gray-fg}${e.description.substring(0, 45)}{/gray-fg}` : '';
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
- renderCatalog();
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
- const confirm = blessed.question({
308
- parent: installBox,
309
- top: 'center', left: 'center',
310
- width: 55, height: 7,
208
+
209
+ const dialog = blessed.box({
210
+ parent: box, top: 'center', left: 'center',
211
+ width: 50, height: 5,
311
212
  border: { type: 'line' },
312
- tags: true,
313
- label: ` ${verb} `,
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
- confirm.ask(`${verb} ${entry.label}? (y/n)`, (_err, ok) => {
318
- confirm.destroy();
319
- if (!ok) { installList.focus(); screen.render(); return; }
320
- doInstall(entry, statusBar, installList, catalog, renderCatalog);
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
- installList.key('escape', () => {
325
- screen.remove(installBox);
326
- installBox.destroy();
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(installBox);
333
- installList.focus();
239
+ screen.append(box);
240
+ list.focus();
334
241
  screen.render();
335
242
  }
336
243
 
337
- function doInstall(entry, statusBar, installList, catalog, renderCatalog) {
338
- statusBar.setContent(` {cyan-fg}Installing ${entry.name}...{/cyan-fg}`);
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 installCmd = installer._resolveInstallCommand(entry.name);
343
- if (!installCmd) {
344
- statusBar.setContent(` {red-fg}✗ No install command for ${entry.name}{/red-fg}`);
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
- installList.focus();
253
+ list.focus();
347
254
  return;
348
255
  }
349
256
 
350
- log(`{cyan-fg}$ ${installCmd}{/cyan-fg}`);
351
- statusBar.setContent(` {gray-fg}$ ${installCmd.substring(0, 80)}{/gray-fg}`);
257
+ log('$ ' + cmd);
258
+ statusBar.setContent(' Running: ' + cmd.substring(0, 70));
352
259
  screen.render();
353
260
 
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 || '');
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(installCmd, [], {
365
- shell: true, env,
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
- const lines = data.toString().split('\n').filter(l => l.trim());
373
- for (const line of lines) {
374
- lastLine = line.trim().substring(0, 100);
375
- lineCount++;
376
- log(` ${lastLine}`);
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(` {green-fg}✓ ${entry.name} installed successfully{/green-fg}`);
387
- log(`{green-fg}✓ ${entry.name} installed successfully{/green-fg}`);
388
- // Mark installed
284
+ statusBar.setContent(` Done! ${entry.name} installed successfully.`);
285
+ statusBar.style.fg = 'green';
286
+ log(entry.name + ' installed successfully');
389
287
  try {
390
- const markerFile = path.join(connector.config?.configDir || '', 'installed_agents.json');
391
- let markers = {};
392
- try { markers = JSON.parse(fs.readFileSync(markerFile, 'utf-8')); } catch {}
393
- markers[entry.name] = { installed_at: new Date().toISOString() };
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(` {red-fg}✗ Failed (exit ${code}): ${lastLine.substring(0, 60)}{/red-fg}`);
410
- log(`{red-fg}✗ ${entry.name} install failed (exit ${code}){/red-fg}`);
297
+ statusBar.setContent(` Failed (exit ${code})`);
298
+ statusBar.style.fg = 'red';
299
+ log(entry.name + ' install failed (exit ' + code + ')');
411
300
  }
412
- installList.focus();
301
+ setTimeout(() => { statusBar.style.fg = 'white'; }, 5000);
302
+ list.focus();
413
303
  screen.render();
414
304
  });
415
305
  }
416
306
 
417
- // ── New Agent Dialog ──
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
- tags: true, keys: true,
425
- label: ' {bold}Create Agent{/bold} ',
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, tags: true, content: '{bold}Name:{/bold}' });
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: 44, height: 3,
432
- border: { type: 'line' },
433
- inputOnFocus: true,
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, tags: true, content: '{bold}Type:{/bold} {gray-fg}(openclaw, claude, codex, aider, goose){/gray-fg}' });
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: 44, height: 3,
440
- border: { type: 'line' },
441
- inputOnFocus: true,
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 cancelBtn = blessed.button({
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
- function doCreate() {
333
+ const doCreate = () => {
461
334
  const name = nameInput.getValue().trim();
462
335
  const type = typeInput.getValue().trim();
463
- if (!name) { statusLabel.setContent('{red-fg}Name is required{/red-fg}'); screen.render(); return; }
464
- if (!type) { statusLabel.setContent('{red-fg}Type is required{/red-fg}'); screen.render(); return; }
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(`{green-fg}✓ Agent '${name}' (${type}) created{/green-fg}`);
468
- screen.remove(dialog);
469
- dialog.destroy();
470
- agentList.focus();
471
- refreshAgentTable();
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
- createBtn.on('press', doCreate);
479
- cancelBtn.on('press', () => {
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
- dialog.destroy();
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
- // ── Keybindings ──
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 idx = agentList.selected;
516
- const agent = agentRows[idx];
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 idx = agentList.selected;
528
- const agent = agentRows[idx];
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
- const pid = connector.getDaemonPid();
540
- if (pid) {
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
- const idx = agentList.selected;
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 idx = agentList.selected;
561
- const agent = agentRows[idx];
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 {bold}OpenAgents{/bold}. Press {bold}i{/bold} to install agents, {bold}n{/bold} to create one.');
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 };