@alexkroman1/aai-cli 0.10.4 → 0.11.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.
@@ -24,8 +24,7 @@ async function apiRequest(url, init, fetchFn = globalThis.fetch.bind(globalThis)
24
24
  headers
25
25
  });
26
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 });
27
+ throw new Error(`${action} failed: could not reach ${url}\n Check your network connection and verify the server URL is correct.`, { cause: err });
29
28
  }
30
29
  }
31
30
  /** Format a non-ok API response into a descriptive error. */
@@ -85,33 +85,34 @@ async function bundleAgent(agent, opts) {
85
85
  throw new BundleError(errorMessage(err), { cause: err });
86
86
  }
87
87
  const worker = await fs.readFile(path.join(buildDir, "worker.js"), "utf-8");
88
+ const clientFiles = await readDirFiles(clientDir);
88
89
  return {
90
+ slug: agent.slug,
89
91
  worker,
90
- clientFiles: await readDirFiles(clientDir),
92
+ clientFiles,
91
93
  clientDir,
92
94
  workerBytes: Buffer.byteLength(worker)
93
95
  };
94
96
  }
95
97
  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 { loadAgent } = await import("./_discover-a8yIuqEp.mjs").then((n) => n.t);
99
+ const { log } = await import("./_ui-DWGXImbO.mjs");
98
100
  const agent = await loadAgent(cwd);
99
101
  if (!agent) throw new Error("No agent found — run `aai init` first");
100
- consola.start(`Bundle ${agent.slug}`);
102
+ log.step(`Bundling ${agent.slug}`);
101
103
  let bundle;
102
104
  try {
103
105
  bundle = await bundleAgent(agent);
104
106
  } catch (err) {
105
- if (err instanceof BundleError) throw new Error(`Bundle failed: ${err.message}`, { cause: err });
107
+ if (err instanceof BundleError) throw new Error(`Build failed: ${err.message}`, { cause: err });
106
108
  throw err;
107
109
  }
108
- consola.log(`worker: ${(bundle.workerBytes / 1024).toFixed(1)} KB, client: ${Object.keys(bundle.clientFiles).length} file(s)`);
109
110
  return bundle;
110
111
  }
111
112
  async function runBuildCommand(cwd) {
112
- const { consola } = await import("./_ui-Cu-v_Bzz.mjs");
113
+ const { log } = await import("./_ui-DWGXImbO.mjs");
113
114
  await buildAgentBundle(cwd);
114
- consola.success("Build ok");
115
+ log.success("Build complete");
115
116
  }
116
117
  //#endregion
117
118
  export { buildAgentBundle, runBuildCommand };
