@alexkroman1/aai-cli 0.9.3 → 0.10.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/{_build-Cf_bB5Vl.mjs → _build-aW_zyPpr.mjs} +57 -11
- package/dist/{_discover-DsorPpR_.mjs → _discover-D7HCLa_N.mjs} +62 -27
- package/dist/_init-CweK2mJJ.mjs +108 -0
- package/dist/{_link-BGXGFYWa.mjs → _link-D6mmDQ0B.mjs} +5 -3
- package/dist/_server-common-B6EXxxB2.mjs +105 -0
- package/dist/_ui-C9IvR7Fh.mjs +50 -0
- package/dist/cli.mjs +186 -31
- package/dist/delete-DxECFYOp.mjs +40 -0
- package/dist/{deploy-2OxZpIfj.mjs → deploy-DB0PeNtc.mjs} +11 -7
- package/dist/dev-DCtHG1pz.mjs +60 -0
- package/dist/doctor-bWuArZi6.mjs +215 -0
- package/dist/generate-D7zJtqoC.mjs +393 -0
- package/dist/{init-nHgDq3Om.mjs → init-COkINbH_.mjs} +13 -17
- package/dist/{rag-D0xqGIrS.mjs → rag-BNfrEr4A.mjs} +12 -7
- package/dist/{secret-BQl3zBOS.mjs → secret-Zzv7fJ_3.mjs} +2 -2
- package/dist/{start-Df8c2ciS.mjs → start-DQypQkOq.mjs} +5 -5
- package/dist/test-9fFn9DCf.mjs +40 -0
- package/package.json +18 -11
- package/dist/_init-l_uoyFCN.mjs +0 -82
- package/dist/_server-common-CpK4rWGs.mjs +0 -36
- package/dist/_ui-kJIua5L9.mjs +0 -44
- package/dist/dev-BIgNj8Dt.mjs +0 -39
- package/templates/_shared/.env.example +0 -5
- package/templates/_shared/CLAUDE.md +0 -1051
- package/templates/_shared/biome.json +0 -32
- package/templates/_shared/global.d.ts +0 -1
- package/templates/_shared/index.html +0 -16
- package/templates/_shared/package.json +0 -23
- package/templates/_shared/tsconfig.json +0 -15
- package/templates/code-interpreter/agent.ts +0 -27
- package/templates/code-interpreter/client.tsx +0 -3
- package/templates/css.d.ts +0 -1
- package/templates/dispatch-center/agent.ts +0 -1227
- package/templates/dispatch-center/client.tsx +0 -505
- package/templates/embedded-assets/agent.ts +0 -48
- package/templates/embedded-assets/client.tsx +0 -3
- package/templates/embedded-assets/knowledge.json +0 -20
- package/templates/health-assistant/agent.ts +0 -160
- package/templates/health-assistant/client.tsx +0 -3
- package/templates/infocom-adventure/agent.ts +0 -164
- package/templates/infocom-adventure/client.tsx +0 -300
- package/templates/math-buddy/agent.ts +0 -21
- package/templates/math-buddy/client.tsx +0 -3
- package/templates/memory-agent/agent.ts +0 -20
- package/templates/memory-agent/client.tsx +0 -3
- package/templates/night-owl/agent.ts +0 -98
- package/templates/night-owl/client.tsx +0 -12
- package/templates/personal-finance/agent.ts +0 -26
- package/templates/personal-finance/client.tsx +0 -3
- package/templates/pizza-ordering/agent.ts +0 -218
- package/templates/pizza-ordering/client.tsx +0 -264
- package/templates/simple/agent.ts +0 -6
- package/templates/simple/client.tsx +0 -3
- package/templates/smart-research/agent.ts +0 -164
- package/templates/smart-research/client.tsx +0 -3
- package/templates/solo-rpg/agent.ts +0 -1244
- package/templates/solo-rpg/client.tsx +0 -698
- package/templates/support/README.md +0 -62
- package/templates/support/agent.ts +0 -19
- package/templates/support/client.tsx +0 -3
- package/templates/travel-concierge/agent.ts +0 -29
- package/templates/travel-concierge/client.tsx +0 -3
- package/templates/tsconfig.json +0 -1
- package/templates/web-researcher/agent.ts +0 -17
- package/templates/web-researcher/client.tsx +0 -3
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { a as
|
|
2
|
+
import { s as loadAgent } from "./_discover-D7HCLa_N.mjs";
|
|
3
|
+
import { a as runCommand, o as step } from "./_ui-C9IvR7Fh.mjs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import fs from "node:fs/promises";
|
|
6
6
|
import { errorMessage } from "@alexkroman1/aai/utils";
|
|
7
7
|
import preact from "@preact/preset-vite";
|
|
8
8
|
import tailwindcss from "@tailwindcss/vite";
|
|
9
|
-
import { build } from "vite";
|
|
9
|
+
import { build, createServer } from "vite";
|
|
10
10
|
//#region _bundler.ts
|
|
11
11
|
/**
|
|
12
12
|
* Error thrown when bundling fails.
|
|
@@ -14,11 +14,28 @@ import { build } from "vite";
|
|
|
14
14
|
* @param message Human-readable error message (typically formatted build output).
|
|
15
15
|
*/
|
|
16
16
|
var BundleError = class extends Error {
|
|
17
|
-
constructor(message) {
|
|
18
|
-
super(message);
|
|
17
|
+
constructor(message, options) {
|
|
18
|
+
super(message, options);
|
|
19
19
|
this.name = "BundleError";
|
|
20
20
|
}
|
|
21
21
|
};
|
|
22
|
+
/** File extensions that are safe to read as UTF-8 text. */
|
|
23
|
+
const TEXT_EXTENSIONS = new Set([
|
|
24
|
+
".html",
|
|
25
|
+
".htm",
|
|
26
|
+
".css",
|
|
27
|
+
".js",
|
|
28
|
+
".mjs",
|
|
29
|
+
".cjs",
|
|
30
|
+
".ts",
|
|
31
|
+
".mts",
|
|
32
|
+
".json",
|
|
33
|
+
".map",
|
|
34
|
+
".svg",
|
|
35
|
+
".xml",
|
|
36
|
+
".txt",
|
|
37
|
+
".md"
|
|
38
|
+
]);
|
|
22
39
|
/** Read all files in a directory as a map of relative paths to contents. */
|
|
23
40
|
async function readDirFiles(dir) {
|
|
24
41
|
let entries;
|
|
@@ -34,7 +51,10 @@ async function readDirFiles(dir) {
|
|
|
34
51
|
const files = {};
|
|
35
52
|
await Promise.all(entries.filter((e) => e.isFile()).map(async (e) => {
|
|
36
53
|
const full = path.join(e.parentPath, e.name);
|
|
37
|
-
|
|
54
|
+
const ext = path.extname(e.name).toLowerCase();
|
|
55
|
+
const rel = path.relative(dir, full);
|
|
56
|
+
if (TEXT_EXTENSIONS.has(ext)) files[rel] = await fs.readFile(full, "utf-8");
|
|
57
|
+
else files[rel] = `base64:${(await fs.readFile(full)).toString("base64")}`;
|
|
38
58
|
}));
|
|
39
59
|
return files;
|
|
40
60
|
}
|
|
@@ -69,9 +89,9 @@ async function bundleAgent(agent, opts) {
|
|
|
69
89
|
}
|
|
70
90
|
});
|
|
71
91
|
} catch (err) {
|
|
72
|
-
throw new BundleError(errorMessage(err));
|
|
92
|
+
throw new BundleError(errorMessage(err), { cause: err });
|
|
73
93
|
}
|
|
74
|
-
if (!(opts?.skipClient
|
|
94
|
+
if (!(opts?.skipClient ?? !agent.clientEntry)) try {
|
|
75
95
|
await build({
|
|
76
96
|
root: agent.dir,
|
|
77
97
|
base: "./",
|
|
@@ -86,7 +106,7 @@ async function bundleAgent(agent, opts) {
|
|
|
86
106
|
}
|
|
87
107
|
});
|
|
88
108
|
} catch (err) {
|
|
89
|
-
throw new BundleError(errorMessage(err));
|
|
109
|
+
throw new BundleError(errorMessage(err), { cause: err });
|
|
90
110
|
}
|
|
91
111
|
const worker = await fs.readFile(path.join(buildDir, "worker.js"), "utf-8");
|
|
92
112
|
return {
|
|
@@ -96,6 +116,32 @@ async function bundleAgent(agent, opts) {
|
|
|
96
116
|
workerBytes: Buffer.byteLength(worker)
|
|
97
117
|
};
|
|
98
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Create a Vite dev server for client HMR during development.
|
|
121
|
+
*
|
|
122
|
+
* The dev server serves client files with hot module replacement enabled and
|
|
123
|
+
* proxies backend requests (`/health`, `/websocket`) to the agent server.
|
|
124
|
+
*/
|
|
125
|
+
async function createClientDevServer(agentDir, backendPort, port) {
|
|
126
|
+
const target = `http://localhost:${backendPort}`;
|
|
127
|
+
return await createServer({
|
|
128
|
+
configFile: false,
|
|
129
|
+
root: agentDir,
|
|
130
|
+
plugins: [preact(), tailwindcss()],
|
|
131
|
+
resolve: { dedupe: ["preact", "@preact/signals"] },
|
|
132
|
+
server: {
|
|
133
|
+
port,
|
|
134
|
+
strictPort: true,
|
|
135
|
+
proxy: {
|
|
136
|
+
"/health": target,
|
|
137
|
+
"/websocket": {
|
|
138
|
+
target,
|
|
139
|
+
ws: true
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
99
145
|
//#endregion
|
|
100
146
|
//#region _build.ts
|
|
101
147
|
/**
|
|
@@ -111,7 +157,7 @@ async function buildAgentBundle(cwd, log) {
|
|
|
111
157
|
try {
|
|
112
158
|
bundle = await bundleAgent(agent);
|
|
113
159
|
} catch (err) {
|
|
114
|
-
if (err instanceof BundleError) throw new Error(`Bundle failed: ${err.message}
|
|
160
|
+
if (err instanceof BundleError) throw new Error(`Bundle failed: ${err.message}`, { cause: err });
|
|
115
161
|
throw err;
|
|
116
162
|
}
|
|
117
163
|
const kb = (bundle.workerBytes / 1024).toFixed(1);
|
|
@@ -127,4 +173,4 @@ async function runBuildCommand(cwd) {
|
|
|
127
173
|
});
|
|
128
174
|
}
|
|
129
175
|
//#endregion
|
|
130
|
-
export { buildAgentBundle, runBuildCommand };
|
|
176
|
+
export { buildAgentBundle, runBuildCommand, createClientDevServer as t };
|
|
@@ -3,44 +3,69 @@ import { existsSync } from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import fs$1 from "node:fs/promises";
|
|
6
|
+
import { consola } from "consola";
|
|
6
7
|
import { humanId } from "human-id";
|
|
7
8
|
import { z } from "zod";
|
|
8
|
-
import
|
|
9
|
+
import { createInterface } from "node:readline";
|
|
9
10
|
//#region _prompts.ts
|
|
11
|
+
const consola$1 = consola.create({ formatOptions: { date: false } });
|
|
10
12
|
/**
|
|
11
13
|
* Prompt the user for a password (masked input).
|
|
12
14
|
* Returns the entered string.
|
|
13
15
|
*/
|
|
14
16
|
async function askPassword(message) {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
const rl = createInterface({
|
|
18
|
+
input: process.stdin,
|
|
19
|
+
output: process.stdout
|
|
20
|
+
});
|
|
21
|
+
process.stdout.write(`${message}: `);
|
|
22
|
+
const stdin = process.stdin;
|
|
23
|
+
const wasRaw = stdin.isRaw;
|
|
24
|
+
if (stdin.isTTY) stdin.setRawMode(true);
|
|
25
|
+
try {
|
|
26
|
+
return await readMasked(stdin);
|
|
27
|
+
} finally {
|
|
28
|
+
if (stdin.isTTY) stdin.setRawMode(wasRaw ?? false);
|
|
29
|
+
rl.close();
|
|
19
30
|
}
|
|
20
|
-
|
|
31
|
+
}
|
|
32
|
+
function readMasked(stdin) {
|
|
33
|
+
return new Promise((resolve) => {
|
|
34
|
+
let buf = "";
|
|
35
|
+
const onData = (ch) => {
|
|
36
|
+
const c = ch.toString();
|
|
37
|
+
if (c === "\n" || c === "\r") {
|
|
38
|
+
stdin.removeListener("data", onData);
|
|
39
|
+
process.stdout.write("\n");
|
|
40
|
+
resolve(buf);
|
|
41
|
+
} else if (c === "") {
|
|
42
|
+
stdin.removeListener("data", onData);
|
|
43
|
+
process.exit(0);
|
|
44
|
+
} else if (c === "" || c === "\b") buf = buf.slice(0, -1);
|
|
45
|
+
else buf += c;
|
|
46
|
+
};
|
|
47
|
+
stdin.on("data", onData);
|
|
48
|
+
});
|
|
21
49
|
}
|
|
22
50
|
/**
|
|
23
51
|
* Prompt the user for text input with a default value.
|
|
24
52
|
* Returns the entered string, or the default if empty.
|
|
25
53
|
*/
|
|
26
54
|
async function askText(message, defaultValue) {
|
|
27
|
-
|
|
28
|
-
|
|
55
|
+
return await consola$1.prompt(message, {
|
|
56
|
+
type: "text",
|
|
29
57
|
placeholder: defaultValue,
|
|
30
|
-
defaultValue
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
p.cancel("Cancelled.");
|
|
34
|
-
process.exit(0);
|
|
35
|
-
}
|
|
36
|
-
return value || defaultValue;
|
|
58
|
+
initial: defaultValue,
|
|
59
|
+
cancel: "reject"
|
|
60
|
+
}) || defaultValue;
|
|
37
61
|
}
|
|
38
62
|
//#endregion
|
|
39
63
|
//#region _discover.ts
|
|
40
64
|
const AuthConfigSchema = z.object({ assemblyai_api_key: z.string().optional() });
|
|
41
65
|
const ProjectConfigSchema = z.object({
|
|
42
66
|
slug: z.string(),
|
|
43
|
-
serverUrl: z.string()
|
|
67
|
+
serverUrl: z.string(),
|
|
68
|
+
sessionId: z.string().optional()
|
|
44
69
|
});
|
|
45
70
|
/** Resolve the working directory from INIT_CWD or process.cwd(). */
|
|
46
71
|
function resolveCwd() {
|
|
@@ -70,24 +95,34 @@ async function writeAuthConfig(config) {
|
|
|
70
95
|
if (process.platform !== "win32") await fs$1.chmod(CONFIG_FILE, 384);
|
|
71
96
|
}
|
|
72
97
|
/**
|
|
73
|
-
* Retrieves the AssemblyAI API key from `~/.config/aai/config.json
|
|
74
|
-
*
|
|
98
|
+
* Retrieves the AssemblyAI API key from `process.env`, `~/.config/aai/config.json`,
|
|
99
|
+
* or by interactively prompting the user (persisting it to config).
|
|
100
|
+
*
|
|
101
|
+
* Does NOT mutate `process.env`. Callers that need the key available in the
|
|
102
|
+
* environment for child processes should use {@link ensureApiKeyInEnv} instead.
|
|
75
103
|
*/
|
|
76
104
|
async function getApiKey() {
|
|
77
105
|
if (process.env.ASSEMBLYAI_API_KEY) return process.env.ASSEMBLYAI_API_KEY;
|
|
78
106
|
const config = await readAuthConfig();
|
|
79
|
-
if (config.assemblyai_api_key)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
107
|
+
if (config.assemblyai_api_key) return config.assemblyai_api_key;
|
|
108
|
+
consola.info("Get your API key at https://www.assemblyai.com/dashboard/signup");
|
|
109
|
+
consola.info("Or set the ASSEMBLYAI_API_KEY environment variable to skip this prompt.\n");
|
|
83
110
|
let key;
|
|
84
111
|
while (!key) key = await askPassword("ASSEMBLYAI_API_KEY");
|
|
85
112
|
config.assemblyai_api_key = key;
|
|
86
|
-
process.env.ASSEMBLYAI_API_KEY = key;
|
|
87
113
|
await writeAuthConfig(config);
|
|
88
114
|
return key;
|
|
89
115
|
}
|
|
90
116
|
/**
|
|
117
|
+
* Resolves the API key via {@link getApiKey} and sets it on `process.env.ASSEMBLYAI_API_KEY`
|
|
118
|
+
* so that child processes and downstream code can read it from the environment.
|
|
119
|
+
*/
|
|
120
|
+
async function ensureApiKeyInEnv() {
|
|
121
|
+
const key = await getApiKey();
|
|
122
|
+
process.env.ASSEMBLYAI_API_KEY = key;
|
|
123
|
+
return key;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
91
126
|
* Reads `.aai/project.json` from an agent directory.
|
|
92
127
|
* Returns null if the file doesn't exist.
|
|
93
128
|
*/
|
|
@@ -129,7 +164,7 @@ function isDevMode() {
|
|
|
129
164
|
}
|
|
130
165
|
/** Resolve the server URL from an explicit value, project config, or default. */
|
|
131
166
|
function resolveServerUrl(explicit, configUrl) {
|
|
132
|
-
return explicit
|
|
167
|
+
return explicit ?? configUrl ?? (isDevMode() ? "http://localhost:8787" : "https://aai-agent.fly.dev");
|
|
133
168
|
}
|
|
134
169
|
async function fileExists(p) {
|
|
135
170
|
try {
|
|
@@ -143,8 +178,8 @@ async function fileExists(p) {
|
|
|
143
178
|
* Loads agent metadata from a directory by checking for `agent.ts` and
|
|
144
179
|
* resolving the client entry point.
|
|
145
180
|
*
|
|
146
|
-
* Env vars
|
|
147
|
-
*
|
|
181
|
+
* Env vars for deployed agents are managed on the server via
|
|
182
|
+
* `aai secret put`. For local dev, `.env` is loaded by `resolveServerEnv`.
|
|
148
183
|
*/
|
|
149
184
|
async function loadAgent(dir) {
|
|
150
185
|
if (!await fileExists(path.join(dir, "agent.ts"))) return null;
|
|
@@ -158,4 +193,4 @@ async function loadAgent(dir) {
|
|
|
158
193
|
};
|
|
159
194
|
}
|
|
160
195
|
//#endregion
|
|
161
|
-
export {
|
|
196
|
+
export { getServerInfo as a, readProjectConfig as c, writeProjectConfig as d, askPassword as f, getApiKey as i, resolveCwd as l, fileExists as n, isDevMode as o, askText as p, generateSlug as r, loadAgent as s, ensureApiKeyInEnv as t, resolveServerUrl as u };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { o as isDevMode } from "./_discover-D7HCLa_N.mjs";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import fs$1 from "node:fs/promises";
|
|
7
|
+
import { downloadTemplate } from "giget";
|
|
8
|
+
//#region _templates.ts
|
|
9
|
+
const GIGET_SOURCE = "github:alexkroman/agent/packages/aai-templates";
|
|
10
|
+
const GIGET_REF = process.env.AAI_TEMPLATES_REF ?? "main";
|
|
11
|
+
/** Resolve the local aai-templates package directory (dev mode only). */
|
|
12
|
+
function resolveLocalTemplatesDir() {
|
|
13
|
+
const cliDir = path.dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const fromSrc = path.resolve(cliDir, "../aai-templates");
|
|
15
|
+
const fromDist = path.resolve(cliDir, "../../aai-templates");
|
|
16
|
+
if (existsSync(fromSrc)) return fromSrc;
|
|
17
|
+
if (existsSync(fromDist)) return fromDist;
|
|
18
|
+
throw new Error("Cannot find local aai-templates package");
|
|
19
|
+
}
|
|
20
|
+
/** Resolve the templates directory — local in dev, giget download in prod. */
|
|
21
|
+
async function resolveTemplatesDir() {
|
|
22
|
+
if (isDevMode()) return resolveLocalTemplatesDir();
|
|
23
|
+
const { dir } = await downloadTemplate(`${GIGET_SOURCE}#${GIGET_REF}`, { force: true });
|
|
24
|
+
return dir;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Download a template into targetDir, merging _shared files underneath.
|
|
28
|
+
*/
|
|
29
|
+
async function downloadAndMergeTemplate(template, targetDir) {
|
|
30
|
+
const templatesDir = await resolveTemplatesDir();
|
|
31
|
+
const names = (await fs$1.readdir(templatesDir, { withFileTypes: true })).filter((e) => e.isDirectory() && !e.name.startsWith("_") && e.name !== "node_modules").map((e) => e.name);
|
|
32
|
+
if (!names.includes(template)) throw new Error(`unknown template '${template}' -- available: ${names.join(", ")}`);
|
|
33
|
+
await fs$1.cp(path.join(templatesDir, template), targetDir, {
|
|
34
|
+
recursive: true,
|
|
35
|
+
force: true
|
|
36
|
+
});
|
|
37
|
+
const sharedDir = path.join(templatesDir, "_shared");
|
|
38
|
+
if (existsSync(sharedDir)) {
|
|
39
|
+
const entries = await fs$1.readdir(sharedDir, {
|
|
40
|
+
recursive: true,
|
|
41
|
+
withFileTypes: true
|
|
42
|
+
});
|
|
43
|
+
for (const entry of entries) {
|
|
44
|
+
if (!entry.isFile()) continue;
|
|
45
|
+
const rel = path.relative(sharedDir, path.join(entry.parentPath, entry.name));
|
|
46
|
+
const destPath = path.join(targetDir, rel);
|
|
47
|
+
await fs$1.mkdir(path.dirname(destPath), { recursive: true });
|
|
48
|
+
try {
|
|
49
|
+
await fs$1.copyFile(path.join(sharedDir, rel), destPath, fs$1.constants.COPYFILE_EXCL);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
if (!(err instanceof Error && "code" in err && err.code === "EEXIST")) throw err;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region _init.ts
|
|
58
|
+
async function runInit(opts) {
|
|
59
|
+
const { targetDir, template } = opts;
|
|
60
|
+
await downloadAndMergeTemplate(template, targetDir);
|
|
61
|
+
try {
|
|
62
|
+
await fs$1.copyFile(path.join(targetDir, ".env.example"), path.join(targetDir, ".env"));
|
|
63
|
+
} catch {}
|
|
64
|
+
const readmePath = path.join(targetDir, "README.md");
|
|
65
|
+
const readme = `# ${path.basename(path.resolve(targetDir))}
|
|
66
|
+
|
|
67
|
+
A voice agent built with [aai](https://github.com/anthropics/aai).
|
|
68
|
+
|
|
69
|
+
## Getting started
|
|
70
|
+
|
|
71
|
+
\`\`\`sh
|
|
72
|
+
npm install # Install dependencies
|
|
73
|
+
npm run dev # Run locally (opens browser)
|
|
74
|
+
npm run deploy # Deploy to production
|
|
75
|
+
\`\`\`
|
|
76
|
+
|
|
77
|
+
## Secrets
|
|
78
|
+
|
|
79
|
+
Access secrets in your agent via \`ctx.env.MY_KEY\`.
|
|
80
|
+
|
|
81
|
+
**Local development** — add secrets to \`.env\` (auto-loaded by \`aai dev\`):
|
|
82
|
+
|
|
83
|
+
\`\`\`sh
|
|
84
|
+
ALPHA_VANTAGE_KEY=sk-abc123
|
|
85
|
+
MY_API_KEY=secret-value
|
|
86
|
+
\`\`\`
|
|
87
|
+
|
|
88
|
+
**Production** — set secrets on the server:
|
|
89
|
+
|
|
90
|
+
\`\`\`sh
|
|
91
|
+
aai secret put MY_KEY # Set a secret (prompts for value)
|
|
92
|
+
aai secret list # List secret names
|
|
93
|
+
aai secret delete MY_KEY # Remove a secret
|
|
94
|
+
\`\`\`
|
|
95
|
+
|
|
96
|
+
## Learn more
|
|
97
|
+
|
|
98
|
+
See \`CLAUDE.md\` for the full agent API reference.
|
|
99
|
+
`;
|
|
100
|
+
try {
|
|
101
|
+
await fs$1.writeFile(readmePath, readme, { flag: "wx" });
|
|
102
|
+
} catch (err) {
|
|
103
|
+
if (!(err instanceof Error && "code" in err && err.code === "EEXIST")) throw err;
|
|
104
|
+
}
|
|
105
|
+
return targetDir;
|
|
106
|
+
}
|
|
107
|
+
//#endregion
|
|
108
|
+
export { runInit };
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { consola } from "consola";
|
|
6
|
+
import { z } from "zod";
|
|
5
7
|
import { execFileSync } from "node:child_process";
|
|
6
8
|
//#region _link.ts
|
|
7
9
|
const WORKSPACE_PKGS = ["aai", "aai-ui"];
|
|
@@ -18,7 +20,7 @@ function rewriteWorkspaceDeps(cwd, rewrite, verb) {
|
|
|
18
20
|
const changed = [];
|
|
19
21
|
for (const pkgDir of WORKSPACE_PKGS) {
|
|
20
22
|
const localPath = path.join(packagesDir, pkgDir);
|
|
21
|
-
const name = JSON.parse(fs.readFileSync(path.join(localPath, "package.json"), "utf-8")).name;
|
|
23
|
+
const name = z.object({ name: z.string() }).parse(JSON.parse(fs.readFileSync(path.join(localPath, "package.json"), "utf-8"))).name;
|
|
22
24
|
if (!deps[name]) continue;
|
|
23
25
|
const newVersion = rewrite(deps[name], localPath);
|
|
24
26
|
if (newVersion !== null) {
|
|
@@ -27,11 +29,11 @@ function rewriteWorkspaceDeps(cwd, rewrite, verb) {
|
|
|
27
29
|
}
|
|
28
30
|
}
|
|
29
31
|
if (changed.length === 0) {
|
|
30
|
-
|
|
32
|
+
consola.info(`No packages to ${verb}.`);
|
|
31
33
|
return;
|
|
32
34
|
}
|
|
33
35
|
fs.writeFileSync(pkgJsonPath, `${JSON.stringify(pkgJson, null, 2)}\n`);
|
|
34
|
-
|
|
36
|
+
consola.start(`${verb}: ${changed.join(", ")} → installing...`);
|
|
35
37
|
execFileSync("npm", ["install"], {
|
|
36
38
|
cwd,
|
|
37
39
|
stdio: "inherit"
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { i as getApiKey } from "./_discover-D7HCLa_N.mjs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import fs from "node:fs/promises";
|
|
5
|
+
//#region _server-common.ts
|
|
6
|
+
/**
|
|
7
|
+
* Return the variable names declared in a `.env` file.
|
|
8
|
+
*
|
|
9
|
+
* Only used to determine *which* keys the developer intended as agent
|
|
10
|
+
* secrets — actual values are resolved from `process.env` (so shell
|
|
11
|
+
* overrides still win).
|
|
12
|
+
*/
|
|
13
|
+
function envFileKeys(content) {
|
|
14
|
+
const keys = [];
|
|
15
|
+
for (const raw of content.split("\n")) {
|
|
16
|
+
const line = raw.trim();
|
|
17
|
+
if (!line || line.startsWith("#")) continue;
|
|
18
|
+
const eq = line.indexOf("=");
|
|
19
|
+
if (eq === -1) continue;
|
|
20
|
+
const key = line.slice(0, eq).trim();
|
|
21
|
+
if (key) keys.push(key);
|
|
22
|
+
}
|
|
23
|
+
return keys;
|
|
24
|
+
}
|
|
25
|
+
/** Load an AgentDef by dynamically importing agent.ts via Node's native TS support. */
|
|
26
|
+
async function loadAgentDef(cwd) {
|
|
27
|
+
const agentDef = (await import(path.resolve(cwd, "agent.ts"))).default;
|
|
28
|
+
if (!agentDef || typeof agentDef !== "object" || !agentDef.name) throw new Error("agent.ts must export a default agent definition (from defineAgent())");
|
|
29
|
+
const missing = [];
|
|
30
|
+
if (typeof agentDef.name !== "string") missing.push("name (string)");
|
|
31
|
+
if (typeof agentDef.instructions !== "string") missing.push("instructions (string)");
|
|
32
|
+
if (typeof agentDef.greeting !== "string") missing.push("greeting (string)");
|
|
33
|
+
if (typeof agentDef.maxSteps !== "number" && typeof agentDef.maxSteps !== "function") missing.push("maxSteps (number or function)");
|
|
34
|
+
if (!agentDef.tools || typeof agentDef.tools !== "object" || Array.isArray(agentDef.tools)) missing.push("tools (object)");
|
|
35
|
+
if (missing.length > 0) throw new Error(`Invalid agent definition: missing or invalid fields: ${missing.join(", ")}. Use defineAgent() to create a valid agent definition.`);
|
|
36
|
+
return agentDef;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Build the `ctx.env` record that agent tools will see at runtime.
|
|
40
|
+
*
|
|
41
|
+
* Only variables explicitly declared in `.env` (plus `ASSEMBLYAI_API_KEY`)
|
|
42
|
+
* are included — matching the platform sandbox behavior where `ctx.env`
|
|
43
|
+
* contains only secrets set via `aai secret put`. This prevents agents
|
|
44
|
+
* from accidentally depending on shell-level vars (PATH, HOME, etc.) that
|
|
45
|
+
* won't exist in production.
|
|
46
|
+
*
|
|
47
|
+
* Values are resolved from `process.env` after loading `.env` via Node's
|
|
48
|
+
* built-in `process.loadEnvFile()`, so shell exports override `.env`.
|
|
49
|
+
*
|
|
50
|
+
* @param cwd - Project directory containing `.env` (optional).
|
|
51
|
+
* @param baseEnv - Override the environment to read values from (tests only).
|
|
52
|
+
*/
|
|
53
|
+
async function resolveServerEnv(cwd, baseEnv) {
|
|
54
|
+
let declaredKeys = [];
|
|
55
|
+
if (cwd) {
|
|
56
|
+
const envPath = path.join(cwd, ".env");
|
|
57
|
+
try {
|
|
58
|
+
declaredKeys = envFileKeys(await fs.readFile(envPath, "utf-8"));
|
|
59
|
+
process.loadEnvFile(envPath);
|
|
60
|
+
} catch {}
|
|
61
|
+
}
|
|
62
|
+
const source = baseEnv ?? process.env;
|
|
63
|
+
const env = {};
|
|
64
|
+
for (const key of declaredKeys) {
|
|
65
|
+
const val = source[key];
|
|
66
|
+
if (val !== void 0) env[key] = val;
|
|
67
|
+
}
|
|
68
|
+
if (!env.ASSEMBLYAI_API_KEY) env.ASSEMBLYAI_API_KEY = source.ASSEMBLYAI_API_KEY ?? await getApiKey();
|
|
69
|
+
return env;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Create and start an agent server with static file serving.
|
|
73
|
+
*
|
|
74
|
+
* NOTE: This dynamically imports `@alexkroman1/aai/server` which has peer
|
|
75
|
+
* dependencies on `hono` and `@hono/node-server`. Those packages are listed
|
|
76
|
+
* as direct dependencies of aai-cli (in package.json) solely to satisfy
|
|
77
|
+
* those peer deps — they are not imported directly by aai-cli code.
|
|
78
|
+
*/
|
|
79
|
+
async function bootServer(agentDef, clientDir, env, port) {
|
|
80
|
+
const { createServer } = await import("@alexkroman1/aai/server");
|
|
81
|
+
const server = createServer({
|
|
82
|
+
agent: agentDef,
|
|
83
|
+
clientDir,
|
|
84
|
+
env
|
|
85
|
+
});
|
|
86
|
+
await server.listen(port);
|
|
87
|
+
return server;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Boot the agent server without client serving.
|
|
91
|
+
*
|
|
92
|
+
* Used in dev mode where Vite handles client files with HMR,
|
|
93
|
+
* and only the backend (health, WebSocket) runs on this server.
|
|
94
|
+
*/
|
|
95
|
+
async function bootBackendServer(agentDef, env, port) {
|
|
96
|
+
const { createServer } = await import("@alexkroman1/aai/server");
|
|
97
|
+
const server = createServer({
|
|
98
|
+
agent: agentDef,
|
|
99
|
+
env
|
|
100
|
+
});
|
|
101
|
+
await server.listen(port);
|
|
102
|
+
return server;
|
|
103
|
+
}
|
|
104
|
+
//#endregion
|
|
105
|
+
export { resolveServerEnv as a, loadAgentDef as i, bootServer as n, envFileKeys as r, bootBackendServer as t };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { colorize } from "consola/utils";
|
|
3
|
+
//#region _ui.ts
|
|
4
|
+
/** Interactive/info color wrapper (blue). */
|
|
5
|
+
function interactive(s) {
|
|
6
|
+
return colorize("blueBright", s);
|
|
7
|
+
}
|
|
8
|
+
/** Colored step message: bold action label + message. */
|
|
9
|
+
function step(action, msg) {
|
|
10
|
+
return `${colorize("bold", colorize("cyanBright", action))} ${msg}`;
|
|
11
|
+
}
|
|
12
|
+
/** Informational step message: bold blue action + message. */
|
|
13
|
+
function stepInfo(action, msg) {
|
|
14
|
+
return `${colorize("bold", colorize("blueBright", action))} ${msg}`;
|
|
15
|
+
}
|
|
16
|
+
/** Dimmed info sub-line (indented). */
|
|
17
|
+
function info(msg) {
|
|
18
|
+
return colorize("dim", ` ${msg}`);
|
|
19
|
+
}
|
|
20
|
+
/** Detail sub-line (indented). */
|
|
21
|
+
function detail(msg) {
|
|
22
|
+
return ` ${msg}`;
|
|
23
|
+
}
|
|
24
|
+
/** Warning message. */
|
|
25
|
+
function warn(msg) {
|
|
26
|
+
return `${colorize("yellowBright", "!")} ${msg}`;
|
|
27
|
+
}
|
|
28
|
+
/** Parse and validate a port string. Returns the numeric port or throws. */
|
|
29
|
+
function parsePort(raw) {
|
|
30
|
+
const port = Number.parseInt(raw, 10);
|
|
31
|
+
if (Number.isNaN(port) || port < 0 || port > 65535) throw new Error(`Invalid port: ${raw}. Must be a number between 0 and 65535.`);
|
|
32
|
+
return port;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Run an async command function, logging each step to stdout.
|
|
36
|
+
* Replaces the Ink `runWithInk` pattern.
|
|
37
|
+
*/
|
|
38
|
+
async function runCommand(fn) {
|
|
39
|
+
const log = (msg) => console.log(msg);
|
|
40
|
+
const setStatus = (msg) => {
|
|
41
|
+
if (msg) process.stdout.write(`\r${colorize("dim", msg)}`);
|
|
42
|
+
else process.stdout.write("\r\x1B[K");
|
|
43
|
+
};
|
|
44
|
+
await fn({
|
|
45
|
+
log,
|
|
46
|
+
setStatus
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
export { runCommand as a, warn as c, parsePort as i, info as n, step as o, interactive as r, stepInfo as s, detail as t };
|