@alexkroman1/aai-cli 0.10.2 → 0.10.4

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.
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ //#region _api-client.ts
3
+ /**
4
+ * Shared HTTP helpers for platform API calls (deploy, delete, secrets).
5
+ */
6
+ const HINT_INVALID_API_KEY = "Your API key may be invalid. Check ~/.config/aai/config.json or set ASSEMBLYAI_API_KEY.";
7
+ /**
8
+ * Send an authenticated request to the platform API.
9
+ *
10
+ * Adds the `Authorization` header and, on network failure, throws with a
11
+ * contextual hint (localhost → "is the dev server running?", remote →
12
+ * "check your network connection").
13
+ */
14
+ async function apiRequest(url, init, fetchFn = globalThis.fetch.bind(globalThis)) {
15
+ const { apiKey, action, ...rest } = init;
16
+ const headers = {
17
+ Authorization: `Bearer ${apiKey}`,
18
+ ...rest.body ? { "Content-Type": "application/json" } : {},
19
+ ...rest.headers
20
+ };
21
+ try {
22
+ return await fetchFn(url, {
23
+ ...rest,
24
+ headers
25
+ });
26
+ } catch (err) {
27
+ const hint = url.startsWith("http://localhost") ? "Is the local dev server running? Start it with `aai dev`." : "Check your network connection and verify the server URL is correct.";
28
+ throw new Error(`${action} failed: could not reach ${url}\n ${hint}`, { cause: err });
29
+ }
30
+ }
31
+ /** Format a non-ok API response into a descriptive error. */
32
+ function apiError(action, status, body, hint) {
33
+ return /* @__PURE__ */ new Error(`${action} failed (HTTP ${status}): ${body}${hint ? `\n ${hint}` : ""}`);
34
+ }
35
+ //#endregion
36
+ export { apiError as n, apiRequest as r, HINT_INVALID_API_KEY as t };
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env node
2
+ import path from "node:path";
3
+ import fs from "node:fs/promises";
4
+ import { errorMessage } from "@alexkroman1/aai/utils";
5
+ import { build } from "vite";
6
+ //#region _bundler.ts
7
+ var BundleError = class extends Error {
8
+ constructor(message, options) {
9
+ super(message, options);
10
+ this.name = "BundleError";
11
+ }
12
+ };
13
+ const TEXT_EXTENSIONS = new Set([
14
+ ".html",
15
+ ".htm",
16
+ ".css",
17
+ ".js",
18
+ ".mjs",
19
+ ".cjs",
20
+ ".ts",
21
+ ".mts",
22
+ ".json",
23
+ ".map",
24
+ ".svg",
25
+ ".xml",
26
+ ".txt",
27
+ ".md"
28
+ ]);
29
+ async function readDirFiles(dir) {
30
+ let entries;
31
+ try {
32
+ entries = await fs.readdir(dir, {
33
+ recursive: true,
34
+ withFileTypes: true
35
+ });
36
+ } catch (err) {
37
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") return {};
38
+ throw err;
39
+ }
40
+ const files = {};
41
+ await Promise.all(entries.filter((e) => e.isFile()).map(async (e) => {
42
+ const full = path.join(e.parentPath, e.name);
43
+ const ext = path.extname(e.name).toLowerCase();
44
+ const rel = path.relative(dir, full);
45
+ if (TEXT_EXTENSIONS.has(ext)) files[rel] = await fs.readFile(full, "utf-8");
46
+ else files[rel] = `base64:${(await fs.readFile(full)).toString("base64")}`;
47
+ }));
48
+ return files;
49
+ }
50
+ /**
51
+ * Bundle an agent project using Vite.
52
+ *
53
+ * - Worker: `vite build --ssr agent.ts` (uses project's vite.config.ts)
54
+ * - Client: `vite build` (uses project's vite.config.ts)
55
+ */
56
+ async function bundleAgent(agent, opts) {
57
+ const aaiDir = path.join(agent.dir, ".aai");
58
+ const buildDir = path.join(aaiDir, "build");
59
+ const clientDir = path.join(aaiDir, "client");
60
+ try {
61
+ await build({
62
+ root: agent.dir,
63
+ logLevel: "warn",
64
+ build: {
65
+ ssr: path.join(agent.dir, "agent.ts"),
66
+ outDir: buildDir,
67
+ emptyOutDir: true,
68
+ rollupOptions: { output: { entryFileNames: "worker.js" } }
69
+ }
70
+ });
71
+ } catch (err) {
72
+ throw new BundleError(errorMessage(err), { cause: err });
73
+ }
74
+ if (!(opts?.skipClient ?? !agent.clientEntry)) try {
75
+ await build({
76
+ root: agent.dir,
77
+ base: "./",
78
+ logLevel: "warn",
79
+ build: {
80
+ outDir: clientDir,
81
+ emptyOutDir: true
82
+ }
83
+ });
84
+ } catch (err) {
85
+ throw new BundleError(errorMessage(err), { cause: err });
86
+ }
87
+ const worker = await fs.readFile(path.join(buildDir, "worker.js"), "utf-8");
88
+ return {
89
+ worker,
90
+ clientFiles: await readDirFiles(clientDir),
91
+ clientDir,
92
+ workerBytes: Buffer.byteLength(worker)
93
+ };
94
+ }
95
+ async function buildAgentBundle(cwd) {
96
+ const { loadAgent } = await import("./_discover-DiRl7b_K.mjs").then((n) => n.t);
97
+ const { consola } = await import("./_ui-Cu-v_Bzz.mjs");
98
+ const agent = await loadAgent(cwd);
99
+ if (!agent) throw new Error("No agent found — run `aai init` first");
100
+ consola.start(`Bundle ${agent.slug}`);
101
+ let bundle;
102
+ try {
103
+ bundle = await bundleAgent(agent);
104
+ } catch (err) {
105
+ if (err instanceof BundleError) throw new Error(`Bundle failed: ${err.message}`, { cause: err });
106
+ throw err;
107
+ }
108
+ consola.log(`worker: ${(bundle.workerBytes / 1024).toFixed(1)} KB, client: ${Object.keys(bundle.clientFiles).length} file(s)`);
109
+ return bundle;
110
+ }
111
+ async function runBuildCommand(cwd) {
112
+ const { consola } = await import("./_ui-Cu-v_Bzz.mjs");
113
+ await buildAgentBundle(cwd);
114
+ consola.success("Build ok");
115
+ }
116
+ //#endregion
117
+ export { buildAgentBundle, runBuildCommand };
@@ -2,13 +2,24 @@
2
2
  import { existsSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- import fs$1 from "node:fs/promises";
5
+ import fs from "node:fs/promises";
6
6
  import { consola } from "consola";
7
7
  import { humanId } from "human-id";
8
8
  import { z } from "zod";
9
9
  import { createInterface } from "node:readline";
10
+ //#region \0rolldown/runtime.js
11
+ var __defProp = Object.defineProperty;
12
+ var __exportAll = (all, no_symbols) => {
13
+ let target = {};
14
+ for (var name in all) __defProp(target, name, {
15
+ get: all[name],
16
+ enumerable: true
17
+ });
18
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
19
+ return target;
20
+ };
21
+ //#endregion
10
22
  //#region _prompts.ts
11
- const consola$1 = consola.create({ formatOptions: { date: false } });
12
23
  /**
13
24
  * Prompt the user for a password (masked input).
14
25
  * Returns the entered string.
@@ -47,20 +58,23 @@ function readMasked(stdin) {
47
58
  stdin.on("data", onData);
48
59
  });
49
60
  }
50
- /**
51
- * Prompt the user for text input with a default value.
52
- * Returns the entered string, or the default if empty.
53
- */
54
- async function askText(message, defaultValue) {
55
- return await consola$1.prompt(message, {
56
- type: "text",
57
- placeholder: defaultValue,
58
- initial: defaultValue,
59
- cancel: "reject"
60
- }) || defaultValue;
61
- }
62
61
  //#endregion
63
62
  //#region _discover.ts
63
+ var _discover_exports = /* @__PURE__ */ __exportAll({
64
+ DEFAULT_DEV_SERVER: () => DEFAULT_DEV_SERVER,
65
+ DEFAULT_SERVER: () => DEFAULT_SERVER,
66
+ ensureApiKeyInEnv: () => ensureApiKeyInEnv,
67
+ fileExists: () => fileExists,
68
+ generateSlug: () => generateSlug,
69
+ getApiKey: () => getApiKey,
70
+ getServerInfo: () => getServerInfo,
71
+ isDevMode: () => isDevMode,
72
+ loadAgent: () => loadAgent,
73
+ readProjectConfig: () => readProjectConfig,
74
+ resolveCwd: () => resolveCwd,
75
+ resolveServerUrl: () => resolveServerUrl,
76
+ writeProjectConfig: () => writeProjectConfig
77
+ });
64
78
  const AuthConfigSchema = z.object({ assemblyai_api_key: z.string().optional() });
65
79
  const ProjectConfigSchema = z.object({
66
80
  slug: z.string(),
@@ -84,15 +98,16 @@ const CONFIG_DIR = path.join(process.env.HOME ?? process.env.USERPROFILE ?? ".",
84
98
  const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
85
99
  async function readAuthConfig() {
86
100
  try {
87
- return AuthConfigSchema.parse(JSON.parse(await fs$1.readFile(CONFIG_FILE, "utf-8")));
88
- } catch {
101
+ return AuthConfigSchema.parse(JSON.parse(await fs.readFile(CONFIG_FILE, "utf-8")));
102
+ } catch (error) {
103
+ consola.debug(`Failed to read auth config from ${CONFIG_FILE}:`, error);
89
104
  return {};
90
105
  }
91
106
  }
92
107
  async function writeAuthConfig(config) {
93
- await fs$1.mkdir(CONFIG_DIR, { recursive: true });
94
- await fs$1.writeFile(CONFIG_FILE, `${JSON.stringify(config, null, 2)}\n`);
95
- if (process.platform !== "win32") await fs$1.chmod(CONFIG_FILE, 384);
108
+ await fs.mkdir(CONFIG_DIR, { recursive: true });
109
+ await fs.writeFile(CONFIG_FILE, `${JSON.stringify(config, null, 2)}\n`);
110
+ if (process.platform !== "win32") await fs.chmod(CONFIG_FILE, 384);
96
111
  }
97
112
  /**
98
113
  * Retrieves the AssemblyAI API key from `process.env`, `~/.config/aai/config.json`,
@@ -128,8 +143,9 @@ async function ensureApiKeyInEnv() {
128
143
  */
129
144
  async function readProjectConfig(agentDir) {
130
145
  try {
131
- return ProjectConfigSchema.parse(JSON.parse(await fs$1.readFile(path.join(agentDir, ".aai", "project.json"), "utf-8")));
132
- } catch {
146
+ return ProjectConfigSchema.parse(JSON.parse(await fs.readFile(path.join(agentDir, ".aai", "project.json"), "utf-8")));
147
+ } catch (error) {
148
+ consola.debug(`Failed to read project config from ${path.join(agentDir, ".aai", "project.json")}:`, error);
133
149
  return null;
134
150
  }
135
151
  }
@@ -138,12 +154,12 @@ async function readProjectConfig(agentDir) {
138
154
  */
139
155
  async function writeProjectConfig(agentDir, data) {
140
156
  const aaiDir = path.join(agentDir, ".aai");
141
- await fs$1.mkdir(aaiDir, { recursive: true });
142
- await fs$1.writeFile(path.join(aaiDir, "project.json"), `${JSON.stringify(data, null, 2)}\n`);
157
+ await fs.mkdir(aaiDir, { recursive: true });
158
+ await fs.writeFile(path.join(aaiDir, "project.json"), `${JSON.stringify(data, null, 2)}\n`);
143
159
  }
144
160
  /**
145
161
  * Read project config (throws if missing), resolve API key and server URL.
146
- * Shared by secret and rag commands.
162
+ * Shared by secret commands.
147
163
  */
148
164
  async function getServerInfo(cwd, explicitServer, explicitApiKey) {
149
165
  const config = await readProjectConfig(cwd);
@@ -155,6 +171,10 @@ async function getServerInfo(cwd, explicitServer, explicitApiKey) {
155
171
  apiKey
156
172
  };
157
173
  }
174
+ /** Default production server URL for agent deployments. */
175
+ const DEFAULT_SERVER = "https://aai-agent.fly.dev";
176
+ /** Default local dev server URL. */
177
+ const DEFAULT_DEV_SERVER = "http://localhost:8787";
158
178
  /** Check if the CLI is running from the monorepo (dev mode). */
159
179
  function isDevMode() {
160
180
  const cliDir = path.dirname(fileURLToPath(import.meta.url));
@@ -164,13 +184,16 @@ function isDevMode() {
164
184
  }
165
185
  /** Resolve the server URL from an explicit value, project config, or default. */
166
186
  function resolveServerUrl(explicit, configUrl) {
167
- return explicit ?? configUrl ?? (isDevMode() ? "http://localhost:8787" : "https://aai-agent.fly.dev");
187
+ if (explicit) return explicit;
188
+ if (isDevMode()) return DEFAULT_DEV_SERVER;
189
+ return configUrl ?? "https://aai-agent.fly.dev";
168
190
  }
169
191
  async function fileExists(p) {
170
192
  try {
171
- await fs$1.access(p);
193
+ await fs.access(p);
172
194
  return true;
173
- } catch {
195
+ } catch (error) {
196
+ consola.debug(`File access check failed for ${p}:`, error);
174
197
  return false;
175
198
  }
176
199
  }
@@ -193,4 +216,4 @@ async function loadAgent(dir) {
193
216
  };
194
217
  }
195
218
  //#endregion
196
- export { getServerInfo as a, readProjectConfig as c, writeProjectConfig as d, askPassword as f, getApiKey as i, resolveCwd as l, fileExists as n, isDevMode as o, askText as p, generateSlug as r, loadAgent as s, ensureApiKeyInEnv as t, resolveServerUrl as u };
219
+ export { getApiKey as a, readProjectConfig as c, writeProjectConfig as d, askPassword as f, generateSlug as i, resolveCwd as l, ensureApiKeyInEnv as n, getServerInfo as o, fileExists as r, isDevMode as s, _discover_exports as t, resolveServerUrl as u };
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+ import { t as downloadAndMergeTemplate } from "./_templates-s5MYfWS9.mjs";
3
+ import path from "node:path";
4
+ import fs from "node:fs/promises";
5
+ //#region _init.ts
6
+ async function runInit(opts) {
7
+ const { targetDir, template } = opts;
8
+ await downloadAndMergeTemplate(template, targetDir);
9
+ try {
10
+ await fs.copyFile(path.join(targetDir, ".env.example"), path.join(targetDir, ".env"));
11
+ } catch {}
12
+ const readmePath = path.join(targetDir, "README.md");
13
+ const readme = `# ${path.basename(path.resolve(targetDir))}
14
+
15
+ A voice agent built with [aai](https://github.com/anthropics/aai).
16
+
17
+ ## Getting started
18
+
19
+ \`\`\`sh
20
+ npm install # Install dependencies
21
+ npm run dev # Run locally (opens browser)
22
+ npm run deploy # Deploy to production
23
+ \`\`\`
24
+
25
+ ## Secrets
26
+
27
+ Access secrets in your agent via \`ctx.env.MY_KEY\`.
28
+
29
+ **Local development** — add secrets to \`.env\` (auto-loaded by \`aai dev\`):
30
+
31
+ \`\`\`sh
32
+ ALPHA_VANTAGE_KEY=sk-abc123
33
+ MY_API_KEY=secret-value
34
+ \`\`\`
35
+
36
+ **Production** — set secrets on the server:
37
+
38
+ \`\`\`sh
39
+ aai secret put MY_KEY # Set a secret (prompts for value)
40
+ aai secret list # List secret names
41
+ aai secret delete MY_KEY # Remove a secret
42
+ \`\`\`
43
+
44
+ ## Learn more
45
+
46
+ See \`CLAUDE.md\` for the full agent API reference.
47
+ `;
48
+ try {
49
+ await fs.writeFile(readmePath, readme, { flag: "wx" });
50
+ } catch (err) {
51
+ if (!(err instanceof Error && "code" in err && err.code === "EEXIST")) throw err;
52
+ }
53
+ return targetDir;
54
+ }
55
+ //#endregion
56
+ export { runInit };
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+ import { s as isDevMode } from "./_discover-DiRl7b_K.mjs";
3
+ import { existsSync } from "node:fs";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import fs from "node:fs/promises";
7
+ import { downloadTemplate } from "giget";
8
+ //#region _templates.ts
9
+ const GIGET_SOURCE = "github:alexkroman/agent/packages/aai-templates";
10
+ const GIGET_REF = process.env.AAI_TEMPLATES_REF ?? "main";
11
+ /** Resolve the local aai-templates package directory (dev mode only). */
12
+ function resolveLocalTemplatesDir() {
13
+ const cliDir = path.dirname(fileURLToPath(import.meta.url));
14
+ const fromSrc = path.resolve(cliDir, "../aai-templates");
15
+ const fromDist = path.resolve(cliDir, "../../aai-templates");
16
+ if (existsSync(fromSrc)) return fromSrc;
17
+ if (existsSync(fromDist)) return fromDist;
18
+ throw new Error("Cannot find local aai-templates package");
19
+ }
20
+ /** Resolve the templates directory — local in dev, giget download in prod. */
21
+ async function resolveTemplatesDir() {
22
+ if (isDevMode()) return resolveLocalTemplatesDir();
23
+ const { dir } = await downloadTemplate(`${GIGET_SOURCE}#${GIGET_REF}`, { force: true });
24
+ return dir;
25
+ }
26
+ /** List available template names. */
27
+ async function listTemplates() {
28
+ const dir = path.join(await resolveTemplatesDir(), "templates");
29
+ return (await fs.readdir(dir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name).sort((a, b) => a.localeCompare(b));
30
+ }
31
+ /**
32
+ * Download a template into targetDir, merging scaffold files underneath.
33
+ */
34
+ async function downloadAndMergeTemplate(template, targetDir) {
35
+ const root = await resolveTemplatesDir();
36
+ const templatesDir = path.join(root, "templates");
37
+ const names = (await fs.readdir(templatesDir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
38
+ if (!names.includes(template)) throw new Error(`unknown template '${template}' -- available: ${names.join(", ")}`);
39
+ await fs.cp(path.join(templatesDir, template), targetDir, {
40
+ recursive: true,
41
+ force: true
42
+ });
43
+ const scaffoldDir = path.join(root, "scaffold");
44
+ if (existsSync(scaffoldDir)) {
45
+ const entries = await fs.readdir(scaffoldDir, {
46
+ recursive: true,
47
+ withFileTypes: true
48
+ });
49
+ for (const entry of entries) {
50
+ if (!entry.isFile()) continue;
51
+ const rel = path.relative(scaffoldDir, path.join(entry.parentPath, entry.name));
52
+ const destPath = path.join(targetDir, rel);
53
+ await fs.mkdir(path.dirname(destPath), { recursive: true });
54
+ try {
55
+ await fs.copyFile(path.join(scaffoldDir, rel), destPath, fs.constants.COPYFILE_EXCL);
56
+ } catch (err) {
57
+ if (!(err instanceof Error && "code" in err && err.code === "EEXIST")) throw err;
58
+ }
59
+ }
60
+ }
61
+ }
62
+ //#endregion
63
+ export { listTemplates as n, downloadAndMergeTemplate as t };
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ import { consola as consola$1 } from "consola";
3
+ //#region _ui.ts
4
+ /** Shared consola instance with date display disabled. */
5
+ const consola = consola$1.create({ formatOptions: { date: false } });
6
+ /** Parse and validate a port string. Returns the numeric port or throws. */
7
+ function parsePort(raw) {
8
+ const port = Number.parseInt(raw, 10);
9
+ if (Number.isNaN(port) || port < 0 || port > 65535) throw new Error(`Invalid port: ${raw}. Must be a number between 0 and 65535.`);
10
+ return port;
11
+ }
12
+ //#endregion
13
+ export { consola, parsePort };