electric-ax 0.1.14 → 0.1.16

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.
@@ -1,4 +1,4 @@
1
- import { ElectricCliEnv } from "./index-BNTf2uZL.cjs";
1
+ import { ElectricCliEnv } from "./index-Dgoj3VzA.cjs";
2
2
 
3
3
  //#region src/completions.d.ts
4
4
  declare function fetchEntityTypeNames(env: ElectricCliEnv): Promise<Array<string>>;
@@ -1,4 +1,4 @@
1
- import { ElectricCliEnv } from "./index-DS2RpaPZ.js";
1
+ import { ElectricCliEnv } from "./index-si6k6XW1.js";
2
2
 
3
3
  //#region src/completions.d.ts
4
4
  declare function fetchEntityTypeNames(env: ElectricCliEnv): Promise<Array<string>>;
@@ -42,6 +42,7 @@ declare function startBuiltinAgentsServer(options: StartBuiltinCommandOptions, p
42
42
  env?: NodeJS.ProcessEnv;
43
43
  cwd?: string;
44
44
  agentServerUrl?: string;
45
+ printStartedMessage?: boolean;
45
46
  }): Promise<StartedBuiltinAgentsEnvironment>;
46
47
 
47
48
  //#endregion
@@ -93,7 +94,19 @@ interface InvocationEnv {
93
94
  npm_command?: string;
94
95
  npm_config_user_agent?: string;
95
96
  }
97
+ interface QuickstartBackendStartedMessageOptions {
98
+ commandPrefix: string;
99
+ uiUrl: string;
100
+ color?: boolean;
101
+ trueColor?: boolean;
102
+ }
96
103
  declare function getElectricCliEnv(env?: NodeJS.ProcessEnv): ElectricCliEnv;
104
+ declare function formatQuickstartBackendStartedMessage({
105
+ commandPrefix,
106
+ uiUrl,
107
+ color,
108
+ trueColor
109
+ }: QuickstartBackendStartedMessageOptions): string;
97
110
  declare function resolveCommandPrefix(argv: Array<string>, env?: InvocationEnv): string;
98
111
  declare function createElectricCliHandlers(env: ElectricCliEnv, commandPrefix?: string): ElectricCliHandlers;
