@openagents-org/agent-launcher 0.1.16 → 0.2.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/src/tui.js CHANGED
@@ -37,18 +37,24 @@ const COLORS = {
37
37
  };
38
38
 
39
39
  const STATE_DISPLAY = {
40
- online: { sym: '\u25CF', color: COLORS.stateRunning },
41
- running: { sym: '\u25CF', color: COLORS.stateRunning },
42
- starting: { sym: '\u25D0', color: COLORS.stateStarting },
43
- reconnecting: { sym: '\u25D0', color: COLORS.stateStarting },
44
- stopped: { sym: '\u25CB', color: COLORS.stateStopped },
45
- 'not configured': { sym: '\u25CB', color: COLORS.stateStopped },
46
- error: { sym: '\u2717', color: COLORS.stateError },
40
+ online: { sym: '\u25CF', color: COLORS.stateRunning, label: 'connected' },
41
+ running: { sym: '\u25CF', color: COLORS.stateRunning, label: 'connected' },
42
+ idle: { sym: '\u25CB', color: COLORS.stateStarting, label: 'ready' },
43
+ starting: { sym: '\u25D0', color: COLORS.stateStarting, label: 'starting' },
44
+ reconnecting: { sym: '\u25D0', color: COLORS.stateStarting, label: 'reconnecting' },
45
+ stopped: { sym: '\u25CB', color: COLORS.stateStopped, label: 'stopped' },
46
+ 'not configured': { sym: '\u25CB', color: COLORS.stateStopped, label: 'not configured' },
47
+ error: { sym: '\u2717', color: COLORS.stateError, label: 'error' },
47
48
  };
48
49
 
49
- function stateMarkup(state) {
50
- const d = STATE_DISPLAY[state] || { sym: '?', color: 'white' };
51
- return `{${d.color}-fg}${d.sym} ${state}{/${d.color}-fg}`;
50
+ function stateMarkup(state, hasWorkspace) {
51
+ const d = STATE_DISPLAY[state] || { sym: '?', color: 'white', label: state };
52
+ let label = d.label;
53
+ // For running/connected agents, clarify workspace status
54
+ if ((state === 'running' || state === 'online') && !hasWorkspace) {
55
+ label = 'running';
56
+ }
57
+ return `{${d.color}-fg}${d.sym} ${label}{/${d.color}-fg}`;
52
58
  }
53
59
 
54
60
  // ── Data helpers ────────────────────────────────────────────────────────────
@@ -79,6 +85,39 @@ function loadAgentRows(connector) {
79
85
  workspace = agent.network;
80
86
  }
81
87
  }
88
+ // Check if agent type needs configuration (API key etc.)
89
+ let notReadyMsg = '';
90
+ try {
91
+ const agentType = agent.type || 'openclaw';
92
+ const entry = connector.registry.getEntry(agentType);
93
+ if (entry && entry.check_ready) {
94
+ const cr = entry.check_ready;
95
+ let isReady = false;
96
+ // Check saved env
97
+ if (cr.saved_env_key) {
98
+ const saved = connector.env.load(agentType);
99
+ if (saved[cr.saved_env_key]) isReady = true;
100
+ }
101
+ // Check process env vars
102
+ if (!isReady && cr.env_vars) {
103
+ for (const v of cr.env_vars) {
104
+ if (process.env[v]) { isReady = true; break; }
105
+ }
106
+ }
107
+ // Check creds file (for claude)
108
+ if (!isReady && cr.creds_file) {
109
+ const credsPath = cr.creds_file.replace('~', process.env.HOME || process.env.USERPROFILE || '');
110
+ try {
111
+ if (fs.existsSync(credsPath)) {
112
+ const creds = JSON.parse(fs.readFileSync(credsPath, 'utf-8'));
113
+ if (cr.creds_key && creds[cr.creds_key]) isReady = true;
114
+ }
115
+ } catch {}
116
+ }
117
+ if (!isReady) notReadyMsg = cr.not_ready_message || 'Not configured';
118
+ }
119
+ } catch {}
120
+
82
121
  return {
83
122
  name: agent.name,
84
123
  type: agent.type || 'openclaw',
@@ -87,6 +126,7 @@ function loadAgentRows(connector) {
87
126
  path: agent.path || '',
88
127
  network: agent.network || '',
89
128
  lastError: info.last_error || '',
129
+ notReadyMsg,
90
130
  configured: true,
91
131
  };
92
132
  });
