@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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AAI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { o as loadAgent } from "./_discover-BzlCDVZ6.mjs";
|
|
3
|
+
import { a as step, i as runCommand } from "./_ui-kJIua5L9.mjs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import fs from "node:fs/promises";
|
|
6
|
+
import { errorMessage } from "@alexkroman1/aai/utils";
|
|
7
|
+
import preact from "@preact/preset-vite";
|
|
8
|
+
import tailwindcss from "@tailwindcss/vite";
|
|
9
|
+
import { build } from "vite";
|
|
10
|
+
//#region _bundler.ts
|
|
11
|
+
/**
|
|
12
|
+
* Error thrown when bundling fails.
|
|
13
|
+
*
|
|
14
|
+
* @param message Human-readable error message (typically formatted build output).
|
|
15
|
+
*/
|
|
16
|
+
var BundleError = class extends Error {
|
|
17
|
+
constructor(message) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = "BundleError";
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
/** Read all files in a directory as a map of relative paths to contents. */
|
|
23
|
+
async function readDirFiles(dir) {
|
|
24
|
+
let entries;
|
|
25
|
+
try {
|
|
26
|
+
entries = await fs.readdir(dir, {
|
|
27
|
+
recursive: true,
|
|
28
|
+
withFileTypes: true
|
|
29
|
+
});
|
|
30
|
+
} catch (err) {
|
|
31
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") return {};
|
|
32
|
+
throw err;
|
|
33
|
+
}
|
|
34
|
+
const files = {};
|
|
35
|
+
await Promise.all(entries.filter((e) => e.isFile()).map(async (e) => {
|
|
36
|
+
const full = path.join(e.parentPath, e.name);
|
|
37
|
+
files[path.relative(dir, full)] = await fs.readFile(full, "utf-8");
|
|
38
|
+
}));
|
|
39
|
+
return files;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Bundles an agent project into deployable artifacts using Vite.
|
|
43
|
+
*
|
|
44
|
+
* Writes all output to `.aai/` on disk:
|
|
45
|
+
* - `.aai/build/worker.js` — the platform worker bundle
|
|
46
|
+
* - `.aai/client/` — standard Vite multi-file output (index.html + assets/)
|
|
47
|
+
*
|
|
48
|
+
* Both `aai dev` and `aai deploy` use this function identically.
|
|
49
|
+
*/
|
|
50
|
+
async function bundleAgent(agent, opts) {
|
|
51
|
+
const aaiDir = path.join(agent.dir, ".aai");
|
|
52
|
+
const buildDir = path.join(aaiDir, "build");
|
|
53
|
+
const clientDir = path.join(aaiDir, "client");
|
|
54
|
+
try {
|
|
55
|
+
await build({
|
|
56
|
+
configFile: false,
|
|
57
|
+
root: agent.dir,
|
|
58
|
+
logLevel: "warn",
|
|
59
|
+
build: {
|
|
60
|
+
rollupOptions: {
|
|
61
|
+
input: path.join(agent.dir, "agent.ts"),
|
|
62
|
+
output: {
|
|
63
|
+
format: "es",
|
|
64
|
+
entryFileNames: "worker.js"
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
outDir: buildDir,
|
|
68
|
+
emptyOutDir: true,
|
|
69
|
+
minify: true,
|
|
70
|
+
target: "es2022"
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
} catch (err) {
|
|
74
|
+
throw new BundleError(errorMessage(err));
|
|
75
|
+
}
|
|
76
|
+
if (!(opts?.skipClient || !agent.clientEntry)) try {
|
|
77
|
+
await build({
|
|
78
|
+
root: agent.dir,
|
|
79
|
+
base: "./",
|
|
80
|
+
logLevel: "warn",
|
|
81
|
+
plugins: [preact(), tailwindcss()],
|
|
82
|
+
resolve: { dedupe: ["preact", "@preact/signals"] },
|
|
83
|
+
build: {
|
|
84
|
+
outDir: clientDir,
|
|
85
|
+
emptyOutDir: true,
|
|
86
|
+
minify: true,
|
|
87
|
+
target: "es2022"
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
} catch (err) {
|
|
91
|
+
throw new BundleError(errorMessage(err));
|
|
92
|
+
}
|
|
93
|
+
const worker = await fs.readFile(path.join(buildDir, "worker.js"), "utf-8");
|
|
94
|
+
return {
|
|
95
|
+
worker,
|
|
96
|
+
clientFiles: await readDirFiles(clientDir),
|
|
97
|
+
clientDir,
|
|
98
|
+
workerBytes: Buffer.byteLength(worker)
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
//#endregion
|
|
102
|
+
//#region _build.ts
|
|
103
|
+
/**
|
|
104
|
+
* Discover the agent entry and bundle both worker and client.
|
|
105
|
+
*
|
|
106
|
+
* Shared by `aai build`, `aai dev`, and `aai deploy`.
|
|
107
|
+
*/
|
|
108
|
+
async function buildAgentBundle(cwd, log) {
|
|
109
|
+
const agent = await loadAgent(cwd);
|
|
110
|
+
if (!agent) throw new Error("No agent found — run `aai init` first");
|
|
111
|
+
log(step("Bundle", agent.slug));
|
|
112
|
+
let bundle;
|
|
113
|
+
try {
|
|
114
|
+
bundle = await bundleAgent(agent);
|
|
115
|
+
} catch (err) {
|
|
116
|
+
if (err instanceof BundleError) throw new Error(`Bundle failed: ${err.message}`);
|
|
117
|
+
throw err;
|
|
118
|
+
}
|
|
119
|
+
const kb = (bundle.workerBytes / 1024).toFixed(1);
|
|
120
|
+
const clientCount = Object.keys(bundle.clientFiles).length;
|
|
121
|
+
log(`worker: ${kb} KB, client: ${clientCount} file(s)`);
|
|
122
|
+
return bundle;
|
|
123
|
+
}
|
|
124
|
+
/** Bundle the agent and report success. Used by `aai build`. */
|
|
125
|
+
async function runBuildCommand(cwd) {
|
|
126
|
+
await runCommand(async ({ log }) => {
|
|
127
|
+
await buildAgentBundle(cwd, log);
|
|
128
|
+
log(step("Build", "ok"));
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
//#endregion
|
|
132
|
+
export { buildAgentBundle, runBuildCommand };
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import fs$1 from "node:fs/promises";
|
|
6
|
+
import { humanId } from "human-id";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import * as p from "@clack/prompts";
|
|
9
|
+
//#region _prompts.ts
|
|
10
|
+
/**
|
|
11
|
+
* Prompt the user for a password (masked input).
|
|
12
|
+
* Returns the entered string.
|
|
13
|
+
*/
|
|
14
|
+
async function askPassword(message) {
|
|
15
|
+
const value = await p.password({ message });
|
|
16
|
+
if (p.isCancel(value)) {
|
|
17
|
+
p.cancel("Cancelled.");
|
|
18
|
+
process.exit(0);
|
|
19
|
+
}
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Prompt the user for text input with a default value.
|
|
24
|
+
* Returns the entered string, or the default if empty.
|
|
25
|
+
*/
|
|
26
|
+
async function askText(message, defaultValue) {
|
|
27
|
+
const value = await p.text({
|
|
28
|
+
message,
|
|
29
|
+
placeholder: defaultValue,
|
|
30
|
+
defaultValue
|
|
31
|
+
});
|
|
32
|
+
if (p.isCancel(value)) {
|
|
33
|
+
p.cancel("Cancelled.");
|
|
34
|
+
process.exit(0);
|
|
35
|
+
}
|
|
36
|
+
return value || defaultValue;
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region _discover.ts
|
|
40
|
+
const AuthConfigSchema = z.object({ assemblyai_api_key: z.string().optional() });
|
|
41
|
+
const ProjectConfigSchema = z.object({
|
|
42
|
+
slug: z.string(),
|
|
43
|
+
serverUrl: z.string()
|
|
44
|
+
});
|
|
45
|
+
/** Resolve the working directory from INIT_CWD or process.cwd(). */
|
|
46
|
+
function resolveCwd() {
|
|
47
|
+
return process.env.INIT_CWD || process.cwd();
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Generates a human-readable slug using human-id.
|
|
51
|
+
*/
|
|
52
|
+
function generateSlug() {
|
|
53
|
+
return humanId({
|
|
54
|
+
separator: "-",
|
|
55
|
+
capitalize: false
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
const CONFIG_DIR = path.join(process.env.HOME ?? process.env.USERPROFILE ?? ".", ".config", "aai");
|
|
59
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
60
|
+
async function readAuthConfig() {
|
|
61
|
+
try {
|
|
62
|
+
return AuthConfigSchema.parse(JSON.parse(await fs$1.readFile(CONFIG_FILE, "utf-8")));
|
|
63
|
+
} catch {
|
|
64
|
+
return {};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function writeAuthConfig(config) {
|
|
68
|
+
await fs$1.mkdir(CONFIG_DIR, { recursive: true });
|
|
69
|
+
await fs$1.writeFile(CONFIG_FILE, `${JSON.stringify(config, null, 2)}\n`);
|
|
70
|
+
if (process.platform !== "win32") await fs$1.chmod(CONFIG_FILE, 384);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Retrieves the AssemblyAI API key from `~/.config/aai/config.json`.
|
|
74
|
+
* If not found, interactively prompts the user and persists it.
|
|
75
|
+
*/
|
|
76
|
+
async function getApiKey() {
|
|
77
|
+
if (process.env.ASSEMBLYAI_API_KEY) return process.env.ASSEMBLYAI_API_KEY;
|
|
78
|
+
const config = await readAuthConfig();
|
|
79
|
+
if (config.assemblyai_api_key) {
|
|
80
|
+
process.env.ASSEMBLYAI_API_KEY = config.assemblyai_api_key;
|
|
81
|
+
return config.assemblyai_api_key;
|
|
82
|
+
}
|
|
83
|
+
let key;
|
|
84
|
+
while (!key) key = await askPassword("ASSEMBLYAI_API_KEY");
|
|
85
|
+
config.assemblyai_api_key = key;
|
|
86
|
+
process.env.ASSEMBLYAI_API_KEY = key;
|
|
87
|
+
await writeAuthConfig(config);
|
|
88
|
+
return key;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Reads `.aai/project.json` from an agent directory.
|
|
92
|
+
* Returns null if the file doesn't exist.
|
|
93
|
+
*/
|
|
94
|
+
async function readProjectConfig(agentDir) {
|
|
95
|
+
try {
|
|
96
|
+
return ProjectConfigSchema.parse(JSON.parse(await fs$1.readFile(path.join(agentDir, ".aai", "project.json"), "utf-8")));
|
|
97
|
+
} catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Writes `.aai/project.json` to an agent directory.
|
|
103
|
+
*/
|
|
104
|
+
async function writeProjectConfig(agentDir, data) {
|
|
105
|
+
const aaiDir = path.join(agentDir, ".aai");
|
|
106
|
+
await fs$1.mkdir(aaiDir, { recursive: true });
|
|
107
|
+
await fs$1.writeFile(path.join(aaiDir, "project.json"), `${JSON.stringify(data, null, 2)}\n`);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Read project config (throws if missing), resolve API key and server URL.
|
|
111
|
+
* Shared by secret and rag commands.
|
|
112
|
+
*/
|
|
113
|
+
async function getServerInfo(cwd, explicitServer, explicitApiKey) {
|
|
114
|
+
const config = await readProjectConfig(cwd);
|
|
115
|
+
if (!config) throw new Error("No .aai/project.json found — deploy first with `aai deploy`");
|
|
116
|
+
const apiKey = explicitApiKey ?? await getApiKey();
|
|
117
|
+
return {
|
|
118
|
+
serverUrl: resolveServerUrl(explicitServer, config.serverUrl),
|
|
119
|
+
slug: config.slug,
|
|
120
|
+
apiKey
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/** Check if the CLI is running from the monorepo (dev mode). */
|
|
124
|
+
function isDevMode() {
|
|
125
|
+
const cliDir = path.dirname(fileURLToPath(import.meta.url));
|
|
126
|
+
const packagesDir = path.resolve(cliDir, "..");
|
|
127
|
+
const altPackagesDir = path.resolve(cliDir, "../..");
|
|
128
|
+
return existsSync(path.join(packagesDir, "aai", "package.json")) || existsSync(path.join(altPackagesDir, "aai", "package.json"));
|
|
129
|
+
}
|
|
130
|
+
/** Resolve the server URL from an explicit value, project config, or default. */
|
|
131
|
+
function resolveServerUrl(explicit, configUrl) {
|
|
132
|
+
return explicit || configUrl || (isDevMode() ? "http://localhost:8787" : "https://aai-agent.fly.dev");
|
|
133
|
+
}
|
|
134
|
+
async function fileExists(p) {
|
|
135
|
+
try {
|
|
136
|
+
await fs$1.access(p);
|
|
137
|
+
return true;
|
|
138
|
+
} catch {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Loads agent metadata from a directory by checking for `agent.ts` and
|
|
144
|
+
* resolving the client entry point.
|
|
145
|
+
*
|
|
146
|
+
* Env vars are NOT read from `.env` — they're managed on the server
|
|
147
|
+
* via `aai env add` (like `vercel env add`).
|
|
148
|
+
*/
|
|
149
|
+
async function loadAgent(dir) {
|
|
150
|
+
if (!await fileExists(path.join(dir, "agent.ts"))) return null;
|
|
151
|
+
const slug = (await readProjectConfig(dir))?.slug ?? generateSlug();
|
|
152
|
+
const clientEntry = await fileExists(path.join(dir, "client.tsx")) ? path.join(dir, "client.tsx") : "";
|
|
153
|
+
return {
|
|
154
|
+
slug,
|
|
155
|
+
dir,
|
|
156
|
+
entryPoint: path.join(dir, "agent.ts"),
|
|
157
|
+
clientEntry
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
//#endregion
|
|
161
|
+
export { isDevMode as a, resolveCwd as c, askPassword as d, askText as f, getServerInfo as i, resolveServerUrl as l, generateSlug as n, loadAgent as o, getApiKey as r, readProjectConfig as s, fileExists as t, writeProjectConfig as u };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
//#region _init.ts
|
|
5
|
+
async function listTemplates(dir) {
|
|
6
|
+
const templates = [];
|
|
7
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
8
|
+
for (const entry of entries) if (entry.isDirectory() && !entry.name.startsWith("_")) templates.push(entry.name);
|
|
9
|
+
return templates.sort();
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Copy all files from `src` into `dest`, skipping files that already exist
|
|
13
|
+
* in `dest` so that template-specific files take precedence over shared ones.
|
|
14
|
+
*/
|
|
15
|
+
async function copyDirNoOverwrite(src, dest) {
|
|
16
|
+
const entries = await fs.readdir(src, {
|
|
17
|
+
recursive: true,
|
|
18
|
+
withFileTypes: true
|
|
19
|
+
});
|
|
20
|
+
for (const entry of entries) {
|
|
21
|
+
if (!entry.isFile()) continue;
|
|
22
|
+
const rel = path.relative(src, path.join(entry.parentPath, entry.name));
|
|
23
|
+
const destPath = path.join(dest, rel);
|
|
24
|
+
await fs.mkdir(path.dirname(destPath), { recursive: true });
|
|
25
|
+
try {
|
|
26
|
+
await fs.copyFile(path.join(src, rel), destPath, fs.constants.COPYFILE_EXCL);
|
|
27
|
+
} catch (err) {
|
|
28
|
+
if (!(err instanceof Error && "code" in err && err.code === "EEXIST")) throw err;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function runInit(opts) {
|
|
33
|
+
const { targetDir, template, templatesDir } = opts;
|
|
34
|
+
const available = await listTemplates(templatesDir);
|
|
35
|
+
if (!available.includes(template)) throw new Error(`unknown template '${template}' -- available: ${available.join(", ")}`);
|
|
36
|
+
await fs.cp(path.join(templatesDir, template), targetDir, {
|
|
37
|
+
recursive: true,
|
|
38
|
+
force: true
|
|
39
|
+
});
|
|
40
|
+
await copyDirNoOverwrite(path.join(templatesDir, "_shared"), targetDir);
|
|
41
|
+
try {
|
|
42
|
+
await fs.copyFile(path.join(targetDir, ".env.example"), path.join(targetDir, ".env"));
|
|
43
|
+
} catch {}
|
|
44
|
+
const readmePath = path.join(targetDir, "README.md");
|
|
45
|
+
const readme = `# ${path.basename(path.resolve(targetDir))}
|
|
46
|
+
|
|
47
|
+
A voice agent built with [aai](https://github.com/anthropics/aai).
|
|
48
|
+
|
|
49
|
+
## Getting started
|
|
50
|
+
|
|
51
|
+
\`\`\`sh
|
|
52
|
+
npm install # Install dependencies
|
|
53
|
+
npm run dev # Run locally (opens browser)
|
|
54
|
+
npm run deploy # Deploy to production
|
|
55
|
+
\`\`\`
|
|
56
|
+
|
|
57
|
+
## Environment variables
|
|
58
|
+
|
|
59
|
+
Secrets are managed on the server, not in local files:
|
|
60
|
+
|
|
61
|
+
\`\`\`sh
|
|
62
|
+
aai env add MY_KEY # Set a secret (prompts for value)
|
|
63
|
+
aai env ls # List secret names
|
|
64
|
+
aai env pull # Pull names into .env for reference
|
|
65
|
+
aai env rm MY_KEY # Remove a secret
|
|
66
|
+
\`\`\`
|
|
67
|
+
|
|
68
|
+
Access secrets in your agent via \`ctx.env.MY_KEY\`.
|
|
69
|
+
|
|
70
|
+
## Learn more
|
|
71
|
+
|
|
72
|
+
See \`CLAUDE.md\` for the full agent API reference.
|
|
73
|
+
`;
|
|
74
|
+
try {
|
|
75
|
+
await fs.writeFile(readmePath, readme, { flag: "wx" });
|
|
76
|
+
} catch (err) {
|
|
77
|
+
if (err.code !== "EEXIST") throw err;
|
|
78
|
+
}
|
|
79
|
+
return targetDir;
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
82
|
+
export { runInit };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { execFileSync } from "node:child_process";
|
|
6
|
+
//#region _link.ts
|
|
7
|
+
const WORKSPACE_PKGS = ["aai", "aai-ui"];
|
|
8
|
+
function getPackagesDir() {
|
|
9
|
+
const cliDir = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const parent = path.resolve(cliDir, "..");
|
|
11
|
+
return fs.existsSync(path.join(parent, "aai", "package.json")) ? parent : path.resolve(parent, "..");
|
|
12
|
+
}
|
|
13
|
+
function rewriteWorkspaceDeps(cwd, rewrite, verb) {
|
|
14
|
+
const packagesDir = getPackagesDir();
|
|
15
|
+
const pkgJsonPath = path.join(cwd, "package.json");
|
|
16
|
+
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
|
|
17
|
+
const deps = pkgJson.dependencies ?? {};
|
|
18
|
+
const changed = [];
|
|
19
|
+
for (const pkgDir of WORKSPACE_PKGS) {
|
|
20
|
+
const localPath = path.join(packagesDir, pkgDir);
|
|
21
|
+
const name = JSON.parse(fs.readFileSync(path.join(localPath, "package.json"), "utf-8")).name;
|
|
22
|
+
if (!deps[name]) continue;
|
|
23
|
+
const newVersion = rewrite(deps[name], localPath);
|
|
24
|
+
if (newVersion !== null) {
|
|
25
|
+
deps[name] = newVersion;
|
|
26
|
+
changed.push(name);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (changed.length === 0) {
|
|
30
|
+
console.log(`No packages to ${verb}.`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
fs.writeFileSync(pkgJsonPath, `${JSON.stringify(pkgJson, null, 2)}\n`);
|
|
34
|
+
console.log(`${verb}: ${changed.join(", ")} → installing...`);
|
|
35
|
+
execFileSync("npm", ["install"], {
|
|
36
|
+
cwd,
|
|
37
|
+
stdio: "inherit"
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function runLinkCommand(cwd) {
|
|
41
|
+
rewriteWorkspaceDeps(cwd, (_cur, localPath) => `file:${localPath}`, "Linked");
|
|
42
|
+
}
|
|
43
|
+
function runUnlinkCommand(cwd) {
|
|
44
|
+
rewriteWorkspaceDeps(cwd, (cur) => cur.startsWith("file:") ? "*" : null, "Unlinked");
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
export { runLinkCommand, runUnlinkCommand };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { r as getApiKey } from "./_discover-BzlCDVZ6.mjs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import fs from "node:fs/promises";
|
|
5
|
+
//#region _server-common.ts
|
|
6
|
+
/** Load an AgentDef by dynamically importing agent.ts via Node's native TS support. */
|
|
7
|
+
async function loadAgentDef(cwd) {
|
|
8
|
+
const agentDef = (await import(path.resolve(cwd, "agent.ts"))).default;
|
|
9
|
+
if (!agentDef || typeof agentDef !== "object" || !agentDef.name) throw new Error("agent.ts must export a default agent definition (from defineAgent())");
|
|
10
|
+
return agentDef;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Build an env record, ensuring ASSEMBLYAI_API_KEY is set.
|
|
14
|
+
*
|
|
15
|
+
* @param baseEnv - Override the base environment (defaults to process.env).
|
|
16
|
+
*/
|
|
17
|
+
async function resolveServerEnv(baseEnv) {
|
|
18
|
+
const env = Object.fromEntries(Object.entries(baseEnv ?? process.env).filter((e) => e[1] !== void 0));
|
|
19
|
+
if (!env.ASSEMBLYAI_API_KEY) env.ASSEMBLYAI_API_KEY = await getApiKey();
|
|
20
|
+
return env;
|
|
21
|
+
}
|
|
22
|
+
/** Create and start an agent server with static file serving. */
|
|
23
|
+
async function bootServer(agentDef, clientDir, env, port) {
|
|
24
|
+
const clientHtml = await fs.readFile(path.join(clientDir, "index.html"), "utf-8");
|
|
25
|
+
const { createServer } = await import("@alexkroman1/aai/server");
|
|
26
|
+
const server = createServer({
|
|
27
|
+
agent: agentDef,
|
|
28
|
+
clientHtml,
|
|
29
|
+
clientDir,
|
|
30
|
+
env
|
|
31
|
+
});
|
|
32
|
+
await server.listen(port);
|
|
33
|
+
return server;
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
export { loadAgentDef as n, resolveServerEnv as r, bootServer as t };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import pc from "picocolors";
|
|
3
|
+
//#region _ui.ts
|
|
4
|
+
/** Interactive/info color wrapper. */
|
|
5
|
+
function interactive(s) {
|
|
6
|
+
return pc.cyan(s);
|
|
7
|
+
}
|
|
8
|
+
/** Colored step message: bold action label + message. */
|
|
9
|
+
function step(action, msg) {
|
|
10
|
+
return `${pc.bold(pc.yellow(action))} ${msg}`;
|
|
11
|
+
}
|
|
12
|
+
/** Informational step message: bold cyan action + message. */
|
|
13
|
+
function stepInfo(action, msg) {
|
|
14
|
+
return `${pc.bold(pc.cyan(action))} ${msg}`;
|
|
15
|
+
}
|
|
16
|
+
/** Dimmed info sub-line (indented). */
|
|
17
|
+
function info(msg) {
|
|
18
|
+
return pc.dim(` ${msg}`);
|
|
19
|
+
}
|
|
20
|
+
/** Detail sub-line (indented). */
|
|
21
|
+
function detail(msg) {
|
|
22
|
+
return ` ${msg}`;
|
|
23
|
+
}
|
|
24
|
+
/** Yellow warning message. */
|
|
25
|
+
function warn(msg) {
|
|
26
|
+
return `${pc.yellow("!")} ${msg}`;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Run an async command function, logging each step to stdout.
|
|
30
|
+
* Replaces the Ink `runWithInk` pattern.
|
|
31
|
+
*/
|
|
32
|
+
async function runCommand(fn) {
|
|
33
|
+
const log = (msg) => console.log(msg);
|
|
34
|
+
const setStatus = (msg) => {
|
|
35
|
+
if (msg) process.stdout.write(`\r${pc.dim(msg)}`);
|
|
36
|
+
else process.stdout.write("\r\x1B[K");
|
|
37
|
+
};
|
|
38
|
+
await fn({
|
|
39
|
+
log,
|
|
40
|
+
setStatus
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
export { step as a, runCommand as i, info as n, stepInfo as o, interactive as r, warn as s, detail as t };
|