@alexkroman1/aai-cli 0.12.3 → 1.0.2

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.
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ //#region \0rolldown/runtime.js
3
+ var __defProp = Object.defineProperty;
4
+ var __exportAll = (all, no_symbols) => {
5
+ let target = {};
6
+ for (var name in all) __defProp(target, name, {
7
+ get: all[name],
8
+ enumerable: true
9
+ });
10
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
11
+ return target;
12
+ };
13
+ //#endregion
14
+ export { __exportAll as t };
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+ import { a as ok, r as fail } from "./_output-BKdAJaM5.mjs";
3
+ import { r as log$1 } from "./_ui-r2t6_2eP.mjs";
4
+ import { getServerInfo } from "./_agent-CzbSa09n.mjs";
5
+ import { t as apiRequestOrThrow } from "./_api-client-CFfjWfNa.mjs";
6
+ import * as p from "@clack/prompts";
7
+ //#region secret.ts
8
+ async function secretRequest(cwd, pathSuffix, init, server) {
9
+ const { serverUrl, slug, apiKey } = await getServerInfo(cwd, server);
10
+ return {
11
+ resp: await apiRequestOrThrow(`${serverUrl}/${slug}/secret${pathSuffix}`, {
12
+ ...init,
13
+ apiKey,
14
+ action: "secret"
15
+ }),
16
+ slug
17
+ };
18
+ }
19
+ /** Read secret value from stdin (for non-TTY / piped input). */
20
+ async function readStdin() {
21
+ const chunks = [];
22
+ for await (const chunk of process.stdin) chunks.push(chunk);
23
+ return Buffer.concat(chunks).toString("utf-8").trim();
24
+ }
25
+ /**
26
+ * Execute secret put. If `value` is provided, use it directly (non-TTY path).
27
+ * If not provided, prompt interactively (TTY path).
28
+ */
29
+ async function executeSecretPut(cwd, name, value, server) {
30
+ let secretValue = value;
31
+ if (!secretValue) {
32
+ const result = await p.password({ message: `Enter value for ${name}` });
33
+ if (p.isCancel(result)) process.exit(0);
34
+ if (!result) return fail("no_input", "No value provided", "Pipe secret value to stdin");
35
+ secretValue = result;
36
+ }
37
+ const { slug } = await secretRequest(cwd, "", {
38
+ method: "PUT",
39
+ body: JSON.stringify({ [name]: secretValue })
40
+ }, server);
41
+ log$1.success(`Set ${name} for ${slug}`);
42
+ return ok({ name });
43
+ }
44
+ async function executeSecretDelete(cwd, name, server) {
45
+ const { slug } = await secretRequest(cwd, `/${name}`, { method: "DELETE" }, server);
46
+ log$1.success(`Deleted ${name} from ${slug}`);
47
+ return ok({ name });
48
+ }
49
+ async function executeSecretList(cwd, server) {
50
+ const { resp } = await secretRequest(cwd, "", void 0, server);
51
+ const { vars } = await resp.json();
52
+ if (vars.length === 0) log$1.info("No secrets set. Use `aai secret put <name>` to add one.");
53
+ else {
54
+ log$1.message(`${vars.length} secret${vars.length === 1 ? "" : "s"}:`);
55
+ for (const v of vars) log$1.message(` ${v}`);
56
+ }
57
+ return ok({ secrets: vars });
58
+ }
59
+ //#endregion
60
+ export { executeSecretDelete, executeSecretList, executeSecretPut, readStdin };
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+ import { a as ok, r as fail } from "./_output-BKdAJaM5.mjs";
3
+ import { r as log } from "./_ui-r2t6_2eP.mjs";
4
+ import { existsSync } from "node:fs";
5
+ import path from "node:path";
6
+ import { execFileSync } from "node:child_process";
7
+ //#region test.ts
8
+ /**
9
+ * `aai test` — run agent tests via vitest.
10
+ */
11
+ /**
12
+ * Run vitest in the given project directory.
13
+ *
14
+ * Returns `true` if tests passed, `false` if no test files exist.
15
+ * Throws on test failure.
16
+ */
17
+ function runVitest(cwd) {
18
+ let testFile = null;
19
+ if (existsSync(path.join(cwd, "agent.test.ts"))) testFile = "agent.test.ts";
20
+ else if (existsSync(path.join(cwd, "agent.test.js"))) testFile = "agent.test.js";
21
+ if (!testFile) return false;
22
+ execFileSync("npx", [
23
+ "vitest",
24
+ "run",
25
+ "--root",
26
+ ".",
27
+ testFile
28
+ ], {
29
+ cwd,
30
+ stdio: "inherit",
31
+ env: {
32
+ ...process.env,
33
+ NODE_OPTIONS: "--experimental-strip-types"
34
+ }
35
+ });
36
+ return true;
37
+ }
38
+ /** Execute agent tests and return structured result. */
39
+ async function executeTest(cwd) {
40
+ log.step("Running agent tests");
41
+ try {
42
+ if (!runVitest(cwd)) {
43
+ log.info("No test file found. Create agent.test.ts to add tests.");
44
+ return ok({
45
+ passed: true,
46
+ skipped: true
47
+ });
48
+ }
49
+ log.success("Tests passed");
50
+ return ok({ passed: true });
51
+ } catch {
52
+ return fail("test_failed", "Tests failed");
53
+ }
54
+ }
55
+ //#endregion
56
+ export { executeTest, runVitest };
package/package.json CHANGED
@@ -1,33 +1,48 @@
1
1
  {
2
2
  "name": "@alexkroman1/aai-cli",
3
- "version": "0.12.3",
3
+ "version": "1.0.2",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "aai": "dist/cli.mjs"
7
7
  },
