electric-ax 0.1.0 → 0.1.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/dist/index.cjs +19 -218
- package/dist/index.js +20 -219
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -7,223 +7,7 @@ const node_os = require_chunk.__toESM(require("node:os"));
|
|
|
7
7
|
const node_path = require_chunk.__toESM(require("node:path"));
|
|
8
8
|
const node_url = require_chunk.__toESM(require("node:url"));
|
|
9
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
10
|
|
|
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
11
|
//#region src/index.ts
|
|
228
12
|
const DEFAULT_ELECTRIC_AGENTS_URL = `http://localhost:4437`;
|
|
229
13
|
var CliError = class extends Error {};
|
|
@@ -436,10 +220,23 @@ async function killEntity(env, url) {
|
|
|
436
220
|
console.log(`Killed ${url}`);
|
|
437
221
|
}
|
|
438
222
|
function printStartedEnvironment(env) {
|
|
439
|
-
console.log(
|
|
223
|
+
console.log([
|
|
224
|
+
`Electric Agents dev environment is up.`,
|
|
225
|
+
`Server + UI: ${env.uiUrl}`,
|
|
226
|
+
`Docker project: ${env.composeProjectName}`
|
|
227
|
+
].join(`\n`));
|
|
440
228
|
}
|
|
441
229
|
function printStoppedEnvironment(env) {
|
|
442
|
-
console.log(
|
|
230
|
+
console.log([
|
|
231
|
+
`Electric Agents dev environment is down.`,
|
|
232
|
+
`Docker project: ${env.composeProjectName}`,
|
|
233
|
+
env.removedVolumes ? `Volumes removed: yes` : `Volumes removed: no`
|
|
234
|
+
].join(`\n`));
|
|
235
|
+
}
|
|
236
|
+
let startModulePromise = null;
|
|
237
|
+
async function loadStartModule() {
|
|
238
|
+
startModulePromise ??= import(`./start.js`);
|
|
239
|
+
return startModulePromise;
|
|
443
240
|
}
|
|
444
241
|
function createElectricCliHandlers(env, commandPrefix = commandExample(`electric`)) {
|
|
445
242
|
return {
|
|
@@ -453,19 +250,23 @@ function createElectricCliHandlers(env, commandPrefix = commandExample(`electric
|
|
|
453
250
|
ps: (options) => listEntities(env, options),
|
|
454
251
|
kill: (url) => killEntity(env, url),
|
|
455
252
|
start: async (options) => {
|
|
253
|
+
const { startElectricAgentsDevEnvironment } = await loadStartModule();
|
|
456
254
|
const started = await startElectricAgentsDevEnvironment(options);
|
|
457
255
|
printStartedEnvironment(started);
|
|
458
256
|
return started;
|
|
459
257
|
},
|
|
460
258
|
startBuiltin: async (options) => {
|
|
259
|
+
const { startBuiltinAgentsServer } = await loadStartModule();
|
|
461
260
|
return startBuiltinAgentsServer(options, { agentServerUrl: env.electricAgentsUrl });
|
|
462
261
|
},
|
|
463
262
|
stop: async (options) => {
|
|
263
|
+
const { stopElectricAgentsDevEnvironment } = await loadStartModule();
|
|
464
264
|
const stopped = await stopElectricAgentsDevEnvironment(options);
|
|
465
265
|
printStoppedEnvironment(stopped);
|
|
466
266
|
return stopped;
|
|
467
267
|
},
|
|
468
268
|
quickstart: async (options) => {
|
|
269
|
+
const { startBuiltinAgentsServer, startElectricAgentsDevEnvironment } = await loadStartModule();
|
|
469
270
|
const started = await startElectricAgentsDevEnvironment();
|
|
470
271
|
printStartedEnvironment(started);
|
|
471
272
|
console.log(``);
|
package/dist/index.js
CHANGED
|
@@ -1,227 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { installCompletions, setupCompletions } from "./completions-BHILvHgZ.js";
|
|
3
|
-
import {
|
|
3
|
+
import { realpathSync } from "node:fs";
|
|
4
4
|
import { hostname, userInfo } from "node:os";
|
|
5
5
|
import { basename, resolve } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { Command } from "commander";
|
|
8
|
-
import { spawn } from "node:child_process";
|
|
9
|
-
import { BuiltinAgentsServer } from "@electric-ax/agents";
|
|
10
8
|
|
|
11
|
-
//#region src/start.ts
|
|
12
|
-
const DEFAULT_ELECTRIC_AGENTS_PORT = 4437;
|
|
13
|
-
const DEFAULT_BUILTIN_AGENTS_PORT = 4448;
|
|
14
|
-
const DOCKER_COMPOSE_FILE = fileURLToPath(new URL(`../docker-compose.full.yml`, import.meta.url));
|
|
15
|
-
function parseDotEnvValue(raw) {
|
|
16
|
-
const trimmed = raw.trim();
|
|
17
|
-
if (trimmed.startsWith(`"`) && trimmed.endsWith(`"`) || trimmed.startsWith(`'`) && trimmed.endsWith(`'`)) return trimmed.slice(1, -1);
|
|
18
|
-
const hashIndex = trimmed.indexOf(`#`);
|
|
19
|
-
return hashIndex === -1 ? trimmed : trimmed.slice(0, hashIndex).trim();
|
|
20
|
-
}
|
|
21
|
-
function readDotEnvFile(cwd = process.cwd()) {
|
|
22
|
-
const envPath = resolve(cwd, `.env`);
|
|
23
|
-
try {
|
|
24
|
-
const content = readFileSync(envPath, `utf8`);
|
|
25
|
-
const values = {};
|
|
26
|
-
for (const line of content.split(/\r?\n/)) {
|
|
27
|
-
const trimmed = line.trim();
|
|
28
|
-
if (!trimmed || trimmed.startsWith(`#`)) continue;
|
|
29
|
-
const equalsIndex = trimmed.indexOf(`=`);
|
|
30
|
-
if (equalsIndex <= 0) continue;
|
|
31
|
-
const key = trimmed.slice(0, equalsIndex).trim();
|
|
32
|
-
const value = parseDotEnvValue(trimmed.slice(equalsIndex + 1));
|
|
33
|
-
values[key] = value;
|
|
34
|
-
}
|
|
35
|
-
return values;
|
|
36
|
-
} catch (error) {
|
|
37
|
-
if (error.code === `ENOENT`) return {};
|
|
38
|
-
throw error;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
function resolveAnthropicApiKey(options, env = process.env, fileEnv = readDotEnvFile()) {
|
|
42
|
-
const candidate = options.anthropicApiKey?.trim() || env.ANTHROPIC_API_KEY?.trim() || fileEnv.ANTHROPIC_API_KEY?.trim();
|
|
43
|
-
if (!candidate) throw new Error(`ANTHROPIC_API_KEY is required. Pass --anthropic-api-key, export it in your shell, or set it in .env.`);
|
|
44
|
-
return candidate;
|
|
45
|
-
}
|
|
46
|
-
function resolveBuiltinAgentsPort(env = process.env, fileEnv = readDotEnvFile()) {
|
|
47
|
-
const raw = env.ELECTRIC_AGENTS_BUILTIN_PORT?.trim() || fileEnv.ELECTRIC_AGENTS_BUILTIN_PORT?.trim();
|
|
48
|
-
const parsed = raw ? Number(raw) : DEFAULT_BUILTIN_AGENTS_PORT;
|
|
49
|
-
if (!Number.isInteger(parsed) || parsed <= 0) throw new Error(`ELECTRIC_AGENTS_BUILTIN_PORT must be a positive integer`);
|
|
50
|
-
return parsed;
|
|
51
|
-
}
|
|
52
|
-
function resolveElectricAgentsPort(env = process.env, fileEnv = readDotEnvFile()) {
|
|
53
|
-
const raw = env.ELECTRIC_AGENTS_PORT?.trim() || fileEnv.ELECTRIC_AGENTS_PORT?.trim();
|
|
54
|
-
const parsed = raw ? Number(raw) : DEFAULT_ELECTRIC_AGENTS_PORT;
|
|
55
|
-
if (!Number.isInteger(parsed) || parsed <= 0) throw new Error(`ELECTRIC_AGENTS_PORT must be a positive integer`);
|
|
56
|
-
return parsed;
|
|
57
|
-
}
|
|
58
|
-
function getStartedEnvironmentMessage(started) {
|
|
59
|
-
return [
|
|
60
|
-
`Electric Agents dev environment is up.`,
|
|
61
|
-
`Server + UI: ${started.uiUrl}`,
|
|
62
|
-
`Docker project: ${started.composeProjectName}`
|
|
63
|
-
].join(`\n`);
|
|
64
|
-
}
|
|
65
|
-
function getStoppedEnvironmentMessage(stopped) {
|
|
66
|
-
return [
|
|
67
|
-
`Electric Agents dev environment is down.`,
|
|
68
|
-
`Docker project: ${stopped.composeProjectName}`,
|
|
69
|
-
stopped.removedVolumes ? `Volumes removed: yes` : `Volumes removed: no`
|
|
70
|
-
].join(`\n`);
|
|
71
|
-
}
|
|
72
|
-
function getStartedBuiltinAgentsMessage(started) {
|
|
73
|
-
return [
|
|
74
|
-
`Builtin Horton server is up.`,
|
|
75
|
-
`Webhook server: ${started.url}`,
|
|
76
|
-
`Registers with: ${started.agentServerUrl}`,
|
|
77
|
-
`Press Ctrl-C to stop.`
|
|
78
|
-
].join(`\n`);
|
|
79
|
-
}
|
|
80
|
-
function slugify(input) {
|
|
81
|
-
return input.toLowerCase().replace(/[^a-z0-9]+/g, `-`).replace(/^-+|-+$/g, ``);
|
|
82
|
-
}
|
|
83
|
-
function resolveComposeProjectName(cwd = process.cwd(), env = process.env) {
|
|
84
|
-
const explicit = env.ELECTRIC_AGENTS_COMPOSE_PROJECT?.trim();
|
|
85
|
-
if (explicit) return explicit;
|
|
86
|
-
const base = slugify(basename(cwd)) || `workspace`;
|
|
87
|
-
return `electric-agents-${base}`;
|
|
88
|
-
}
|
|
89
|
-
async function runDockerCompose(args, env) {
|
|
90
|
-
await new Promise((resolve$1, reject) => {
|
|
91
|
-
const child = spawn(`docker`, args, {
|
|
92
|
-
cwd: process.cwd(),
|
|
93
|
-
env,
|
|
94
|
-
stdio: `inherit`
|
|
95
|
-
});
|
|
96
|
-
child.on(`error`, (error) => {
|
|
97
|
-
reject(new Error(`Failed to run docker compose: ${error instanceof Error ? error.message : String(error)}`));
|
|
98
|
-
});
|
|
99
|
-
child.on(`exit`, (code) => {
|
|
100
|
-
if (code === 0) {
|
|
101
|
-
resolve$1();
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
reject(new Error(`docker compose exited with code ${code ?? `unknown`}`));
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
function delay(ms) {
|
|
109
|
-
return new Promise((resolve$1) => {
|
|
110
|
-
setTimeout(resolve$1, ms);
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
async function waitForElectricAgentsServer(baseUrl, options = {}) {
|
|
114
|
-
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
115
|
-
const timeoutMs = options.timeoutMs ?? 6e4;
|
|
116
|
-
const intervalMs = options.intervalMs ?? 1e3;
|
|
117
|
-
const deadline = Date.now() + timeoutMs;
|
|
118
|
-
const healthUrl = `${baseUrl.replace(/\/$/, ``)}/_electric/health`;
|
|
119
|
-
let lastError = null;
|
|
120
|
-
while (Date.now() < deadline) {
|
|
121
|
-
try {
|
|
122
|
-
const response = await fetchImpl(healthUrl, { signal: AbortSignal.timeout(5e3) });
|
|
123
|
-
if (response.ok) return;
|
|
124
|
-
lastError = `healthcheck returned ${response.status}`;
|
|
125
|
-
} catch (error) {
|
|
126
|
-
lastError = error instanceof Error ? error.message : String(error);
|
|
127
|
-
}
|
|
128
|
-
await delay(intervalMs);
|
|
129
|
-
}
|
|
130
|
-
throw new Error(`Timed out waiting for Electric Agents server at ${healthUrl}${lastError ? `: ${lastError}` : ``}`);
|
|
131
|
-
}
|
|
132
|
-
async function startElectricAgentsDevEnvironment(_options = {}, env = process.env, cwd = process.cwd()) {
|
|
133
|
-
const fileEnv = readDotEnvFile(cwd);
|
|
134
|
-
const port = resolveElectricAgentsPort(env, fileEnv);
|
|
135
|
-
const composeProjectName = resolveComposeProjectName(cwd, env);
|
|
136
|
-
await runDockerCompose([
|
|
137
|
-
`compose`,
|
|
138
|
-
`-f`,
|
|
139
|
-
DOCKER_COMPOSE_FILE,
|
|
140
|
-
`up`,
|
|
141
|
-
`-d`
|
|
142
|
-
], {
|
|
143
|
-
...env,
|
|
144
|
-
COMPOSE_PROJECT_NAME: composeProjectName,
|
|
145
|
-
ELECTRIC_AGENTS_PORT: String(port)
|
|
146
|
-
});
|
|
147
|
-
const uiUrl = `http://localhost:${port}`;
|
|
148
|
-
await waitForElectricAgentsServer(uiUrl);
|
|
149
|
-
return {
|
|
150
|
-
port,
|
|
151
|
-
uiUrl,
|
|
152
|
-
composeProjectName
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
async function stopElectricAgentsDevEnvironment(options, env = process.env, cwd = process.cwd()) {
|
|
156
|
-
const composeProjectName = resolveComposeProjectName(cwd, env);
|
|
157
|
-
const args = [
|
|
158
|
-
`compose`,
|
|
159
|
-
`-f`,
|
|
160
|
-
DOCKER_COMPOSE_FILE,
|
|
161
|
-
`down`
|
|
162
|
-
];
|
|
163
|
-
if (options.removeVolumes) args.push(`--volumes`);
|
|
164
|
-
await runDockerCompose(args, {
|
|
165
|
-
...env,
|
|
166
|
-
COMPOSE_PROJECT_NAME: composeProjectName
|
|
167
|
-
});
|
|
168
|
-
return {
|
|
169
|
-
composeProjectName,
|
|
170
|
-
removedVolumes: options.removeVolumes ?? false
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
function waitForShutdown(stop, signalSource = process) {
|
|
174
|
-
return new Promise((resolve$1, reject) => {
|
|
175
|
-
let stopping = false;
|
|
176
|
-
const cleanup = () => {
|
|
177
|
-
signalSource.off(`SIGINT`, onSigint);
|
|
178
|
-
signalSource.off(`SIGTERM`, onSigterm);
|
|
179
|
-
};
|
|
180
|
-
const shutdown = (signal) => {
|
|
181
|
-
if (stopping) return;
|
|
182
|
-
stopping = true;
|
|
183
|
-
cleanup();
|
|
184
|
-
stop().then(resolve$1).catch((error) => {
|
|
185
|
-
reject(new Error(`Failed to stop builtin agents server after ${signal}: ${error instanceof Error ? error.message : String(error)}`));
|
|
186
|
-
});
|
|
187
|
-
};
|
|
188
|
-
const onSigint = () => {
|
|
189
|
-
shutdown(`SIGINT`);
|
|
190
|
-
};
|
|
191
|
-
const onSigterm = () => {
|
|
192
|
-
shutdown(`SIGTERM`);
|
|
193
|
-
};
|
|
194
|
-
signalSource.on(`SIGINT`, onSigint);
|
|
195
|
-
signalSource.on(`SIGTERM`, onSigterm);
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
async function startBuiltinAgentsServer(options, params = {}) {
|
|
199
|
-
const env = params.env ?? process.env;
|
|
200
|
-
const cwd = params.cwd ?? process.cwd();
|
|
201
|
-
const fileEnv = readDotEnvFile(cwd);
|
|
202
|
-
const anthropicApiKey = resolveAnthropicApiKey(options, env, fileEnv);
|
|
203
|
-
const port = resolveBuiltinAgentsPort(env, fileEnv);
|
|
204
|
-
const agentServerUrl = params.agentServerUrl ?? env.ELECTRIC_AGENTS_URL?.trim() ?? `http://localhost:${resolveElectricAgentsPort(env, fileEnv)}`;
|
|
205
|
-
process.env.ANTHROPIC_API_KEY = anthropicApiKey;
|
|
206
|
-
await waitForElectricAgentsServer(agentServerUrl);
|
|
207
|
-
const server = new BuiltinAgentsServer({
|
|
208
|
-
agentServerUrl,
|
|
209
|
-
port,
|
|
210
|
-
workingDirectory: cwd
|
|
211
|
-
});
|
|
212
|
-
await server.start();
|
|
213
|
-
const started = {
|
|
214
|
-
port,
|
|
215
|
-
url: server.url,
|
|
216
|
-
registeredBaseUrl: server.registeredBaseUrl,
|
|
217
|
-
agentServerUrl
|
|
218
|
-
};
|
|
219
|
-
console.log(getStartedBuiltinAgentsMessage(started));
|
|
220
|
-
await waitForShutdown(() => server.stop());
|
|
221
|
-
return started;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
//#endregion
|
|
225
9
|
//#region src/index.ts
|
|
226
10
|
const DEFAULT_ELECTRIC_AGENTS_URL = `http://localhost:4437`;
|
|
227
11
|
var CliError = class extends Error {};
|
|
@@ -434,10 +218,23 @@ async function killEntity(env, url) {
|
|
|
434
218
|
console.log(`Killed ${url}`);
|
|
435
219
|
}
|
|
436
220
|
function printStartedEnvironment(env) {
|
|
437
|
-
console.log(
|
|
221
|
+
console.log([
|
|
222
|
+
`Electric Agents dev environment is up.`,
|
|
223
|
+
`Server + UI: ${env.uiUrl}`,
|
|
224
|
+
`Docker project: ${env.composeProjectName}`
|
|
225
|
+
].join(`\n`));
|
|
438
226
|
}
|
|
439
227
|
function printStoppedEnvironment(env) {
|
|
440
|
-
console.log(
|
|
228
|
+
console.log([
|
|
229
|
+
`Electric Agents dev environment is down.`,
|
|
230
|
+
`Docker project: ${env.composeProjectName}`,
|
|
231
|
+
env.removedVolumes ? `Volumes removed: yes` : `Volumes removed: no`
|
|
232
|
+
].join(`\n`));
|
|
233
|
+
}
|
|
234
|
+
let startModulePromise = null;
|
|
235
|
+
async function loadStartModule() {
|
|
236
|
+
startModulePromise ??= import(`./start.js`);
|
|
237
|
+
return startModulePromise;
|
|
441
238
|
}
|
|
442
239
|
function createElectricCliHandlers(env, commandPrefix = commandExample(`electric`)) {
|
|
443
240
|
return {
|
|
@@ -451,19 +248,23 @@ function createElectricCliHandlers(env, commandPrefix = commandExample(`electric
|
|
|
451
248
|
ps: (options) => listEntities(env, options),
|
|
452
249
|
kill: (url) => killEntity(env, url),
|
|
453
250
|
start: async (options) => {
|
|
251
|
+
const { startElectricAgentsDevEnvironment } = await loadStartModule();
|
|
454
252
|
const started = await startElectricAgentsDevEnvironment(options);
|
|
455
253
|
printStartedEnvironment(started);
|
|
456
254
|
return started;
|
|
457
255
|
},
|
|
458
256
|
startBuiltin: async (options) => {
|
|
257
|
+
const { startBuiltinAgentsServer } = await loadStartModule();
|
|
459
258
|
return startBuiltinAgentsServer(options, { agentServerUrl: env.electricAgentsUrl });
|
|
460
259
|
},
|
|
461
260
|
stop: async (options) => {
|
|
261
|
+
const { stopElectricAgentsDevEnvironment } = await loadStartModule();
|
|
462
262
|
const stopped = await stopElectricAgentsDevEnvironment(options);
|
|
463
263
|
printStoppedEnvironment(stopped);
|
|
464
264
|
return stopped;
|
|
465
265
|
},
|
|
466
266
|
quickstart: async (options) => {
|
|
267
|
+
const { startBuiltinAgentsServer, startElectricAgentsDevEnvironment } = await loadStartModule();
|
|
467
268
|
const started = await startElectricAgentsDevEnvironment();
|
|
468
269
|
printStartedEnvironment(started);
|
|
469
270
|
console.log(``);
|