99
112
  declare function createElectricProgram({
@@ -110,4 +123,4 @@ declare function createElectricProgram({
110
123
  declare function run(argv?: Array<string>): Promise<void>;
111
124
 
112
125
  //#endregion
113
- export { DEFAULT_ELECTRIC_AGENTS_URL, ElectricCliEnv, ElectricCliHandlers, ObserveCommandOptions, PsCommandOptions, SendCommandOptions, SpawnCommandOptions, StartBuiltinCommandOptions, StartCommandOptions, StartedBuiltinAgentsEnvironment, StartedDevEnvironment, StopCommandOptions, StoppedDevEnvironment, createElectricCliHandlers, createElectricProgram, getElectricCliEnv, getStartedBuiltinAgentsMessage, getStartedEnvironmentMessage, getStoppedEnvironmentMessage, readDotEnvFile, resolveAnthropicApiKey, resolveBuiltinAgentsPort, resolveCommandPrefix, resolveComposeProjectName, resolveElectricAgentsPort, run, startBuiltinAgentsServer, startElectricAgentsDevEnvironment, stopElectricAgentsDevEnvironment, waitForElectricAgentsServer };
126
+ export { DEFAULT_ELECTRIC_AGENTS_URL, ElectricCliEnv, ElectricCliHandlers, ObserveCommandOptions, PsCommandOptions, SendCommandOptions, SpawnCommandOptions, StartBuiltinCommandOptions, StartCommandOptions, StartedBuiltinAgentsEnvironment, StartedDevEnvironment, StopCommandOptions, StoppedDevEnvironment, createElectricCliHandlers, createElectricProgram, formatQuickstartBackendStartedMessage, getElectricCliEnv, getStartedBuiltinAgentsMessage, getStartedEnvironmentMessage, getStoppedEnvironmentMessage, readDotEnvFile, resolveAnthropicApiKey, resolveBuiltinAgentsPort, resolveCommandPrefix, resolveComposeProjectName, resolveElectricAgentsPort, run, startBuiltinAgentsServer, startElectricAgentsDevEnvironment, stopElectricAgentsDevEnvironment, waitForElectricAgentsServer };
@@ -42,6 +42,7 @@ declare function startBuiltinAgentsServer(options: StartBuiltinCommandOptions, p
42
42
  env?: NodeJS.ProcessEnv;
43
43
  cwd?: string;
44
44
  agentServerUrl?: string;
45
+ printStartedMessage?: boolean;
45
46
  }): Promise<StartedBuiltinAgentsEnvironment>;
46
47
 
47
48
  //#endregion
@@ -93,7 +94,19 @@ interface InvocationEnv {
93
94
  npm_command?: string;
94
95
  npm_config_user_agent?: string;
95
96
  }
97
+ interface QuickstartBackendStartedMessageOptions {
98
+ commandPrefix: string;
99
+ uiUrl: string;
100
+ color?: boolean;
101
+ trueColor?: boolean;
102
+ }
96
103
  declare function getElectricCliEnv(env?: NodeJS.ProcessEnv): ElectricCliEnv;
104
+ declare function formatQuickstartBackendStartedMessage({
105
+ commandPrefix,
106
+ uiUrl,
107
+ color,
108
+ trueColor
109
+ }: QuickstartBackendStartedMessageOptions): string;
97
110
  declare function resolveCommandPrefix(argv: Array<string>, env?: InvocationEnv): string;
98
111
  declare function createElectricCliHandlers(env: ElectricCliEnv, commandPrefix?: string): ElectricCliHandlers;
99
112
  declare function createElectricProgram({
@@ -110,4 +123,4 @@ declare function createElectricProgram({
110
123
  declare function run(argv?: Array<string>): Promise<void>;
111
124
 
112
125
  //#endregion
113
- export { DEFAULT_ELECTRIC_AGENTS_URL, ElectricCliEnv, ElectricCliHandlers, ObserveCommandOptions, PsCommandOptions, SendCommandOptions, SpawnCommandOptions, StartBuiltinCommandOptions, StartCommandOptions, StartedBuiltinAgentsEnvironment, StartedDevEnvironment, StopCommandOptions, StoppedDevEnvironment, createElectricCliHandlers, createElectricProgram, getElectricCliEnv, getStartedBuiltinAgentsMessage, getStartedEnvironmentMessage, getStoppedEnvironmentMessage, readDotEnvFile, resolveAnthropicApiKey, resolveBuiltinAgentsPort, resolveCommandPrefix, resolveComposeProjectName, resolveElectricAgentsPort, run, startBuiltinAgentsServer, startElectricAgentsDevEnvironment, stopElectricAgentsDevEnvironment, waitForElectricAgentsServer };
126
+ export { DEFAULT_ELECTRIC_AGENTS_URL, ElectricCliEnv, ElectricCliHandlers, ObserveCommandOptions, PsCommandOptions, SendCommandOptions, SpawnCommandOptions, StartBuiltinCommandOptions, StartCommandOptions, StartedBuiltinAgentsEnvironment, StartedDevEnvironment, StopCommandOptions, StoppedDevEnvironment, createElectricCliHandlers, createElectricProgram, formatQuickstartBackendStartedMessage, getElectricCliEnv, getStartedBuiltinAgentsMessage, getStartedEnvironmentMessage, getStoppedEnvironmentMessage, readDotEnvFile, resolveAnthropicApiKey, resolveBuiltinAgentsPort, resolveCommandPrefix, resolveComposeProjectName, resolveElectricAgentsPort, run, startBuiltinAgentsServer, startElectricAgentsDevEnvironment, stopElectricAgentsDevEnvironment, waitForElectricAgentsServer };
package/dist/index.cjs CHANGED
@@ -11,33 +11,11 @@ const commander = require_chunk.__toESM(require("commander"));
11
11
  const node_readline_promises = require_chunk.__toESM(require("node:readline/promises"));
12
12
 
13
13
  //#region src/prompt-api-key.ts
14
- const FRIENDLY_INTRO = [
15
- ``,
16
- `The Electric Agents quickstart requires setting an API key to connect to an LLM provider.`,
17
- ``,
18
- `Currently (in this initial developer preview release) the key must be an ANTHROPIC_API_KEY.`,
19
- `Support for other LLM providers is coming very soon.`,
20
- ``,
21
- `Note that your API key never leaves your local computer. It's used by the agent entities`,
22
- `installed by default in the Electric Agents runtime and by the example agents included`,
23
- `in the quickstart template.`,
24
- ``,
25
- `Would you like to:`,
26
- ``,
27
- ` 1. manually setup a .env file with ANTHROPIC_API_KEY=...`,
28
- ` 2. paste your api key into a prompt and we'll set it up for you`,
29
- ``
30
- ].join(`\n`);
31
- const MANUAL_SETUP_INSTRUCTIONS = [
32
- ``,
33
- `No problem. To finish setup:`,
34
- ``,
35
- ` 1. Get a key from https://console.anthropic.com/settings/keys`,
36
- ` 2. Add a line to your .env file:`,
37
- ` ANTHROPIC_API_KEY=sk-ant-...`,
38
- ` 3. Re-run the command.`,
39
- ``
40
- ].join(`\n`);
14
+ const FRIENDLY_INTRO = `\n${[
15
+ `Provide an Anthropic Claude key to connect Electric Agents to an LLM provider. Support for other LLM providers is coming soon.`,
16
+ `Your API key never leaves your local computer. It's used by the agent entities installed by default in the Electric Agents runtime and by the example agents included in the quickstart template.`,
17
+ `Paste the Anthropic key, or press Enter without typing a key to cancel and set it up manually in .env or pass --anthropic-api-key on the command line.`
18
+ ].join(`\n\n`)}\n\n`;
41
19
  function defaultPromptIO() {
42
20
  return {
43
21
  input: process.stdin,
@@ -47,37 +25,91 @@ function defaultPromptIO() {
47
25
  exit: process.exit.bind(process)
48
26
  };
49
27
  }
28
+ function parsePastedAnthropicApiKey(input) {
29
+ const trimmed = input.trim();
30
+ const match = trimmed.match(/^(?:export\s+)?ANTHROPIC_API_KEY\s*[:=]\s*(.*)$/s);
31
+ const value = (match?.[1] ?? trimmed).trim();
32
+ if (value.startsWith(`"`) && value.endsWith(`"`) || value.startsWith(`'`) && value.endsWith(`'`)) return value.slice(1, -1).trim();
33
+ return value;
34
+ }
35
+ function assertAnthropicApiKeyPrefix(key) {
36
+ if (!key.startsWith(`sk-ant-`)) throw new Error(`ANTHROPIC_API_KEY must look like an Anthropic API key (expected it to start with sk-ant-).`);
37
+ }
38
+ async function validateAnthropicApiKey(key, fetchImpl = globalThis.fetch) {
39
+ let response;
40
+ try {
41
+ response = await fetchImpl(`https://api.anthropic.com/v1/models?limit=1`, {
42
+ headers: {
43
+ "anthropic-version": `2023-06-01`,
44
+ "x-api-key": key
45
+ },
46
+ signal: AbortSignal.timeout(5e3)
47
+ });
48
+ } catch (error) {
49
+ throw new Error(`Could not validate ANTHROPIC_API_KEY: ${error instanceof Error ? error.message : String(error)}`);
50
+ }
51
+ if (response.ok) return;
52
+ const body = await response.text();
53
+ let detail = body.trim();
54
+ try {
55
+ const parsed = JSON.parse(body);
56
+ detail = parsed.error?.message ?? detail;
57
+ } catch {}
58
+ if (response.status === 401 || response.status === 403) throw new Error(`ANTHROPIC_API_KEY validation failed: ${detail || response.statusText}`);
59
+ throw new Error(`Could not validate ANTHROPIC_API_KEY: Anthropic returned ${response.status}${detail ? ` (${detail})` : ``}`);
60
+ }
50
61
  async function ensureAnthropicApiKey(options, io = defaultPromptIO()) {
62
+ const validate = io.validateAnthropicApiKey ?? validateAnthropicApiKey;
63
+ const explicitKey = options.anthropicApiKey?.trim();
64
+ let initialError;
65
+ if (explicitKey) {
66
+ assertAnthropicApiKeyPrefix(explicitKey);
67
+ await validate(explicitKey);
68
+ return explicitKey;
69
+ }
51
70
  try {
52
- return require_env.resolveAnthropicApiKey(options);
71
+ const key = require_env.resolveAnthropicApiKey(options);
72
+ assertAnthropicApiKeyPrefix(key);
73
+ await validate(key);
74
+ return key;
53
75
  } catch (error) {
54
76
  if (!io.isTTY) throw error;
77
+ initialError = error;
55
78
  }
56
79
  io.output.write(FRIENDLY_INTRO);
80
+ if (initialError) io.output.write(`${formatValidationError(initialError)}\n\n`);
57
81
  const rl = (0, node_readline_promises.createInterface)({
58
82
  input: io.input,
59
83
  output: io.output
60
84
  });
61
85
  try {
62
- const choice = (await rl.question(`Enter 1/2: `)).trim();
63
- if (choice === `1`) {
64
- io.output.write(MANUAL_SETUP_INSTRUCTIONS);
65
- io.exit(0);
66
- throw new Error(`unreachable`);
67
- }
68
- if (choice === `2`) {
69
- const key = (await rl.question(`Paste your ANTHROPIC_API_KEY: `)).trim();
70
- if (!key) throw new Error(`No API key provided. Re-run the command and try again.`);
86
+ for (;;) {
87
+ const pasted = await rl.question(`ANTHROPIC_API_KEY: `);
88
+ const key = parsePastedAnthropicApiKey(pasted);
89
+ if (!key) {
90
+ io.output.write(`\nCancelled. Add ANTHROPIC_API_KEY=sk-ant-... to .env and re-run the command.\n\n`);
91
+ io.exit(0);
92
+ throw new Error(`unreachable`);
93
+ }
94
+ try {
95
+ assertAnthropicApiKeyPrefix(key);
96
+ await validate(key);
97
+ } catch (error) {
98
+ io.output.write(`${formatValidationError(error)}\n\n`);
99
+ continue;
100
+ }
71
101
  const envPath = writeApiKeyToDotEnv(key, io.cwd);
72
102
  process.env.ANTHROPIC_API_KEY = key;
73
103
  io.output.write(`\nSaved ANTHROPIC_API_KEY to ${envPath}\n\n`);
74
104
  return key;
75
105
  }
76
- throw new Error(`Unrecognized choice ${JSON.stringify(choice)}. Please re-run the command and enter 1 or 2.`);
77
106
  } finally {
78
107
  rl.close();
79
108
  }
80
109
  }
110
+ function formatValidationError(error) {
111
+ return `Could not use that Anthropic key: ${error instanceof Error ? error.message : String(error)}`;
112
+ }
81
113
  function writeApiKeyToDotEnv(key, cwd) {
82
114
  const envPath = (0, node_path.resolve)(cwd, `.env`);
83
115
  const newLine = `ANTHROPIC_API_KEY=${key}`;
@@ -150,6 +182,79 @@ function resolveCommandName(argv) {
150
182
  function commandExample(commandName) {
151
183
  return `${commandName} agents`;
152
184
  }
185
+ function stripAnsi(value) {
186
+ return value.replace(/\x1b\[[0-9;]*m/g, ``);
187
+ }
188
+ function visibleLength(value) {
189
+ return stripAnsi(value).length;
190
+ }
191
+ function supportsColor(output = process.stdout, env = process.env) {
192
+ if (env.NO_COLOR !== void 0) return false;
193
+ if (env.FORCE_COLOR !== void 0 && env.FORCE_COLOR !== `0`) return true;
194
+ if (!output.isTTY) return false;
195
+ return output.getColorDepth(env) >= 4;
196
+ }
197
+ function supportsTrueColor(output = process.stdout, env = process.env) {
198
+ if (!supportsColor(output, env)) return false;
199
+ return output.getColorDepth(env) >= 24;
200
+ }
201
+ function colorBorder(value, opts) {
202
+ if (!opts.color) return value;
203
+ const color = opts.trueColor ? `\x1b[38;2;86;232;234m` : `\x1b[36m`;
204
+ return `${color}${value}\x1b[0m`;
205
+ }
206
+ function styleCommandPrefix(commandPrefix) {
207
+ return commandPrefix;
208
+ }
209
+ function styleElectricAgentsWithOptions(value, opts) {
210
+ if (!opts.color) return value;
211
+ const color = opts.trueColor ? `\x1b[38;2;86;232;234m` : `\x1b[36m`;
212
+ return `\x1b[1m${color}${value}\x1b[0m`;
213
+ }
214
+ function underline(value, color) {
215
+ if (!color) return value;
216
+ return `\x1b[4m${value}\x1b[24m`;
217
+ }
218
+ function grey(value, color) {
219
+ if (!color) return value;
220
+ return `\x1b[90m${value}\x1b[0m`;
221
+ }
222
+ function boxLines(lines, opts) {
223
+ const width = Math.max(...lines.map(visibleLength));
224
+ const top = colorBorder(`╔${`═`.repeat(width + 2)}╗`, opts);
225
+ const body = lines.map((line) => {
226
+ const padding = ` `.repeat(width - visibleLength(line));
227
+ return `${colorBorder(`║`, opts)} ${line}${padding} ` + colorBorder(`║`, opts);
228
+ });
229
+ const bottom = colorBorder(`╚${`═`.repeat(width + 2)}╝`, opts);
230
+ return [
231
+ top,
232
+ ...body,
233
+ bottom
234
+ ].join(`\n`);
235
+ }
236
+ function formatQuickstartBackendStartedMessage({ commandPrefix, uiUrl, color = supportsColor(), trueColor = supportsTrueColor() }) {
237
+ const styledCommandPrefix = styleCommandPrefix(commandPrefix);
238
+ return boxLines([
239
+ `${styleElectricAgentsWithOptions(`electric agents`, {
240
+ color,
241
+ trueColor
242
+ })} server is up`,
243
+ ``,
244
+ `Open a separate terminal and run:`,
245
+ ` ${styledCommandPrefix} spawn /horton/onboarding`,
246
+ ` ${styledCommandPrefix} send /horton/onboarding "Onboard me to Electric Agents"`,
247
+ ``,
248
+ `You can see all agents in the UI or CLI:`,
249
+ ` UI: ${underline(uiUrl, color)}`,
250
+ ` CLI: ${styledCommandPrefix} observe /horton/onboarding`,
251
+ ``,
252
+ grey(`Keep this window open to run the built-in agents`, color)
253
+ ], {
254
+ color,
255
+ trueColor
256
+ });
257
+ }
153
258
  function resolveCommandPrefix(argv, env = process.env) {
154
259
  if (env.npm_command === `exec`) {
155
260
  const userAgent = env.npm_config_user_agent ?? ``;
@@ -345,7 +450,11 @@ function createElectricCliHandlers(env, commandPrefix = commandExample(`electric
345
450
  return started;
346
451
  },
347
452
  startBuiltin: async (options) => {
348
- options.anthropicApiKey = await ensureAnthropicApiKey(options);
453
+ try {
454
+ options.anthropicApiKey = await ensureAnthropicApiKey(options);
455
+ } catch (error) {
456
+ fail(getErrorMessage(error));
457
+ }
349
458
  const { startBuiltinAgentsServer } = await loadStartModule();
350
459
  return startBuiltinAgentsServer(options, { agentServerUrl: env.electricAgentsUrl });
351
460
  },
@@ -356,24 +465,24 @@ function createElectricCliHandlers(env, commandPrefix = commandExample(`electric
356
465
  return stopped;
357
466
  },
358
467
  quickstart: async (options) => {
359
- options.anthropicApiKey = await ensureAnthropicApiKey(options);
468
+ try {
469
+ options.anthropicApiKey = await ensureAnthropicApiKey(options);
470
+ } catch (error) {
471
+ fail(getErrorMessage(error));
472
+ }
360
473
  const { startBuiltinAgentsServer, startElectricAgentsDevEnvironment } = await loadStartModule();
361
474
  const started = await startElectricAgentsDevEnvironment();
362
475
  printStartedEnvironment(started);
363
476
  console.log(``);
364
- console.log([
365
- `electric agents server is up`,
366
- ``,
367
- `Open a separate terminal and run:`,
368
- ` ${commandPrefix} spawn /horton/onboarding`,
369
- ` ${commandPrefix} send /horton/onboarding "Please walk me through onboarding for the Electric agents"`,
370
- ` ${commandPrefix} observe /horton/onboarding`,
371
- ``,
372
- `UI: ${started.uiUrl}`,
373
- `This terminal will now run the built-in Horton server in the foreground.`
374
- ].join(`\n`));
477
+ console.log(formatQuickstartBackendStartedMessage({
478
+ commandPrefix,
479
+ uiUrl: started.uiUrl
480
+ }));
375
481
  console.log(``);
376
- await startBuiltinAgentsServer(options, { agentServerUrl: started.uiUrl });
482
+ await startBuiltinAgentsServer(options, {
483
+ agentServerUrl: started.uiUrl,
484
+ printStartedMessage: false
485
+ });
377
486
  },
378
487
  init: async (projectName) => {
379
488
  const { initProject } = await import(`./init.js`);
@@ -392,8 +501,8 @@ Environment:
392
501
  Examples:
393
502
  $ ${agentsCommand} types
394
503
  $ ${agentsCommand} spawn /horton/onboarding
395
- $ ${agentsCommand} send /horton/onboarding "Please walk me through onboarding for the Electric agents"
396
- $ ${agentsCommand} observe /horton/onboarding --from 0
504
+ $ ${agentsCommand} send /horton/onboarding "Help me onboard to Electric Agents"
505
+ $ ${agentsCommand} observe /horton/onboarding
397
506
  $ ${agentsCommand} start
398
507
  $ ${agentsCommand} start-builtin --anthropic-api-key sk-ant-...
399
508
  $ ${agentsCommand} stop --remove-volumes
@@ -524,6 +633,7 @@ if (isMainModule()) run().catch((error) => {
524
633
  exports.DEFAULT_ELECTRIC_AGENTS_URL = DEFAULT_ELECTRIC_AGENTS_URL
525
634
  exports.createElectricCliHandlers = createElectricCliHandlers
526
635
  exports.createElectricProgram = createElectricProgram
636
+ exports.formatQuickstartBackendStartedMessage = formatQuickstartBackendStartedMessage
527
637
  exports.getElectricCliEnv = getElectricCliEnv
528
638
  exports.resolveCommandPrefix = resolveCommandPrefix
529
639
  exports.run = run
package/dist/index.d.cts CHANGED
@@ -1,3 +1,3 @@
1
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-BNTf2uZL.cjs";
3
- export { DEFAULT_ELECTRIC_AGENTS_URL, ElectricCliEnv, ElectricCliHandlers, ObserveCommandOptions, PsCommandOptions, SendCommandOptions, SpawnCommandOptions, StartBuiltinCommandOptions, StartCommandOptions, StartedBuiltinAgentsEnvironment, StartedDevEnvironment, StopCommandOptions, StoppedDevEnvironment, createElectricCliHandlers, createElectricProgram, getElectricCliEnv, resolveCommandPrefix, run };
2
+ import { DEFAULT_ELECTRIC_AGENTS_URL, ElectricCliEnv, ElectricCliHandlers, ObserveCommandOptions, PsCommandOptions, SendCommandOptions, SpawnCommandOptions, StartBuiltinCommandOptions, StartCommandOptions, StartedBuiltinAgentsEnvironment, StartedDevEnvironment, StopCommandOptions, StoppedDevEnvironment, createElectricCliHandlers, createElectricProgram, formatQuickstartBackendStartedMessage, getElectricCliEnv, resolveCommandPrefix, run } from "./index-Dgoj3VzA.cjs";
3
+ export { DEFAULT_ELECTRIC_AGENTS_URL, ElectricCliEnv, ElectricCliHandlers, ObserveCommandOptions, PsCommandOptions, SendCommandOptions, SpawnCommandOptions, StartBuiltinCommandOptions, StartCommandOptions, StartedBuiltinAgentsEnvironment, StartedDevEnvironment, StopCommandOptions, StoppedDevEnvironment, createElectricCliHandlers, createElectricProgram, formatQuickstartBackendStartedMessage, getElectricCliEnv, resolveCommandPrefix, run };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
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-DS2RpaPZ.js";
3
- export { DEFAULT_ELECTRIC_AGENTS_URL, ElectricCliEnv, ElectricCliHandlers, ObserveCommandOptions, PsCommandOptions, SendCommandOptions, SpawnCommandOptions, StartBuiltinCommandOptions, StartCommandOptions, StartedBuiltinAgentsEnvironment, StartedDevEnvironment, StopCommandOptions, StoppedDevEnvironment, createElectricCliHandlers, createElectricProgram, getElectricCliEnv, resolveCommandPrefix, run };
2
+ import { DEFAULT_ELECTRIC_AGENTS_URL, ElectricCliEnv, ElectricCliHandlers, ObserveCommandOptions, PsCommandOptions, SendCommandOptions, SpawnCommandOptions, StartBuiltinCommandOptions, StartCommandOptions, StartedBuiltinAgentsEnvironment, StartedDevEnvironment, StopCommandOptions, StoppedDevEnvironment, createElectricCliHandlers, createElectricProgram, formatQuickstartBackendStartedMessage, getElectricCliEnv, resolveCommandPrefix, run } from "./index-si6k6XW1.js";
3
+ export { DEFAULT_ELECTRIC_AGENTS_URL, ElectricCliEnv, ElectricCliHandlers, ObserveCommandOptions, PsCommandOptions, SendCommandOptions, SpawnCommandOptions, StartBuiltinCommandOptions, StartCommandOptions, StartedBuiltinAgentsEnvironment, StartedDevEnvironment, StopCommandOptions, StoppedDevEnvironment, createElectricCliHandlers, createElectricProgram, formatQuickstartBackendStartedMessage, getElectricCliEnv, resolveCommandPrefix, run };
package/dist/index.js CHANGED
@@ -9,33 +9,11 @@ import { Command } from "commander";
9
9
  import { createInterface } from "node:readline/promises";
10
10
 
11
11
  //#region src/prompt-api-key.ts
12
- const FRIENDLY_INTRO = [
13
- ``,
14
- `The Electric Agents quickstart requires setting an API key to connect to an LLM provider.`,
15
- ``,
16
- `Currently (in this initial developer preview release) the key must be an ANTHROPIC_API_KEY.`,
17
- `Support for other LLM providers is coming very soon.`,
18
- ``,
19
- `Note that your API key never leaves your local computer. It's used by the agent entities`,
20
- `installed by default in the Electric Agents runtime and by the example agents included`,
21
- `in the quickstart template.`,
22
- ``,
23
- `Would you like to:`,
24
- ``,
25
- ` 1. manually setup a .env file with ANTHROPIC_API_KEY=...`,
26
- ` 2. paste your api key into a prompt and we'll set it up for you`,
27
- ``
28
- ].join(`\n`);
29
- const MANUAL_SETUP_INSTRUCTIONS = [
30
- ``,
31
- `No problem. To finish setup:`,
32
- ``,
33
- ` 1. Get a key from https://console.anthropic.com/settings/keys`,
34
- ` 2. Add a line to your .env file:`,
35
- ` ANTHROPIC_API_KEY=sk-ant-...`,
36
- ` 3. Re-run the command.`,
37
- ``
38
- ].join(`\n`);
12
+ const FRIENDLY_INTRO = `\n${[
13
+ `Provide an Anthropic Claude key to connect Electric Agents to an LLM provider. Support for other LLM providers is coming soon.`,
14
+ `Your API key never leaves your local computer. It's used by the agent entities installed by default in the Electric Agents runtime and by the example agents included in the quickstart template.`,
15
+ `Paste the Anthropic key, or press Enter without typing a key to cancel and set it up manually in .env or pass --anthropic-api-key on the command line.`
16
+ ].join(`\n\n`)}\n\n`;
39
17
  function defaultPromptIO() {
40
18
  return {
41
19
  input: process.stdin,
@@ -45,37 +23,91 @@ function defaultPromptIO() {
45
23
  exit: process.exit.bind(process)
46
24
  };
47
25
  }
26
+ function parsePastedAnthropicApiKey(input) {
27
+ const trimmed = input.trim();
28
+ const match = trimmed.match(/^(?:export\s+)?ANTHROPIC_API_KEY\s*[:=]\s*(.*)$/s);
29
+ const value = (match?.[1] ?? trimmed).trim();
30
+ if (value.startsWith(`"`) && value.endsWith(`"`) || value.startsWith(`'`) && value.endsWith(`'`)) return value.slice(1, -1).trim();
31
+ return value;
32
+ }
33
+ function assertAnthropicApiKeyPrefix(key) {
34
+ if (!key.startsWith(`sk-ant-`)) throw new Error(`ANTHROPIC_API_KEY must look like an Anthropic API key (expected it to start with sk-ant-).`);
35
+ }
36
+ async function validateAnthropicApiKey(key, fetchImpl = globalThis.fetch) {
37
+ let response;
38
+ try {
39
+ response = await fetchImpl(`https://api.anthropic.com/v1/models?limit=1`, {
40
+ headers: {
41
+ "anthropic-version": `2023-06-01`,
42
+ "x-api-key": key
43
+ },
44
+ signal: AbortSignal.timeout(5e3)
45
+ });
46
+ } catch (error) {
47
+ throw new Error(`Could not validate ANTHROPIC_API_KEY: ${error instanceof Error ? error.message : String(error)}`);
48
+ }
49
+ if (response.ok) return;
50
+ const body = await response.text();
51
+ let detail = body.trim();
52
+ try {
53
+ const parsed = JSON.parse(body);
54
+ detail = parsed.error?.message ?? detail;
55
+ } catch {}
56
+ if (response.status === 401 || response.status === 403) throw new Error(`ANTHROPIC_API_KEY validation failed: ${detail || response.statusText}`);
57
+ throw new Error(`Could not validate ANTHROPIC_API_KEY: Anthropic returned ${response.status}${detail ? ` (${detail})` : ``}`);
58
+ }
48
59
  async function ensureAnthropicApiKey(options, io = defaultPromptIO()) {
60
+ const validate = io.validateAnthropicApiKey ?? validateAnthropicApiKey;
61
+ const explicitKey = options.anthropicApiKey?.trim();
62
+ let initialError;
63
+ if (explicitKey) {
64
+ assertAnthropicApiKeyPrefix(explicitKey);
65
+ await validate(explicitKey);
66
+ return explicitKey;
67
+ }
49
68
  try {
50
- return resolveAnthropicApiKey(options);
69
+ const key = resolveAnthropicApiKey(options);
70
+ assertAnthropicApiKeyPrefix(key);
71
+ await validate(key);
72
+ return key;
51
73
  } catch (error) {
52
74
  if (!io.isTTY) throw error;
75
+ initialError = error;
53
76
  }
54
77
  io.output.write(FRIENDLY_INTRO);
78
+ if (initialError) io.output.write(`${formatValidationError(initialError)}\n\n`);
55
79
  const rl = createInterface({
56
80
  input: io.input,
57
81
  output: io.output
58
82
  });
59
83
  try {
60
- const choice = (await rl.question(`Enter 1/2: `)).trim();
61
- if (choice === `1`) {
62
- io.output.write(MANUAL_SETUP_INSTRUCTIONS);
63
- io.exit(0);
64
- throw new Error(`unreachable`);
65
- }
66
- if (choice === `2`) {
67
- const key = (await rl.question(`Paste your ANTHROPIC_API_KEY: `)).trim();
68
- if (!key) throw new Error(`No API key provided. Re-run the command and try again.`);
84
+ for (;;) {
85
+ const pasted = await rl.question(`ANTHROPIC_API_KEY: `);
86
+ const key = parsePastedAnthropicApiKey(pasted);
87
+ if (!key) {
88
+ io.output.write(`\nCancelled. Add ANTHROPIC_API_KEY=sk-ant-... to .env and re-run the command.\n\n`);
89
+ io.exit(0);
90
+ throw new Error(`unreachable`);
91
+ }
92
+ try {
93
+ assertAnthropicApiKeyPrefix(key);
94
+ await validate(key);
95
+ } catch (error) {
96
+ io.output.write(`${formatValidationError(error)}\n\n`);
97
+ continue;
98
+ }
69
99
  const envPath = writeApiKeyToDotEnv(key, io.cwd);
70
100
  process.env.ANTHROPIC_API_KEY = key;
71
101
  io.output.write(`\nSaved ANTHROPIC_API_KEY to ${envPath}\n\n`);
72
102
  return key;
73
103
  }
74
- throw new Error(`Unrecognized choice ${JSON.stringify(choice)}. Please re-run the command and enter 1 or 2.`);
75
104
  } finally {
76
105
  rl.close();
77
106
  }
78
107
  }
108
+ function formatValidationError(error) {
109
+ return `Could not use that Anthropic key: ${error instanceof Error ? error.message : String(error)}`;
110
+ }
79
111
  function writeApiKeyToDotEnv(key, cwd) {
80
112
  const envPath = resolve(cwd, `.env`);
81
113
  const newLine = `ANTHROPIC_API_KEY=${key}`;
@@ -148,6 +180,79 @@ function resolveCommandName(argv) {
148
180
  function commandExample(commandName) {
149
181
  return `${commandName} agents`;
150
182
  }
183
+ function stripAnsi(value) {
184
+ return value.replace(/\x1b\[[0-9;]*m/g, ``);
185
+ }
186
+ function visibleLength(value) {
187
+ return stripAnsi(value).length;
188
+ }
189
+ function supportsColor(output = process.stdout, env = process.env) {
190
+ if (env.NO_COLOR !== void 0) return false;
191
+ if (env.FORCE_COLOR !== void 0 && env.FORCE_COLOR !== `0`) return true;
192
+ if (!output.isTTY) return false;
193
+ return output.getColorDepth(env) >= 4;
194
+ }
195
+ function supportsTrueColor(output = process.stdout, env = process.env) {
196
+ if (!supportsColor(output, env)) return false;
197
+ return output.getColorDepth(env) >= 24;
198
+ }
199
+ function colorBorder(value, opts) {
200
+ if (!opts.color) return value;
201
+ const color = opts.trueColor ? `\x1b[38;2;86;232;234m` : `\x1b[36m`;
202
+ return `${color}${value}\x1b[0m`;
203
+ }
204
+ function styleCommandPrefix(commandPrefix) {
205
+ return commandPrefix;
206
+ }
207
+ function styleElectricAgentsWithOptions(value, opts) {
208
+ if (!opts.color) return value;
209
+ const color = opts.trueColor ? `\x1b[38;2;86;232;234m` : `\x1b[36m`;
210
+ return `\x1b[1m${color}${value}\x1b[0m`;
211
+ }
212
+ function underline(value, color) {
213
+ if (!color) return value;
214
+ return `\x1b[4m${value}\x1b[24m`;
215
+ }
216
+ function grey(value, color) {
217
+ if (!color) return value;
218
+ return `\x1b[90m${value}\x1b[0m`;
219
+ }
220
+ function boxLines(lines, opts) {
221
+ const width = Math.max(...lines.map(visibleLength));
222
+ const top = colorBorder(`╔${`═`.repeat(width + 2)}╗`, opts);
223
+ const body = lines.map((line) => {
224
+ const padding = ` `.repeat(width - visibleLength(line));
225
+ return `${colorBorder(`║`, opts)} ${line}${padding} ` + colorBorder(`║`, opts);
226
+ });
227
+ const bottom = colorBorder(`╚${`═`.repeat(width + 2)}╝`, opts);
228
+ return [
229
+ top,
230
+ ...body,
231
+ bottom
232
+ ].join(`\n`);
233
+ }
234
+ function formatQuickstartBackendStartedMessage({ commandPrefix, uiUrl, color = supportsColor(), trueColor = supportsTrueColor() }) {
235
+ const styledCommandPrefix = styleCommandPrefix(commandPrefix);
236
+ return boxLines([
237
+ `${styleElectricAgentsWithOptions(`electric agents`, {
238
+ color,
239
+ trueColor
240
+ })} server is up`,
241
+ ``,
242
+ `Open a separate terminal and run:`,
243
+ ` ${styledCommandPrefix} spawn /horton/onboarding`,
244
+ ` ${styledCommandPrefix} send /horton/onboarding "Onboard me to Electric Agents"`,
245
+ ``,
246
+ `You can see all agents in the UI or CLI:`,
247
+ ` UI: ${underline(uiUrl, color)}`,
248
+ ` CLI: ${styledCommandPrefix} observe /horton/onboarding`,
249
+ ``,
250
+ grey(`Keep this window open to run the built-in agents`, color)
251
+ ], {
252
+ color,
253
+ trueColor
254
+ });
255
+ }
151
256
  function resolveCommandPrefix(argv, env = process.env) {
152
257
  if (env.npm_command === `exec`) {
153
258
  const userAgent = env.npm_config_user_agent ?? ``;
@@ -343,7 +448,11 @@ function createElectricCliHandlers(env, commandPrefix = commandExample(`electric
343
448
  return started;
344
449
  },
345
450
  startBuiltin: async (options) => {
346
- options.anthropicApiKey = await ensureAnthropicApiKey(options);
451
+ try {
452
+ options.anthropicApiKey = await ensureAnthropicApiKey(options);
453
+ } catch (error) {
454
+ fail(getErrorMessage(error));
455
+ }
347
456
  const { startBuiltinAgentsServer } = await loadStartModule();
348
457
  return startBuiltinAgentsServer(options, { agentServerUrl: env.electricAgentsUrl });
349
458
  },
@@ -354,24 +463,24 @@ function createElectricCliHandlers(env, commandPrefix = commandExample(`electric
354
463
  return stopped;
355
464
  },
356
465
  quickstart: async (options) => {
357
- options.anthropicApiKey = await ensureAnthropicApiKey(options);
466
+ try {
467
+ options.anthropicApiKey = await ensureAnthropicApiKey(options);
468
+ } catch (error) {
469
+ fail(getErrorMessage(error));
470
+ }
358
471
  const { startBuiltinAgentsServer, startElectricAgentsDevEnvironment } = await loadStartModule();
359
472
  const started = await startElectricAgentsDevEnvironment();
360
473
  printStartedEnvironment(started);
361
474
  console.log(``);
362
- console.log([
363
- `electric agents server is up`,
364
- ``,
365
- `Open a separate terminal and run:`,
366
- ` ${commandPrefix} spawn /horton/onboarding`,
367
- ` ${commandPrefix} send /horton/onboarding "Please walk me through onboarding for the Electric agents"`,
368
- ` ${commandPrefix} observe /horton/onboarding`,
369
- ``,
370
- `UI: ${started.uiUrl}`,
371
- `This terminal will now run the built-in Horton server in the foreground.`
372
- ].join(`\n`));
475
+ console.log(formatQuickstartBackendStartedMessage({
476
+ commandPrefix,
477
+ uiUrl: started.uiUrl
478
+ }));
373
479
  console.log(``);
374
- await startBuiltinAgentsServer(options, { agentServerUrl: started.uiUrl });
480
+ await startBuiltinAgentsServer(options, {
481
+ agentServerUrl: started.uiUrl,
482
+ printStartedMessage: false
483
+ });
375
484
  },
376
485
  init: async (projectName) => {
377
486
  const { initProject } = await import(`./init.js`);
@@ -390,8 +499,8 @@ Environment:
390
499
  Examples:
391
500
  $ ${agentsCommand} types
392
501
  $ ${agentsCommand} spawn /horton/onboarding
393
- $ ${agentsCommand} send /horton/onboarding "Please walk me through onboarding for the Electric agents"
394
- $ ${agentsCommand} observe /horton/onboarding --from 0
502
+ $ ${agentsCommand} send /horton/onboarding "Help me onboard to Electric Agents"
503
+ $ ${agentsCommand} observe /horton/onboarding
395
504
  $ ${agentsCommand} start
396
505
  $ ${agentsCommand} start-builtin --anthropic-api-key sk-ant-...
397
506
  $ ${agentsCommand} stop --remove-volumes
@@ -519,4 +628,4 @@ if (isMainModule()) run().catch((error) => {
519
628
  });
520
629
 
521
630
  //#endregion
522
- export { DEFAULT_ELECTRIC_AGENTS_URL, createElectricCliHandlers, createElectricProgram, getElectricCliEnv, resolveCommandPrefix, run };
631
+ export { DEFAULT_ELECTRIC_AGENTS_URL, createElectricCliHandlers, createElectricProgram, formatQuickstartBackendStartedMessage, getElectricCliEnv, resolveCommandPrefix, run };
package/dist/start.cjs CHANGED
@@ -193,7 +193,7 @@ async function startBuiltinAgentsServer(options, params = {}) {
193
193
  registeredBaseUrl: server.registeredBaseUrl,
194
194
  agentServerUrl
195
195
  };
196
- console.log(getStartedBuiltinAgentsMessage(started));
196
+ if (params.printStartedMessage ?? true) console.log(getStartedBuiltinAgentsMessage(started));
197
197
  await waitForShutdown(() => server.stop());
198
198
  return started;
199
199
  }
package/dist/start.d.cts CHANGED
@@ -1,2 +1,2 @@
1
- import { StartedBuiltinAgentsEnvironment, StartedDevEnvironment, StoppedDevEnvironment, getStartedBuiltinAgentsMessage, getStartedEnvironmentMessage, getStoppedEnvironmentMessage, readDotEnvFile, resolveAnthropicApiKey, resolveBuiltinAgentsPort, resolveComposeProjectName, resolveElectricAgentsPort, startBuiltinAgentsServer, startElectricAgentsDevEnvironment, stopElectricAgentsDevEnvironment, waitForElectricAgentsServer } from "./index-BNTf2uZL.cjs";
1
+ import { StartedBuiltinAgentsEnvironment, StartedDevEnvironment, StoppedDevEnvironment, getStartedBuiltinAgentsMessage, getStartedEnvironmentMessage, getStoppedEnvironmentMessage, readDotEnvFile, resolveAnthropicApiKey, resolveBuiltinAgentsPort, resolveComposeProjectName, resolveElectricAgentsPort, startBuiltinAgentsServer, startElectricAgentsDevEnvironment, stopElectricAgentsDevEnvironment, waitForElectricAgentsServer } from "./index-Dgoj3VzA.cjs";
2
2
  export { StartedBuiltinAgentsEnvironment, StartedDevEnvironment, StoppedDevEnvironment, getStartedBuiltinAgentsMessage, getStartedEnvironmentMessage, getStoppedEnvironmentMessage, readDotEnvFile, resolveAnthropicApiKey, resolveBuiltinAgentsPort, resolveComposeProjectName, resolveElectricAgentsPort, startBuiltinAgentsServer, startElectricAgentsDevEnvironment, stopElectricAgentsDevEnvironment, waitForElectricAgentsServer };
package/dist/start.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { StartedBuiltinAgentsEnvironment, StartedDevEnvironment, StoppedDevEnvironment, getStartedBuiltinAgentsMessage, getStartedEnvironmentMessage, getStoppedEnvironmentMessage, readDotEnvFile, resolveAnthropicApiKey, resolveBuiltinAgentsPort, resolveComposeProjectName, resolveElectricAgentsPort, startBuiltinAgentsServer, startElectricAgentsDevEnvironment, stopElectricAgentsDevEnvironment, waitForElectricAgentsServer } from "./index-DS2RpaPZ.js";
1
+ import { StartedBuiltinAgentsEnvironment, StartedDevEnvironment, StoppedDevEnvironment, getStartedBuiltinAgentsMessage, getStartedEnvironmentMessage, getStoppedEnvironmentMessage, readDotEnvFile, resolveAnthropicApiKey, resolveBuiltinAgentsPort, resolveComposeProjectName, resolveElectricAgentsPort, startBuiltinAgentsServer, startElectricAgentsDevEnvironment, stopElectricAgentsDevEnvironment, waitForElectricAgentsServer } from "./index-si6k6XW1.js";
2
2
  export { StartedBuiltinAgentsEnvironment, StartedDevEnvironment, StoppedDevEnvironment, getStartedBuiltinAgentsMessage, getStartedEnvironmentMessage, getStoppedEnvironmentMessage, readDotEnvFile, resolveAnthropicApiKey, resolveBuiltinAgentsPort, resolveComposeProjectName, resolveElectricAgentsPort, startBuiltinAgentsServer, startElectricAgentsDevEnvironment, stopElectricAgentsDevEnvironment, waitForElectricAgentsServer };
package/dist/start.js CHANGED
@@ -191,7 +191,7 @@ async function startBuiltinAgentsServer(options, params = {}) {
191
191
  registeredBaseUrl: server.registeredBaseUrl,
192
192
  agentServerUrl
193
193
  };
194
- console.log(getStartedBuiltinAgentsMessage(started));
194
+ if (params.printStartedMessage ?? true) console.log(getStartedBuiltinAgentsMessage(started));
195
195
  await waitForShutdown(() => server.stop());
196
196
  return started;
197
197
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "electric-ax",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "CLI for Electric Agents",
5
5
  "author": "ElectricSQL team and contributors",
6
6
  "license": "Apache-2.0",
@@ -39,15 +39,15 @@
39
39
  "dependencies": {
40
40
  "@durable-streams/client": "npm:@electric-ax/durable-streams-client-beta@^0.3.0",
41
41
  "@durable-streams/state": "npm:@electric-ax/durable-streams-state-beta@^0.3.0",
42
- "@electric-sql/client": "^1.5.14",
42
+ "@electric-sql/client": "^1.5.16",
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.2",
50
- "@electric-ax/agents-runtime": "0.1.1"
49
+ "@electric-ax/agents": "0.2.4",
50
+ "@electric-ax/agents-runtime": "0.1.2"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@vitest/coverage-v8": "^4.1.0",