@openagents-org/agent-launcher 0.1.17 → 0.2.2

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
@@ -85,6 +85,39 @@ function loadAgentRows(connector) {
85
85
  workspace = agent.network;
86
86
  }
87
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
+
88
121
  return {
89
122
  name: agent.name,
90
123
  type: agent.type || 'openclaw',
@@ -93,6 +126,7 @@ function loadAgentRows(connector) {
93
126
  path: agent.path || '',
94
127
  network: agent.network || '',
95
128
  lastError: info.last_error || '',
129
+ notReadyMsg,
96
130
  configured: true,
97
131
  };
98
132
  });
@@ -148,6 +182,7 @@ function createTUI() {
148
182
  title: 'OpenAgents',
149
183
  fullUnicode: true,
150
184
  tags: true,
185
+ style: { bg: 'black', fg: 'white' },
151
186
  });
152
187
  const connector = getConnector();
153
188
  let pkg;
@@ -168,7 +203,7 @@ function createTUI() {
168
203
  top: 1, left: 0, width: '100%', height: 1,
169
204
  tags: true,
170
205
  content: ` {bold}OpenAgents{/bold} {gray-fg}v${pkg.version}{/gray-fg}`,
171
- style: { fg: 'white' },
206
+ style: { fg: 'white', bg: 'black' },
172
207
  });
173
208
 
174
209
  // ── Agent Panel (bordered) ──
@@ -177,7 +212,7 @@ function createTUI() {
177
212
  border: { type: 'line' },
178
213
  label: ' {bold}Agents{/bold} ',
179
214
  tags: true,
180
- style: { border: { fg: COLORS.panelBorder }, label: { fg: COLORS.accent } },
215
+ style: { bg: 'black', border: { fg: COLORS.panelBorder }, label: { fg: COLORS.accent } },
181
216
  });
182
217
 
183
218
  // ── Column Headers ──
@@ -196,8 +231,9 @@ function createTUI() {
196
231
  keys: true, vi: true, mouse: true,
197
232
  tags: true,
198
233
  style: {
234
+ bg: 'black',
199
235
  selected: { bg: COLORS.selected.bg, fg: COLORS.selected.fg, bold: true },
200
- item: { fg: 'white' },
236
+ item: { fg: 'white', bg: 'black' },
201
237
  },
202
238
  });
203
239
 
@@ -207,7 +243,7 @@ function createTUI() {
207
243
  border: { type: 'line' },
208
244
  label: ' {bold}Activity Log{/bold} ',
209
245
  tags: true,
210
- style: { border: { fg: COLORS.logBorder }, label: { fg: COLORS.primary } },
246
+ style: { bg: 'black', border: { fg: COLORS.logBorder }, label: { fg: COLORS.primary } },
211
247
  });
212
248
 
213
249
  const logContent = blessed.log({
@@ -215,7 +251,7 @@ function createTUI() {
215
251
  top: 0, left: 0, width: '100%-2', height: '100%-2',
216
252
  scrollable: true, scrollOnInput: true,
217
253
  tags: true,
218
- style: { fg: 'white' },
254
+ style: { fg: 'white', bg: 'black' },
219
255
  });
220
256
 
221
257
  // ── Footer (clickable buttons) ──
@@ -308,7 +344,8 @@ function createTUI() {
308
344
  const state = stateMarkup(r.state, !!r.workspace);
309
345
  const ws = r.workspace || '{gray-fg}-{/gray-fg}';
310
346
  const pathInfo = r.path ? `{gray-fg} ${r.path}{/gray-fg}` : '';
311
- return ` ${r.name.padEnd(22)} ${r.type.padEnd(14)} ${state.padEnd(30)} ${ws}${pathInfo}`;
347
+ const warning = r.notReadyMsg ? ` {yellow-fg} ${r.notReadyMsg}{/yellow-fg}` : '';
348
+ return ` ${r.name.padEnd(22)} ${r.type.padEnd(14)} ${state.padEnd(30)} ${ws}${pathInfo}${warning}`;
312
349
  });
313
350
  agentList.setItems(items);
314
351
  }