@@ -299,10 +339,11 @@ function createTUI() {
299
339
  agentList.setItems([' {gray-fg}No agents configured. Press {bold}i{/bold} to install, {bold}n{/bold} to create.{/gray-fg}']);
300
340
  } else {
301
341
  const items = agentRows.map(r => {
302
- const state = stateMarkup(r.state);
342
+ const state = stateMarkup(r.state, !!r.workspace);
303
343
  const ws = r.workspace || '{gray-fg}-{/gray-fg}';
304
344
  const pathInfo = r.path ? `{gray-fg} ${r.path}{/gray-fg}` : '';
305
- return ` ${r.name.padEnd(22)} ${r.type.padEnd(14)} ${state.padEnd(30)} ${ws}${pathInfo}`;
345
+ const warning = r.notReadyMsg ? ` {yellow-fg} ${r.notReadyMsg}{/yellow-fg}` : '';
346
+ return ` ${r.name.padEnd(22)} ${r.type.padEnd(14)} ${state.padEnd(30)} ${ws}${pathInfo}${warning}`;
306
347
  });
307
348
  agentList.setItems(items);
308
349
  }
@@ -322,7 +363,20 @@ function createTUI() {
322
363
  const dot = pid ? `{green-fg}\u25CF{/green-fg}` : `{gray-fg}\u25CB{/gray-fg}`;
323
364
  const state = pid ? 'Daemon running' : 'Daemon idle';
324
365
  const count = agentRows.length;
325
- header.setContent(` ${dot} ${state} {gray-fg}|{/gray-fg} ${count} agent${count !== 1 ? 's' : ''} configured`);
366
+
367
+ // Show installed runtimes
368
+ let installed = [];
369
+ try {
370
+ const catalog = loadCatalog(connector);
371
+ installed = catalog.filter(e => e.installed).map(e => e.name);
372
+ } catch {}
373
+ const installedStr = installed.length
374
+ ? ` {gray-fg}|{/gray-fg} {green-fg}${installed.join(', ')}{/green-fg} installed`
375
+ : '';
376
+
377
+ header.setContent(
378
+ ` ${dot} ${state} {gray-fg}|{/gray-fg} ${count} agent${count !== 1 ? 's' : ''} configured${installedStr}`
379
+ );
326
380
  }
327
381
 
328
382
  // Update footer when selection changes
