@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.
- package/dist/{_api-client-Br1a_Dsp.mjs → _api-client-H4MFOr8j.mjs} +1 -2
- package/dist/{_bundler-BM-vnJUp.mjs → _bundler-BjgPrg7s.mjs} +9 -8
- package/dist/{_discover-DiRl7b_K.mjs → _discover-a8yIuqEp.mjs} +4 -3
- package/dist/{_init-CVkKf8t_.mjs → _init-VzVTpJFZ.mjs} +3 -3
- package/dist/{_templates-s5MYfWS9.mjs → _templates-CtZBILce.mjs} +14 -5
- package/dist/_ui-DWGXImbO.mjs +30 -0
- package/dist/cli.mjs +30 -91
- package/dist/{delete-CSPF9vdh.mjs → delete-BvRel3Tw.mjs} +5 -5
- package/dist/{deploy-C2s4C_ff.mjs → deploy-ChUnIpu5.mjs} +9 -9
- package/dist/{dev-CBKWhwaQ.mjs → dev-DEZKrw8v.mjs} +3 -5
- package/dist/{init-BAYELcXE.mjs → init-CNoNUGRN.mjs} +20 -19
- package/dist/{secret-CRLbxN5P.mjs → secret-DLosn47q.mjs} +9 -6
- package/dist/{test-CuMTKw38.mjs → test-CYoqKJP0.mjs} +4 -4
- package/package.json +2 -4
- package/dist/_ui-Cu-v_Bzz.mjs +0 -13
- package/dist/generate-DQt7C4zz.mjs +0 -388
|
@@ -24,8 +24,7 @@ async function apiRequest(url, init, fetchFn = globalThis.fetch.bind(globalThis)
|
|
|
24
24
|
headers
|
|
25
25
|
});
|
|
26
26
|
} catch (err) {
|
|
27
|
-
|
|
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
|
|
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-
|
|
97
|
-
const {
|
|
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
|
-
|
|
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(`
|
|
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 {
|
|
113
|
+
const { log } = await import("./_ui-DWGXImbO.mjs");
|
|
113
114
|
await buildAgentBundle(cwd);
|
|
114
|
-
|
|
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
|
-
|
|
124
|
-
|
|
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 —
|
|
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-
|
|
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
|
-
|
|
22
|
-
|
|
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-
|
|
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
|
|
26
|
+
/** List available templates with descriptions. */
|
|
27
27
|
async function listTemplates() {
|
|
28
|
-
const
|
|
29
|
-
|
|
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(`
|
|
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-
|
|
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: "
|
|
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-
|
|
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
|
|
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
|
|
78
|
+
description: "Skip deploy after scaffolding"
|
|
79
79
|
}
|
|
80
80
|
},
|
|
81
81
|
async run({ args }) {
|
|
82
|
-
const { runInitCommand } = await import("./init-
|
|
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-
|
|
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-
|
|
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
|
|
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-
|
|
140
|
+
const { runVitest } = await import("./test-CYoqKJP0.mjs");
|
|
141
141
|
runVitest(cwd);
|
|
142
142
|
}
|
|
143
|
-
const { runBuildCommand } = await import("./_bundler-
|
|
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-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
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-
|
|
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-
|
|
3
|
-
import {
|
|
4
|
-
import { n as apiError, r as apiRequest, t as HINT_INVALID_API_KEY } from "./_api-client-
|
|
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
|
-
|
|
25
|
+
log.step(`Deleting ${slug}`);
|
|
26
26
|
await runDelete({
|
|
27
27
|
url: serverUrl,
|
|
28
28
|
slug,
|
|
29
29
|
apiKey
|
|
30
30
|
});
|
|
31
|
-
|
|
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-
|
|
3
|
-
import { buildAgentBundle } from "./_bundler-
|
|
4
|
-
import {
|
|
5
|
-
import { n as apiError, r as apiRequest, t as HINT_INVALID_API_KEY } from "./_api-client-
|
|
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(`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
16
|
-
|
|
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-
|
|
3
|
-
import { n as listTemplates } from "./_templates-
|
|
4
|
-
import {
|
|
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((
|
|
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
|
-
|
|
77
|
-
|
|
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
|
|
81
|
-
function devCommand(
|
|
82
|
-
return
|
|
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("
|
|
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("
|
|
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-
|
|
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-
|
|
103
|
+
const { runDeployCommand } = await import("./deploy-ChUnIpu5.mjs");
|
|
103
104
|
await runDeployCommand({ cwd });
|
|
104
105
|
}
|
|
105
106
|
if (!extra?.quiet) {
|
|
106
|
-
|
|
107
|
-
|
|
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-
|
|
3
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
41
|
-
else
|
|
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 {
|
|
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
|
-
|
|
30
|
+
log.step("Running agent tests");
|
|
31
31
|
if (!runVitest(cwd)) {
|
|
32
|
-
|
|
32
|
+
log.info("No test file found. Create agent.test.ts to add tests.");
|
|
33
33
|
return;
|
|
34
34
|
}
|
|
35
|
-
|
|
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.
|
|
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.
|
|
19
|
+
"@alexkroman1/aai": "0.11.0"
|
|
22
20
|
},
|
|
23
21
|
"devDependencies": {
|
|
24
22
|
"playwright": "^1.58.2",
|
package/dist/_ui-Cu-v_Bzz.mjs
DELETED
|
@@ -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 };
|