@@ -120,8 +120,9 @@ async function getApiKey() {
120
120
  if (process.env.ASSEMBLYAI_API_KEY) return process.env.ASSEMBLYAI_API_KEY;
121
121
  const config = await readAuthConfig();
122
122
  if (config.assemblyai_api_key) return config.assemblyai_api_key;
123
- consola.info("Get your API key at https://www.assemblyai.com/dashboard/signup");
124
- consola.info("Or set the ASSEMBLYAI_API_KEY environment variable to skip this prompt.\n");
123
+ const { log } = await import("./_ui-DWGXImbO.mjs");
124
+ log.info("Get your API key at https://www.assemblyai.com/dashboard/signup");
125
+ log.info("Or set the ASSEMBLYAI_API_KEY environment variable to skip this prompt.");
125
126
  let key;
126
127
  while (!key) key = await askPassword("ASSEMBLYAI_API_KEY");
127
128
  config.assemblyai_api_key = key;
@@ -163,7 +164,7 @@ async function writeProjectConfig(agentDir, data) {
163
164
  */
164
165
  async function getServerInfo(cwd, explicitServer, explicitApiKey) {
165
166
  const config = await readProjectConfig(cwd);
166
- if (!config) throw new Error("No .aai/project.json found — deploy first with `aai deploy`");
167
+ if (!config) throw new Error("No .aai/project.json found — run `aai deploy` first");
167
168
  const apiKey = explicitApiKey ?? await getApiKey();
168
169
  return {
169
170
  serverUrl: resolveServerUrl(explicitServer, config.serverUrl),
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as downloadAndMergeTemplate } from "./_templates-s5MYfWS9.mjs";
2
+ import { t as downloadAndMergeTemplate } from "./_templates-CtZBILce.mjs";
3
3
  import path from "node:path";
4
4
  import fs from "node:fs/promises";
5
5
  //#region _init.ts
@@ -18,8 +18,8 @@ A voice agent built with [aai](https://github.com/anthropics/aai).
18
18
 
19
19
  \`\`\`sh
20
20
  npm install # Install dependencies
21
- npm run dev # Run locally (opens browser)
22
- npm run deploy # Deploy to production
21
+ aai dev # Run locally (opens browser)
22
+ aai deploy # Deploy to production
23
23
  \`\`\`
24
24
 
25
25
  ## Secrets
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { s as isDevMode } from "./_discover-DiRl7b_K.mjs";
2
+ import { s as isDevMode } from "./_discover-a8yIuqEp.mjs";
3
3
  import { existsSync } from "node:fs";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
@@ -23,10 +23,19 @@ async function resolveTemplatesDir() {
23
23
  const { dir } = await downloadTemplate(`${GIGET_SOURCE}#${GIGET_REF}`, { force: true });
24
24
  return dir;
25
25
  }
26
- /** List available template names. */
26
+ /** List available templates with descriptions. */
27
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));
28
+ const root = await resolveTemplatesDir();
29
+ const dir = path.join(root, "templates");
30
+ const names = (await fs.readdir(dir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name).sort((a, b) => a.localeCompare(b));
31
+ let descriptions = {};
32
+ try {
33
+ descriptions = JSON.parse(await fs.readFile(path.join(root, "templates.json"), "utf-8"));
34
+ } catch {}
35
+ return names.map((name) => ({
36
+ name,
37
+ description: descriptions[name] ?? ""
38
+ }));
30
39
  }
31
40
  /**
32
41
  * Download a template into targetDir, merging scaffold files underneath.
@@ -35,7 +44,7 @@ async function downloadAndMergeTemplate(template, targetDir) {
35
44
  const root = await resolveTemplatesDir();
36
45
  const templatesDir = path.join(root, "templates");
37
46
  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(", ")}`);
47
+ if (!names.includes(template)) throw new Error(`Unknown template "${template}". Available templates: ${names.join(", ")}`);
39
48
  await fs.cp(path.join(templatesDir, template), targetDir, {
40
49
  recursive: true,
41
50
  force: true
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ import * as p from "@clack/prompts";
3
+ import { colorize } from "consola/utils";
4
+ //#region _ui.ts
5
+ /**
6
+ * Unified CLI output using @clack/prompts style (◐ ◇ │).
7
+ *
8
+ * All commands should use these helpers instead of consola directly
9
+ * so the output is visually consistent.
10
+ */
11
+ const log = {
12
+ step: (msg) => p.log.step(msg),
13
+ success: (msg) => p.log.success(msg),
14
+ info: (msg) => p.log.info(msg),
15
+ warn: (msg) => p.log.warn(msg),
16
+ error: (msg) => p.log.error(msg),
17
+ message: (msg) => p.log.message(msg)
18
+ };
19
+ /** Format a URL for display. */
20
+ function fmtUrl(url) {
21
+ return colorize("cyanBright", url);
22
+ }
23
+ /** Parse and validate a port string. Returns the numeric port or throws. */
24
+ function parsePort(raw) {
25
+ const port = Number.parseInt(raw, 10);
26
+ if (Number.isNaN(port) || port < 0 || port > 65535) throw new Error(`Invalid port: ${raw}. Must be a number between 0 and 65535.`);
27
+ return port;
28
+ }
29
+ //#endregion
30
+ export { fmtUrl, log, parsePort };
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { l as resolveCwd, n as ensureApiKeyInEnv, r as fileExists } from "./_discover-DiRl7b_K.mjs";
2
+ import { l as resolveCwd, n as ensureApiKeyInEnv, r as fileExists } from "./_discover-a8yIuqEp.mjs";
3
3
  import { readFileSync } from "node:fs";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
@@ -16,7 +16,7 @@ const sharedArgs = {
16
16
  server: {
17
17
  type: "string",
18
18
  alias: "s",
19
- description: "Server URL"
19
+ description: "Platform server URL"
20
20
  },
21
21
  yes: {
22
22
  type: "boolean",
@@ -35,7 +35,7 @@ function findPkgJson(dir) {
35
35
  const VERSION = JSON.parse(findPkgJson(cliDir)).version;
36
36
  async function ensureAgent(cwd, yes) {
37
37
  if (!await fileExists(path.join(cwd, "agent.ts"))) {
38
- const { runInitCommand } = await import("./init-BAYELcXE.mjs");
38
+ const { runInitCommand } = await import("./init-CNoNUGRN.mjs");
39
39
  return runInitCommand({ yes }, { quiet: true });
40
40
  }
41
41
  return cwd;
@@ -66,7 +66,7 @@ const init = defineCommand({
66
66
  force: {
67
67
  type: "boolean",
68
68
  alias: "f",
69
- description: "Overwrite existing agent.ts"
69
+ description: "Overwrite existing files"
70
70
  },
71
71
  yes: sharedArgs.yes,
72
72
  skipApi: {
@@ -75,11 +75,11 @@ const init = defineCommand({
75
75
  },
76
76
  skipDeploy: {
77
77
  type: "boolean",
78
- description: "Skip post-init deploy"
78
+ description: "Skip deploy after scaffolding"
79
79
  }
80
80
  },
81
81
  async run({ args }) {
82
- const { runInitCommand } = await import("./init-BAYELcXE.mjs");
82
+ const { runInitCommand } = await import("./init-CNoNUGRN.mjs");
83
83
  await runInitCommand({
84
84
  dir: args.dir,
85
85
  template: args.template,
@@ -104,7 +104,7 @@ const dev = defineCommand({
104
104
  agent: true,
105
105
  apiKey: true
106
106
  });
107
- const { runDevCommand } = await import("./dev-CBKWhwaQ.mjs");
107
+ const { runDevCommand } = await import("./dev-DEZKrw8v.mjs");
108
108
  await runDevCommand({
109
109
  cwd,
110
110
  port: args.port
@@ -118,14 +118,14 @@ const test = defineCommand({
118
118
  },
119
119
  async run() {
120
120
  const cwd = await setup();
121
- const { runTestCommand } = await import("./test-CuMTKw38.mjs");
121
+ const { runTestCommand } = await import("./test-CYoqKJP0.mjs");
122
122
  await runTestCommand(cwd);
123
123
  }
124
124
  });
125
125
  const build = defineCommand({
126
126
  meta: {
127
127
  name: "build",
128
- description: "Bundle and validate (no server or deploy)"
128
+ description: "Bundle agent without deploying"
129
129
  },
130
130
  args: {
131
131
  yes: sharedArgs.yes,
@@ -137,10 +137,10 @@ const build = defineCommand({
137
137
  async run({ args }) {
138
138
  const cwd = await setup(args, { agent: true });
139
139
  if (!args.skipTests) {
140
- const { runVitest } = await import("./test-CuMTKw38.mjs");
140
+ const { runVitest } = await import("./test-CYoqKJP0.mjs");
141
141
  runVitest(cwd);
142
142
  }
143
- const { runBuildCommand } = await import("./_bundler-BM-vnJUp.mjs");
143
+ const { runBuildCommand } = await import("./_bundler-BjgPrg7s.mjs");
144
144
  await runBuildCommand(cwd);
145
145
  }
146
146
  });
@@ -159,12 +159,18 @@ const deploy = defineCommand({
159
159
  },
160
160
  async run({ args }) {
161
161
  const cwd = await setup(args, { agent: true });
162
- const { runDeployCommand } = await import("./deploy-C2s4C_ff.mjs");
163
- await runDeployCommand({
164
- cwd,
165
- ...args.server ? { server: args.server } : {},
166
- ...args.dryRun ? { dryRun: args.dryRun } : {}
167
- });
162
+ const { runDeployCommand } = await import("./deploy-ChUnIpu5.mjs");
163
+ try {
164
+ await runDeployCommand({
165
+ cwd,
166
+ ...args.server ? { server: args.server } : {},
167
+ ...args.dryRun ? { dryRun: args.dryRun } : {}
168
+ });
169
+ } catch (err) {
170
+ const { log } = await import("./_ui-DWGXImbO.mjs");
171
+ log.error(err instanceof Error ? err.message : String(err));
172
+ process.exit(1);
173
+ }
168
174
  }
169
175
  });
170
176
  const del = defineCommand({
@@ -175,7 +181,7 @@ const del = defineCommand({
175
181
  args: { server: sharedArgs.server },
176
182
  async run({ args }) {
177
183
  const cwd = await setup();
178
- const { runDeleteCommand } = await import("./delete-CSPF9vdh.mjs");
184
+ const { runDeleteCommand } = await import("./delete-BvRel3Tw.mjs");
179
185
  await runDeleteCommand({
180
186
  cwd,
181
187
  ...args.server ? { server: args.server } : {}
@@ -185,7 +191,7 @@ const del = defineCommand({
185
191
  const secret = defineCommand({
186
192
  meta: {
187
193
  name: "secret",
188
- description: "Manage secrets"
194
+ description: "Manage agent secrets"
189
195
  },
190
196
  subCommands: {
191
197
  put: defineCommand({
@@ -200,7 +206,7 @@ const secret = defineCommand({
200
206
  } },
201
207
  async run({ args }) {
202
208
  const cwd = await setup(void 0, { apiKey: true });
203
- const { runSecretPut } = await import("./secret-CRLbxN5P.mjs");
209
+ const { runSecretPut } = await import("./secret-DLosn47q.mjs");
204
210
  await runSecretPut(cwd, args.name);
205
211
  }
206
212
  }),
@@ -216,72 +222,23 @@ const secret = defineCommand({
216
222
  } },
217
223
  async run({ args }) {
218
224
  const cwd = await setup(void 0, { apiKey: true });
219
- const { runSecretDelete } = await import("./secret-CRLbxN5P.mjs");
225
+ const { runSecretDelete } = await import("./secret-DLosn47q.mjs");
220
226
  await runSecretDelete(cwd, args.name);
221
227
  }
222
228
  }),
223
229
  list: defineCommand({
224
230
  meta: {
225
231
  name: "list",
226
- description: "List secret names"
232
+ description: "List all secrets"
227
233
  },
228
234
  async run() {
229
235
  const cwd = await setup(void 0, { apiKey: true });
230
- const { runSecretList } = await import("./secret-CRLbxN5P.mjs");
236
+ const { runSecretList } = await import("./secret-DLosn47q.mjs");
231
237
  await runSecretList(cwd);
232
238
  }
233
239
  })
234
240
  }
235
241
  });
236
- const generate = defineCommand({
237
- meta: {
238
- name: "generate",
239
- description: "Generate or modify agent code using AI"
240
- },
241
- args: { prompt: {
242
- type: "positional",
243
- description: "What to generate",
244
- required: true
245
- } },
246
- async run({ args }) {
247
- const cwd = await setup(void 0, { apiKey: true });
248
- const { runGenerateCommand } = await import("./generate-DQt7C4zz.mjs");
249
- await runGenerateCommand({
250
- cwd,
251
- prompt: args.prompt
252
- });
253
- }
254
- });
255
- const run = defineCommand({
256
- meta: {
257
- name: "run",
258
- description: "Init, generate, and deploy in one step"
259
- },
260
- args: {
261
- prompt: {
262
- type: "positional",
263
- description: "What to build",
264
- required: true
265
- },
266
- server: sharedArgs.server
267
- },
268
- async run({ args }) {
269
- const cwd = await setup({ yes: true }, {
270
- agent: true,
271
- apiKey: true
272
- });
273
- const { runGenerateCommand } = await import("./generate-DQt7C4zz.mjs");
274
- await runGenerateCommand({
275
- cwd,
276
- prompt: args.prompt
277
- });
278
- const { runDeployCommand } = await import("./deploy-C2s4C_ff.mjs");
279
- await runDeployCommand({
280
- cwd,
281
- ...args.server ? { server: args.server } : {}
282
- });
283
- }
284
- });
285
242
  const mainCommand = defineCommand({
286
243
  meta: {
287
244
  name: "aai",
@@ -295,14 +252,11 @@ const mainCommand = defineCommand({
295
252
  build,
296
253
  deploy,
297
254
  delete: del,
298
- secret,
299
- generate,
300
- run
255
+ secret
301
256
  }
302
257
  });
303
258
  if (process.env.VITEST !== "true") {
304
259
  const sub = process.argv[2];
305
- const knownCommands = new Set(Object.keys(mainCommand.subCommands ?? {}));
306
260
  const helpFlags = new Set([
307
261
  "--help",
308
262
  "--version",
@@ -310,21 +264,6 @@ if (process.env.VITEST !== "true") {
310
264
  "-V"
311
265
  ]);
312
266
  if (!sub || sub.startsWith("-") && !helpFlags.has(sub)) process.argv.splice(2, 0, "init");
313
- else if (!(sub.startsWith("-") || knownCommands.has(sub))) {
314
- const promptParts = [];
315
- const flagArgs = [];
316
- for (let i = 2; i < process.argv.length; i++) if (process.argv[i]?.startsWith("-")) {
317
- flagArgs.push(process.argv[i]);
318
- if (i + 1 < process.argv.length && !process.argv[i + 1]?.startsWith("-")) flagArgs.push(process.argv[++i]);
319
- } else promptParts.push(process.argv[i]);
320
- process.argv = [
321
- process.argv[0],
322
- process.argv[1],
323
- "run",
324
- promptParts.join(" "),
325
- ...flagArgs
326
- ];
327
- }
328
267
  runMain(mainCommand);
329
268
  }
330
269
  //#endregion
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { o as getServerInfo } from "./_discover-DiRl7b_K.mjs";
3
- import { consola } from "./_ui-Cu-v_Bzz.mjs";
4
- import { n as apiError, r as apiRequest, t as HINT_INVALID_API_KEY } from "./_api-client-Br1a_Dsp.mjs";
2
+ import { o as getServerInfo } from "./_discover-a8yIuqEp.mjs";
3
+ import { log } from "./_ui-DWGXImbO.mjs";
4
+ import { n as apiError, r as apiRequest, t as HINT_INVALID_API_KEY } from "./_api-client-H4MFOr8j.mjs";
5
5
  //#region _delete.ts
6
6
  async function runDelete(opts) {
7
7
  const fetchFn = opts.fetch ?? globalThis.fetch.bind(globalThis);
@@ -22,13 +22,13 @@ async function runDelete(opts) {
22
22
  async function runDeleteCommand(opts) {
23
23
  const { cwd } = opts;
24
24
  const { serverUrl, slug, apiKey } = await getServerInfo(cwd, opts.server);
25
- consola.start(`Delete ${slug}`);
25
+ log.step(`Deleting ${slug}`);
26
26
  await runDelete({
27
27
  url: serverUrl,
28
28
  slug,
29
29
  apiKey
30
30
  });
31
- consola.success(`Deleted ${serverUrl}/${slug}`);
31
+ log.success(`Deleted ${serverUrl}/${slug}`);
32
32
  }
33
33
  //#endregion
34
34
  export { runDeleteCommand };
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { a as getApiKey, c as readProjectConfig, d as writeProjectConfig, i as generateSlug, u as resolveServerUrl } from "./_discover-DiRl7b_K.mjs";
3
- import { buildAgentBundle } from "./_bundler-BM-vnJUp.mjs";
4
- import { consola } from "./_ui-Cu-v_Bzz.mjs";
5
- import { n as apiError, r as apiRequest, t as HINT_INVALID_API_KEY } from "./_api-client-Br1a_Dsp.mjs";
2
+ import { a as getApiKey, c as readProjectConfig, d as writeProjectConfig, i as generateSlug, u as resolveServerUrl } from "./_discover-a8yIuqEp.mjs";
3
+ import { buildAgentBundle } from "./_bundler-BjgPrg7s.mjs";
4
+ import { fmtUrl, log } from "./_ui-DWGXImbO.mjs";
5
+ import { n as apiError, r as apiRequest, t as HINT_INVALID_API_KEY } from "./_api-client-H4MFOr8j.mjs";
6
6
  //#region _deploy.ts
7
7
  const MAX_SLUG_RETRIES = 20;
8
8
  async function attempt(fetchFn, url, slug, body, apiKey) {
@@ -45,14 +45,14 @@ async function runDeploy(opts) {
45
45
  if (!result.retry) throw new Error(result.error);
46
46
  slug = generateSlug();
47
47
  }
48
- throw new Error(`could not find an available agent slug after ${MAX_SLUG_RETRIES} attempts`);
48
+ throw new Error(`Could not find an available slug after ${MAX_SLUG_RETRIES} attempts. Set one manually in .aai/project.json.`);
49
49
  }
50
50
  //#endregion
51
51
  //#region deploy.ts
52
52
  async function deployBundle(opts) {
53
53
  const { bundle, serverUrl, apiKey, cwd } = opts;
54
54
  let { slug } = opts;
55
- consola.start(`Deploy ${slug}`);
55
+ log.step(`Deploying ${slug}`);
56
56
  slug = (await runDeploy({
57
57
  url: serverUrl,
58
58
  bundle,
@@ -65,7 +65,7 @@ async function deployBundle(opts) {
65
65
  serverUrl
66
66
  });
67
67
  const agentUrl = `${serverUrl}/${slug}`;
68
- consola.success(`Ready ${agentUrl}`);
68
+ log.success(`Deployed ${fmtUrl(agentUrl)}`);
69
69
  return agentUrl;
70
70
  }
71
71
  async function runDeployCommand(opts) {
@@ -74,10 +74,10 @@ async function runDeployCommand(opts) {
74
74
  const apiKey = dryRun ? "" : await getApiKey();
75
75
  const projectConfig = await readProjectConfig(cwd);
76
76
  const serverUrl = resolveServerUrl(opts.server, projectConfig?.serverUrl);
77
- const slug = projectConfig?.slug ?? generateSlug();
78
77
  const bundle = await buildAgentBundle(cwd);
78
+ const slug = projectConfig?.slug ?? bundle.slug;
79
79
  if (dryRun) {
80
- consola.info(`Dry run: would deploy as ${slug}`);
80
+ log.info(`Dry run complete — would deploy as ${slug}`);
81
81
  return;
82
82
  }
83
83
  await deployBundle({
@@ -1,8 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { consola, parsePort } from "./_ui-Cu-v_Bzz.mjs";
2
+ import { fmtUrl, log, parsePort } from "./_ui-DWGXImbO.mjs";
3
3
  import path from "node:path";
4
4
  import { createServer } from "vite";
5
- import * as p from "@clack/prompts";
6
5
  import { colorize } from "consola/utils";
7
6
  //#region dev.ts
8
7
  async function runDevCommand(opts) {
@@ -12,9 +11,8 @@ async function runDevCommand(opts) {
12
11
  root: opts.cwd,
13
12
  server: { port }
14
13
  })).listen();
15
- const url = colorize("blueBright", `http://localhost:${port}`);
16
- p.note(`Agent: ${colorize("bold", agentName)}\nLocal: ${url}`, "aai dev");
17
- consola.info("Press Ctrl-C to stop");
14
+ log.success(`${colorize("bold", agentName)} running at ${fmtUrl(`http://localhost:${port}`)}`);
15
+ log.info("Press Ctrl-C to stop");
18
16
  }
19
17
  //#endregion
20
18
  export { runDevCommand };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { l as resolveCwd, n as ensureApiKeyInEnv, r as fileExists } from "./_discover-DiRl7b_K.mjs";
3
- import { n as listTemplates } from "./_templates-s5MYfWS9.mjs";
4
- import { consola } from "./_ui-Cu-v_Bzz.mjs";
2
+ import { l as resolveCwd, n as ensureApiKeyInEnv, r as fileExists } from "./_discover-a8yIuqEp.mjs";
3
+ import { n as listTemplates } from "./_templates-CtZBILce.mjs";
4
+ import { log } from "./_ui-DWGXImbO.mjs";
5
5
  import path from "node:path";
6
6
  import fs from "node:fs/promises";
7
7
  import { errorMessage } from "@alexkroman1/aai/utils";
@@ -30,7 +30,7 @@ async function promptProjectName(yes) {
30
30
  defaultValue: DEFAULT_PROJECT_NAME
31
31
  });
32
32
  if (p.isCancel(result)) {
33
- p.cancel("Setup cancelled.");
33
+ p.cancel("Setup cancelled");
34
34
  process.exit(0);
35
35
  }
36
36
  return result || DEFAULT_PROJECT_NAME;
@@ -41,14 +41,15 @@ async function promptTemplate(yes) {
41
41
  const templates = await listTemplates();
42
42
  const result = await p.select({
43
43
  message: "Which template would you like to use?",
44
- options: templates.map((name) => ({
45
- value: name,
46
- label: name
44
+ options: templates.map((t) => ({
45
+ value: t.name,
46
+ label: t.name,
47
+ hint: t.description
47
48
  })),
48
49
  initialValue: DEFAULT_TEMPLATE
49
50
  });
50
51
  if (p.isCancel(result)) {
51
- p.cancel("Setup cancelled.");
52
+ p.cancel("Setup cancelled");
52
53
  process.exit(0);
53
54
  }
54
55
  return result;
@@ -73,25 +74,25 @@ async function installDeps(cwd, pm) {
73
74
  } catch (err) {
74
75
  const msg = errorMessage(err);
75
76
  s.stop("Dependency install failed");
76
- consola.warn(`${pm} install failed: ${msg}`);
77
- consola.warn(`Run \`${pm} install\` manually in the project directory.`);
77
+ log.warn(`${pm} install failed: ${msg}`);
78
+ log.warn(`Run \`${pm} install\` manually in the project directory.`);
78
79
  }
79
80
  }
80
- /** Format the run command for the detected package manager. */
81
- function devCommand(pm) {
82
- return pm === "npm" ? `${pm} run dev` : `${pm} dev`;
81
+ /** Format the dev command for the "Next steps" note. */
82
+ function devCommand() {
83
+ return "aai dev";
83
84
  }
84
85
  async function runInitCommand(opts, extra) {
85
86
  const pm = detectPackageManager();
86
- if (!extra?.quiet) p.intro(colorize("blueBright", "Create a new voice agent"));
87
+ if (!extra?.quiet) p.intro(colorize("cyanBright", "Create a new voice agent"));
87
88
  if (!opts.skipApi) await ensureApiKeyInEnv();
88
89
  const dir = opts.dir ?? await promptProjectName(opts.yes);
89
90
  const cwd = path.resolve(resolveCwd(), dir);
90
- if (!opts.force && await fileExists(path.join(cwd, "agent.ts"))) throw new Error(`agent.ts already exists in this directory. Use ${colorize("blueBright", "--force")} to overwrite.`);
91
+ if (!opts.force && await fileExists(path.join(cwd, "agent.ts"))) throw new Error(`agent.ts already exists in this directory. Use ${colorize("cyanBright", "--force")} to overwrite.`);
91
92
  const template = opts.template ?? await promptTemplate(opts.yes);
92
93
  const s = p.spinner();
93
94
  s.start(`Creating ${dir} from ${template} template`);
94
- const { runInit } = await import("./_init-CVkKf8t_.mjs");
95
+ const { runInit } = await import("./_init-VzVTpJFZ.mjs");
95
96
  await runInit({
96
97
  targetDir: cwd,
97
98
  template
@@ -99,12 +100,12 @@ async function runInitCommand(opts, extra) {
99
100
  s.stop("Project created");
100
101
  await installDeps(cwd, pm);
101
102
  if (!(opts.skipDeploy || extra?.quiet)) {
102
- const { runDeployCommand } = await import("./deploy-C2s4C_ff.mjs");
103
+ const { runDeployCommand } = await import("./deploy-ChUnIpu5.mjs");
103
104
  await runDeployCommand({ cwd });
104
105
  }
105
106
  if (!extra?.quiet) {
106
- p.note(`cd ${dir}\n${devCommand(pm)}`, "Next steps");
107
- p.outro("Happy building!");
107
+ log.success(`Created ${dir}`);
108
+ log.info(`Next: cd ${dir} && ${devCommand()}`);
108
109
  }
109
110
  return cwd;
110
111
  }
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { f as askPassword, o as getServerInfo } from "./_discover-DiRl7b_K.mjs";
3
- import { consola } from "./_ui-Cu-v_Bzz.mjs";
2
+ import { f as askPassword, o as getServerInfo } from "./_discover-a8yIuqEp.mjs";
3
+ import { log } from "./_ui-DWGXImbO.mjs";
4
4
  //#region secret.ts
5
5
  async function apiFetch(cwd, pathSuffix, init) {
6
6
  const { serverUrl, slug, apiKey } = await getServerInfo(cwd);
@@ -28,17 +28,20 @@ async function runSecretPut(cwd, name) {
28
28
  headers: { "Content-Type": "application/json" },
29
29
  body: JSON.stringify({ [name]: value })
30
30
  });
31
- consola.success(`Set ${name} for ${slug}`);
31
+ log.success(`Set ${name} for ${slug}`);
32
32
  }
33
33
  async function runSecretDelete(cwd, name) {
34
34
  const { slug } = await apiFetch(cwd, `/${name}`, { method: "DELETE" });
35
- consola.success(`Deleted ${name} from ${slug}`);
35
+ log.success(`Deleted ${name} from ${slug}`);
36
36
  }
37
37
  async function runSecretList(cwd) {
38
38
  const { resp } = await apiFetch(cwd, "");
39
39
  const { vars } = await resp.json();
40
- if (vars.length === 0) consola.info("Secrets: none set");
41
- else for (const name of vars) consola.log(` ${name}`);
40
+ if (vars.length === 0) log.info("No secrets set. Use `aai secret put <name>` to add one.");
41
+ else {
42
+ log.message(`${vars.length} secret${vars.length === 1 ? "" : "s"}:`);
43
+ for (const name of vars) log.message(` ${name}`);
44
+ }
42
45
  }
43
46
  //#endregion
44
47
  export { runSecretDelete, runSecretList, runSecretPut };
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { consola } from "./_ui-Cu-v_Bzz.mjs";
2
+ import { log } from "./_ui-DWGXImbO.mjs";
3
3
  import { existsSync } from "node:fs";
4
4
  import path from "node:path";
5
5
  import { execSync } from "node:child_process";
@@ -27,12 +27,12 @@ function runVitest(cwd) {
27
27
  }
28
28
  /** Run agent tests. Used by `aai test`. */
29
29
  async function runTestCommand(cwd) {
30
- consola.start("Running agent tests");
30
+ log.step("Running agent tests");
31
31
  if (!runVitest(cwd)) {
32
- consola.info("No test files found (agent.test.ts). Skipping.");
32
+ log.info("No test file found. Create agent.test.ts to add tests.");
33
33
  return;
34
34
  }
35
- consola.success("Tests passed");
35
+ log.success("Tests passed");
36
36
  }
37
37
  //#endregion
38
38
  export { runTestCommand, runVitest };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alexkroman1/aai-cli",
3
- "version": "0.10.4",
3
+ "version": "0.11.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "aai": "dist/cli.mjs"
@@ -9,16 +9,14 @@
9
9
  "dist"
10
10
  ],
11
11
  "dependencies": {
12
- "@ai-sdk/openai-compatible": "^2.0.37",
13
12
  "@clack/prompts": "^1.1.0",
14
- "ai": "^6.0.140",
15
13
  "citty": "^0.2.1",
16
14
  "consola": "^3.4.2",
17
15
  "giget": "^2.0.0",
18
16
  "human-id": "^4.1.3",
19
17
  "vite": "^8.0.3",
20
18
  "zod": "^4.3.6",
21
- "@alexkroman1/aai": "0.10.4"
19
+ "@alexkroman1/aai": "0.11.0"
22
20
  },
23
21
  "devDependencies": {
24
22
  "playwright": "^1.58.2",
@@ -1,13 +0,0 @@
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 };
@@ -1,388 +0,0 @@
1
- #!/usr/bin/env node
2
- import { a as getApiKey } from "./_discover-DiRl7b_K.mjs";
3
- import path from "node:path";
4
- import fs from "node:fs/promises";
5
- import { consola } from "consola";
6
- import { z } from "zod";
7
- import { errorMessage } from "@alexkroman1/aai/utils";
8
- import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
9
- import { generateText, stepCountIs } from "ai";
10
- import { execFile } from "node:child_process";
11
- import { promisify } from "node:util";
12
- /** Max characters per line when reading files. */
13
- const MAX_LINE_LENGTH = 2e3;
14
- /** Max output bytes for shell commands (50 KB). */
15
- const MAX_OUTPUT_BYTES = 50 * 1024;
16
- //#endregion
17
- //#region _generate-tools.ts
18
- const execFileAsync = promisify(execFile);
19
- const SKIP_DIRS = new Set([
20
- "node_modules",
21
- ".git",
22
- ".aai",
23
- "dist",
24
- "coverage"
25
- ]);
26
- function safePath(workDir, filePath) {
27
- const abs = path.resolve(workDir, filePath);
28
- if (!abs.startsWith(workDir + path.sep) && abs !== workDir) return null;
29
- return abs;
30
- }
31
- function formatGrepOutput(workDir, stdout) {
32
- if (!stdout.trim()) return "No matches found.";
33
- const byFile = /* @__PURE__ */ new Map();
34
- for (const line of stdout.trim().split("\n")) {
35
- const sep = line.indexOf(":");
36
- const sep2 = line.indexOf(":", sep + 1);
37
- if (sep === -1 || sep2 === -1) continue;
38
- const file = path.relative(workDir, line.slice(0, sep));
39
- const lineNum = line.slice(sep + 1, sep2);
40
- let text = line.slice(sep2 + 1);
41
- if (text.length > 2e3) text = `${text.slice(0, MAX_LINE_LENGTH)}...`;
42
- const entries = byFile.get(file) ?? [];
43
- entries.push(` Line ${lineNum}: ${text}`);
44
- byFile.set(file, entries);
45
- }
46
- const output = [];
47
- for (const [file, lines] of byFile) {
48
- output.push(`${file}:`);
49
- output.push(...lines);
50
- }
51
- return output.join("\n");
52
- }
53
- function formatExecError(err) {
54
- if (err && typeof err === "object" && "stdout" in err) {
55
- const e = err;
56
- return `Exit code ${e.code}\n${`${e.stdout}\n${e.stderr}`.trim()}`;
57
- }
58
- return `Error: ${errorMessage(err)}`;
59
- }
60
- function truncateOutput(output) {
61
- if (!output) return "(no output)";
62
- if (output.length > 51200) return `${output.slice(0, MAX_OUTPUT_BYTES)}\n...(truncated, ${output.length} bytes total)`;
63
- return output;
64
- }
65
- function readFileWithLineNumbers(content, offset, limit) {
66
- const allLines = content.split("\n");
67
- const startLine = Math.max(1, offset ?? 1);
68
- const maxLines = limit ?? 2e3;
69
- const endLine = Math.min(allLines.length, startLine + maxLines - 1);
70
- const lines = allLines.slice(startLine - 1, endLine);
71
- let output = "";
72
- let bytes = 0;
73
- let truncatedByBytes = false;
74
- for (let i = 0; i < lines.length; i++) {
75
- const raw = lines[i] ?? "";
76
- const text = raw.length > 2e3 ? `${raw.slice(0, MAX_LINE_LENGTH)}... (truncated)` : raw;
77
- const numbered = `${startLine + i}: ${text}\n`;
78
- if (bytes + numbered.length > 51200) {
79
- truncatedByBytes = true;
80
- break;
81
- }
82
- output += numbered;
83
- bytes += numbered.length;
84
- }
85
- const total = allLines.length;
86
- if (truncatedByBytes || endLine < total) {
87
- const shown = endLine - startLine + 1;
88
- output += `\n(Showing lines ${startLine}-${startLine + shown - 1} of ${total}. Use offset=${endLine + 1} to continue.)`;
89
- }
90
- return output;
91
- }
92
- function isRgNoMatch(err) {
93
- return Boolean(err && typeof err === "object" && "code" in err && err.code === 1);
94
- }
95
- function makeFileTools(workDir) {
96
- return {
97
- read: {
98
- description: "Read a file or directory. Returns lines prefixed with line numbers (e.g. `1: content`). Use offset/limit to paginate large files. Defaults to first 2000 lines. For directories, returns a listing of entries. Call this tool in parallel when reading multiple files.",
99
- inputSchema: z.object({
100
- filePath: z.string().describe("Path to the file or directory to read"),
101
- offset: z.number().optional().describe("Line number to start from (1-indexed)"),
102
- limit: z.number().optional().describe("Max number of lines to read (default 2000)")
103
- }),
104
- execute: async (args) => {
105
- const { filePath, offset, limit } = args;
106
- const abs = safePath(workDir, filePath);
107
- if (!abs) return "Error: path outside working directory";
108
- let stat;
109
- try {
110
- stat = await fs.stat(abs);
111
- } catch {
112
- return `Error: file not found: ${filePath}`;
113
- }
114
- if (stat.isDirectory()) return (await fs.readdir(abs, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name)).map((e) => e.isDirectory() ? `${e.name}/` : e.name).join("\n");
115
- return readFileWithLineNumbers(await fs.readFile(abs, "utf-8"), offset, limit);
116
- }
117
- },
118
- edit: {
119
- description: "Performs exact string replacement in a file. You must read the file first before editing. The edit will fail if oldString is not found or matches multiple locations (unless replaceAll is true). Provide enough surrounding context in oldString to make the match unique. Preserve exact indentation from the file.",
120
- inputSchema: z.object({
121
- filePath: z.string().describe("Path to the file to modify"),
122
- oldString: z.string().describe("The exact text to replace"),
123
- newString: z.string().describe("The replacement text (must be different from oldString)"),
124
- replaceAll: z.boolean().optional().describe("Replace all occurrences of oldString (default false)")
125
- }),
126
- execute: async (args) => {
127
- const { filePath, oldString, newString, replaceAll } = args;
128
- if (oldString === newString) return "Error: oldString and newString are identical";
129
- const abs = safePath(workDir, filePath);
130
- if (!abs) return "Error: path outside working directory";
131
- let content;
132
- try {
133
- content = await fs.readFile(abs, "utf-8");
134
- } catch {
135
- return `Error: file not found: ${filePath}`;
136
- }
137
- if (!content.includes(oldString)) return "Error: oldString not found in file. Make sure you are matching the exact text including whitespace and indentation.";
138
- if (replaceAll) {
139
- await fs.writeFile(abs, content.replaceAll(oldString, newString));
140
- return `Replaced ${content.split(oldString).length - 1} occurrence(s).`;
141
- }
142
- const first = content.indexOf(oldString);
143
- if (content.indexOf(oldString, first + 1) !== -1) return "Error: found multiple matches for oldString. Provide more surrounding context to make it unique, or set replaceAll to true.";
144
- await fs.writeFile(abs, content.replace(oldString, newString));
145
- return "OK";
146
- }
147
- },
148
- write: {
149
- description: "Write content to a file, creating it if it doesn't exist or overwriting if it does. Always prefer editing existing files with the edit tool. Only use write for new files or complete rewrites.",
150
- inputSchema: z.object({
151
- filePath: z.string().describe("Path to the file to write"),
152
- content: z.string().describe("The full file content to write")
153
- }),
154
- execute: async (args) => {
155
- const { filePath, content } = args;
156
- const abs = safePath(workDir, filePath);
157
- if (!abs) return "Error: path outside working directory";
158
- await fs.mkdir(path.dirname(abs), { recursive: true });
159
- await fs.writeFile(abs, content);
160
- return `Wrote ${content.length} bytes to ${filePath}`;
161
- }
162
- }
163
- };
164
- }
165
- function makeSearchTools(workDir) {
166
- return {
167
- glob: {
168
- description: "Fast file pattern matching. Supports glob patterns like '**/*.ts' or 'src/**/*.tsx'. Returns matching file paths sorted by modification time (newest first). Use this to find files by name or extension.",
169
- inputSchema: z.object({
170
- pattern: z.string().describe("Glob pattern to match files against"),
171
- path: z.string().optional().describe("Directory to search in (defaults to project root)")
172
- }),
173
- execute: async (args) => {
174
- const { pattern, path: searchPath } = args;
175
- const dir = searchPath ? safePath(workDir, searchPath) ?? workDir : workDir;
176
- try {
177
- const { stdout } = await execFileAsync("rg", [
178
- "--files",
179
- "--glob",
180
- pattern,
181
- "--sort=modified",
182
- dir
183
- ], {
184
- maxBuffer: 1024 * 1024,
185
- timeout: 1e4
186
- });
187
- const files = stdout.trim().split("\n").filter(Boolean).slice(0, 100).map((f) => path.relative(workDir, f));
188
- if (files.length === 0) return "No files found matching pattern.";
189
- const truncated = files.length >= 100 ? "\n(Showing first 100 results.)" : "";
190
- return files.join("\n") + truncated;
191
- } catch (err) {
192
- if (isRgNoMatch(err)) return "No files found matching pattern.";
193
- return `Error running glob: ${errorMessage(err)}`;
194
- }
195
- }
196
- },
197
- grep: {
198
- description: "Fast content search using regex. Searches file contents and returns file paths with line numbers and matching lines. Supports full regex syntax. Use the include parameter to filter by file extension (e.g. '*.ts').",
199
- inputSchema: z.object({
200
- pattern: z.string().describe("Regex pattern to search for"),
201
- path: z.string().optional().describe("Directory to search in (defaults to project root)"),
202
- include: z.string().optional().describe("File pattern to include (e.g. \"*.ts\", \"*.{ts,tsx}\")")
203
- }),
204
- execute: async (args) => {
205
- const { pattern, path: searchPath, include } = args;
206
- const dir = searchPath ? safePath(workDir, searchPath) ?? workDir : workDir;
207
- const rgArgs = [
208
- "-nH",
209
- "--hidden",
210
- "--no-messages",
211
- "--max-count=100",
212
- ...include ? ["--glob", include] : [],
213
- "--regexp",
214
- pattern,
215
- dir
216
- ];
217
- try {
218
- const { stdout } = await execFileAsync("rg", rgArgs, {
219
- maxBuffer: 1024 * 1024,
220
- timeout: 1e4
221
- });
222
- return formatGrepOutput(workDir, stdout);
223
- } catch (err) {
224
- if (isRgNoMatch(err)) return "No matches found.";
225
- return `Error running grep: ${errorMessage(err)}`;
226
- }
227
- }
228
- },
229
- bash: {
230
- description: "Execute a shell command. Use for git, npm, and other terminal operations. Do NOT use for file reading/writing/searching — use the dedicated tools instead. Commands run in the project directory by default.",
231
- inputSchema: z.object({
232
- command: z.string().describe("The shell command to execute"),
233
- description: z.string().describe("Brief description of what this command does (5-10 words)"),
234
- timeout: z.number().optional().describe("Timeout in milliseconds (default 120000)")
235
- }),
236
- execute: async (args) => {
237
- const { command, timeout } = args;
238
- try {
239
- const { stdout, stderr } = await execFileAsync(process.env.SHELL ?? "bash", ["-c", command], {
240
- cwd: workDir,
241
- maxBuffer: 1024 * 1024,
242
- timeout: timeout ?? 12e4,
243
- env: process.env
244
- });
245
- return truncateOutput(`${stdout}${stderr ? `\n${stderr}` : ""}`.trim());
246
- } catch (err) {
247
- return formatExecError(err);
248
- }
249
- }
250
- },
251
- ls: {
252
- description: "List files and directories in a path. Returns entries with '/' suffix for directories. Prefer glob or grep if you know what you're looking for.",
253
- inputSchema: z.object({ path: z.string().optional().describe("Directory path (defaults to project root)") }),
254
- execute: async (args) => {
255
- const { path: dirPath } = args;
256
- const abs = dirPath ? safePath(workDir, dirPath) ?? workDir : workDir;
257
- try {
258
- return (await fs.readdir(abs, { withFileTypes: true })).filter((e) => !SKIP_DIRS.has(e.name)).sort((a, b) => a.name.localeCompare(b.name)).map((e) => e.isDirectory() ? `${e.name}/` : e.name).join("\n");
259
- } catch {
260
- return `Error: directory not found: ${dirPath ?? "."}`;
261
- }
262
- }
263
- }
264
- };
265
- }
266
- function makeTools(workDir) {
267
- return {
268
- ...makeFileTools(workDir),
269
- ...makeSearchTools(workDir)
270
- };
271
- }
272
- //#endregion
273
- //#region generate.ts
274
- const consola$1 = consola.create({
275
- defaults: { message: "" },
276
- formatOptions: { date: false }
277
- });
278
- const TOOL_ICONS = {
279
- read: "→",
280
- edit: "✎",
281
- write: "✏",
282
- glob: "✱",
283
- grep: "◇",
284
- bash: "$",
285
- ls: "▪"
286
- };
287
- const SYSTEM_PROMPT = `You are a pragmatic, expert coding agent that builds voice agents using the AAI framework. You persist until the task is fully handled — do not stop at analysis or partial fixes.
288
-
289
- # Workflow
290
-
291
- 1. Use glob or ls to see the project structure.
292
- 2. Use read on agent.ts to see the current code.
293
- 3. Plan your approach: what name, instructions, greeting, tools, state, and builtinTools does this agent need?
294
- 4. Use write to update agent.ts with the complete implementation. Write the entire file — no placeholders or TODOs.
295
- 5. Use read to verify your changes. If something is wrong, use edit to fix it.
296
-
297
- # Rules
298
-
299
- - The API reference is included below — do NOT read CLAUDE.md, it is already in your context.
300
- - agent.ts must export a default defineAgent() call.
301
- - Only modify agent.ts (and optionally client.tsx for custom UI).
302
- - Do NOT create extra files or install packages.
303
- - Write production-quality code. Tools should have clear descriptions and .describe() on each Zod parameter.
304
- - Handle edge cases in tool execute functions.
305
- - Parallelize tool calls when possible — e.g. read multiple files at once.`;
306
- function toolLabel(name, input) {
307
- const icon = TOOL_ICONS[name] ?? "•";
308
- const file = input.filePath ?? input.path ?? input.pattern ?? "";
309
- if (name === "bash") return `${icon} ${name} ${String(input.command ?? "").slice(0, 60)}`;
310
- if (file) return `${icon} ${name} ${file}`;
311
- return `${icon} ${name}`;
312
- }
313
- function formatDuration(ms) {
314
- if (ms < 1) return "<1ms";
315
- if (ms < 1e3) return `${Math.round(ms)}ms`;
316
- return `${(ms / 1e3).toFixed(1)}s`;
317
- }
318
- function printCode(filePath, content) {
319
- const lines = content.split("\n");
320
- const maxLines = 40;
321
- const truncated = lines.length > maxLines;
322
- const shown = truncated ? lines.slice(0, maxLines).join("\n") : content;
323
- const suffix = truncated ? `\n... (${lines.length - maxLines} more lines)` : "";
324
- consola$1.box({
325
- title: filePath,
326
- message: shown + suffix,
327
- style: { borderColor: "dim" }
328
- });
329
- }
330
- function maybeShowCode(toolName, input) {
331
- if ((toolName === "write" || toolName === "edit") && input) {
332
- const filePath = String(input.filePath ?? "");
333
- const content = String(input.content ?? input.newString ?? "");
334
- if (filePath && content) printCode(filePath, content);
335
- }
336
- }
337
- async function runGenerateCommand(opts) {
338
- const { cwd, prompt } = opts;
339
- const baseURL = process.env.LLM_BASE_URL ?? "https://llm-gateway.assemblyai.com/v1";
340
- const modelId = process.env.LLM_MODEL ?? "gpt-5.2";
341
- const apiKey = await getApiKey();
342
- let systemPrompt = SYSTEM_PROMPT;
343
- try {
344
- const claudeMd = await fs.readFile(path.join(cwd, "CLAUDE.md"), "utf-8");
345
- systemPrompt += `\n\n# API Reference (CLAUDE.md)\n\n${claudeMd}`;
346
- } catch {}
347
- consola$1.start("Planning...");
348
- try {
349
- const result = await generateText({
350
- model: createOpenAICompatible({
351
- name: "assemblyai",
352
- baseURL,
353
- apiKey
354
- })(modelId),
355
- system: systemPrompt,
356
- prompt,
357
- tools: makeTools(cwd),
358
- maxOutputTokens: 65536,
359
- toolChoice: "auto",
360
- stopWhen: stepCountIs(20),
361
- experimental_onStepStart: ({ stepNumber }) => {
362
- consola$1.start(`Step ${stepNumber + 1} · Thinking...`);
363
- },
364
- experimental_onToolCallStart: ({ toolCall }) => {
365
- const input = toolCall.input;
366
- consola$1.info(toolLabel(toolCall.toolName, input ?? {}));
367
- },
368
- experimental_onToolCallFinish: ({ toolCall, durationMs, ...rest }) => {
369
- const input = toolCall.input;
370
- const label = toolLabel(toolCall.toolName, input ?? {});
371
- const time = formatDuration(durationMs);
372
- if ("success" in rest && rest.success) {
373
- consola$1.success(`${label} ${time}`);
374
- maybeShowCode(toolCall.toolName, input);
375
- } else consola$1.fail(`${label} ${time}`);
376
- },
377
- onStepFinish: ({ finishReason, text }) => {
378
- if (finishReason === "stop" && text) consola$1.box(text);
379
- }
380
- });
381
- consola$1.success(`Done (${result.steps.length} steps, ${result.usage.totalTokens} tokens)`);
382
- } catch (err) {
383
- consola$1.error(errorMessage(err));
384
- process.exitCode = 1;
385
- }
386
- }
387
- //#endregion
388
- export { runGenerateCommand };