@@ -328,7 +365,20 @@ function createTUI() {
328
365
  const dot = pid ? `{green-fg}\u25CF{/green-fg}` : `{gray-fg}\u25CB{/gray-fg}`;
329
366
  const state = pid ? 'Daemon running' : 'Daemon idle';
330
367
  const count = agentRows.length;
331
- header.setContent(` ${dot} ${state} {gray-fg}|{/gray-fg} ${count} agent${count !== 1 ? 's' : ''} configured`);
368
+
369
+ // Show installed runtimes
370
+ let installed = [];
371
+ try {
372
+ const catalog = loadCatalog(connector);
373
+ installed = catalog.filter(e => e.installed).map(e => e.name);
374
+ } catch {}
375
+ const installedStr = installed.length
376
+ ? ` {gray-fg}|{/gray-fg} {green-fg}${installed.join(', ')}{/green-fg} installed`
377
+ : '';
378
+
379
+ header.setContent(
380
+ ` ${dot} ${state} {gray-fg}|{/gray-fg} ${count} agent${count !== 1 ? 's' : ''} configured${installedStr}`
381
+ );
332
382
  }
333
383
 
334
384
  // Update footer when selection changes
@@ -542,6 +592,8 @@ function createTUI() {
542
592
  }).then(() => {
543
593
  installLog.log('');
544
594
  installLog.log(`{green-fg}\u2713 ${entry.name} installed successfully!{/green-fg}`);
595
+ installLog.log('');
596
+ installLog.log(`{cyan-fg}Press c to create a ${entry.name} agent, or Esc to go back.{/cyan-fg}`);
545
597
  logPanel.setLabel(` {bold}{green-fg}Install Complete{/green-fg}{/bold} `);
546
598
  log(`{green-fg}\u2713{/green-fg} ${entry.name} installed`);
547
599
  const idx = catalog.findIndex(c => c.name === entry.name);
@@ -550,6 +602,36 @@ function createTUI() {
550
602
  onDone();
551
603
  list.focus();
552
604
  screen.render();
605
+
606
+ // Listen for 'c' to create agent from just-installed type
607
+ const onCreateKey = (ch) => {
608
+ if (ch === 'c') {
609
+ screen.unkey(['c', 'escape'], onCreateKey);
610
+ // Go back to main and start agent creation flow
611
+ list.emit('keypress', null, { name: 'escape' });
612
+ setTimeout(() => {
613
+ showStartAgentScreen(entry.name, (result) => {
614
+ try {
615
+ connector.addAgent({ name: result.name, type: result.type, path: result.path });
616
+ log(`{green-fg}\u2713{/green-fg} Created agent {cyan-fg}${result.name}{/cyan-fg} (${result.type})`);
617
+ const pid = connector.getDaemonPid();
618
+ if (!pid) {
619
+ connector.startDaemon();
620
+ log('{green-fg}\u2713{/green-fg} Daemon starting...');
621
+ } else {
622
+ signalDaemonReload();
623
+ }
624
+ } catch (e) {
625
+ log(`{red-fg}\u2717{/red-fg} ${e.message}`);
626
+ }
627
+ setTimeout(refreshAgentTable, 3000);
628
+ });
629
+ }, 200);
630
+ } else {
631
+ screen.unkey(['c', 'escape'], onCreateKey);
632
+ }
633
+ };
634
+ screen.key(['c', 'escape'], onCreateKey);
553
635
  }).catch((e) => {
554
636
  installLog.log('');
555
637
  installLog.log(`{red-fg}\u2717 Failed: ${e.message}{/red-fg}`);
@@ -652,6 +734,7 @@ function createTUI() {
652
734
  const pathInput = blessed.textbox({
653
735
  parent: dialog, top: 6, left: 2, width: 50, height: 3,
654
736
  border: { type: 'line' }, inputOnFocus: true,
737
+ value: defaultPath,
655
738
  style: { fg: 'white', bg: COLORS.surface, focus: { border: { fg: COLORS.accent } }, border: { fg: 'grey' } },
656
739
  });
657
740
 
@@ -667,6 +750,18 @@ function createTUI() {
667
750
  nameInput.focus();
668
751
  screen.render();
669
752
 
753
+ // Override _listener on textboxes to intercept Tab before it's inserted
754
+ const origNameListener = nameInput._listener.bind(nameInput);
755
+ nameInput._listener = function(ch, key) {
756
+ if (key.name === 'tab') { nameInput._done(null, nameInput.value); pathInput.focus(); return; }
757
+ return origNameListener(ch, key);
758
+ };
759
+ const origPathListener = pathInput._listener.bind(pathInput);
760
+ pathInput._listener = function(ch, key) {
761
+ if (key.name === 'tab') { pathInput._done(null, pathInput.value); nameInput.focus(); return; }
762
+ return origPathListener(ch, key);
763
+ };
764
+
670
765
  const close = () => {
671
766
  screen.remove(dialog);
672
767
  dialog.destroy();
@@ -768,7 +863,7 @@ function createTUI() {
768
863
  parent: box, bottom: 0, left: 0, width: '100%', height: 1,
769
864
  tags: true,
770
865
  style: { bg: COLORS.footerBg, fg: COLORS.footerFg },
771
- 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',
866
+ 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',
772
867
  });
773
868
 
774
869
  screen.append(box);
@@ -786,6 +881,35 @@ function createTUI() {
786
881
  });
787
882
  }
788
883
 
884
+ // Override _listener on textboxes to intercept Tab and Escape
885
+ for (let i = 0; i < inputs.length; i++) {
886
+ const orig = inputs[i]._listener.bind(inputs[i]);
887
+ const idx = i;
888
+ inputs[i]._listener = function(ch, key) {
889
+ if (key.name === 'tab' && inputs.length > 1) {
890
+ inputs[idx]._done(null, inputs[idx].value);
891
+ inputs[(idx + 1) % inputs.length].focus();
892
+ return;
893
+ }
894
+ if (key.name === 'escape') {
895
+ inputs[idx]._done(null, inputs[idx].value);
896
+ closeConfig();
897
+ return;
898
+ }
899
+ // Ctrl+U to clear field
900
+ if (key.ctrl && key.name === 'u') {
901
+ inputs[idx].value = '';
902
+ inputs[idx].setValue('');
903
+ screen.render();
904
+ return;
905
+ }
906
+ // Ctrl+S to save, Ctrl+T to test
907
+ if (key.ctrl && key.name === 's') { inputs[idx]._done(null, inputs[idx].value); doSave(); return; }
908
+ if (key.ctrl && key.name === 't') { inputs[idx]._done(null, inputs[idx].value); doTest(); return; }
909
+ return orig(ch, key);
910
+ };
911
+ }
912
+
789
913
  function gatherEnv() {
790
914
  const env = {};
791
915
  for (const input of inputs) {
@@ -1341,6 +1465,21 @@ function createTUI() {
1341
1465
  agentList.focus();
1342
1466
  refreshAgentTable();
1343
1467
  log('Welcome to {bold}OpenAgents{/bold}. Press {cyan-fg}i{/cyan-fg} to install agents, {cyan-fg}n{/cyan-fg} to create one.');
1468
+
1469
+ // Show installed runtimes that don't have any agent instances yet
1470
+ try {
1471
+ const catalog = loadCatalog(connector);
1472
+ const installed = catalog.filter(e => e.installed).map(e => e.name);
1473
+ const configuredTypes = new Set(agentRows.map(r => r.type));
1474
+ const unused = installed.filter(t => !configuredTypes.has(t));
1475
+ if (unused.length > 0) {
1476
+ log(`{green-fg}\u2713{/green-fg} Installed: {bold}${unused.join(', ')}{/bold} — press {cyan-fg}n{/cyan-fg} to create an agent`);
1477
+ }
1478
+ if (installed.length === 0) {
1479
+ log('{yellow-fg}!{/yellow-fg} No agent runtimes installed. Press {cyan-fg}i{/cyan-fg} to install one.');
1480
+ }
1481
+ } catch {}
1482
+
1344
1483
  setInterval(refreshAgentTable, 5000);
1345
1484
  screen.render();
1346
1485
  }