8
+ "exports": {
9
+ "./types": {
10
+ "@dev/source": "./types.ts",
11
+ "import": "./dist/types.mjs"
12
+ }
13
+ },
8
14
  "files": [
9
15
  "dist"
10
16
  ],
11
17
  "dependencies": {
12
18
  "@clack/prompts": "^1.2.0",
13
- "ci-info": "^4.4.0",
14
- "citty": "^0.2.1",
19
+ "citty": "^0.2.2",
15
20
  "consola": "^3.4.2",
16
- "giget": "^2.0.0",
21
+ "dotenv": "^17.4.1",
22
+ "giget": "^3.2.0",
23
+ "p-debounce": "^5.1.0",
17
24
  "vite": "^8.0.3",
18
25
  "zod": "^4.3.6",
19
- "@alexkroman1/aai": "0.12.3"
26
+ "@alexkroman1/aai": "1.0.2"
20
27
  },
21
28
  "devDependencies": {
22
29
  "get-port": "^7.2.0",
23
- "playwright": "^1.59.0",
30
+ "playwright": "^1.59.1",
24
31
  "tree-kill": "^1.2.2",
25
32
  "tsdown": "^0.21.7",
26
- "verdaccio": "^6.3.2",
27
- "vitest": "^4.1.2"
33
+ "verdaccio": "^6.4.0",
34
+ "vitest": "^4.1.3"
35
+ },
36
+ "peerDependencies": {
37
+ "vitest": "^4.1.3"
38
+ },
39
+ "peerDependenciesMeta": {
40
+ "vitest": {
41
+ "optional": true
42
+ }
28
43
  },
29
44
  "engines": {
30
- "node": ">=22.6"
45
+ "node": ">=24"
31
46
  },
32
47
  "repository": {
33
48
  "type": "git",
@@ -40,7 +55,7 @@
40
55
  "build": "tsdown",
41
56
  "typecheck": "tsc --noEmit",
42
57
  "lint": "biome check .",
43
- "test:e2e": "vitest run e2e.test.ts -c vitest.slow.config.ts",
58
+ "test:e2e": "VITEST_PROFILE=e2e VITEST_INCLUDE=e2e.test.ts vitest run -c ../../vitest.slow.config.ts",
44
59
  "check:e2e": "pnpm run test:e2e"
45
60
  }
46
61
  }
