@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/package.json +1 -1
- package/registry.json +50 -0
- package/src/adapters/base.js +14 -1
- package/src/adapters/claude.js +98 -9
- package/src/adapters/cursor.js +22 -0
- package/src/adapters/index.js +10 -1
- package/src/adapters/llm-direct.js +180 -0
- package/src/adapters/nanoclaw.js +22 -0
- package/src/adapters/openclaw.js +5 -10
- package/src/adapters/opencode.js +380 -0
- package/src/adapters/workspace-prompt.js +37 -0
- package/src/daemon.js +32 -6
- package/src/identity.js +113 -0
- package/src/index.js +5 -0
- package/src/tui.js +163 -18
- package/src/workspace-client.js +311 -21
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}
|
|
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:
|
|
1134
|
-
width:
|
|
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
|
|
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
|
}
|