electric-ax 0.1.17 → 0.2.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/start.js CHANGED
@@ -1,4 +1,5 @@
1
- import { readDotEnvFile$1 as readDotEnvFile, resolveAnthropicApiKey$1 as resolveAnthropicApiKey } from "./env-LZtIfFz1.js";
1
+ import { readDotEnvFile$1 as readDotEnvFile, resolveAnthropicApiKey$1 as resolveAnthropicApiKey } from "./env-DIhTv987.js";
2
+ import { appendPathToUrl } from "@electric-ax/agents-runtime";
2
3
  import { fileURLToPath } from "node:url";
3
4
  import { spawn } from "node:child_process";
4
5
  import { BuiltinAgentsServer } from "@electric-ax/agents";
@@ -17,19 +18,11 @@ const ELECTRIC_AGENTS_SERVER_IMAGE_TAG = `latest`;
17
18
  //#endregion
18
19
  //#region src/start.ts
19
20
  const DEFAULT_ELECTRIC_AGENTS_PORT = 4437;
20
- const DEFAULT_BUILTIN_AGENTS_PORT = 4448;
21
- const DEFAULT_BUILTIN_AGENTS_HOST = `0.0.0.0`;
22
21
  const DEFAULT_COMPOSE_PROJECT_NAME = `electric-agents`;
22
+ const DEFAULT_PULL_WAKE_RUNNER_ID = `builtin-agents`;
23
+ const DEFAULT_PULL_WAKE_OWNER_ID = `builtin-agents`;
24
+ const DEFAULT_PULL_WAKE_OWNER_NAME = `Built-in agents`;
23
25
  const DOCKER_COMPOSE_FILE = fileURLToPath(new URL(`../docker-compose.full.yml`, import.meta.url));