@@ -1,125 +0,0 @@
1
- #!/usr/bin/env node
2
- import path from "node:path";
3
- import fs from "node:fs/promises";
4
- import { errorMessage } from "@alexkroman1/aai/utils";
5
- import { build } from "vite";
6
- //#region _bundler.ts
7
- var BundleError = class extends Error {
8
- constructor(message, options) {
9
- super(message, options);
10
- this.name = "BundleError";
11
- }
12
- };
13
- const TEXT_EXTENSIONS = new Set([
14
- ".html",
15
- ".htm",
16
- ".css",
17
- ".js",
18
- ".mjs",
19
- ".cjs",
20
- ".ts",
21
- ".mts",
22
- ".json",
23
- ".map",
24
- ".svg",
25
- ".xml",
26
- ".txt",
27
- ".md"
28
- ]);
29
- async function readDirFiles(dir) {
30
- let entries;
31
- try {
32
- entries = await fs.readdir(dir, {
33
- recursive: true,
34
- withFileTypes: true
35
- });
36
- } catch (err) {
37
- if (err instanceof Error && "code" in err && err.code === "ENOENT") return {};
38
- throw err;
39
- }
40
- const files = {};
41
- await Promise.all(entries.filter((e) => e.isFile()).map(async (e) => {
42
- const full = path.join(e.parentPath, e.name);
43
- const ext = path.extname(e.name).toLowerCase();
44
- const rel = path.relative(dir, full);
45
- if (TEXT_EXTENSIONS.has(ext)) files[rel] = await fs.readFile(full, "utf-8");
46
- else files[rel] = `base64:${(await fs.readFile(full)).toString("base64")}`;
47
- }));
48
- return files;
49
- }
50
- /**
51
- * Bundle an agent project using Vite.
52
- *
53
- * - Worker: `vite build --ssr agent.ts` (uses project's vite.config.ts)
54
- * - Client: `vite build` (uses project's vite.config.ts)
55
- */
56
- async function bundleAgent(agent, opts) {
57
- const aaiDir = path.join(agent.dir, ".aai");
58
- const buildDir = path.join(aaiDir, "build");
59
- const clientDir = path.join(aaiDir, "client");
60
- try {
61
- await build({
62
- root: agent.dir,
63
- logLevel: "warn",
64
- build: {
65
- ssr: path.join(agent.dir, "agent.ts"),
66
- outDir: buildDir,
67
- emptyOutDir: true,
68
- rollupOptions: {
69
- external: ["zod"],
70
- output: {
71
- entryFileNames: "worker.js",
72
- paths: { zod: "/app/_zod.mjs" }
73
- }
74
- }
75
- },
76
- ssr: { external: ["zod"] }
77
- });
78
- } catch (err) {
79
- throw new BundleError(errorMessage(err), { cause: err });
80
- }
81
- if (!opts?.skipClient && agent.clientEntry) try {
82
- await build({
83
- root: agent.dir,
84
- base: "./",
85
- logLevel: "warn",
86
- build: {
87
- outDir: clientDir,
88
- emptyOutDir: true
89
- }
90
- });
91
- } catch (err) {
92
- throw new BundleError(errorMessage(err), { cause: err });
93
- }
94
- const worker = await fs.readFile(path.join(buildDir, "worker.js"), "utf-8");
95
- const clientFiles = await readDirFiles(clientDir);
96
- return {
97
- slug: agent.slug,
98
- worker,
99
- clientFiles,
100
- clientDir,
101
- workerBytes: Buffer.byteLength(worker)
102
- };
103
- }
104
- async function buildAgentBundle(cwd) {
105
- const { loadAgent } = await import("./_discover-DzsMlg3G.mjs").then((n) => n.t);
106
- const { log } = await import("./_ui-DWGXImbO.mjs");
107
- const agent = await loadAgent(cwd);
108
- if (!agent) throw new Error("No agent found — run `aai init` first");
109
- log.step(`Bundling ${agent.slug}`);
110
- let bundle;
111
- try {
112
- bundle = await bundleAgent(agent);
113
- } catch (err) {
114
- if (err instanceof BundleError) throw new Error(`Build failed: ${err.message}`, { cause: err });
115
- throw err;
116
- }
117
- return bundle;
118
- }
119
- async function runBuildCommand(cwd) {
120
- const { log } = await import("./_ui-DWGXImbO.mjs");
121
- await buildAgentBundle(cwd);
122
- log.success("Build complete");
123
- }
124
- //#endregion
125
- export { buildAgentBundle, runBuildCommand };
@@ -1,218 +0,0 @@
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 from "node:fs/promises";
6
- import os from "node:os";
7
- import ci from "ci-info";
8
- import { consola } from "consola";
9
- import { z } from "zod";
10
- import { createInterface } from "node:readline";
11
- //#region \0rolldown/runtime.js
12
- var __defProp = Object.defineProperty;
13
- var __exportAll = (all, no_symbols) => {
14
- let target = {};
15
- for (var name in all) __defProp(target, name, {
16
- get: all[name],
17
- enumerable: true
18
- });
19
- if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
20
- return target;
21
- };
22
- //#endregion
23
- //#region _prompts.ts
24
- /**
25
- * Prompt the user for a password (masked input).
26
- * Throws in CI or non-TTY environments instead of hanging.
27
- */
28
- async function askPassword(message) {
29
- if (ci.isCI || !process.stdin.isTTY) throw new Error(`Interactive prompt requires a terminal. Set ${message} as an environment variable in CI.`);
30
- const rl = createInterface({
31
- input: process.stdin,
32
- output: process.stdout
33
- });
34
- process.stdout.write(`${message}: `);
35
- const stdin = process.stdin;
36
- const wasRaw = stdin.isRaw;
37
- stdin.setRawMode(true);
38
- try {
39
- return await readMasked(stdin);
40
- } finally {
41
- stdin.setRawMode(wasRaw ?? false);
42
- rl.close();
43
- }
44
- }
45
- function readMasked(stdin) {
46
- return new Promise((resolve) => {
47
- let buf = "";
48
- const onData = (ch) => {
49
- const c = ch.toString();
50
- if (c === "\n" || c === "\r") {
51
- stdin.removeListener("data", onData);
52
- process.stdout.write("\n");
53
- resolve(buf);
54
- } else if (c === "") {
55
- stdin.removeListener("data", onData);
56
- process.exit(0);
57
- } else if (c === "" || c === "\b") buf = buf.slice(0, -1);
58
- else buf += c;
59
- };
60
- stdin.on("data", onData);
61
- });
62
- }
63
- //#endregion
64
- //#region _discover.ts
65
- var _discover_exports = /* @__PURE__ */ __exportAll({
66
- DEFAULT_DEV_SERVER: () => DEFAULT_DEV_SERVER,
67
- DEFAULT_SERVER: () => DEFAULT_SERVER,
68
- ensureApiKeyInEnv: () => ensureApiKeyInEnv,
69
- fileExists: () => fileExists,
70
- getApiKey: () => getApiKey,
71
- getConfigDir: () => getConfigDir,
72
- getServerInfo: () => getServerInfo,
73
- isDevMode: () => isDevMode,
74
- loadAgent: () => loadAgent,
75
- readProjectConfig: () => readProjectConfig,
76
- resolveCwd: () => resolveCwd,
77
- resolveServerUrl: () => resolveServerUrl,
78
- writeProjectConfig: () => writeProjectConfig
79
- });
80
- const AuthConfigSchema = z.object({ assemblyai_api_key: z.string().optional() });
81
- const ProjectConfigSchema = z.object({
82
- slug: z.string(),
83
- serverUrl: z.string(),
84
- sessionId: z.string().optional()
85
- });
86
- /** Resolve the working directory from INIT_CWD or process.cwd(). */
87
- function resolveCwd() {
88
- return process.env.INIT_CWD || process.cwd();
89
- }
90
- function getConfigDir() {
91
- if (process.platform === "win32") return path.join(process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming"), "aai");
92
- return path.join(process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config"), "aai");
93
- }
94
- const CONFIG_DIR = getConfigDir();
95
- const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
96
- async function readAuthConfig() {
97
- try {
98
- return AuthConfigSchema.parse(JSON.parse(await fs.readFile(CONFIG_FILE, "utf-8")));
99
- } catch (error) {
100
- consola.debug(`Failed to read auth config from ${CONFIG_FILE}:`, error);
101
- return {};
102
- }
103
- }
104
- async function writeAuthConfig(config) {
105
- await fs.mkdir(CONFIG_DIR, { recursive: true });
106
- await fs.writeFile(CONFIG_FILE, `${JSON.stringify(config, null, 2)}\n`);
107
- if (process.platform !== "win32") await fs.chmod(CONFIG_FILE, 384);
108
- }
109
- /**
110
- * Retrieves the AssemblyAI API key from `process.env`, `~/.config/aai/config.json`,
111
- * or by interactively prompting the user (persisting it to config).
112
- *
113
- * Does NOT mutate `process.env`. Callers that need the key available in the
114
- * environment for child processes should use {@link ensureApiKeyInEnv} instead.
115
- */
116
- async function getApiKey() {
117
- if (process.env.ASSEMBLYAI_API_KEY) return process.env.ASSEMBLYAI_API_KEY;
118
- const config = await readAuthConfig();
119
- if (config.assemblyai_api_key) return config.assemblyai_api_key;
120
- if (ci.isCI || !process.stdin.isTTY) throw new Error("No ASSEMBLYAI_API_KEY found. Set the ASSEMBLYAI_API_KEY environment variable in CI or non-interactive environments.");
121
- const { log } = await import("./_ui-DWGXImbO.mjs");
122
- log.info("Get your API key at https://www.assemblyai.com/dashboard/signup");
123
- log.info("Or set the ASSEMBLYAI_API_KEY environment variable to skip this prompt.");
124
- let key;
125
- while (!key) key = await askPassword("ASSEMBLYAI_API_KEY");
126
- config.assemblyai_api_key = key;
127
- await writeAuthConfig(config);
128
- return key;
129
- }
130
- /**
131
- * Resolves the API key via {@link getApiKey} and sets it on `process.env.ASSEMBLYAI_API_KEY`
132
- * so that child processes and downstream code can read it from the environment.
133
- */
134
- async function ensureApiKeyInEnv() {
135
- const key = await getApiKey();
136
- process.env.ASSEMBLYAI_API_KEY = key;
137
- return key;
138
- }
139
- /**
140
- * Reads `.aai/project.json` from an agent directory.
141
- * Returns null if the file doesn't exist.
142
- */
143
- async function readProjectConfig(agentDir) {
144
- try {
145
- return ProjectConfigSchema.parse(JSON.parse(await fs.readFile(path.join(agentDir, ".aai", "project.json"), "utf-8")));
146
- } catch (error) {
147
- consola.debug(`Failed to read project config from ${path.join(agentDir, ".aai", "project.json")}:`, error);
148
- return null;
149
- }
150
- }
151
- /**
152
- * Writes `.aai/project.json` to an agent directory.
153
- */
154
- async function writeProjectConfig(agentDir, data) {
155
- const aaiDir = path.join(agentDir, ".aai");
156
- await fs.mkdir(aaiDir, { recursive: true });
157
- await fs.writeFile(path.join(aaiDir, "project.json"), `${JSON.stringify(data, null, 2)}\n`);
158
- }
159
- /**
160
- * Read project config (throws if missing), resolve API key and server URL.
161
- * Shared by secret commands.
162
- */
163
- async function getServerInfo(cwd, explicitServer, explicitApiKey) {
164
- const config = await readProjectConfig(cwd);
165
- if (!config) throw new Error("No .aai/project.json found — run `aai deploy` first");
166
- const apiKey = explicitApiKey ?? await getApiKey();
167
- return {
168
- serverUrl: resolveServerUrl(explicitServer, config.serverUrl),
169
- slug: config.slug,
170
- apiKey
171
- };
172
- }
173
- /** Default production server URL for agent deployments. */
174
- const DEFAULT_SERVER = "https://aai-agent.fly.dev";
175
- /** Default local dev server URL. */
176
- const DEFAULT_DEV_SERVER = "http://localhost:8787";
177
- /** Check if the CLI is running from the monorepo (dev mode). */
178
- function isDevMode() {
179
- const cliDir = path.dirname(fileURLToPath(import.meta.url));
180
- const root1 = path.resolve(cliDir, "../..");
181
- const root2 = path.resolve(cliDir, "../../..");
182
- return existsSync(path.join(root1, "pnpm-workspace.yaml")) || existsSync(path.join(root2, "pnpm-workspace.yaml"));
183
- }
184
- /** Resolve the server URL from an explicit value, project config, or default. */
185
- function resolveServerUrl(explicit, configUrl) {
186
- if (explicit) return explicit;
187
- if (isDevMode()) return DEFAULT_DEV_SERVER;
188
- return configUrl ?? "https://aai-agent.fly.dev";
189
- }
190
- async function fileExists(p) {
191
- try {
192
- await fs.access(p);
193
- return true;
194
- } catch (error) {
195
- consola.debug(`File access check failed for ${p}:`, error);
196
- return false;
197
- }
198
- }
199
- /**
200
- * Loads agent metadata from a directory by checking for `agent.ts` and
201
- * resolving the client entry point.
202
- *
203
- * Env vars for deployed agents are managed on the server via
204
- * `aai secret put`. For local dev, `.env` is loaded by `resolveServerEnv`.
205
- */
206
- async function loadAgent(dir) {
207
- if (!await fileExists(path.join(dir, "agent.ts"))) return null;
208
- const slug = (await readProjectConfig(dir))?.slug ?? "";
209
- const clientEntry = await fileExists(path.join(dir, "client.tsx")) ? path.join(dir, "client.tsx") : "";
210
- return {
211
- slug,
212
- dir,
213
- entryPoint: path.join(dir, "agent.ts"),
214
- clientEntry
215
- };
216
- }
217
- //#endregion
218
- export { getServerInfo as a, resolveCwd as c, askPassword as d, getApiKey as i, resolveServerUrl as l, ensureApiKeyInEnv as n, isDevMode as o, fileExists as r, readProjectConfig as s, _discover_exports as t, writeProjectConfig as u };
@@ -1,56 +0,0 @@
1
- #!/usr/bin/env node
2
- import { t as downloadAndMergeTemplate } from "./_templates-BDkbj3TM.mjs";
3
- import path from "node:path";
4
- import fs from "node:fs/promises";
5
- //#region _init.ts
6
- async function runInit(opts) {
7
- const { targetDir, template } = opts;
8
- await downloadAndMergeTemplate(template, targetDir);
9
- try {
10
- await fs.copyFile(path.join(targetDir, ".env.example"), path.join(targetDir, ".env"));
11
- } catch {}
12
- const readmePath = path.join(targetDir, "README.md");
13
- const readme = `# ${path.basename(path.resolve(targetDir))}
14
-
15
- A voice agent built with [aai](https://github.com/anthropics/aai).
16
-
17
- ## Getting started
18
-
19
- \`\`\`sh
20
- npm install # Install dependencies
21
- aai dev # Run locally (opens browser)
22
- aai deploy # Deploy to production
23
- \`\`\`
24
-
25
- ## Secrets
26
-
27
- Access secrets in your agent via \`ctx.env.MY_KEY\`.
28
-
29
- **Local development** — add secrets to \`.env\` (auto-loaded by \`aai dev\`):
30
-
31
- \`\`\`sh
32
- ALPHA_VANTAGE_KEY=sk-abc123
33
- MY_API_KEY=secret-value
34
- \`\`\`
35
-
36
- **Production** — set secrets on the server:
37
-
38
- \`\`\`sh
39
- aai secret put MY_KEY # Set a secret (prompts for value)
40
- aai secret list # List secret names
41
- aai secret delete MY_KEY # Remove a secret
42
- \`\`\`
43
-
44
- ## Learn more
45
-
46
- See \`CLAUDE.md\` for the full agent API reference.
47
- `;
48
- try {
49
- await fs.writeFile(readmePath, readme, { flag: "wx" });
50
- } catch (err) {
51
- if (!(err instanceof Error && "code" in err && err.code === "EEXIST")) throw err;
52
- }
53
- return targetDir;
54
- }
55
- //#endregion
56
- export { runInit };
@@ -1,72 +0,0 @@
1
- #!/usr/bin/env node
2
- import { o as isDevMode } from "./_discover-DzsMlg3G.mjs";
3
- import { existsSync } from "node:fs";
4
- import path from "node:path";
5
- import { fileURLToPath } from "node:url";
6
- import fs 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
- /** List available templates with descriptions. */
27
- async function listTemplates() {
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
- }));
39
- }
40
- /**
41
- * Download a template into targetDir, merging scaffold files underneath.
42
- */
43
- async function downloadAndMergeTemplate(template, targetDir) {
44
- const root = await resolveTemplatesDir();
45
- const templatesDir = path.join(root, "templates");
46
- const names = (await fs.readdir(templatesDir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
47
- if (!names.includes(template)) throw new Error(`Unknown template "${template}". Available templates: ${names.join(", ")}`);
48
- await fs.cp(path.join(templatesDir, template), targetDir, {
49
- recursive: true,
50
- force: true
51
- });
52
- const scaffoldDir = path.join(root, "scaffold");
53
- if (existsSync(scaffoldDir)) {
54
- const entries = await fs.readdir(scaffoldDir, {
55
- recursive: true,
56
- withFileTypes: true
57
- });
58
- for (const entry of entries) {
59
- if (!entry.isFile()) continue;
60
- const rel = path.relative(scaffoldDir, path.join(entry.parentPath, entry.name));
61
- const destPath = path.join(targetDir, rel);
62
- await fs.mkdir(path.dirname(destPath), { recursive: true });
63
- try {
64
- await fs.copyFile(path.join(scaffoldDir, rel), destPath, fs.constants.COPYFILE_EXCL);
65
- } catch (err) {
66
- if (!(err instanceof Error && "code" in err && err.code === "EEXIST")) throw err;
67
- }
68
- }
69
- }
70
- }
71
- //#endregion
72
- export { listTemplates as n, downloadAndMergeTemplate as t };