@@ -536,6 +590,8 @@ function createTUI() {
536
590
  }).then(() => {
537
591
  installLog.log('');
538
592
  installLog.log(`{green-fg}\u2713 ${entry.name} installed successfully!{/green-fg}`);
593
+ installLog.log('');
594
+ installLog.log(`{cyan-fg}Press c to create a ${entry.name} agent, or Esc to go back.{/cyan-fg}`);
539
595
  logPanel.setLabel(` {bold}{green-fg}Install Complete{/green-fg}{/bold} `);
540
596
  log(`{green-fg}\u2713{/green-fg} ${entry.name} installed`);
541
597
  const idx = catalog.findIndex(c => c.name === entry.name);
@@ -544,6 +600,36 @@ function createTUI() {
544
600
  onDone();
545
601
  list.focus();
546
602
  screen.render();
603
+
604
+ // Listen for 'c' to create agent from just-installed type
605
+ const onCreateKey = (ch) => {
606
+ if (ch === 'c') {
607
+ screen.unkey(['c', 'escape'], onCreateKey);
608
+ // Go back to main and start agent creation flow
609
+ list.emit('keypress', null, { name: 'escape' });
610
+ setTimeout(() => {
611
+ showStartAgentScreen(entry.name, (result) => {
612
+ try {
613
+ connector.addAgent({ name: result.name, type: result.type, path: result.path });
614
+ log(`{green-fg}\u2713{/green-fg} Created agent {cyan-fg}${result.name}{/cyan-fg} (${result.type})`);
615
+ const pid = connector.getDaemonPid();
616
+ if (!pid) {
617
+ connector.startDaemon();
618
+ log('{green-fg}\u2713{/green-fg} Daemon starting...');
619
+ } else {
620
+ signalDaemonReload();
621
+ }
622
+ } catch (e) {
623
+ log(`{red-fg}\u2717{/red-fg} ${e.message}`);
624
+ }
625
+ setTimeout(refreshAgentTable, 3000);
626
+ });
627
+ }, 200);
628
+ } else {
629
+ screen.unkey(['c', 'escape'], onCreateKey);
630
+ }
631
+ };
632
+ screen.key(['c', 'escape'], onCreateKey);
547
633
  }).catch((e) => {
548
634
  installLog.log('');
549
635
  installLog.log(`{red-fg}\u2717 Failed: ${e.message}{/red-fg}`);
@@ -646,6 +732,7 @@ function createTUI() {
646
732
  const pathInput = blessed.textbox({
647
733
  parent: dialog, top: 6, left: 2, width: 50, height: 3,
648
734
  border: { type: 'line' }, inputOnFocus: true,
735
+ value: defaultPath,
649
736
  style: { fg: 'white', bg: COLORS.surface, focus: { border: { fg: COLORS.accent } }, border: { fg: 'grey' } },
650
737
  });
651
738
 
@@ -661,6 +748,18 @@ function createTUI() {
661
748
  nameInput.focus();
662
749
  screen.render();
663
750
 
751
+ // Override _listener on textboxes to intercept Tab before it's inserted
752
+ const origNameListener = nameInput._listener.bind(nameInput);
753
+ nameInput._listener = function(ch, key) {
754
+ if (key.name === 'tab') { nameInput._done(null, nameInput.value); pathInput.focus(); return; }
755
+ return origNameListener(ch, key);
756
+ };
757
+ const origPathListener = pathInput._listener.bind(pathInput);
758
+ pathInput._listener = function(ch, key) {
759
+ if (key.name === 'tab') { pathInput._done(null, pathInput.value); nameInput.focus(); return; }
760
+ return origPathListener(ch, key);
761
+ };
762
+
664
763
  const close = () => {
665
764
  screen.remove(dialog);
666
765
  dialog.destroy();
@@ -762,7 +861,7 @@ function createTUI() {
762
861
  parent: box, bottom: 0, left: 0, width: '100%', height: 1,
763
862
  tags: true,
764
863
  style: { bg: COLORS.footerBg, fg: COLORS.footerFg },
765
- content: ' {cyan-fg}Enter{/cyan-fg} Next field {cyan-fg}Ctrl+S{/cyan-fg} Save {cyan-fg}Ctrl+T{/cyan-fg} Test {cyan-fg}Esc{/cyan-fg} Back',
864
+ content: ' {cyan-fg}Tab{/cyan-fg} Next {cyan-fg}Ctrl+U{/cyan-fg} Clear {cyan-fg}Ctrl+S{/cyan-fg} Save {cyan-fg}Ctrl+T{/cyan-fg} Test {cyan-fg}Esc{/cyan-fg} Back',
766
865
  });
767
866
 
768
867
  screen.append(box);
@@ -780,6 +879,35 @@ function createTUI() {
780
879
  });
781
880
  }
782
881
 
882
+ // Override _listener on textboxes to intercept Tab and Escape
883
+ for (let i = 0; i < inputs.length; i++) {
884
+ const orig = inputs[i]._listener.bind(inputs[i]);
885
+ const idx = i;
886
+ inputs[i]._listener = function(ch, key) {
887
+ if (key.name === 'tab' && inputs.length > 1) {
888
+ inputs[idx]._done(null, inputs[idx].value);
889
+ inputs[(idx + 1) % inputs.length].focus();
890
+ return;
891
+ }
892
+ if (key.name === 'escape') {
893
+ inputs[idx]._done(null, inputs[idx].value);
894
+ closeConfig();
895
+ return;
896
+ }
897
+ // Ctrl+U to clear field
898
+ if (key.ctrl && key.name === 'u') {
899
+ inputs[idx].value = '';
900
+ inputs[idx].setValue('');
901
+ screen.render();
902
+ return;
903
+ }
904
+ // Ctrl+S to save, Ctrl+T to test
905
+ if (key.ctrl && key.name === 's') { inputs[idx]._done(null, inputs[idx].value); doSave(); return; }
906
+ if (key.ctrl && key.name === 't') { inputs[idx]._done(null, inputs[idx].value); doTest(); return; }
907
+ return orig(ch, key);
908
+ };
909
+ }
910
+
783
911
  function gatherEnv() {
784
912
  const env = {};
785
913
  for (const input of inputs) {
@@ -1128,15 +1256,17 @@ function createTUI() {
1128
1256
  opened = true;
1129
1257
  } catch {}
1130
1258
 
1131
- // Show URL in a dialog
1259
+ // Show URL in a dialog — full width, auto-height for long URLs
1260
+ const innerW = screen.width - 4;
1261
+ const urlLines = Math.ceil(url.length / innerW);
1132
1262
  const dialog = blessed.box({
1133
- top: 'center', left: 'center',
1134
- width: 70, height: 7,
1263
+ top: 'center', left: 0,
1264
+ width: '100%', height: 4 + urlLines + 2,
1135
1265
  border: { type: 'line' },
1136
1266
  tags: true,
1137
1267
  label: ' {bold}Workspace URL{/bold} ',
1138
1268
  style: { border: { fg: COLORS.accent }, bg: COLORS.surface },
1139
- content: `\n ${url}\n\n {gray-fg}${opened ? 'Opened in browser.' : 'Copy the URL above.'} Press Esc to close.{/gray-fg}`,
1269
+ content: `\n {bold}${url}{/bold}\n\n {gray-fg}${opened ? 'Opened in browser.' : 'Copy the URL above.'} Press Esc to close.{/gray-fg}`,
1140
1270
  });
1141
1271
 
1142
1272
  screen.append(dialog);
@@ -1333,6 +1463,21 @@ function createTUI() {
1333
1463
  agentList.focus();
1334
1464
  refreshAgentTable();
1335
1465
  log('Welcome to {bold}OpenAgents{/bold}. Press {cyan-fg}i{/cyan-fg} to install agents, {cyan-fg}n{/cyan-fg} to create one.');
1466
+
1467
+ // Show installed runtimes that don't have any agent instances yet
1468
+ try {
1469
+ const catalog = loadCatalog(connector);
1470
+ const installed = catalog.filter(e => e.installed).map(e => e.name);
1471
+ const configuredTypes = new Set(agentRows.map(r => r.type));
1472
+ const unused = installed.filter(t => !configuredTypes.has(t));
1473
+ if (unused.length > 0) {
1474
+ log(`{green-fg}\u2713{/green-fg} Installed: {bold}${unused.join(', ')}{/bold} — press {cyan-fg}n{/cyan-fg} to create an agent`);
1475
+ }
1476
+ if (installed.length === 0) {
1477
+ log('{yellow-fg}!{/yellow-fg} No agent runtimes installed. Press {cyan-fg}i{/cyan-fg} to install one.');
1478
+ }
1479
+ } catch {}
1480
+
1336
1481
  setInterval(refreshAgentTable, 5000);
1337
1482
  screen.render();
1338
1483
  }