electric-ax 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/dist/index.cjs ADDED
@@ -0,0 +1,629 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ const require_chunk = require('./chunk-BCwAaXi7.cjs');
4
+ const require_completions = require('./completions-CO5UBiNh.cjs');
5
+ const node_fs = require_chunk.__toESM(require("node:fs"));
6
+ const node_os = require_chunk.__toESM(require("node:os"));
7
+ const node_path = require_chunk.__toESM(require("node:path"));
8
+ const node_url = require_chunk.__toESM(require("node:url"));
9
+ const commander = require_chunk.__toESM(require("commander"));
10
+ const node_child_process = require_chunk.__toESM(require("node:child_process"));
11
+ const __electric_ax_agents = require_chunk.__toESM(require("@electric-ax/agents"));
12
+
13
+ //#region src/start.ts
14
+ const DEFAULT_ELECTRIC_AGENTS_PORT = 4437;
15
+ const DEFAULT_BUILTIN_AGENTS_PORT = 4448;
16
+ const DOCKER_COMPOSE_FILE = (0, node_url.fileURLToPath)(new URL(`../docker-compose.full.yml`, require("url").pathToFileURL(__filename).href));
17
+ function parseDotEnvValue(raw) {
18
+ const trimmed = raw.trim();
19
+ if (trimmed.startsWith(`"`) && trimmed.endsWith(`"`) || trimmed.startsWith(`'`) && trimmed.endsWith(`'`)) return trimmed.slice(1, -1);
20
+ const hashIndex = trimmed.indexOf(`#`);
21
+ return hashIndex === -1 ? trimmed : trimmed.slice(0, hashIndex).trim();
22
+ }
23
+ function readDotEnvFile(cwd = process.cwd()) {
24
+ const envPath = (0, node_path.resolve)(cwd, `.env`);
25
+ try {
26
+ const content = (0, node_fs.readFileSync)(envPath, `utf8`);
27
+ const values = {};
28
+ for (const line of content.split(/\r?\n/)) {
29
+ const trimmed = line.trim();
30
+ if (!trimmed || trimmed.startsWith(`#`)) continue;
31
+ const equalsIndex = trimmed.indexOf(`=`);
32
+ if (equalsIndex <= 0) continue;
33
+ const key = trimmed.slice(0, equalsIndex).trim();
34
+ const value = parseDotEnvValue(trimmed.slice(equalsIndex + 1));
35
+ values[key] = value;
36
+ }
37
+ return values;
38
+ } catch (error) {
39
+ if (error.code === `ENOENT`) return {};
40
+ throw error;
41
+ }
42
+ }
43
+ function resolveAnthropicApiKey(options, env = process.env, fileEnv = readDotEnvFile()) {
44
+ const candidate = options.anthropicApiKey?.trim() || env.ANTHROPIC_API_KEY?.trim() || fileEnv.ANTHROPIC_API_KEY?.trim();
45
+ if (!candidate) throw new Error(`ANTHROPIC_API_KEY is required. Pass --anthropic-api-key, export it in your shell, or set it in .env.`);
46
+ return candidate;
47
+ }
48
+ function resolveBuiltinAgentsPort(env = process.env, fileEnv = readDotEnvFile()) {
49
+ const raw = env.ELECTRIC_AGENTS_BUILTIN_PORT?.trim() || fileEnv.ELECTRIC_AGENTS_BUILTIN_PORT?.trim();
50
+ const parsed = raw ? Number(raw) : DEFAULT_BUILTIN_AGENTS_PORT;
51
+ if (!Number.isInteger(parsed) || parsed <= 0) throw new Error(`ELECTRIC_AGENTS_BUILTIN_PORT must be a positive integer`);
52
+ return parsed;
53
+ }
54
+ function resolveElectricAgentsPort(env = process.env, fileEnv = readDotEnvFile()) {
55
+ const raw = env.ELECTRIC_AGENTS_PORT?.trim() || fileEnv.ELECTRIC_AGENTS_PORT?.trim();
56
+ const parsed = raw ? Number(raw) : DEFAULT_ELECTRIC_AGENTS_PORT;
57
+ if (!Number.isInteger(parsed) || parsed <= 0) throw new Error(`ELECTRIC_AGENTS_PORT must be a positive integer`);
58
+ return parsed;
59
+ }
60
+ function getStartedEnvironmentMessage(started) {
61
+ return [
62
+ `Electric Agents dev environment is up.`,
63
+ `Server + UI: ${started.uiUrl}`,
64
+ `Docker project: ${started.composeProjectName}`
65
+ ].join(`\n`);
66
+ }
67
+ function getStoppedEnvironmentMessage(stopped) {
68
+ return [
69
+ `Electric Agents dev environment is down.`,
70
+ `Docker project: ${stopped.composeProjectName}`,
71
+ stopped.removedVolumes ? `Volumes removed: yes` : `Volumes removed: no`
72
+ ].join(`\n`);
73
+ }
74
+ function getStartedBuiltinAgentsMessage(started) {
75
+ return [
76
+ `Builtin Horton server is up.`,
77
+ `Webhook server: ${started.url}`,
78
+ `Registers with: ${started.agentServerUrl}`,
79
+ `Press Ctrl-C to stop.`
80
+ ].join(`\n`);
81
+ }
82
+ function slugify(input) {
83
+ return input.toLowerCase().replace(/[^a-z0-9]+/g, `-`).replace(/^-+|-+$/g, ``);
84
+ }
85
+ function resolveComposeProjectName(cwd = process.cwd(), env = process.env) {
86
+ const explicit = env.ELECTRIC_AGENTS_COMPOSE_PROJECT?.trim();
87
+ if (explicit) return explicit;
88
+ const base = slugify((0, node_path.basename)(cwd)) || `workspace`;
89
+ return `electric-agents-${base}`;
90
+ }
91
+ async function runDockerCompose(args, env) {
92
+ await new Promise((resolve, reject) => {
93
+ const child = (0, node_child_process.spawn)(`docker`, args, {
94
+ cwd: process.cwd(),
95
+ env,
96
+ stdio: `inherit`
97
+ });
98
+ child.on(`error`, (error) => {
99
+ reject(new Error(`Failed to run docker compose: ${error instanceof Error ? error.message : String(error)}`));
100
+ });
101
+ child.on(`exit`, (code) => {
102
+ if (code === 0) {
103
+ resolve();
104
+ return;
105
+ }
106
+ reject(new Error(`docker compose exited with code ${code ?? `unknown`}`));
107
+ });
108
+ });
109
+ }
110
+ function delay(ms) {
111
+ return new Promise((resolve) => {
112
+ setTimeout(resolve, ms);
113
+ });
114
+ }
115
+ async function waitForElectricAgentsServer(baseUrl, options = {}) {
116
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
117
+ const timeoutMs = options.timeoutMs ?? 6e4;
118
+ const intervalMs = options.intervalMs ?? 1e3;
119
+ const deadline = Date.now() + timeoutMs;
120
+ const healthUrl = `${baseUrl.replace(/\/$/, ``)}/_electric/health`;
121
+ let lastError = null;
122
+ while (Date.now() < deadline) {
123
+ try {
124
+ const response = await fetchImpl(healthUrl, { signal: AbortSignal.timeout(5e3) });
125
+ if (response.ok) return;
126
+ lastError = `healthcheck returned ${response.status}`;
127
+ } catch (error) {
128
+ lastError = error instanceof Error ? error.message : String(error);
129
+ }
130
+ await delay(intervalMs);
131
+ }
132
+ throw new Error(`Timed out waiting for Electric Agents server at ${healthUrl}${lastError ? `: ${lastError}` : ``}`);
133
+ }
134
+ async function startElectricAgentsDevEnvironment(_options = {}, env = process.env, cwd = process.cwd()) {
135
+ const fileEnv = readDotEnvFile(cwd);
136
+ const port = resolveElectricAgentsPort(env, fileEnv);
137
+ const composeProjectName = resolveComposeProjectName(cwd, env);
138
+ await runDockerCompose([
139
+ `compose`,
140
+ `-f`,
141
+ DOCKER_COMPOSE_FILE,
142
+ `up`,
143
+ `-d`
144
+ ], {
145
+ ...env,
146
+ COMPOSE_PROJECT_NAME: composeProjectName,
147
+ ELECTRIC_AGENTS_PORT: String(port)
148
+ });
149
+ const uiUrl = `http://localhost:${port}`;
150
+ await waitForElectricAgentsServer(uiUrl);
151
+ return {
152
+ port,
153
+ uiUrl,
154
+ composeProjectName
155
+ };
156
+ }
157
+ async function stopElectricAgentsDevEnvironment(options, env = process.env, cwd = process.cwd()) {
158
+ const composeProjectName = resolveComposeProjectName(cwd, env);
159
+ const args = [
160
+ `compose`,
161
+ `-f`,
162
+ DOCKER_COMPOSE_FILE,
163
+ `down`
164
+ ];
165
+ if (options.removeVolumes) args.push(`--volumes`);
166
+ await runDockerCompose(args, {
167
+ ...env,
168
+ COMPOSE_PROJECT_NAME: composeProjectName
169
+ });
170
+ return {
171
+ composeProjectName,
172
+ removedVolumes: options.removeVolumes ?? false
173
+ };
174
+ }
175
+ function waitForShutdown(stop, signalSource = process) {
176
+ return new Promise((resolve, reject) => {
177
+ let stopping = false;
178
+ const cleanup = () => {
179
+ signalSource.off(`SIGINT`, onSigint);
180
+ signalSource.off(`SIGTERM`, onSigterm);
181
+ };
182
+ const shutdown = (signal) => {
183
+ if (stopping) return;
184
+ stopping = true;
185
+ cleanup();
186
+ stop().then(resolve).catch((error) => {
187
+ reject(new Error(`Failed to stop builtin agents server after ${signal}: ${error instanceof Error ? error.message : String(error)}`));
188
+ });
189
+ };
190
+ const onSigint = () => {
191
+ shutdown(`SIGINT`);
192
+ };
193
+ const onSigterm = () => {
194
+ shutdown(`SIGTERM`);
195
+ };
196
+ signalSource.on(`SIGINT`, onSigint);
197
+ signalSource.on(`SIGTERM`, onSigterm);
198
+ });
199
+ }
200
+ async function startBuiltinAgentsServer(options, params = {}) {
201
+ const env = params.env ?? process.env;
202
+ const cwd = params.cwd ?? process.cwd();
203
+ const fileEnv = readDotEnvFile(cwd);
204
+ const anthropicApiKey = resolveAnthropicApiKey(options, env, fileEnv);
205
+ const port = resolveBuiltinAgentsPort(env, fileEnv);
206
+ const agentServerUrl = params.agentServerUrl ?? env.ELECTRIC_AGENTS_URL?.trim() ?? `http://localhost:${resolveElectricAgentsPort(env, fileEnv)}`;
207
+ process.env.ANTHROPIC_API_KEY = anthropicApiKey;
208
+ await waitForElectricAgentsServer(agentServerUrl);
209
+ const server = new __electric_ax_agents.BuiltinAgentsServer({
210
+ agentServerUrl,
211
+ port,
212
+ workingDirectory: cwd
213
+ });
214
+ await server.start();
215
+ const started = {
216
+ port,
217
+ url: server.url,
218
+ registeredBaseUrl: server.registeredBaseUrl,
219
+ agentServerUrl
220
+ };
221
+ console.log(getStartedBuiltinAgentsMessage(started));
222
+ await waitForShutdown(() => server.stop());
223
+ return started;
224
+ }
225
+
226
+ //#endregion
227
+ //#region src/index.ts
228
+ const DEFAULT_ELECTRIC_AGENTS_URL = `http://localhost:4437`;
229
+ var CliError = class extends Error {};
230
+ function getDefaultElectricAgentsIdentity() {
231
+ return `${(0, node_os.userInfo)().username}@${(0, node_os.hostname)()}`;
232
+ }
233
+ function getElectricCliEnv(env = process.env) {
234
+ return {
235
+ electricAgentsUrl: env.ELECTRIC_AGENTS_URL || DEFAULT_ELECTRIC_AGENTS_URL,
236
+ electricAgentsIdentity: env.ELECTRIC_AGENTS_IDENTITY || getDefaultElectricAgentsIdentity()
237
+ };
238
+ }
239
+ function getErrorMessage(error) {
240
+ return error instanceof Error ? error.message : String(error);
241
+ }
242
+ function fail(message) {
243
+ throw new CliError(message);
244
+ }
245
+ function relativeTime(epochMs) {
246
+ const seconds = Math.floor((Date.now() - epochMs) / 1e3);
247
+ if (seconds < 5) return `just now`;
248
+ if (seconds < 60) return `${seconds}s ago`;
249
+ const minutes = Math.floor(seconds / 60);
250
+ if (minutes < 60) return `${minutes}m ago`;
251
+ const hours = Math.floor(minutes / 60);
252
+ if (hours < 24) return `${hours}h ago`;
253
+ const days = Math.floor(hours / 24);
254
+ return `${days}d ago`;
255
+ }
256
+ function parsePayload(input, json) {
257
+ if (json) try {
258
+ return JSON.parse(input);
259
+ } catch (error) {
260
+ fail(`Invalid JSON: ${getErrorMessage(error)}`);
261
+ }
262
+ return { text: input };
263
+ }
264
+ function normalizeVariadicArg(value) {
265
+ if (value === void 0) return [];
266
+ return Array.isArray(value) ? value : [value];
267
+ }
268
+ function getCommandActionArg(args) {
269
+ return args[args.length - 1];
270
+ }
271
+ function resolveCommandName(argv) {
272
+ const invoked = (0, node_path.basename)(argv[1] ?? ``);
273
+ if (!invoked) return `electric`;
274
+ if (invoked === `index.js` || invoked === `index.ts` || invoked === `node`) return `electric`;
275
+ return invoked.replace(/\.(c|m)?js$/, ``);
276
+ }
277
+ function commandExample(commandName) {
278
+ return `${commandName} agent`;
279
+ }
280
+ function resolveCommandPrefix(argv, env = process.env) {
281
+ if (env.npm_command === `exec`) {
282
+ const userAgent = env.npm_config_user_agent ?? ``;
283
+ if (userAgent.startsWith(`pnpm/`)) return `pnpx electric-ax agent`;
284
+ if (userAgent.startsWith(`npm/`)) return `npx electric-ax agent`;
285
+ }
286
+ return commandExample(resolveCommandName(argv));
287
+ }
288
+ async function electricAgentsFetch(env, path, opts = {}) {
289
+ try {
290
+ return await fetch(`${env.electricAgentsUrl}${path}`, {
291
+ ...opts,
292
+ headers: {
293
+ "content-type": `application/json`,
294
+ ...opts.headers
295
+ }
296
+ });
297
+ } catch (error) {
298
+ fail(`Could not connect to ${env.electricAgentsUrl} — is the Electric Agents server running?\n Set ELECTRIC_AGENTS_URL to point to a different server.\n Original error: ${getErrorMessage(error)}`);
299
+ }
300
+ }
301
+ async function parseJsonResponse(res) {
302
+ const text = await res.text();
303
+ try {
304
+ return JSON.parse(text);
305
+ } catch (error) {
306
+ if (!(error instanceof SyntaxError)) throw error;
307
+ return { error: { message: text || res.statusText } };
308
+ }
309
+ }
310
+ function failFromResponse(data, res) {
311
+ const err = data.error;
312
+ fail(String(err?.message ?? res.statusText));
313
+ }
314
+ async function fetchEntityTypes(env) {
315
+ const res = await electricAgentsFetch(env, `/_electric/entity-types`);
316
+ const data = await res.json();
317
+ if (!res.ok) fail(typeof data === `object` && data !== null ? String(data.error?.message ?? res.statusText) : res.statusText);
318
+ if (!Array.isArray(data)) fail(`Unexpected response from server when listing entity types`);
319
+ return data;
320
+ }
321
+ async function listTypes(env) {
322
+ const rows = await fetchEntityTypes(env);
323
+ if (rows.length === 0) {
324
+ console.log(`No entity types found`);
325
+ return;
326
+ }
327
+ const types = rows.map((entityType) => ({
328
+ name: entityType.name,
329
+ description: entityType.description,
330
+ serve_endpoint: entityType.serve_endpoint
331
+ }));
332
+ const { renderTypesTable } = await import(`./types-table.js`);
333
+ renderTypesTable(types);
334
+ }
335
+ async function fetchEntities(env, options = {}) {
336
+ const searchParams = new URLSearchParams();
337
+ if (options.type) searchParams.set(`type`, options.type);
338
+ if (options.status) searchParams.set(`status`, options.status);
339
+ if (options.parent) searchParams.set(`parent`, options.parent);
340
+ const suffix = searchParams.size > 0 ? `?${searchParams.toString()}` : ``;
341
+ const res = await electricAgentsFetch(env, `/_electric/entities${suffix}`);
342
+ const data = await res.json();
343
+ if (!res.ok) fail(typeof data === `object` && data !== null ? String(data.error?.message ?? res.statusText) : res.statusText);
344
+ if (!Array.isArray(data)) fail(`Unexpected response from server when listing entities`);
345
+ return data;
346
+ }
347
+ async function inspectType(env, name) {
348
+ const res = await electricAgentsFetch(env, `/_electric/entity-types/${encodeURIComponent(name)}`);
349
+ const data = await parseJsonResponse(res);
350
+ if (!res.ok) failFromResponse(data, res);
351
+ console.log(JSON.stringify(data, null, 2));
352
+ }
353
+ async function deleteType(env, name) {
354
+ const res = await electricAgentsFetch(env, `/_electric/entity-types/${encodeURIComponent(name)}`, { method: `DELETE` });
355
+ if (!res.ok) {
356
+ const data = await parseJsonResponse(res);
357
+ failFromResponse(data, res);
358
+ }
359
+ console.log(`Deleted entity type ${name}`);
360
+ }
361
+ async function spawnEntity(env, urlPath, options) {
362
+ let spawnArgs = {};
363
+ if (options.args) try {
364
+ spawnArgs = JSON.parse(options.args);
365
+ } catch (error) {
366
+ fail(`--args must be valid JSON: ${getErrorMessage(error)}`);
367
+ }
368
+ const res = await electricAgentsFetch(env, urlPath, {
369
+ method: `PUT`,
370
+ body: JSON.stringify({ args: spawnArgs })
371
+ });
372
+ const data = await parseJsonResponse(res);
373
+ if (!res.ok) failFromResponse(data, res);
374
+ const entity = data;
375
+ if (!entity.url || !entity.status) fail(`Unexpected response from server: ${JSON.stringify(data)}`);
376
+ console.log(`Spawned ${entity.url} (type: ${entity.type ?? `unknown`}, status: ${entity.status})`);
377
+ }
378
+ async function sendMessage(env, url, message, options) {
379
+ const payload = parsePayload(message, options.json ?? false);
380
+ const body = {
381
+ from: env.electricAgentsIdentity,
382
+ payload
383
+ };
384
+ if (options.type) body.type = options.type;
385
+ const res = await electricAgentsFetch(env, `${url}/send`, {
386
+ method: `POST`,
387
+ body: JSON.stringify(body)
388
+ });
389
+ if (!res.ok) {
390
+ const data = await parseJsonResponse(res);
391
+ failFromResponse(data, res);
392
+ }
393
+ console.log(`Message sent`);
394
+ }
395
+ async function observeEntity(env, url, options) {
396
+ if (!process.stdout.isTTY) fail(`observe requires an interactive terminal`);
397
+ const { renderObserve } = await import(`./observe-ui.js`);
398
+ renderObserve({
399
+ entityUrl: url,
400
+ baseUrl: env.electricAgentsUrl,
401
+ identity: env.electricAgentsIdentity,
402
+ initialOffset: options.from
403
+ });
404
+ }
405
+ async function inspectEntity(env, url) {
406
+ const res = await electricAgentsFetch(env, url);
407
+ const data = await parseJsonResponse(res);
408
+ if (!res.ok) failFromResponse(data, res);
409
+ console.log(JSON.stringify(data, null, 2));
410
+ }
411
+ async function listEntities(env, options) {
412
+ const entities = await fetchEntities(env, options);
413
+ if (entities.length === 0) {
414
+ console.log(`No entities found`);
415
+ return;
416
+ }
417
+ entities.sort((a, b) => {
418
+ const aTime = Number(a.updated_at) || 0;
419
+ const bTime = Number(b.updated_at) || 0;
420
+ return bTime - aTime;
421
+ });
422
+ console.log(`${`URL`.padEnd(30)} ${`STATUS`.padEnd(10)} ${`CREATED`.padEnd(16)} LAST ACTIVE`);
423
+ console.log(`${`─`.repeat(30)} ${`─`.repeat(10)} ${`─`.repeat(16)} ${`─`.repeat(16)}`);
424
+ for (const entity of entities) {
425
+ const created = entity.created_at ? relativeTime(Number(entity.created_at)) : `-`;
426
+ const lastActive = entity.updated_at ? relativeTime(Number(entity.updated_at)) : `-`;
427
+ console.log(`${entity.url.padEnd(30)} ${entity.status.padEnd(10)} ${created.padEnd(16)} ${lastActive}`);
428
+ }
429
+ }
430
+ async function killEntity(env, url) {
431
+ const res = await electricAgentsFetch(env, url, { method: `DELETE` });
432
+ if (!res.ok) {
433
+ const data = await parseJsonResponse(res);
434
+ failFromResponse(data, res);
435
+ }
436
+ console.log(`Killed ${url}`);
437
+ }
438
+ function printStartedEnvironment(env) {
439
+ console.log(getStartedEnvironmentMessage(env));
440
+ }
441
+ function printStoppedEnvironment(env) {
442
+ console.log(getStoppedEnvironmentMessage(env));
443
+ }
444
+ function createElectricCliHandlers(env, commandPrefix = commandExample(`electric`)) {
445
+ return {
446
+ listTypes: () => listTypes(env),
447
+ inspectType: (name) => inspectType(env, name),
448
+ deleteType: (name) => deleteType(env, name),
449
+ spawn: (urlPath, options) => spawnEntity(env, urlPath, options),
450
+ send: (url, message, options) => sendMessage(env, url, message, options),
451
+ observe: (url, options) => observeEntity(env, url, options),
452
+ inspect: (url) => inspectEntity(env, url),
453
+ ps: (options) => listEntities(env, options),
454
+ kill: (url) => killEntity(env, url),
455
+ start: async (options) => {
456
+ const started = await startElectricAgentsDevEnvironment(options);
457
+ printStartedEnvironment(started);
458
+ return started;
459
+ },
460
+ startBuiltin: async (options) => {
461
+ return startBuiltinAgentsServer(options, { agentServerUrl: env.electricAgentsUrl });
462
+ },
463
+ stop: async (options) => {
464
+ const stopped = await stopElectricAgentsDevEnvironment(options);
465
+ printStoppedEnvironment(stopped);
466
+ return stopped;
467
+ },
468
+ quickstart: async (options) => {
469
+ const started = await startElectricAgentsDevEnvironment();
470
+ printStartedEnvironment(started);
471
+ console.log(``);
472
+ console.log([
473
+ `electric agents server is up`,
474
+ ``,
475
+ `Open a separate terminal and run:`,
476
+ ` ${commandPrefix} spawn /horton/onboarding`,
477
+ ` ${commandPrefix} send /horton/onboarding "Please walk me through onboarding for the Electric agents"`,
478
+ ` ${commandPrefix} observe /horton/onboarding`,
479
+ ``,
480
+ `UI: ${started.uiUrl}`,
481
+ `This terminal will now run the built-in Horton server in the foreground.`
482
+ ].join(`\n`));
483
+ console.log(``);
484
+ await startBuiltinAgentsServer(options, { agentServerUrl: started.uiUrl });
485
+ }
486
+ };
487
+ }
488
+ function getHelpText(commandName) {
489
+ const agentsCommand = commandExample(commandName);
490
+ return `
491
+ Environment:
492
+ ELECTRIC_AGENTS_URL Base URL of the server (default: ${DEFAULT_ELECTRIC_AGENTS_URL})
493
+ ELECTRIC_AGENTS_IDENTITY Sender identity for messages (default: ${getDefaultElectricAgentsIdentity()})
494
+ ANTHROPIC_API_KEY Required for '${agentsCommand} start-builtin' and '${agentsCommand} quickstart'
495
+
496
+ Examples:
497
+ $ ${agentsCommand} types
498
+ $ ${agentsCommand} spawn /horton/onboarding
499
+ $ ${agentsCommand} send /horton/onboarding "Please walk me through onboarding for the Electric agents"
500
+ $ ${agentsCommand} observe /horton/onboarding --from 0
501
+ $ ${agentsCommand} start
502
+ $ ${agentsCommand} start-builtin --anthropic-api-key sk-ant-...
503
+ $ ${agentsCommand} stop --remove-volumes
504
+ `;
505
+ }
506
+ function createElectricProgram({ env = getElectricCliEnv(), commandName = `electric`, commandPrefix = commandExample(commandName), handlers = createElectricCliHandlers(env, commandPrefix) } = {}) {
507
+ const program = new commander.Command();
508
+ program.name(commandName).description(`Manage Electric tooling`).showHelpAfterError().showSuggestionAfterError().addHelpText(`after`, getHelpText(commandName));
509
+ const agentsCommand = program.command(`agent`).alias(`agents`).description(`Manage Electric Agents`);
510
+ const typesCommand = agentsCommand.command(`types`).description(`List entity types`).action(async () => {
511
+ await handlers.listTypes();
512
+ });
513
+ typesCommand.command(`inspect <name>`).description(`Show entity type details`).action(async (name) => {
514
+ await handlers.inspectType(name);
515
+ });
516
+ typesCommand.command(`delete <name>`).description(`Delete an entity type`).action(async (name) => {
517
+ await handlers.deleteType(name);
518
+ });
519
+ agentsCommand.command(`spawn <url-path>`).description(`Spawn an entity from a typed URL path`).option(`--args <json>`, `Spawn arguments as JSON`).action(async (...actionArgs) => {
520
+ const urlPath = actionArgs[0];
521
+ const command = getCommandActionArg(actionArgs);
522
+ await handlers.spawn(urlPath, command.opts());
523
+ });
524
+ agentsCommand.command(`send <url> <message...>`).description(`Send a message to an entity`).option(`--type <msg-type>`, `Message type`).option(`--json`, `Parse message as JSON`).action(async (...actionArgs) => {
525
+ const url = actionArgs[0];
526
+ const message = actionArgs[1];
527
+ const command = getCommandActionArg(actionArgs);
528
+ const messageText = normalizeVariadicArg(message).join(` `);
529
+ await handlers.send(url, messageText, command.opts());
530
+ });
531
+ agentsCommand.command(`observe <url>`).description(`Observe an entity conversation`).option(`--from <offset>`, `Initial offset`).action(async (...actionArgs) => {
532
+ const url = actionArgs[0];
533
+ const command = getCommandActionArg(actionArgs);
534
+ await handlers.observe(url, command.opts());
535
+ });
536
+ agentsCommand.command(`inspect <url>`).description(`Show entity details`).action(async (url) => {
537
+ await handlers.inspect(url);
538
+ });
539
+ agentsCommand.command(`ps`).description(`List entities`).option(`--type <type>`, `Filter by entity type`).option(`--status <status>`, `Filter by status`).option(`--parent <url>`, `Filter by parent URL`).action(async (...actionArgs) => {
540
+ const command = getCommandActionArg(actionArgs);
541
+ await handlers.ps(command.opts());
542
+ });
543
+ agentsCommand.command(`kill <url>`).description(`Delete an entity`).action(async (url) => {
544
+ await handlers.kill(url);
545
+ });
546
+ agentsCommand.command(`start`).description(`Start the Electric Agents coordinator server`).action(async (...actionArgs) => {
547
+ const command = getCommandActionArg(actionArgs);
548
+ await handlers.start(command.opts());
549
+ });
550
+ agentsCommand.command(`start-builtin`).description(`Start runtime for Horton & other builtin agents`).option(`--anthropic-api-key <key>`, `Anthropic API key for the builtin Horton server`).action(async (...actionArgs) => {
551
+ const command = getCommandActionArg(actionArgs);
552
+ await handlers.startBuiltin(command.opts());
553
+ });
554
+ agentsCommand.command(`stop`).description(`Stop the local Electric Agents dev environment`).option(`--remove-volumes`, `Remove Docker volumes as well`).action(async (...actionArgs) => {
555
+ const command = getCommandActionArg(actionArgs);
556
+ await handlers.stop(command.opts());
557
+ });
558
+ agentsCommand.command(`quickstart`).description(`Start the coordinator server, print onboarding steps, and run builtin agents locally`).option(`--anthropic-api-key <key>`, `Anthropic API key for the builtin Horton server`).action(async (...actionArgs) => {
559
+ const command = getCommandActionArg(actionArgs);
560
+ await handlers.quickstart(command.opts());
561
+ });
562
+ const completionCommand = agentsCommand.command(`completion [action]`).description(`Set up shell completion`).addHelpText(`after`, `
563
+ Setup (add to your shell init file):
564
+
565
+ Bash: eval "$(${commandName} --completion)" # add to .bashrc
566
+ Zsh: eval "$(${commandName} --completion)" # add to .zshrc
567
+ Fish: ${commandName} --completion-fish | source # add to config.fish
568
+
569
+ Auto-install (detects your shell and updates init file):
570
+ ${commandName} agent completion install
571
+ `).action((action) => {
572
+ if (action === `install`) {
573
+ try {
574
+ console.log(`Installing shell completions...`);
575
+ require_completions.installCompletions(commandName);
576
+ } catch (error) {
577
+ fail(`Could not install completions: ${getErrorMessage(error)}\n Try manual setup instead: eval "$(${commandName} --completion)"`);
578
+ }
579
+ return;
580
+ }
581
+ console.log(`Set up shell completions for ${commandName}.\n`);
582
+ console.log(`Add to your shell init file:`);
583
+ console.log(` Bash/Zsh: eval "$(${commandName} --completion)"`);
584
+ console.log(` Fish: ${commandName} --completion-fish | source\n`);
585
+ console.log(`Or auto-install: ${commandName} agent completion install`);
586
+ });
587
+ completionCommand.alias(`completions`);
588
+ return program;
589
+ }
590
+ async function run(argv = process.argv) {
591
+ const env = getElectricCliEnv();
592
+ const commandName = resolveCommandName(argv);
593
+ const commandPrefix = resolveCommandPrefix(argv);
594
+ require_completions.setupCompletions(env, commandName);
595
+ if (argv.includes(`--compgen`)) return;
596
+ const program = createElectricProgram({
597
+ env,
598
+ commandName,
599
+ commandPrefix
600
+ });
601
+ if (argv.length <= 2) program.help({ error: true });
602
+ await program.parseAsync(argv);
603
+ }
604
+ function isMainModule() {
605
+ if (!process.argv[1]) return false;
606
+ try {
607
+ const scriptPath = (0, node_fs.realpathSync)((0, node_path.resolve)(process.argv[1]));
608
+ const modulePath = (0, node_fs.realpathSync)((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
609
+ return scriptPath === modulePath;
610
+ } catch (error) {
611
+ if (!(error instanceof Error) || !(`code` in error) || error.code !== `ENOENT`) throw error;
612
+ const scriptPath = (0, node_path.resolve)(process.argv[1]);
613
+ const modulePath = (0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href);
614
+ return scriptPath === modulePath;
615
+ }
616
+ }
617
+ if (isMainModule()) run().catch((error) => {
618
+ if (error instanceof CliError) console.error(`Error: ${error.message}`);
619
+ else console.error(`Fatal: ${getErrorMessage(error)}`);
620
+ process.exit(1);
621
+ });
622
+
623
+ //#endregion
624
+ exports.DEFAULT_ELECTRIC_AGENTS_URL = DEFAULT_ELECTRIC_AGENTS_URL
625
+ exports.createElectricCliHandlers = createElectricCliHandlers
626
+ exports.createElectricProgram = createElectricProgram
627
+ exports.getElectricCliEnv = getElectricCliEnv
628
+ exports.resolveCommandPrefix = resolveCommandPrefix
629
+ exports.run = run
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { DEFAULT_ELECTRIC_AGENTS_URL, ElectricCliEnv, ElectricCliHandlers, ObserveCommandOptions, PsCommandOptions, SendCommandOptions, SpawnCommandOptions, StartBuiltinCommandOptions, StartCommandOptions, StartedBuiltinAgentsEnvironment, StartedDevEnvironment, StopCommandOptions, StoppedDevEnvironment, createElectricCliHandlers, createElectricProgram, getElectricCliEnv, resolveCommandPrefix, run } from "./index-B6gIlecl.cjs";
3
+ export { DEFAULT_ELECTRIC_AGENTS_URL, ElectricCliEnv, ElectricCliHandlers, ObserveCommandOptions, PsCommandOptions, SendCommandOptions, SpawnCommandOptions, StartBuiltinCommandOptions, StartCommandOptions, StartedBuiltinAgentsEnvironment, StartedDevEnvironment, StopCommandOptions, StoppedDevEnvironment, createElectricCliHandlers, createElectricProgram, getElectricCliEnv, resolveCommandPrefix, run };
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { DEFAULT_ELECTRIC_AGENTS_URL, ElectricCliEnv, ElectricCliHandlers, ObserveCommandOptions, PsCommandOptions, SendCommandOptions, SpawnCommandOptions, StartBuiltinCommandOptions, StartCommandOptions, StartedBuiltinAgentsEnvironment, StartedDevEnvironment, StopCommandOptions, StoppedDevEnvironment, createElectricCliHandlers, createElectricProgram, getElectricCliEnv, resolveCommandPrefix, run } from "./index-BJvlcIKL.js";
3
+ export { DEFAULT_ELECTRIC_AGENTS_URL, ElectricCliEnv, ElectricCliHandlers, ObserveCommandOptions, PsCommandOptions, SendCommandOptions, SpawnCommandOptions, StartBuiltinCommandOptions, StartCommandOptions, StartedBuiltinAgentsEnvironment, StartedDevEnvironment, StopCommandOptions, StoppedDevEnvironment, createElectricCliHandlers, createElectricProgram, getElectricCliEnv, resolveCommandPrefix, run };