@alexkroman1/aai-cli 0.9.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/LICENSE +21 -0
- package/dist/_build-p1HHkdon.mjs +132 -0
- package/dist/_discover-BzlCDVZ6.mjs +161 -0
- package/dist/_init-l_uoyFCN.mjs +82 -0
- package/dist/_link-BGXGFYWa.mjs +47 -0
- package/dist/_server-common-qLA1QU2C.mjs +36 -0
- package/dist/_ui-kJIua5L9.mjs +44 -0
- package/dist/cli.mjs +318 -0
- package/dist/deploy-KyNJaoP5.mjs +86 -0
- package/dist/dev-DBFvKyzk.mjs +39 -0
- package/dist/init-BWG5OrQa.mjs +65 -0
- package/dist/rag-BnCMnccf.mjs +173 -0
- package/dist/secret-CzeHIGzE.mjs +50 -0
- package/dist/start-C1qkhU4O.mjs +23 -0
- package/package.json +39 -0
- package/templates/_shared/.env.example +5 -0
- package/templates/_shared/CLAUDE.md +1051 -0
- package/templates/_shared/biome.json +32 -0
- package/templates/_shared/global.d.ts +1 -0
- package/templates/_shared/index.html +16 -0
- package/templates/_shared/package.json +23 -0
- package/templates/_shared/tsconfig.json +15 -0
- package/templates/code-interpreter/agent.ts +27 -0
- package/templates/code-interpreter/client.tsx +3 -0
- package/templates/css.d.ts +1 -0
- package/templates/dispatch-center/agent.ts +1227 -0
- package/templates/dispatch-center/client.tsx +505 -0
- package/templates/embedded-assets/agent.ts +48 -0
- package/templates/embedded-assets/client.tsx +3 -0
- package/templates/embedded-assets/knowledge.json +20 -0
- package/templates/health-assistant/agent.ts +160 -0
- package/templates/health-assistant/client.tsx +3 -0
- package/templates/infocom-adventure/agent.ts +164 -0
- package/templates/infocom-adventure/client.tsx +300 -0
- package/templates/math-buddy/agent.ts +21 -0
- package/templates/math-buddy/client.tsx +3 -0
- package/templates/memory-agent/agent.ts +20 -0
- package/templates/memory-agent/client.tsx +3 -0
- package/templates/night-owl/agent.ts +98 -0
- package/templates/night-owl/client.tsx +12 -0
- package/templates/personal-finance/agent.ts +26 -0
- package/templates/personal-finance/client.tsx +3 -0
- package/templates/pizza-ordering/agent.ts +218 -0
- package/templates/pizza-ordering/client.tsx +264 -0
- package/templates/simple/agent.ts +6 -0
- package/templates/simple/client.tsx +3 -0
- package/templates/smart-research/agent.ts +164 -0
- package/templates/smart-research/client.tsx +3 -0
- package/templates/solo-rpg/agent.ts +1244 -0
- package/templates/solo-rpg/client.tsx +698 -0
- package/templates/support/README.md +62 -0
- package/templates/support/agent.ts +19 -0
- package/templates/support/client.tsx +3 -0
- package/templates/travel-concierge/agent.ts +29 -0
- package/templates/travel-concierge/client.tsx +3 -0
- package/templates/tsconfig.json +1 -0
- package/templates/web-researcher/agent.ts +17 -0
- package/templates/web-researcher/client.tsx +3 -0
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { c as resolveCwd, r as getApiKey, t as fileExists } from "./_discover-BzlCDVZ6.mjs";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { defineCommand, runMain } from "citty";
|
|
7
|
+
//#region cli.ts
|
|
8
|
+
const cliDir = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
function findPkgJson(dir) {
|
|
10
|
+
try {
|
|
11
|
+
return readFileSync(path.join(dir, "package.json"), "utf-8");
|
|
12
|
+
} catch {
|
|
13
|
+
return readFileSync(path.join(dir, "..", "package.json"), "utf-8");
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const VERSION = JSON.parse(findPkgJson(cliDir)).version;
|
|
17
|
+
async function ensureAgent(cwd, yes) {
|
|
18
|
+
if (!await fileExists(path.join(cwd, "agent.ts"))) {
|
|
19
|
+
const { runInitCommand } = await import("./init-BWG5OrQa.mjs");
|
|
20
|
+
await runInitCommand({ yes }, { quiet: true });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const init = defineCommand({
|
|
24
|
+
meta: {
|
|
25
|
+
name: "init",
|
|
26
|
+
description: "Scaffold a new agent project"
|
|
27
|
+
},
|
|
28
|
+
args: {
|
|
29
|
+
dir: {
|
|
30
|
+
type: "positional",
|
|
31
|
+
description: "Project directory",
|
|
32
|
+
required: false
|
|
33
|
+
},
|
|
34
|
+
template: {
|
|
35
|
+
type: "string",
|
|
36
|
+
alias: "t",
|
|
37
|
+
description: "Template to use"
|
|
38
|
+
},
|
|
39
|
+
force: {
|
|
40
|
+
type: "boolean",
|
|
41
|
+
alias: "f",
|
|
42
|
+
description: "Overwrite existing agent.ts"
|
|
43
|
+
},
|
|
44
|
+
yes: {
|
|
45
|
+
type: "boolean",
|
|
46
|
+
alias: "y",
|
|
47
|
+
description: "Accept defaults (no prompts)"
|
|
48
|
+
},
|
|
49
|
+
skipApi: {
|
|
50
|
+
type: "boolean",
|
|
51
|
+
description: "Skip API key check"
|
|
52
|
+
},
|
|
53
|
+
skipDeploy: {
|
|
54
|
+
type: "boolean",
|
|
55
|
+
description: "Skip post-init deploy"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
async run({ args }) {
|
|
59
|
+
const { runInitCommand } = await import("./init-BWG5OrQa.mjs");
|
|
60
|
+
await runInitCommand({
|
|
61
|
+
dir: args.dir,
|
|
62
|
+
template: args.template,
|
|
63
|
+
force: args.force,
|
|
64
|
+
yes: args.yes,
|
|
65
|
+
skipApi: args.skipApi,
|
|
66
|
+
skipDeploy: args.skipDeploy
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
const dev = defineCommand({
|
|
71
|
+
meta: {
|
|
72
|
+
name: "dev",
|
|
73
|
+
description: "Start a local development server"
|
|
74
|
+
},
|
|
75
|
+
args: {
|
|
76
|
+
port: {
|
|
77
|
+
type: "string",
|
|
78
|
+
alias: "p",
|
|
79
|
+
description: "Port to listen on",
|
|
80
|
+
default: "3000"
|
|
81
|
+
},
|
|
82
|
+
check: {
|
|
83
|
+
type: "boolean",
|
|
84
|
+
description: "Start server, verify health, then exit"
|
|
85
|
+
},
|
|
86
|
+
yes: {
|
|
87
|
+
type: "boolean",
|
|
88
|
+
alias: "y",
|
|
89
|
+
description: "Accept defaults (no prompts)"
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
async run({ args }) {
|
|
93
|
+
const cwd = resolveCwd();
|
|
94
|
+
await ensureAgent(cwd, args.yes);
|
|
95
|
+
await getApiKey();
|
|
96
|
+
const { runDevCommand } = await import("./dev-DBFvKyzk.mjs");
|
|
97
|
+
await runDevCommand({
|
|
98
|
+
cwd,
|
|
99
|
+
port: args.port,
|
|
100
|
+
...args.check ? { check: args.check } : {}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
const build = defineCommand({
|
|
105
|
+
meta: {
|
|
106
|
+
name: "build",
|
|
107
|
+
description: "Bundle and validate (no server or deploy)"
|
|
108
|
+
},
|
|
109
|
+
args: { yes: {
|
|
110
|
+
type: "boolean",
|
|
111
|
+
alias: "y",
|
|
112
|
+
description: "Accept defaults (no prompts)"
|
|
113
|
+
} },
|
|
114
|
+
async run({ args }) {
|
|
115
|
+
const cwd = resolveCwd();
|
|
116
|
+
await ensureAgent(cwd, args.yes);
|
|
117
|
+
const { runBuildCommand } = await import("./_build-p1HHkdon.mjs");
|
|
118
|
+
await runBuildCommand(cwd);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
const deploy = defineCommand({
|
|
122
|
+
meta: {
|
|
123
|
+
name: "deploy",
|
|
124
|
+
description: "Bundle and deploy to production"
|
|
125
|
+
},
|
|
126
|
+
args: {
|
|
127
|
+
server: {
|
|
128
|
+
type: "string",
|
|
129
|
+
alias: "s",
|
|
130
|
+
description: "Server URL"
|
|
131
|
+
},
|
|
132
|
+
dryRun: {
|
|
133
|
+
type: "boolean",
|
|
134
|
+
description: "Validate and bundle without deploying"
|
|
135
|
+
},
|
|
136
|
+
yes: {
|
|
137
|
+
type: "boolean",
|
|
138
|
+
alias: "y",
|
|
139
|
+
description: "Accept defaults (no prompts)"
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
async run({ args }) {
|
|
143
|
+
const cwd = resolveCwd();
|
|
144
|
+
await ensureAgent(cwd, args.yes);
|
|
145
|
+
const { runDeployCommand } = await import("./deploy-KyNJaoP5.mjs");
|
|
146
|
+
await runDeployCommand({
|
|
147
|
+
cwd,
|
|
148
|
+
...args.server ? { server: args.server } : {},
|
|
149
|
+
...args.dryRun ? { dryRun: args.dryRun } : {}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
const start = defineCommand({
|
|
154
|
+
meta: {
|
|
155
|
+
name: "start",
|
|
156
|
+
description: "Start production server from build"
|
|
157
|
+
},
|
|
158
|
+
args: {
|
|
159
|
+
port: {
|
|
160
|
+
type: "string",
|
|
161
|
+
alias: "p",
|
|
162
|
+
description: "Port to listen on",
|
|
163
|
+
default: "3000"
|
|
164
|
+
},
|
|
165
|
+
yes: {
|
|
166
|
+
type: "boolean",
|
|
167
|
+
alias: "y",
|
|
168
|
+
description: "Accept defaults (no prompts)"
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
async run({ args }) {
|
|
172
|
+
const cwd = resolveCwd();
|
|
173
|
+
await getApiKey();
|
|
174
|
+
const { runStartCommand } = await import("./start-C1qkhU4O.mjs");
|
|
175
|
+
await runStartCommand({
|
|
176
|
+
cwd,
|
|
177
|
+
port: args.port
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
const secret = defineCommand({
|
|
182
|
+
meta: {
|
|
183
|
+
name: "secret",
|
|
184
|
+
description: "Manage secrets"
|
|
185
|
+
},
|
|
186
|
+
subCommands: {
|
|
187
|
+
put: defineCommand({
|
|
188
|
+
meta: {
|
|
189
|
+
name: "put",
|
|
190
|
+
description: "Create or update a secret"
|
|
191
|
+
},
|
|
192
|
+
args: { name: {
|
|
193
|
+
type: "positional",
|
|
194
|
+
description: "Secret name",
|
|
195
|
+
required: true
|
|
196
|
+
} },
|
|
197
|
+
async run({ args }) {
|
|
198
|
+
const cwd = resolveCwd();
|
|
199
|
+
await getApiKey();
|
|
200
|
+
const { runSecretPut } = await import("./secret-CzeHIGzE.mjs");
|
|
201
|
+
await runSecretPut(cwd, args.name);
|
|
202
|
+
}
|
|
203
|
+
}),
|
|
204
|
+
delete: defineCommand({
|
|
205
|
+
meta: {
|
|
206
|
+
name: "delete",
|
|
207
|
+
description: "Delete a secret"
|
|
208
|
+
},
|
|
209
|
+
args: { name: {
|
|
210
|
+
type: "positional",
|
|
211
|
+
description: "Secret name",
|
|
212
|
+
required: true
|
|
213
|
+
} },
|
|
214
|
+
async run({ args }) {
|
|
215
|
+
const cwd = resolveCwd();
|
|
216
|
+
await getApiKey();
|
|
217
|
+
const { runSecretDelete } = await import("./secret-CzeHIGzE.mjs");
|
|
218
|
+
await runSecretDelete(cwd, args.name);
|
|
219
|
+
}
|
|
220
|
+
}),
|
|
221
|
+
list: defineCommand({
|
|
222
|
+
meta: {
|
|
223
|
+
name: "list",
|
|
224
|
+
description: "List secret names"
|
|
225
|
+
},
|
|
226
|
+
async run() {
|
|
227
|
+
const cwd = resolveCwd();
|
|
228
|
+
await getApiKey();
|
|
229
|
+
const { runSecretList } = await import("./secret-CzeHIGzE.mjs");
|
|
230
|
+
await runSecretList(cwd);
|
|
231
|
+
}
|
|
232
|
+
})
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
const rag = defineCommand({
|
|
236
|
+
meta: {
|
|
237
|
+
name: "rag",
|
|
238
|
+
description: "Ingest a site's llms-full.txt into the vector store"
|
|
239
|
+
},
|
|
240
|
+
args: {
|
|
241
|
+
url: {
|
|
242
|
+
type: "positional",
|
|
243
|
+
description: "URL to ingest",
|
|
244
|
+
required: true
|
|
245
|
+
},
|
|
246
|
+
server: {
|
|
247
|
+
type: "string",
|
|
248
|
+
alias: "s",
|
|
249
|
+
description: "Server URL"
|
|
250
|
+
},
|
|
251
|
+
chunkSize: {
|
|
252
|
+
type: "string",
|
|
253
|
+
description: "Max chunk size in tokens",
|
|
254
|
+
default: "512"
|
|
255
|
+
},
|
|
256
|
+
yes: {
|
|
257
|
+
type: "boolean",
|
|
258
|
+
alias: "y",
|
|
259
|
+
description: "Accept defaults (no prompts)"
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
async run({ args }) {
|
|
263
|
+
const cwd = resolveCwd();
|
|
264
|
+
await getApiKey();
|
|
265
|
+
const { runRagCommand } = await import("./rag-BnCMnccf.mjs");
|
|
266
|
+
await runRagCommand({
|
|
267
|
+
url: args.url,
|
|
268
|
+
cwd,
|
|
269
|
+
...args.server ? { server: args.server } : {},
|
|
270
|
+
chunkSize: args.chunkSize
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
const link = defineCommand({
|
|
275
|
+
meta: {
|
|
276
|
+
name: "link",
|
|
277
|
+
description: "Link local workspace packages into the current project (dev only)"
|
|
278
|
+
},
|
|
279
|
+
async run() {
|
|
280
|
+
const { runLinkCommand } = await import("./_link-BGXGFYWa.mjs");
|
|
281
|
+
runLinkCommand(resolveCwd());
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
const unlink = defineCommand({
|
|
285
|
+
meta: {
|
|
286
|
+
name: "unlink",
|
|
287
|
+
description: "Restore published package versions (reverses aai link)"
|
|
288
|
+
},
|
|
289
|
+
async run() {
|
|
290
|
+
const { runUnlinkCommand } = await import("./_link-BGXGFYWa.mjs");
|
|
291
|
+
runUnlinkCommand(resolveCwd());
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
const mainCommand = defineCommand({
|
|
295
|
+
meta: {
|
|
296
|
+
name: "aai",
|
|
297
|
+
version: VERSION,
|
|
298
|
+
description: "Voice agent development kit"
|
|
299
|
+
},
|
|
300
|
+
subCommands: {
|
|
301
|
+
init,
|
|
302
|
+
dev,
|
|
303
|
+
build,
|
|
304
|
+
deploy,
|
|
305
|
+
start,
|
|
306
|
+
secret,
|
|
307
|
+
rag,
|
|
308
|
+
link,
|
|
309
|
+
unlink
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
if (process.env.VITEST !== "true") {
|
|
313
|
+
const sub = process.argv[2];
|
|
314
|
+
if (!sub || sub.startsWith("-")) process.argv.splice(2, 0, "init");
|
|
315
|
+
runMain(mainCommand);
|
|
316
|
+
}
|
|
317
|
+
//#endregion
|
|
318
|
+
export { mainCommand };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { l as resolveServerUrl, n as generateSlug, r as getApiKey, s as readProjectConfig, u as writeProjectConfig } from "./_discover-BzlCDVZ6.mjs";
|
|
3
|
+
import { a as step, i as runCommand, o as stepInfo } from "./_ui-kJIua5L9.mjs";
|
|
4
|
+
import { buildAgentBundle } from "./_build-p1HHkdon.mjs";
|
|
5
|
+
//#region _deploy.ts
|
|
6
|
+
async function attemptDeploy(fetchFn, url, slug, apiKey, env, worker, clientFiles) {
|
|
7
|
+
try {
|
|
8
|
+
return await fetchFn(`${url}/${slug}/deploy`, {
|
|
9
|
+
method: "POST",
|
|
10
|
+
headers: {
|
|
11
|
+
"Content-Type": "application/json",
|
|
12
|
+
Authorization: `Bearer ${apiKey}`
|
|
13
|
+
},
|
|
14
|
+
body: JSON.stringify({
|
|
15
|
+
env,
|
|
16
|
+
worker,
|
|
17
|
+
clientFiles
|
|
18
|
+
})
|
|
19
|
+
});
|
|
20
|
+
} catch {
|
|
21
|
+
throw new Error(`deployment failed: could not reach ${url}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const MAX_RETRIES = 20;
|
|
25
|
+
async function runDeploy(opts) {
|
|
26
|
+
const { worker, clientFiles } = opts.bundle;
|
|
27
|
+
const fetchFn = opts.fetch ?? globalThis.fetch.bind(globalThis);
|
|
28
|
+
let slug = opts.slug;
|
|
29
|
+
for (let i = 0; i < MAX_RETRIES; i++) {
|
|
30
|
+
const resp = await attemptDeploy(fetchFn, opts.url, slug, opts.apiKey, opts.env, worker, clientFiles);
|
|
31
|
+
if (resp.ok) return { slug };
|
|
32
|
+
const text = await resp.text();
|
|
33
|
+
if (resp.status === 403 && text.includes("Slug")) {
|
|
34
|
+
slug = generateSlug();
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
throw new Error(`deploy failed (${resp.status}): ${text}`);
|
|
38
|
+
}
|
|
39
|
+
throw new Error(`deploy failed: could not find available slug after ${MAX_RETRIES} attempts`);
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region deploy.ts
|
|
43
|
+
async function deployBundle(opts) {
|
|
44
|
+
const { bundle, serverUrl, apiKey, cwd, log } = opts;
|
|
45
|
+
let { slug } = opts;
|
|
46
|
+
log(step("Deploy", slug));
|
|
47
|
+
slug = (await runDeploy({
|
|
48
|
+
url: serverUrl,
|
|
49
|
+
bundle,
|
|
50
|
+
env: { ASSEMBLYAI_API_KEY: apiKey },
|
|
51
|
+
slug,
|
|
52
|
+
apiKey
|
|
53
|
+
})).slug;
|
|
54
|
+
await writeProjectConfig(cwd, {
|
|
55
|
+
slug,
|
|
56
|
+
serverUrl
|
|
57
|
+
});
|
|
58
|
+
const agentUrl = `${serverUrl}/${slug}`;
|
|
59
|
+
log(step("Ready", agentUrl));
|
|
60
|
+
return agentUrl;
|
|
61
|
+
}
|
|
62
|
+
async function runDeployCommand(opts) {
|
|
63
|
+
const { cwd } = opts;
|
|
64
|
+
const dryRun = opts.dryRun ?? false;
|
|
65
|
+
const apiKey = dryRun ? "" : await getApiKey();
|
|
66
|
+
const projectConfig = await readProjectConfig(cwd);
|
|
67
|
+
const serverUrl = resolveServerUrl(opts.server, projectConfig?.serverUrl);
|
|
68
|
+
const slug = projectConfig?.slug ?? generateSlug();
|
|
69
|
+
await runCommand(async ({ log }) => {
|
|
70
|
+
const bundle = await buildAgentBundle(cwd, log);
|
|
71
|
+
if (dryRun) {
|
|
72
|
+
log(stepInfo("Dry run", `would deploy as ${slug}`));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
await deployBundle({
|
|
76
|
+
bundle,
|
|
77
|
+
serverUrl,
|
|
78
|
+
apiKey,
|
|
79
|
+
slug,
|
|
80
|
+
cwd,
|
|
81
|
+
log
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
export { runDeployCommand };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "./_discover-BzlCDVZ6.mjs";
|
|
3
|
+
import { a as step, i as runCommand, n as info } from "./_ui-kJIua5L9.mjs";
|
|
4
|
+
import { buildAgentBundle } from "./_build-p1HHkdon.mjs";
|
|
5
|
+
import { n as loadAgentDef, r as resolveServerEnv, t as bootServer } from "./_server-common-qLA1QU2C.mjs";
|
|
6
|
+
//#region dev.ts
|
|
7
|
+
async function _startDevServer(cwd, port, log, opts) {
|
|
8
|
+
const bundle = await buildAgentBundle(cwd, log);
|
|
9
|
+
const agentDef = await loadAgentDef(cwd);
|
|
10
|
+
const env = await resolveServerEnv();
|
|
11
|
+
const server = await bootServer(agentDef, bundle.clientDir, env, port);
|
|
12
|
+
log(step("Ready", `http://localhost:${port}`));
|
|
13
|
+
const base = `http://localhost:${port}`;
|
|
14
|
+
try {
|
|
15
|
+
const healthRes = await fetch(`${base}/health`);
|
|
16
|
+
if (!healthRes.ok) throw new Error(`GET /health returned ${healthRes.status}`);
|
|
17
|
+
const health = await healthRes.json();
|
|
18
|
+
if (health.status !== "ok") throw new Error(`GET /health returned ${JSON.stringify(health)}`);
|
|
19
|
+
log(step("Health", health.name ?? "ok"));
|
|
20
|
+
const pageRes = await fetch(`${base}/`);
|
|
21
|
+
if (!pageRes.ok) throw new Error(`GET / returned ${pageRes.status}`);
|
|
22
|
+
const html = await pageRes.text();
|
|
23
|
+
if (!(html.includes("<") && html.includes("html"))) throw new Error("GET / did not return valid HTML");
|
|
24
|
+
log(step("Client", "ok"));
|
|
25
|
+
} catch (err) {
|
|
26
|
+
await server.close();
|
|
27
|
+
throw err;
|
|
28
|
+
}
|
|
29
|
+
if (opts?.check) await server.close();
|
|
30
|
+
else log(info("Ctrl-C to quit"));
|
|
31
|
+
}
|
|
32
|
+
async function runDevCommand(opts) {
|
|
33
|
+
const port = Number.parseInt(opts.port, 10);
|
|
34
|
+
await runCommand(async ({ log }) => {
|
|
35
|
+
await _startDevServer(opts.cwd, port, log, opts.check ? { check: true } : void 0);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
export { runDevCommand };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { a as isDevMode, c as resolveCwd, f as askText, r as getApiKey, t as fileExists } from "./_discover-BzlCDVZ6.mjs";
|
|
3
|
+
import { a as step, i as runCommand, r as interactive, s as warn } from "./_ui-kJIua5L9.mjs";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import fs$1 from "node:fs/promises";
|
|
8
|
+
import { execFile } from "node:child_process";
|
|
9
|
+
import { promisify } from "node:util";
|
|
10
|
+
//#region init.ts
|
|
11
|
+
const execFileAsync = promisify(execFile);
|
|
12
|
+
/** Install deps — uses `aai link` in dev mode, `npm install` otherwise. */
|
|
13
|
+
async function installDeps(cwd, log) {
|
|
14
|
+
if (await fileExists(path.join(cwd, "node_modules"))) return;
|
|
15
|
+
if (isDevMode()) {
|
|
16
|
+
log(step("Link", "local workspace packages (dev mode)"));
|
|
17
|
+
const { runLinkCommand } = await import("./_link-BGXGFYWa.mjs");
|
|
18
|
+
runLinkCommand(cwd);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
let pkgJson;
|
|
22
|
+
try {
|
|
23
|
+
pkgJson = JSON.parse(await fs$1.readFile(path.join(cwd, "package.json"), "utf-8"));
|
|
24
|
+
} catch {
|
|
25
|
+
pkgJson = {};
|
|
26
|
+
}
|
|
27
|
+
const deps = Object.keys(pkgJson.dependencies ?? {});
|
|
28
|
+
const devDeps = Object.keys(pkgJson.devDependencies ?? {});
|
|
29
|
+
if (deps.length > 0) log(step("Install", deps.join(", ")));
|
|
30
|
+
if (devDeps.length > 0) log(step("Install", `dev: ${devDeps.join(", ")}`));
|
|
31
|
+
try {
|
|
32
|
+
await execFileAsync("npm", ["install"], { cwd });
|
|
33
|
+
} catch {
|
|
34
|
+
log(warn("npm install failed"));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async function runInitCommand(opts, extra) {
|
|
38
|
+
if (!opts.skipApi) await getApiKey();
|
|
39
|
+
let dir = opts.dir;
|
|
40
|
+
if (!dir) dir = await askText("What is your project named?", "my-voice-agent");
|
|
41
|
+
const cwd = path.resolve(resolveCwd(), dir);
|
|
42
|
+
if (!opts.force && await fileExists(path.join(cwd, "agent.ts"))) throw new Error(`agent.ts already exists in this directory. Use ${interactive("--force")} to overwrite.`);
|
|
43
|
+
const cliDir = path.dirname(fileURLToPath(import.meta.url));
|
|
44
|
+
const templatesDir = existsSync(path.join(cliDir, "templates")) ? path.join(cliDir, "templates") : path.join(cliDir, "..", "templates");
|
|
45
|
+
const { runInit } = await import("./_init-l_uoyFCN.mjs");
|
|
46
|
+
const template = opts.template || "simple";
|
|
47
|
+
await runCommand(async ({ log }) => {
|
|
48
|
+
log(step("Create", dir));
|
|
49
|
+
await runInit({
|
|
50
|
+
targetDir: cwd,
|
|
51
|
+
template,
|
|
52
|
+
templatesDir
|
|
53
|
+
});
|
|
54
|
+
await installDeps(cwd, log);
|
|
55
|
+
});
|
|
56
|
+
process.chdir(cwd);
|
|
57
|
+
delete process.env.INIT_CWD;
|
|
58
|
+
if (!(opts.skipDeploy || extra?.quiet)) {
|
|
59
|
+
const { runDeployCommand } = await import("./deploy-KyNJaoP5.mjs");
|
|
60
|
+
await runDeployCommand({ cwd });
|
|
61
|
+
}
|
|
62
|
+
return cwd;
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
export { runInitCommand };
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { i as getServerInfo } from "./_discover-BzlCDVZ6.mjs";
|
|
3
|
+
import { a as step, i as runCommand, n as info, s as warn, t as detail } from "./_ui-kJIua5L9.mjs";
|
|
4
|
+
import { errorMessage } from "@alexkroman1/aai/utils";
|
|
5
|
+
import pLimit from "p-limit";
|
|
6
|
+
//#region rag.ts
|
|
7
|
+
const FETCH_TIMEOUT_MS = 6e4;
|
|
8
|
+
async function runRag(opts) {
|
|
9
|
+
const { url, apiKey, serverUrl, slug, chunkSize, log, setStatus } = opts;
|
|
10
|
+
log(step("Fetch", url));
|
|
11
|
+
const resp = await fetch(url, {
|
|
12
|
+
headers: { "User-Agent": "aai-cli/1.0" },
|
|
13
|
+
redirect: "follow",
|
|
14
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
15
|
+
});
|
|
16
|
+
if (!resp.ok) throw new Error(`Failed to fetch: ${resp.status} ${resp.statusText}`);
|
|
17
|
+
const content = await resp.text();
|
|
18
|
+
if (content.length === 0) {
|
|
19
|
+
log(warn("File is empty"));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
log(info(`${(content.length / 1024).toFixed(0)} KB`));
|
|
23
|
+
const origin = new URL(url).origin;
|
|
24
|
+
const pages = splitPages(content);
|
|
25
|
+
log(step("Parse", `${pages.length} pages`));
|
|
26
|
+
const { RecursiveChunker } = await import("@chonkiejs/core");
|
|
27
|
+
const allChunks = await chunkPages(pages, await RecursiveChunker.create({ chunkSize }), origin, slugify(origin));
|
|
28
|
+
log(step("Chunk", `${allChunks.length} chunks`));
|
|
29
|
+
const vectorUrl = `${serverUrl}/${slug}/vector`;
|
|
30
|
+
log(info(`target: ${vectorUrl}`));
|
|
31
|
+
const result = await upsertChunks(allChunks, vectorUrl, apiKey, setStatus);
|
|
32
|
+
log(step("Done", `${result.upserted} chunks upserted`));
|
|
33
|
+
if (result.errors > 0) {
|
|
34
|
+
log(warn(`${result.errors} failed`));
|
|
35
|
+
if (result.lastError) log(info(`last error: ${result.lastError}`));
|
|
36
|
+
}
|
|
37
|
+
log(detail(`Agent: ${slug}`));
|
|
38
|
+
}
|
|
39
|
+
async function chunkPages(pages, chunker, origin, siteSlug) {
|
|
40
|
+
const allChunks = [];
|
|
41
|
+
for (const page of pages) {
|
|
42
|
+
page.body = stripNoise(page.body);
|
|
43
|
+
if (!page.body) continue;
|
|
44
|
+
const raw = await chunker.chunk(page.body);
|
|
45
|
+
for (const [i, c] of raw.entries()) {
|
|
46
|
+
const data = page.title ? `${page.title}\n\n${c.text}` : c.text;
|
|
47
|
+
const id = `${siteSlug}:${slugify(page.title || "page")}:${i}`;
|
|
48
|
+
allChunks.push({
|
|
49
|
+
id,
|
|
50
|
+
data,
|
|
51
|
+
metadata: {
|
|
52
|
+
source: origin,
|
|
53
|
+
...page.title ? { title: page.title } : {},
|
|
54
|
+
tokenCount: c.tokenCount
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return allChunks;
|
|
60
|
+
}
|
|
61
|
+
async function upsertChunks(chunks, vectorUrl, apiKey, setStatus, fetchFn = globalThis.fetch) {
|
|
62
|
+
const total = chunks.length;
|
|
63
|
+
let completed = 0;
|
|
64
|
+
let upserted = 0;
|
|
65
|
+
let errors = 0;
|
|
66
|
+
let lastError = "";
|
|
67
|
+
const updateStatus = () => {
|
|
68
|
+
const pct = Math.round(completed / total * 100);
|
|
69
|
+
setStatus(` Upsert ${completed}/${total} (${pct}%)`);
|
|
70
|
+
};
|
|
71
|
+
updateStatus();
|
|
72
|
+
const limit = pLimit(5);
|
|
73
|
+
await Promise.all(chunks.map((chunk) => limit(async () => {
|
|
74
|
+
try {
|
|
75
|
+
const r = await fetchFn(vectorUrl, {
|
|
76
|
+
method: "POST",
|
|
77
|
+
headers: {
|
|
78
|
+
"Content-Type": "application/json",
|
|
79
|
+
Authorization: `Bearer ${apiKey}`
|
|
80
|
+
},
|
|
81
|
+
body: JSON.stringify({
|
|
82
|
+
op: "upsert",
|
|
83
|
+
id: chunk.id,
|
|
84
|
+
data: chunk.data,
|
|
85
|
+
metadata: chunk.metadata
|
|
86
|
+
})
|
|
87
|
+
});
|
|
88
|
+
if (!r.ok) {
|
|
89
|
+
lastError = await r.text();
|
|
90
|
+
errors++;
|
|
91
|
+
} else upserted++;
|
|
92
|
+
} catch (err) {
|
|
93
|
+
lastError = errorMessage(err);
|
|
94
|
+
errors++;
|
|
95
|
+
}
|
|
96
|
+
completed++;
|
|
97
|
+
updateStatus();
|
|
98
|
+
})));
|
|
99
|
+
setStatus(null);
|
|
100
|
+
return {
|
|
101
|
+
upserted,
|
|
102
|
+
errors,
|
|
103
|
+
lastError
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
async function runRagCommand(opts) {
|
|
107
|
+
const { url, cwd } = opts;
|
|
108
|
+
try {
|
|
109
|
+
new URL(url);
|
|
110
|
+
} catch {
|
|
111
|
+
throw new Error(`Invalid URL: ${url}`);
|
|
112
|
+
}
|
|
113
|
+
const { apiKey, serverUrl, slug } = await getServerInfo(cwd, opts.server);
|
|
114
|
+
const chunkSize = Number.parseInt(opts.chunkSize ?? "512", 10);
|
|
115
|
+
await runCommand(async ({ log, setStatus }) => {
|
|
116
|
+
await runRag({
|
|
117
|
+
url,
|
|
118
|
+
apiKey,
|
|
119
|
+
serverUrl,
|
|
120
|
+
slug,
|
|
121
|
+
chunkSize,
|
|
122
|
+
log,
|
|
123
|
+
setStatus
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/** Split llms-full.txt on `***` page separators and extract titles. */
|
|
128
|
+
function splitPages(content) {
|
|
129
|
+
const raw = content.split(/^\*{3,}$/m);
|
|
130
|
+
const pages = [];
|
|
131
|
+
for (const section of raw) {
|
|
132
|
+
const trimmed = section.trim();
|
|
133
|
+
if (!trimmed) continue;
|
|
134
|
+
const page = parsePage(trimmed);
|
|
135
|
+
if (page.body.length > 0) pages.push(page);
|
|
136
|
+
}
|
|
137
|
+
return pages;
|
|
138
|
+
}
|
|
139
|
+
/** Extract title and body from a single page section. */
|
|
140
|
+
function parsePage(trimmed) {
|
|
141
|
+
let title = "";
|
|
142
|
+
let body = trimmed;
|
|
143
|
+
const dashIndex = trimmed.search(/^-{3,}$/m);
|
|
144
|
+
if (dashIndex !== -1) {
|
|
145
|
+
const frontmatter = trimmed.slice(0, dashIndex);
|
|
146
|
+
body = trimmed.slice(dashIndex).replace(/^-+$/m, "").trim();
|
|
147
|
+
const titleMatch = frontmatter.match(/^title:\s*(.+)$/m);
|
|
148
|
+
if (titleMatch) title = titleMatch[1]?.trim() ?? "";
|
|
149
|
+
}
|
|
150
|
+
if (!title) {
|
|
151
|
+
const titleLineMatch = body.match(/^#{1,2}\s+title:\s*(.+)$/m);
|
|
152
|
+
if (titleLineMatch) {
|
|
153
|
+
title = titleLineMatch[1]?.trim() ?? "";
|
|
154
|
+
body = body.replace(/^#{1,2}\s+title:\s*.+\n?/m, "").trim();
|
|
155
|
+
} else {
|
|
156
|
+
const headingMatch = body.match(/^(#{1,3})\s+(.+)$/m);
|
|
157
|
+
if (headingMatch) title = headingMatch[2]?.trim() ?? "";
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
title,
|
|
162
|
+
body
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/** Strip code blocks, HTML/JSX tags, and collapse whitespace from markdown. */
|
|
166
|
+
function stripNoise(text) {
|
|
167
|
+
return text.replace(/^(`{3,}|~{3,}).*[\s\S]*?^\1/gm, "").replace(/^(?:[ ]{4,}|\t).+$/gm, "").replace(/`[^`]+`/g, "").replace(/\{\/\*[\s\S]*?\*\/\}/g, "").replace(/<[^>]+>/g, "").replace(/^\s*\}[^}\n]*$/gm, "").replace(/^\s+$/gm, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
168
|
+
}
|
|
169
|
+
function slugify(s) {
|
|
170
|
+
return s.replace(/^https?:\/\//, "").replace(/^#+\s*/, "").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase().slice(0, 80);
|
|
171
|
+
}
|
|
172
|
+
//#endregion
|
|
173
|
+
export { runRagCommand };
|