@openagents-org/agent-connector 0.1.0

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/cli.js ADDED
@@ -0,0 +1,526 @@
1
+ 'use strict';
2
+
3
+ const { AgentConnector, Daemon } = require('./index');
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // Arg parsing
7
+ // ---------------------------------------------------------------------------
8
+
9
+ function parseArgs(argv) {
10
+ const args = argv.slice(2);
11
+ const flags = {};
12
+ const allPositional = [];
13
+
14
+ for (let i = 0; i < args.length; i++) {
15
+ const a = args[i];
16
+ if (a.startsWith('--')) {
17
+ const eq = a.indexOf('=');
18
+ if (eq > 0) {
19
+ flags[a.slice(2, eq)] = a.slice(eq + 1);
20
+ } else if (i + 1 < args.length && !args[i + 1].startsWith('--')) {
21
+ flags[a.slice(2)] = args[i + 1];
22
+ i++;
23
+ } else {
24
+ flags[a.slice(2)] = true;
25
+ }
26
+ } else {
27
+ allPositional.push(a);
28
+ }
29
+ }
30
+
31
+ const cmd = allPositional[0] || 'status';
32
+ const positional = allPositional.slice(1);
33
+
34
+ return { cmd, flags, positional };
35
+ }
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // Helpers
39
+ // ---------------------------------------------------------------------------
40
+
41
+ function getConnector(flags) {
42
+ const opts = {};
43
+ if (flags.config) opts.configDir = flags.config;
44
+ return new AgentConnector(opts);
45
+ }
46
+
47
+ function print(msg) { process.stdout.write(msg + '\n'); }
48
+
49
+ function table(rows, headers) {
50
+ if (rows.length === 0) return;
51
+ const widths = headers.map((h, i) =>
52
+ Math.max(h.length, ...rows.map((r) => String(r[i] || '').length))
53
+ );
54
+ print(headers.map((h, i) => h.padEnd(widths[i])).join(' '));
55
+ print(widths.map((w) => '-'.repeat(w)).join(' '));
56
+ for (const row of rows) {
57
+ print(row.map((c, i) => String(c || '').padEnd(widths[i])).join(' '));
58
+ }
59
+ }
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // Commands
63
+ // ---------------------------------------------------------------------------
64
+
65
+ async function cmdUp(connector, flags) {
66
+ const pid = connector.getDaemonPid();
67
+ if (pid) {
68
+ print(`Daemon already running (PID ${pid})`);
69
+ return;
70
+ }
71
+
72
+ if (flags.foreground) {
73
+ // Run in foreground (used by daemonize child)
74
+ const daemon = connector.createDaemon();
75
+ await daemon.start();
76
+ } else {
77
+ // Daemonize
78
+ const foregroundArgs = [process.argv[1], 'up', '--foreground'];
79
+ if (flags.config) foregroundArgs.push('--config', flags.config);
80
+ connector.startDaemon(foregroundArgs);
81
+ }
82
+ }
83
+
84
+ async function cmdDown(connector) {
85
+ const stopped = connector.stopDaemon();
86
+ if (stopped) {
87
+ print('Daemon stopped');
88
+ } else {
89
+ print('Daemon is not running');
90
+ }
91
+ }
92
+
93
+ async function cmdStatus(connector) {
94
+ const pid = connector.getDaemonPid();
95
+ if (!pid) {
96
+ print('Daemon is not running');
97
+ } else {
98
+ print(`Daemon running (PID ${pid})`);
99
+ }
100
+
101
+ const agents = connector.listAgents();
102
+ if (agents.length === 0) {
103
+ print('\nNo agents configured. Run: agent-connector create <name> --type <type>');
104
+ return;
105
+ }
106
+
107
+ const status = connector.getDaemonStatus();
108
+ const rows = agents.map((a) => {
109
+ const s = status[a.name] || {};
110
+ const state = s.state || (pid ? 'stopped' : '-');
111
+ const restarts = s.restarts || 0;
112
+ return [a.name, a.type, state, a.network || '(local)', restarts > 0 ? `${restarts}` : ''];
113
+ });
114
+ print('');
115
+ table(rows, ['NAME', 'TYPE', 'STATE', 'NETWORK', 'RESTARTS']);
116
+ }
117
+
118
+ async function cmdCreate(connector, flags, positional) {
119
+ const name = positional[0];
120
+ if (!name) { print('Usage: agent-connector create <name> [--type <type>]'); return; }
121
+ const type = flags.type || 'openclaw';
122
+ const role = flags.role || 'worker';
123
+
124
+ try {
125
+ connector.addAgent({ name, type, role, path: flags.path });
126
+ print(`Agent '${name}' created (type: ${type})`);
127
+
128
+ // Auto-install if not installed
129
+ if (!connector.isInstalled(type)) {
130
+ print(`Installing ${type}...`);
131
+ try {
132
+ await connector.install(type);
133
+ print(`${type} installed`);
134
+ } catch (e) {
135
+ print(`Warning: install failed: ${e.message}`);
136
+ }
137
+ }
138
+ } catch (e) {
139
+ print(`Error: ${e.message}`);
140
+ process.exitCode = 1;
141
+ }
142
+ }
143
+
144
+ async function cmdRemove(connector, _flags, positional) {
145
+ const name = positional[0];
146
+ if (!name) { print('Usage: agent-connector remove <name>'); return; }
147
+ connector.removeAgent(name);
148
+ print(`Agent '${name}' removed`);
149
+ }
150
+
151
+ async function cmdStart(connector, _flags, positional) {
152
+ const name = positional[0];
153
+ if (!name) { print('Usage: agent-connector start <name>'); return; }
154
+ connector.sendDaemonCommand(`restart:${name}`);
155
+ print(`Sent start command for '${name}'`);
156
+ }
157
+
158
+ async function cmdStop(connector, _flags, positional) {
159
+ const name = positional[0];
160
+ if (!name) { print('Usage: agent-connector stop <name>'); return; }
161
+ connector.sendDaemonCommand(`stop:${name}`);
162
+ print(`Sent stop command for '${name}'`);
163
+ }
164
+
165
+ async function cmdInstall(connector, _flags, positional) {
166
+ const type = positional[0];
167
+ if (!type) { print('Usage: agent-connector install <type>'); return; }
168
+
169
+ if (connector.isInstalled(type)) {
170
+ print(`${type} is already installed`);
171
+ return;
172
+ }
173
+
174
+ print(`Installing ${type}...`);
175
+ try {
176
+ const result = await connector.install(type);
177
+ print(`${type} installed successfully`);
178
+ if (result.output) print(result.output);
179
+ } catch (e) {
180
+ print(`Error: ${e.message}`);
181
+ process.exitCode = 1;
182
+ }
183
+ }
184
+
185
+ async function cmdUninstall(connector, _flags, positional) {
186
+ const type = positional[0];
187
+ if (!type) { print('Usage: agent-connector uninstall <type>'); return; }
188
+
189
+ print(`Uninstalling ${type}...`);
190
+ try {
191
+ const result = await connector.uninstall(type);
192
+ print(`${type} uninstalled`);
193
+ if (result.output) print(result.output);
194
+ } catch (e) {
195
+ print(`Error: ${e.message}`);
196
+ process.exitCode = 1;
197
+ }
198
+ }
199
+
200
+ async function cmdSearch(connector, flags, positional) {
201
+ const query = positional[0] || '';
202
+ let catalog;
203
+ try {
204
+ catalog = await connector.getCatalog();
205
+ } catch {
206
+ catalog = connector.registry.getCatalogSync().map((e) => ({
207
+ ...e,
208
+ installed: connector.isInstalled(e.name),
209
+ }));
210
+ }
211
+
212
+ if (query) {
213
+ const q = query.toLowerCase();
214
+ catalog = catalog.filter((e) =>
215
+ e.name.includes(q) || (e.label || '').toLowerCase().includes(q) ||
216
+ (e.description || '').toLowerCase().includes(q) ||
217
+ (e.tags || []).some((t) => t.includes(q))
218
+ );
219
+ }
220
+
221
+ if (catalog.length === 0) {
222
+ print(query ? `No agents matching '${query}'` : 'No agents in catalog');
223
+ return;
224
+ }
225
+
226
+ const rows = catalog.map((e) => [
227
+ e.name,
228
+ e.label || e.name,
229
+ e.installed ? 'installed' : '',
230
+ (e.description || '').slice(0, 50),
231
+ ]);
232
+ table(rows, ['NAME', 'LABEL', 'STATUS', 'DESCRIPTION']);
233
+ }
234
+
235
+ async function cmdList(connector) {
236
+ const agents = connector.listAgents();
237
+ if (agents.length === 0) {
238
+ print('No agents configured');
239
+ return;
240
+ }
241
+ const rows = agents.map((a) => [
242
+ a.name, a.type, a.role, a.network || '(local)',
243
+ ]);
244
+ table(rows, ['NAME', 'TYPE', 'ROLE', 'NETWORK']);
245
+ }
246
+
247
+ async function cmdRuntimes(connector) {
248
+ let catalog;
249
+ try {
250
+ catalog = await connector.getCatalog();
251
+ } catch {
252
+ catalog = connector.registry.getCatalogSync().map((e) => ({
253
+ ...e, installed: connector.isInstalled(e.name),
254
+ }));
255
+ }
256
+
257
+ const installed = catalog.filter((e) => e.installed);
258
+ if (installed.length === 0) {
259
+ print('No agent runtimes installed');
260
+ return;
261
+ }
262
+
263
+ const rows = installed.map((e) => {
264
+ const binary = connector.installer.which(e.name) || '-';
265
+ return [e.name, e.label || e.name, binary];
266
+ });
267
+ table(rows, ['NAME', 'LABEL', 'PATH']);
268
+ }
269
+
270
+ async function cmdConnect(connector, flags, positional) {
271
+ const name = positional[0];
272
+ const token = positional[1] || flags.token;
273
+ if (!name || !token) {
274
+ print('Usage: agent-connector connect <agent-name> <token>');
275
+ return;
276
+ }
277
+
278
+ print(`Resolving workspace token...`);
279
+ try {
280
+ const info = await connector.resolveToken(token);
281
+ const slug = info.slug || info.workspace_id;
282
+ const wsName = info.name || slug;
283
+
284
+ // Save network
285
+ connector.config.addNetwork({
286
+ id: info.workspace_id,
287
+ slug,
288
+ name: wsName,
289
+ endpoint: info.endpoint || connector.workspace.endpoint,
290
+ token,
291
+ });
292
+
293
+ // Connect agent
294
+ connector.connectWorkspace(name, slug);
295
+ print(`'${name}' connected to workspace '${wsName}'`);
296
+
297
+ // Signal daemon reload
298
+ const pid = connector.getDaemonPid();
299
+ if (pid) {
300
+ connector.sendDaemonCommand(`restart:${name}`);
301
+ print('Daemon notified');
302
+ }
303
+ } catch (e) {
304
+ print(`Error: ${e.message}`);
305
+ process.exitCode = 1;
306
+ }
307
+ }
308
+
309
+ async function cmdDisconnect(connector, _flags, positional) {
310
+ const name = positional[0];
311
+ if (!name) { print('Usage: agent-connector disconnect <agent-name>'); return; }
312
+ connector.disconnectWorkspace(name);
313
+ print(`'${name}' disconnected from workspace`);
314
+
315
+ const pid = connector.getDaemonPid();
316
+ if (pid) {
317
+ connector.sendDaemonCommand(`restart:${name}`);
318
+ }
319
+ }
320
+
321
+ async function cmdLogs(connector, flags, positional) {
322
+ const agent = positional[0] || flags.agent;
323
+ const lines = parseInt(flags.lines || flags.n || '50', 10);
324
+ const logLines = connector.getLogs(agent, lines);
325
+ for (const line of logLines) {
326
+ if (line) print(line);
327
+ }
328
+ }
329
+
330
+ async function cmdWorkspace(connector, flags, positional) {
331
+ const sub = positional[0] || 'list';
332
+ const subArgs = positional.slice(1);
333
+
334
+ switch (sub) {
335
+ case 'create': {
336
+ const name = subArgs[0] || flags.name || 'My Workspace';
337
+ print(`Creating workspace '${name}'...`);
338
+ try {
339
+ const result = await connector.createWorkspace({ name });
340
+ print(`Workspace created: ${result.name}`);
341
+ print(` Slug: ${result.slug}`);
342
+ print(` Token: ${result.token}`);
343
+ print(` URL: ${result.url}`);
344
+ } catch (e) {
345
+ print(`Error: ${e.message}`);
346
+ process.exitCode = 1;
347
+ }
348
+ break;
349
+ }
350
+
351
+ case 'join': {
352
+ const token = subArgs[0] || flags.token;
353
+ if (!token) { print('Usage: agent-connector workspace join <token>'); return; }
354
+ try {
355
+ const info = await connector.resolveToken(token);
356
+ connector.config.addNetwork({
357
+ id: info.workspace_id,
358
+ slug: info.slug || info.workspace_id,
359
+ name: info.name || info.slug,
360
+ endpoint: info.endpoint || connector.workspace.endpoint,
361
+ token,
362
+ });
363
+ print(`Joined workspace '${info.name || info.slug}'`);
364
+ } catch (e) {
365
+ print(`Error: ${e.message}`);
366
+ process.exitCode = 1;
367
+ }
368
+ break;
369
+ }
370
+
371
+ case 'list':
372
+ default: {
373
+ const workspaces = connector.listWorkspaces();
374
+ if (workspaces.length === 0) {
375
+ print('No workspaces configured');
376
+ return;
377
+ }
378
+ const rows = workspaces.map((w) => [w.slug, w.name, w.endpoint || '-']);
379
+ table(rows, ['SLUG', 'NAME', 'ENDPOINT']);
380
+ break;
381
+ }
382
+ }
383
+ }
384
+
385
+ async function cmdEnv(connector, flags, positional) {
386
+ const type = positional[0];
387
+ if (!type) { print('Usage: agent-connector env <type> [--set KEY=VALUE]'); return; }
388
+
389
+ const setVal = flags.set;
390
+ if (setVal) {
391
+ const eq = setVal.indexOf('=');
392
+ if (eq < 1) { print('Usage: --set KEY=VALUE'); return; }
393
+ const key = setVal.slice(0, eq);
394
+ const val = setVal.slice(eq + 1);
395
+ connector.saveAgentEnv(type, { [key]: val });
396
+ print(`Saved ${key} for ${type}`);
397
+ return;
398
+ }
399
+
400
+ // Show current env
401
+ const env = connector.getAgentEnv(type);
402
+ const fields = connector.getEnvFields(type);
403
+
404
+ if (fields.length > 0) {
405
+ for (const field of fields) {
406
+ const val = env[field.name];
407
+ const display = field.password && val ? '***' : (val || '(not set)');
408
+ print(` ${field.name}: ${display} ${field.required ? '(required)' : ''}`);
409
+ }
410
+ } else {
411
+ const entries = Object.entries(env);
412
+ if (entries.length === 0) {
413
+ print(`No env vars configured for ${type}`);
414
+ } else {
415
+ for (const [k, v] of entries) {
416
+ print(` ${k}: ${v}`);
417
+ }
418
+ }
419
+ }
420
+ }
421
+
422
+ async function cmdTestLLM(connector, _flags, positional) {
423
+ const type = positional[0];
424
+ if (!type) { print('Usage: agent-connector test-llm <type>'); return; }
425
+
426
+ const env = connector.getAgentEnv(type);
427
+ const resolved = connector.resolveAgentEnv(type, env);
428
+ const effective = { ...env, ...resolved };
429
+
430
+ print(`Testing LLM connection for ${type}...`);
431
+ const result = await connector.testLLM(effective);
432
+ if (result.success) {
433
+ print(`Success! Model: ${result.model}, Response: ${result.response}`);
434
+ } else {
435
+ print(`Failed: ${result.error}`);
436
+ process.exitCode = 1;
437
+ }
438
+ }
439
+
440
+ async function cmdVersion() {
441
+ const pkg = require('../package.json');
442
+ print(`${pkg.name} v${pkg.version}`);
443
+ }
444
+
445
+ async function cmdHelp() {
446
+ print(`Usage: agent-connector <command> [options]
447
+
448
+ Commands:
449
+ up [--foreground] Start daemon (background by default)
450
+ down Stop daemon
451
+ status Show agent status
452
+ list List configured agents
453
+ create <name> [--type T] Create a new agent
454
+ remove <name> Remove an agent
455
+ start <name> Start a single agent
456
+ stop <name> Stop a single agent
457
+ install <type> Install an agent runtime
458
+ uninstall <type> Uninstall an agent runtime
459
+ search [query] Browse agent catalog
460
+ runtimes List installed runtimes
461
+ connect <agent> <token> Connect agent to workspace
462
+ disconnect <agent> Disconnect agent from workspace
463
+ env <type> [--set K=V] View/set env vars for agent type
464
+ test-llm <type> Test LLM connection
465
+ logs [agent] [--lines N] View daemon logs
466
+ workspace create [name] Create a new workspace
467
+ workspace join <token> Join workspace with token
468
+ workspace list List configured workspaces
469
+ version Show version
470
+ help Show this help
471
+
472
+ Options:
473
+ --config <dir> Config directory (default: ~/.openagents)
474
+ `);
475
+ }
476
+
477
+ // ---------------------------------------------------------------------------
478
+ // Main
479
+ // ---------------------------------------------------------------------------
480
+
481
+ async function main() {
482
+ const { cmd, flags, positional } = parseArgs(process.argv);
483
+
484
+ if (cmd === 'help' || flags.help) { await cmdHelp(); return; }
485
+ if (cmd === 'version' || flags.version) { await cmdVersion(); return; }
486
+
487
+ const connector = getConnector(flags);
488
+
489
+ const commands = {
490
+ up: () => cmdUp(connector, flags),
491
+ down: () => cmdDown(connector),
492
+ status: () => cmdStatus(connector),
493
+ list: () => cmdList(connector),
494
+ create: () => cmdCreate(connector, flags, positional),
495
+ remove: () => cmdRemove(connector, flags, positional),
496
+ start: () => cmdStart(connector, flags, positional),
497
+ stop: () => cmdStop(connector, flags, positional),
498
+ install: () => cmdInstall(connector, flags, positional),
499
+ uninstall: () => cmdUninstall(connector, flags, positional),
500
+ search: () => cmdSearch(connector, flags, positional),
501
+ runtimes: () => cmdRuntimes(connector),
502
+ connect: () => cmdConnect(connector, flags, positional),
503
+ disconnect: () => cmdDisconnect(connector, flags, positional),
504
+ logs: () => cmdLogs(connector, flags, positional),
505
+ workspace: () => cmdWorkspace(connector, flags, positional),
506
+ env: () => cmdEnv(connector, flags, positional),
507
+ 'test-llm': () => cmdTestLLM(connector, flags, positional),
508
+ };
509
+
510
+ const handler = commands[cmd];
511
+ if (!handler) {
512
+ print(`Unknown command: ${cmd}`);
513
+ print('Run: agent-connector help');
514
+ process.exitCode = 1;
515
+ return;
516
+ }
517
+
518
+ try {
519
+ await handler();
520
+ } catch (e) {
521
+ print(`Error: ${e.message}`);
522
+ process.exitCode = 1;
523
+ }
524
+ }
525
+
526
+ main();