24
- function resolveBuiltinAgentsPort(env = process.env, fileEnv = readDotEnvFile()) {
25
- const raw = env.ELECTRIC_AGENTS_BUILTIN_PORT?.trim() || fileEnv.ELECTRIC_AGENTS_BUILTIN_PORT?.trim();
26
- const parsed = raw ? Number(raw) : DEFAULT_BUILTIN_AGENTS_PORT;
27
- if (!Number.isInteger(parsed) || parsed <= 0) throw new Error(`ELECTRIC_AGENTS_BUILTIN_PORT must be a positive integer`);
28
- return parsed;
29
- }
30
- function resolveBuiltinAgentsHost(env = process.env, fileEnv = readDotEnvFile()) {
31
- return env.ELECTRIC_AGENTS_BUILTIN_HOST?.trim() || fileEnv.ELECTRIC_AGENTS_BUILTIN_HOST?.trim() || DEFAULT_BUILTIN_AGENTS_HOST;
32
- }
33
26
  function resolveElectricAgentsPort(env = process.env, fileEnv = readDotEnvFile()) {
34
27
  const raw = env.ELECTRIC_AGENTS_PORT?.trim() || fileEnv.ELECTRIC_AGENTS_PORT?.trim();
35
28
  const parsed = raw ? Number(raw) : DEFAULT_ELECTRIC_AGENTS_PORT;
@@ -52,12 +45,73 @@ function getStoppedEnvironmentMessage(stopped) {
52
45
  }
53
46
  function getStartedBuiltinAgentsMessage(started) {
54
47
  return [
55
- `Builtin Horton server is up.`,
56
- `Webhook server: ${started.url}`,
48
+ `Builtin agents pull-wake runner is up.`,
49
+ `Runner: ${started.runnerId}`,
50
+ `Runtime: ${started.url}`,
57
51
  `Registers with: ${started.agentServerUrl}`,
58
52
  `Press Ctrl-C to stop.`
59
53
  ].join(`\n`);
60
54
  }
55
+ function readConfigValue(env, fileEnv, names) {
56
+ for (const name of names) {
57
+ const value = env[name]?.trim() || fileEnv[name]?.trim();
58
+ if (value) return value;
59
+ }
60
+ return void 0;
61
+ }
62
+ function runnerIdFromIdentity(identity) {
63
+ if (!identity) return DEFAULT_PULL_WAKE_RUNNER_ID;
64
+ const slug = identity.toLowerCase().replace(/[^a-z0-9._-]+/g, `-`).replace(/^-+|-+$/g, ``);
65
+ return slug ? `builtin-${slug}` : DEFAULT_PULL_WAKE_RUNNER_ID;
66
+ }
67
+ function resolvePullWakeRunnerId(env = process.env, fileEnv = readDotEnvFile()) {
68
+ return readConfigValue(env, fileEnv, [`ELECTRIC_AGENTS_PULL_WAKE_RUNNER_ID`, `PULL_WAKE_RUNNER_ID`]) ?? runnerIdFromIdentity(readConfigValue(env, fileEnv, [`ELECTRIC_ASSERTED_AUTH_EMAIL`, `ELECTRIC_AGENTS_IDENTITY`]));
69
+ }
70
+ function resolvePullWakeOwnerId(env = process.env, fileEnv = readDotEnvFile()) {
71
+ return readConfigValue(env, fileEnv, [`ELECTRIC_ASSERTED_AUTH_EMAIL`, `ELECTRIC_AGENTS_IDENTITY`]) ?? DEFAULT_PULL_WAKE_OWNER_ID;
72
+ }
73
+ function buildAssertedAuthHeaders(env, fileEnv) {
74
+ const email = resolvePullWakeOwnerId(env, fileEnv);
75
+ const name = readConfigValue(env, fileEnv, [`ELECTRIC_ASSERTED_AUTH_NAME`]) ?? DEFAULT_PULL_WAKE_OWNER_NAME;
76
+ const headers = {};
77
+ headers[`X-Electric-Asserted-Email`] = email;
78
+ headers[`X-Electric-Asserted-Name`] = name;
79
+ return {
80
+ headers,
81
+ ownerUserId: email,
82
+ name
83
+ };
84
+ }
85
+ function parseAdditionalServerHeaders(env, fileEnv) {
86
+ const raw = readConfigValue(env, fileEnv, [`ELECTRIC_AGENTS_SERVER_HEADERS`]);
87
+ if (!raw) return void 0;
88
+ let parsed;
89
+ try {
90
+ parsed = JSON.parse(raw);
91
+ } catch {
92
+ throw new Error(`Invalid ELECTRIC_AGENTS_SERVER_HEADERS: expected JSON`);
93
+ }
94
+ if (!parsed || typeof parsed !== `object` || Array.isArray(parsed)) throw new Error(`Invalid ELECTRIC_AGENTS_SERVER_HEADERS: expected a JSON object`);
95
+ const headers = new Headers();
96
+ for (const [name, value] of Object.entries(parsed)) {
97
+ if (typeof value !== `string`) throw new Error(`Invalid ELECTRIC_AGENTS_SERVER_HEADERS: header "${name}" must be a string`);
98
+ headers.set(name, value);
99
+ }
100
+ const normalized = Object.fromEntries(headers.entries());
101
+ return Object.keys(normalized).length > 0 ? normalized : void 0;
102
+ }
103
+ function mergeHeaders(...sources) {
104
+ const headers = new Headers();
105
+ for (const source of sources) {
106
+ if (!source) continue;
107
+ new Headers(source).forEach((value, key) => headers.set(key, value));
108
+ }
109
+ const merged = Object.fromEntries(headers.entries());
110
+ return Object.keys(merged).length > 0 ? merged : void 0;
111
+ }
112
+ function hasHeader(headers, name) {
113
+ return headers ? new Headers(headers).has(name) : false;
114
+ }
61
115
  function resolveComposeProjectName(_cwd = process.cwd(), env = process.env) {
62
116
  const explicit = env.ELECTRIC_AGENTS_COMPOSE_PROJECT?.trim();
63
117
  if (explicit) return explicit;
@@ -92,11 +146,14 @@ async function waitForElectricAgentsServer(baseUrl, options = {}) {
92
146
  const timeoutMs = options.timeoutMs ?? 6e4;
93
147
  const intervalMs = options.intervalMs ?? 1e3;
94
148
  const deadline = Date.now() + timeoutMs;
95
- const healthUrl = `${baseUrl.replace(/\/$/, ``)}/_electric/health`;
149
+ const healthUrl = appendPathToUrl(baseUrl, `/_electric/health`);
96
150
  let lastError = null;
97
151
  while (Date.now() < deadline) {
98
152
  try {
99
- const response = await fetchImpl(healthUrl, { signal: AbortSignal.timeout(5e3) });
153
+ const response = await fetchImpl(healthUrl, {
154
+ headers: options.headers,
155
+ signal: AbortSignal.timeout(5e3)
156
+ });
100
157
  if (response.ok) return;
101
158
  lastError = `healthcheck returned ${response.status}`;
102
159
  } catch (error) {
@@ -110,6 +167,7 @@ async function startElectricAgentsDevEnvironment(_options = {}, env = process.en
110
167
  const fileEnv = readDotEnvFile(cwd);
111
168
  const port = resolveElectricAgentsPort(env, fileEnv);
112
169
  const composeProjectName = resolveComposeProjectName(cwd, env);
170
+ const assertedAuth = buildAssertedAuthHeaders(env, fileEnv);
113
171
  await runDockerCompose([
114
172
  `compose`,
115
173
  `-f`,
@@ -121,7 +179,10 @@ async function startElectricAgentsDevEnvironment(_options = {}, env = process.en
121
179
  COMPOSE_PROJECT_NAME: composeProjectName,
122
180
  ELECTRIC_AGENTS_PORT: String(port),
123
181
  ELECTRIC_IMAGE_TAG: env.ELECTRIC_IMAGE_TAG ?? ELECTRIC_IMAGE_TAG,
124
- ELECTRIC_AGENTS_SERVER_IMAGE_TAG: env.ELECTRIC_AGENTS_SERVER_IMAGE_TAG ?? ELECTRIC_AGENTS_SERVER_IMAGE_TAG
182
+ ELECTRIC_AGENTS_SERVER_IMAGE_TAG: env.ELECTRIC_AGENTS_SERVER_IMAGE_TAG ?? ELECTRIC_AGENTS_SERVER_IMAGE_TAG,
183
+ ELECTRIC_AGENTS_DEV_ASSERTED_AUTH: env.ELECTRIC_AGENTS_DEV_ASSERTED_AUTH ?? fileEnv.ELECTRIC_AGENTS_DEV_ASSERTED_AUTH ?? `1`,
184
+ ELECTRIC_ASSERTED_AUTH_EMAIL: env.ELECTRIC_ASSERTED_AUTH_EMAIL ?? fileEnv.ELECTRIC_ASSERTED_AUTH_EMAIL ?? assertedAuth.ownerUserId,
185
+ ELECTRIC_ASSERTED_AUTH_NAME: env.ELECTRIC_ASSERTED_AUTH_NAME ?? fileEnv.ELECTRIC_ASSERTED_AUTH_NAME ?? assertedAuth.name
125
186
  });
126
187
  const uiUrl = `http://localhost:${port}`;
127
188
  await waitForElectricAgentsServer(uiUrl);
@@ -179,22 +240,29 @@ async function startBuiltinAgentsServer(options, params = {}) {
179
240
  const cwd = params.cwd ?? process.cwd();
180
241
  const fileEnv = readDotEnvFile(cwd);
181
242
  const anthropicApiKey = resolveAnthropicApiKey(options, env, fileEnv);
182
- const host = resolveBuiltinAgentsHost(env, fileEnv);
183
- const port = resolveBuiltinAgentsPort(env, fileEnv);
243
+ const runnerId = resolvePullWakeRunnerId(env, fileEnv);
244
+ const assertedAuth = buildAssertedAuthHeaders(env, fileEnv);
245
+ const serverHeaders = mergeHeaders(assertedAuth.headers, parseAdditionalServerHeaders(env, fileEnv));
184
246
  const agentServerUrl = params.agentServerUrl ?? env.ELECTRIC_AGENTS_URL?.trim() ?? `http://localhost:${resolveElectricAgentsPort(env, fileEnv)}`;
185
247
  process.env.ANTHROPIC_API_KEY = anthropicApiKey;
186
- await waitForElectricAgentsServer(agentServerUrl);
248
+ await waitForElectricAgentsServer(agentServerUrl, { headers: serverHeaders });
187
249
  const server = new BuiltinAgentsServer({
188
250
  agentServerUrl,
189
- host,
190
- port,
191
- workingDirectory: cwd
251
+ workingDirectory: cwd,
252
+ loadProjectMcpConfig: true,
253
+ pullWake: {
254
+ runnerId,
255
+ ownerUserId: assertedAuth.ownerUserId,
256
+ registerRunner: true,
257
+ headers: serverHeaders,
258
+ claimHeaders: serverHeaders,
259
+ claimTokenHeader: hasHeader(serverHeaders, `authorization`) ? `electric-claim-token` : void 0
260
+ }
192
261
  });
193
- await server.start();
262
+ const url = await server.start();
194
263
  const started = {
195
- port,
196
- url: server.url,
197
- registeredBaseUrl: server.registeredBaseUrl,
264
+ runnerId,
265
+ url,
198
266
  agentServerUrl
199
267
  };
200
268
  if (params.printStartedMessage ?? true) console.log(getStartedBuiltinAgentsMessage(started));
@@ -203,4 +271,4 @@ async function startBuiltinAgentsServer(options, params = {}) {
203
271
  }
204
272
 
205
273
  //#endregion
206
- export { getStartedBuiltinAgentsMessage, getStartedEnvironmentMessage, getStoppedEnvironmentMessage, readDotEnvFile, resolveAnthropicApiKey, resolveBuiltinAgentsHost, resolveBuiltinAgentsPort, resolveComposeProjectName, resolveElectricAgentsPort, startBuiltinAgentsServer, startElectricAgentsDevEnvironment, stopElectricAgentsDevEnvironment, waitForElectricAgentsServer };
274
+ export { getStartedBuiltinAgentsMessage, getStartedEnvironmentMessage, getStoppedEnvironmentMessage, readDotEnvFile, resolveAnthropicApiKey, resolveComposeProjectName, resolveElectricAgentsPort, resolvePullWakeOwnerId, resolvePullWakeRunnerId, startBuiltinAgentsServer, startElectricAgentsDevEnvironment, stopElectricAgentsDevEnvironment, waitForElectricAgentsServer };
@@ -38,7 +38,7 @@ services:
38
38
 
39
39
  electric-agents:
40
40
  image: electricax/agents-server:${ELECTRIC_AGENTS_SERVER_IMAGE_TAG:-latest}
41
- pull_policy: always
41
+ pull_policy: ${ELECTRIC_AGENTS_SERVER_PULL_POLICY:-missing}
42
42
  restart: unless-stopped
43
43
  extra_hosts:
44
44
  - 'host.docker.internal:host-gateway'
@@ -59,6 +59,9 @@ services:
59
59
  ELECTRIC_AGENTS_STREAMS_PORT: ${ELECTRIC_AGENTS_STREAMS_PORT:-0}
60
60
  ELECTRIC_AGENTS_ELECTRIC_URL: ${ELECTRIC_AGENTS_ELECTRIC_URL:-http://electric:3000}
61
61
  ELECTRIC_AGENTS_REWRITE_LOOPBACK_WEBHOOKS_TO: ${ELECTRIC_AGENTS_REWRITE_LOOPBACK_WEBHOOKS_TO:-host.docker.internal}
62
+ ELECTRIC_AGENTS_DEV_ASSERTED_AUTH: ${ELECTRIC_AGENTS_DEV_ASSERTED_AUTH:-1}
63
+ ELECTRIC_ASSERTED_AUTH_EMAIL: ${ELECTRIC_ASSERTED_AUTH_EMAIL:-builtin-agents}
64
+ ELECTRIC_ASSERTED_AUTH_NAME: ${ELECTRIC_ASSERTED_AUTH_NAME:-Built-in agents}
62
65
  ports:
63
66
  - '${ELECTRIC_AGENTS_PORT:-4437}:4437'
64
67
  volumes:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "electric-ax",
3
- "version": "0.1.17",
3
+ "version": "0.2.0",
4
4
  "description": "CLI for Electric Agents",
5
5
  "author": "ElectricSQL team and contributors",
6
6
  "license": "Apache-2.0",
@@ -37,17 +37,17 @@
37
37
  "./package.json": "./package.json"
38
38
  },
39
39
  "dependencies": {
40
- "@durable-streams/client": "npm:@electric-ax/durable-streams-client-beta@^0.3.0",
41
- "@durable-streams/state": "npm:@electric-ax/durable-streams-state-beta@^0.3.0",
42
- "@electric-sql/client": "^1.5.16",
40
+ "@durable-streams/client": "https://pkg.pr.new/durable-streams/durable-streams/@durable-streams/client@350",
41
+ "@durable-streams/state": "https://pkg.pr.new/durable-streams/durable-streams/@durable-streams/state@350",
42
+ "@electric-sql/client": "^1.5.17",
43
43
  "@tanstack/db": "^0.6.4",
44
44
  "@tanstack/react-db": "^0.1.82",
45
45
  "commander": "^13.1.0",
46
46
  "ink": "^6.8.0",
47
47
  "omelette": "^0.4.17",
48
48
  "react": "^19.2.0",
49
- "@electric-ax/agents": "0.2.4",
50
- "@electric-ax/agents-runtime": "0.1.2"
49
+ "@electric-ax/agents": "0.4.0",
50
+ "@electric-ax/agents-runtime": "0.2.0"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@vitest/coverage-v8": "^4.1.0",
File without changes
File without changes