@alexkroman1/aai-cli 4.0.0 → 5.0.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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as serverOrigin, i as readProjectConfig, n as ensureApiKey, r as readGlobalConfig, t as approveServer } from "./_config-B7Ywbpfb.mjs";
2
+ import { a as serverOrigin, i as readProjectConfig, n as ensureApiKey, r as readGlobalConfig, t as approveServer } from "./_config-Czjhvaax.mjs";
3
3
  import { existsSync } from "node:fs";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env node
2
+ import { n as log, u as ok } from "./_ui-DXZ9prrM.mjs";
3
+ import { c as validateAgentExport } from "./_utils-C502jKo8.mjs";
4
+ import { t as buildClient } from "./client-bundler-DO3GgO6p.mjs";
5
+ import { buildWorker } from "./worker-bundler.mjs";
6
+ import path from "node:path";
7
+ import { pathToFileURL } from "node:url";
8
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
9
+ import { hash } from "node:crypto";
10
+ import { tmpdir } from "node:os";
11
+ //#region _bundler.ts
12
+ /**
13
+ * Bundle an agent directory: build agent.ts into worker ESM + client files.
14
+ *
15
+ * agent.ts is the single entry point: `export default agent({...})`. The
16
+ * worker self-describes (it exports `__aaiConfig` — see `worker-bundler.ts`),
17
+ * so nothing here evaluates the bundle: the server extracts the config inside
18
+ * a guest sandbox at deploy time.
19
+ */
20
+ async function buildAgentBundle(cwd, opts = {}) {
21
+ const [worker, clientFiles] = await Promise.all([buildWorker(cwd, opts), buildClient(cwd)]);
22
+ return {
23
+ worker,
24
+ clientFiles
25
+ };
26
+ }
27
+ /**
28
+ * Import the worker ESM from a uniquely named temp file and return the
29
+ * AgentDef default export. A real `file:` URL, not a `data:` URL: deploy
30
+ * bundles ship the SDK runtime, whose CJS interop calls
31
+ * `createRequire(import.meta.url)` — which rejects anything that isn't a
32
+ * file URL or absolute path. (The guest harness imports bundles the same
33
+ * way, for the same reason.) The file is removed after import; the module
34
+ * lives on in memory.
35
+ *
36
+ * Each call imports a unique URL, and Node's ESM registry never evicts — so
37
+ * every call retains one bundle for the process lifetime. That is fine for
38
+ * one-shot commands (`aai build`); long-lived callers must go through
39
+ * `createWorkerEvaluator` to at least dedupe identical builds. Evaluating in
40
+ * a discardable context is not an option: tool `execute` functions from the
41
+ * returned AgentDef are called in-process by the dev runtime, which rules
42
+ * out worker threads, and `node:vm` ESM evaluation is still flagged
43
+ * experimental.
44
+ */
45
+ async function evalWorkerBundle(code) {
46
+ const dir = await mkdtemp(path.join(tmpdir(), "aai-worker-"));
47
+ const file = path.join(dir, "worker.mjs");
48
+ let mod;
49
+ try {
50
+ await writeFile(file, code, "utf-8");
51
+ mod = await import(pathToFileURL(file).href);
52
+ } finally {
53
+ await rm(dir, {
54
+ recursive: true,
55
+ force: true
56
+ }).catch(() => void 0);
57
+ }
58
+ const agentDef = mod.default ?? mod;
59
+ validateAgentExport(agentDef);
60
+ return agentDef;
61
+ }
62
+ /**
63
+ * Memoizing wrapper around `evalWorkerBundle` for long-lived callers (the
64
+ * dev server): byte-identical worker code returns the previously evaluated
65
+ * AgentDef without touching the ESM registry. No-op saves and formatter
66
+ * churn are the common watcher events, so this caps the registry leak (see
67
+ * `evalWorkerBundle`) to genuinely-new bundles — the residual one-module-per-
68
+ * distinct-build leak is accepted for the reasons documented there.
69
+ */
70
+ function createWorkerEvaluator() {
71
+ let lastHash;
72
+ let lastAgentDef;
73
+ return async (code) => {
74
+ const codeHash = hash("sha256", code);
75
+ if (lastAgentDef && codeHash === lastHash) return lastAgentDef;
76
+ const agentDef = await evalWorkerBundle(code);
77
+ lastHash = codeHash;
78
+ lastAgentDef = agentDef;
79
+ return agentDef;
80
+ };
81
+ }
82
+ async function executeBuild(cwd) {
83
+ const bundle = await buildAgentBundle(cwd, { minify: true });
84
+ const agentDef = await evalWorkerBundle(bundle.worker);
85
+ log.success("Build complete");
86
+ return ok({
87
+ name: agentDef.name,
88
+ workerBytes: bundle.worker.length
89
+ });
90
+ }
91
+ //#endregion
92
+ export { buildAgentBundle, createWorkerEvaluator, executeBuild };
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as unwrapCancel, n as log, o as CliError } from "./_ui-CKEIHAtB.mjs";
2
+ import { a as unwrapCancel, n as log, o as CliError } from "./_ui-DXZ9prrM.mjs";
3
3
  import { l as writeJson, o as readJson, r as errorMessage } from "./_utils-C502jKo8.mjs";
4
4
  import path from "node:path";
5
5
  import * as p from "@clack/prompts";
@@ -1,129 +1,19 @@
1
1
  #!/usr/bin/env node
2
- import { n as log } from "./_ui-CKEIHAtB.mjs";
2
+ import { n as log } from "./_ui-DXZ9prrM.mjs";
3
3
  import { r as errorMessage, t as errorCode } from "./_utils-C502jKo8.mjs";
4
4
  import { n as fallbackHtmlPlugin } from "./client-bundler-DO3GgO6p.mjs";
5
5
  import { buildWorker } from "./worker-bundler.mjs";
6
- import { createWorkerEvaluator } from "./_bundler-DnZnKsIU.mjs";
7
- import { n as ensureApiKey } from "./_config-B7Ywbpfb.mjs";
8
- import { t as resolveServerEnv } from "./_server-common-CpVD57yn.mjs";
6
+ import { createWorkerEvaluator } from "./_bundler-DdmuZzeR.mjs";
7
+ import { n as ensureApiKey } from "./_config-Czjhvaax.mjs";
8
+ import { t as resolveServerEnv } from "./_server-common-B75oco06.mjs";
9
9
  import { createRequire } from "node:module";
10
10
  import { existsSync } from "node:fs";
11
11
  import path from "node:path";
12
- import fs from "node:fs/promises";
12
+ import { setTimeout } from "node:timers/promises";
13
13
  import { requiredProviderEnvVars, withHostCredentialFallback } from "@alexkroman1/aai/runtime";
14
14
  import { watch } from "chokidar";
15
15
  import getPort, { portNumbers } from "get-port";
16
16
  import pDebounce from "p-debounce";
17
- //#region _dev-bundler.ts
18
- /**
19
- * Fast worker builds for `aai dev`.
20
- *
21
- * The deploy path (`buildWorker` in `worker-bundler.ts`) is a full Vite
22
- * pipeline pass — right for a one-shot `aai deploy`, but 1–3 s per save when
23
- * the dev watcher runs it on every change. This module builds with Rolldown
24
- * directly (the native bundler Vite 8 itself runs on, so it adds no install
25
- * weight), skipping Vite's config/plugin/CSS machinery: a from-scratch build
26
- * of a typical agent lands in tens of ms. Deploy keeps Vite untouched, so
27
- * nothing that ships is produced by this path.
28
- *
29
- * Parity with `buildWorker` where it matters for dev:
30
- *
31
- * - single-file ESM output, unminified (dev builds never minify);
32
- * - `node:` builtins external (Rolldown's `platform: "node"`), everything
33
- * else — zod, workspace deps, local imports — bundled in;
34
- * - `.md` imports resolve to their raw text (`mdPlugin` below is
35
- * `rawMdPlugin`'s transform), and Vite-style `?raw` suffix imports are
36
- * honored via `rawSuffixPlugin`.
37
- *
38
- * Known dev/deploy differences, accepted: Vite's lib build applies
39
- * `define`/`import.meta.env` replacements this path does not. When a build
40
- * fails for anything other than a compile error in the agent's code, the
41
- * caller falls back to the cold Vite path (see `_dev-server.ts`), so a
42
- * resolution gap here degrades to the old slow-but-correct behavior rather
43
- * than a broken dev server.
44
- *
45
- * Rolldown does not touch `process.env` the way Vite's `build()` does, so
46
- * the `withPreservedNodeEnv` wrapper Vite builds need is not required here.
47
- */
48
- const RAW_NAMESPACE = "\0aai-raw:";
49
- /**
50
- * Vite serves `import x from "./file?raw"` as the file's text. Rolldown
51
- * treats the suffix as part of the filename, so resolve it explicitly and
52
- * load the real file as a string export.
53
- */
54
- const rawSuffixPlugin = {
55
- name: "aai-raw-suffix",
56
- resolveId: {
57
- filter: { id: /\?raw$/ },
58
- handler(id, importer) {
59
- const file = id.slice(0, -4);
60
- const base = importer ? path.dirname(importer) : process.cwd();
61
- return RAW_NAMESPACE + path.resolve(base, file);
62
- }
63
- },
64
- load: {
65
- filter: { id: new RegExp(`^${RAW_NAMESPACE}`) },
66
- async handler(id) {
67
- const text = await fs.readFile(id.slice(9), "utf8");
68
- return `export default ${JSON.stringify(text)};`;
69
- }
70
- }
71
- };
72
- /** `.md` imports resolve to their raw text (rawMdPlugin parity). */
73
- const mdPlugin = {
74
- name: "aai-raw-md",
75
- load: {
76
- filter: { id: /\.md$/ },
77
- async handler(id) {
78
- const text = await fs.readFile(id, "utf8");
79
- return `export default ${JSON.stringify(text)};`;
80
- }
81
- }
82
- };
83
- /**
84
- * True for bundler build failures — compile/resolve errors in the code being
85
- * built (Rolldown aggregates its diagnostics onto an `errors` array, the same
86
- * shape esbuild used). Anything else coming out of `build()` is a
87
- * bundler-infrastructure problem, which callers treat as "fall back to the
88
- * cold Vite path" rather than "the user's code is broken".
89
- */
90
- function isBundlerBuildFailure(err) {
91
- return err instanceof Error && "errors" in err && Array.isArray(err.errors);
92
- }
93
- /**
94
- * Create a dev builder for the agent at `cwd`.
95
- *
96
- * Each `build()` is a from-scratch Rolldown pass — native-code bundling is
97
- * fast enough (tens of ms for a typical agent) that no incremental context
98
- * is worth holding between saves. `dispose()` exists for interface parity
99
- * with resource-holding builders and is a no-op.
100
- */
101
- function createDevWorkerBuilder(cwd) {
102
- return {
103
- async build() {
104
- const { rolldown } = await import("rolldown");
105
- const bundle = await rolldown({
106
- input: path.join(cwd, "agent.ts"),
107
- cwd,
108
- platform: "node",
109
- logLevel: "silent",
110
- plugins: [rawSuffixPlugin, mdPlugin]
111
- });
112
- try {
113
- const file = (await bundle.generate({
114
- format: "esm",
115
- minify: false
116
- })).output[0];
117
- if (!file) throw new Error("Rolldown produced no output for agent.ts");
118
- return file.code;
119
- } finally {
120
- await bundle.close().catch(() => void 0);
121
- }
122
- },
123
- async dispose() {}
124
- };
125
- }
126
- //#endregion
127
17
  //#region _dev-server.ts
128
18
  /**
129
19
  * Dev server for directory-based agents.
@@ -133,12 +23,36 @@ function createDevWorkerBuilder(cwd) {
133
23
  * file changes and restarts automatically. Optionally runs Vite for
134
24
  * client SPA HMR.
135
25
  */
26
+ /**
27
+ * Warnings about the agent's credentials, computed against the `.env`-derived
28
+ * env and the shell. Pure so it is directly testable; `resolveAgentEnv` logs
29
+ * each entry. Three cases, in increasing subtlety:
30
+ *
31
+ * - a provider key found nowhere → the first session will fail auth;
32
+ * - a provider key found only in the shell → works here (the
33
+ * `withHostCredentialFallback` ergonomic) but is invisible to `aai deploy`,
34
+ * which uploads `.env` — the classic "works locally, dead on deploy";
35
+ * - a declared `requiredEnv` key absent from `.env` → `ctx.env` won't contain
36
+ * it at all: custom keys never fall back to the shell, so a shell export
37
+ * can't mask one that would be missing both here and after deploy.
38
+ */
39
+ function agentEnvWarnings(agentDef, env, shellEnv = process.env) {
40
+ const s = (names) => names.length > 1 ? "s" : "";
41
+ const them = (names) => names.length > 1 ? "them" : "it";
42
+ const required = requiredProviderEnvVars(agentDef);
43
+ const warnings = [];
44
+ const missing = required.filter((name) => !(env[name] || shellEnv[name]));
45
+ if (missing.length > 0) warnings.push(`Missing provider credential${s(missing)}: ${missing.join(", ")}. Set ${them(missing)} in .env or the environment.`);
46
+ const shellOnly = required.filter((name) => !env[name] && shellEnv[name]);
47
+ if (shellOnly.length > 0) warnings.push(`${shellOnly.join(", ")} resolved from your shell, not .env — deployed agents won't have ${them(shellOnly)}. Declare ${them(shellOnly)} in .env before \`aai deploy\`.`);
48
+ const declared = (agentDef.requiredEnv ?? []).filter((name) => !env[name]);
49
+ if (declared.length > 0) warnings.push(`Missing requiredEnv key${s(declared)} declared by the agent: ${declared.join(", ")}. Set ${them(declared)} in .env — ctx.env will not contain ${them(declared)} otherwise.`);
50
+ return warnings;
51
+ }
136
52
  async function resolveAgentEnv(root, agentDef) {
137
53
  const env = await resolveServerEnv(root);
138
- const required = requiredProviderEnvVars(agentDef);
139
- if (required.includes("ASSEMBLYAI_API_KEY") && !env.ASSEMBLYAI_API_KEY) env.ASSEMBLYAI_API_KEY = await ensureApiKey();
140
- const missing = required.filter((name) => !(env[name] || process.env[name]));
141
- if (missing.length > 0) log.warn(`Missing provider credential${missing.length > 1 ? "s" : ""}: ${missing.join(", ")}. Set ${missing.length > 1 ? "them" : "it"} in .env or the environment.`);
54
+ if (requiredProviderEnvVars(agentDef).includes("ASSEMBLYAI_API_KEY") && !env.ASSEMBLYAI_API_KEY) env.ASSEMBLYAI_API_KEY = await ensureApiKey();
55
+ for (const warning of agentEnvWarnings(agentDef, env)) log.warn(warning);
142
56
  return env;
143
57
  }
144
58
  /**
@@ -170,23 +84,14 @@ function devBindHost() {
170
84
  * stay in Node's ESM registry, so edits to them are ignored on reload.
171
85
  * Bundling picks them up.
172
86
  *
173
- * The bundle comes from the fast Rolldown builder (`_dev-bundler.ts`)
174
- * rather than the deploy path's cold Vite build a save rebuilds in tens of
175
- * ms instead of 1–3 s. Compile errors in the agent's code propagate (the
176
- * restart loop reports them and keeps the old server); any other builder
177
- * failure falls back to the cold Vite build so a Rolldown-specific gap can't
178
- * take the dev loop down. Evaluation goes through the memoizing evaluator so
87
+ * The bundle comes from the same Vite pass deploy runs (`buildWorker`), so
88
+ * dev and deploy can't drift; a warm rebuild is well under 100ms. Compile
89
+ * errors in the agent's code propagate the restart loop reports them and
90
+ * keeps the old server. Evaluation goes through the memoizing evaluator so
179
91
  * a no-op save doesn't leak another module into the ESM registry.
180
92
  */
181
- async function loadAgentDefWith(cwd, builder, evaluate) {
182
- let code;
183
- try {
184
- code = await builder.build();
185
- } catch (err) {
186
- if (isBundlerBuildFailure(err)) throw err;
187
- code = await buildWorker(cwd);
188
- }
189
- return evaluate(code);
93
+ async function loadAgentDef(cwd, evaluate) {
94
+ return evaluate(await buildWorker(cwd, { runtime: false }));
190
95
  }
191
96
  /**
192
97
  * True for paths that should never trigger a restart: anything inside
@@ -283,11 +188,10 @@ async function startDevServer(opts) {
283
188
  const backendPort = hasClient ? await getPort({ port: portNumbers(port + 1, port + 100) }) : port;
284
189
  const vitePort = port;
285
190
  const clientDirOpt = hasClient ? {} : { clientDir: resolveDefaultClientDir() };
286
- const devBuilder = createDevWorkerBuilder(cwd);
287
- const evaluateWorker = createWorkerEvaluator(cwd);
191
+ const evaluateWorker = createWorkerEvaluator();
288
192
  /** Full build sequence, shared by initial startup and every restart. */
289
193
  async function buildServer() {
290
- const agentDef = await loadAgentDefWith(cwd, devBuilder, evaluateWorker);
194
+ const agentDef = await loadAgentDef(cwd, evaluateWorker);
291
195
  const env = await resolveAgentEnv(cwd, agentDef);
292
196
  const providerEnv = withHostCredentialFallback(env);
293
197
  const runtime = createRuntime({
@@ -335,7 +239,6 @@ async function startDevServer(opts) {
335
239
  }
336
240
  } catch (err) {
337
241
  await watcher.close().catch(() => void 0);
338
- await devBuilder.dispose().catch(() => void 0);
339
242
  await viteServer?.close().catch(() => void 0);
340
243
  throw err;
341
244
  }
@@ -389,7 +292,7 @@ async function startDevServer(opts) {
389
292
  return;
390
293
  } catch (err) {
391
294
  if (attempt >= LISTEN_ATTEMPTS || closed) throw err;
392
- await new Promise((resolve) => setTimeout(resolve, LISTEN_RETRY_DELAY_MS));
295
+ await setTimeout(LISTEN_RETRY_DELAY_MS);
393
296
  }
394
297
  }
395
298
  let cleanupPromise;
@@ -397,7 +300,6 @@ async function startDevServer(opts) {
397
300
  cleanupPromise ??= (async () => {
398
301
  closed = true;
399
302
  await watcher.close().catch(() => void 0);
400
- await devBuilder.dispose().catch(() => void 0);
401
303
  await viteServer?.close().catch(() => void 0);
402
304
  await currentServer.close().catch(() => void 0);
403
305
  })();
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as isEexist, l as writeJson, o as readJson, r as errorMessage } from "./_utils-C502jKo8.mjs";
3
- import { r as isDevMode, t as getMonorepoRoot } from "./_agent-dJAeDb38.mjs";
3
+ import { r as isDevMode, t as getMonorepoRoot } from "./_agent-D39qatZJ.mjs";
4
4
  import { existsSync } from "node:fs";
5
5
  import path from "node:path";
6
6
  import fs from "node:fs/promises";
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ import { n as log, o as CliError } from "./_ui-DXZ9prrM.mjs";
3
+ import { typecheckProject } from "./typecheck.mjs";
4
+ //#region _typecheck-gate.ts
5
+ /**
6
+ * The build/deploy typecheck gate: run the project's own `tsc --noEmit`
7
+ * (see `typecheck.ts`) and turn a failure into a structured CliError. The
8
+ * bundlers strip types unchecked, so without this a type-broken agent
9
+ * ships and misbehaves at runtime instead of failing here.
10
+ */
11
+ async function assertTypechecks(cwd) {
12
+ log.step("Type checking…");
13
+ const result = await typecheckProject(cwd);
14
+ if (!result.ok) throw new CliError("typecheck_failed", result.output, "Fix the type errors, or pass --skipTypecheck to build anyway");
15
+ }
16
+ //#endregion
17
+ export { assertTypechecks };
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
+ import { styleText } from "node:util";
2
3
  import * as p from "@clack/prompts";
3
- import pc from "picocolors";
4
4
  //#region _output.ts
5
5
  /**
6
6
  * Determine output mode from CLI flags and TTY state.
@@ -93,7 +93,7 @@ function unwrapCancel(result) {
93
93
  }
94
94
  /** Format a URL for display. */
95
95
  function fmtUrl(url) {
96
- return pc.cyanBright(url);
96
+ return styleText("cyanBright", url);
97
97
  }
98
98
  /**
99
99
  * Parse and validate a port string. Returns the numeric port or throws.
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { c as getOutputMode, d as writeLine, i as silenceOutput, l as installStdoutGuard, n as log, o as CliError, s as fail } from "./_ui-CKEIHAtB.mjs";
2
+ import { c as getOutputMode, d as writeLine, i as silenceOutput, l as installStdoutGuard, n as log, o as CliError, s as fail } from "./_ui-DXZ9prrM.mjs";
3
3
  import { i as fileExists, r as errorMessage, s as resolveCwd } from "./_utils-C502jKo8.mjs";
4
4
  import { existsSync, readFileSync } from "node:fs";
5
5
  import path from "node:path";
@@ -115,7 +115,7 @@ const init = defineCommand({
115
115
  },
116
116
  async run({ args }) {
117
117
  await runCommand(args, async (mode) => {
118
- const { executeInit } = await import("./init-Dn3RNhsR.mjs");
118
+ const { executeInit } = await import("./init-B53rR3yD.mjs");
119
119
  return executeInit({
120
120
  dir: args.dir,
121
121
  force: args.force,
@@ -139,7 +139,7 @@ const dev = defineCommand({
139
139
  async run({ args }) {
140
140
  await runCommand(args, async () => {
141
141
  const cwd = await setup({ agent: true });
142
- const { executeDev } = await import("./dev-CQPY9Cfx.mjs");
142
+ const { executeDev } = await import("./dev-C8o-p13B.mjs");
143
143
  return executeDev({
144
144
  cwd,
145
145
  port: args.port
@@ -156,7 +156,7 @@ const test = defineCommand({
156
156
  async run({ args }) {
157
157
  await runCommand(args, async () => {
158
158
  const cwd = await setup();
159
- const { executeTest } = await import("./test-B3BkLNn_.mjs");
159
+ const { executeTest } = await import("./test-CgWhus_Z.mjs");
160
160
  return executeTest(cwd);
161
161
  });
162
162
  }
@@ -171,13 +171,17 @@ const build = defineCommand({
171
171
  skipTests: {
172
172
  type: "boolean",
173
173
  description: "Skip running tests before build"
174
+ },
175
+ skipTypecheck: {
176
+ type: "boolean",
177
+ description: "Skip type checking before build"
174
178
  }
175
179
  },
176
180
  async run({ args }) {
177
181
  await runCommand(args, async () => {
178
182
  const cwd = await setup({ agent: true });
179
183
  if (!args.skipTests) {
180
- const { classifyVitestError, runVitest } = await import("./test-B3BkLNn_.mjs");
184
+ const { classifyVitestError, runVitest } = await import("./test-CgWhus_Z.mjs");
181
185
  const toCliError = (err) => {
182
186
  const { code, message } = classifyVitestError(err);
183
187
  return new CliError(code, message, "Re-run with --skipTests to build without tests", { cause: err });
@@ -188,7 +192,11 @@ const build = defineCommand({
188
192
  throw toCliError(err);
189
193
  }
190
194
  }
191
- const { executeBuild } = await import("./_bundler-DnZnKsIU.mjs");
195
+ if (!args.skipTypecheck) {
196
+ const { assertTypechecks } = await import("./_typecheck-gate-B1GcKZX-.mjs");
197
+ await assertTypechecks(cwd);
198
+ }
199
+ const { executeBuild } = await import("./_bundler-DdmuZzeR.mjs");
192
200
  return executeBuild(cwd);
193
201
  });
194
202
  }
@@ -200,15 +208,25 @@ const deploy = defineCommand({
200
208
  },
201
209
  args: {
202
210
  server: sharedArgs.server,
203
- json: sharedArgs.json
211
+ json: sharedArgs.json,
212
+ allowMissingSecrets: {
213
+ type: "boolean",
214
+ description: "Deploy even when the agent's providers are missing credentials (the server warns instead of rejecting; set them afterwards with `aai secret put`)"
215
+ },
216
+ skipTypecheck: {
217
+ type: "boolean",
218
+ description: "Skip type checking before deploy"
219
+ }
204
220
  },
205
221
  async run({ args }) {
206
222
  await runCommand(args, async () => {
207
223
  const cwd = await setup({ agent: true });
208
- const { executeDeploy } = await import("./deploy-DzGsORJx.mjs");
224
+ const { executeDeploy } = await import("./deploy-D-dnt9L8.mjs");
209
225
  return executeDeploy({
210
226
  cwd,
211
- ...args.server ? { server: args.server } : {}
227
+ ...args.server ? { server: args.server } : {},
228
+ ...args.allowMissingSecrets ? { allowMissingSecrets: true } : {},
229
+ ...args.skipTypecheck ? { skipTypecheck: true } : {}
212
230
  });
213
231
  });
214
232
  }
@@ -225,7 +243,7 @@ const del = defineCommand({
225
243
  async run({ args }) {
226
244
  await runCommand(args, async () => {
227
245
  const cwd = await setup();
228
- const { executeDelete } = await import("./delete-Br2Jj28e.mjs");
246
+ const { executeDelete } = await import("./delete-K24hW1ab.mjs");
229
247
  return executeDelete({
230
248
  cwd,
231
249
  ...args.server ? { server: args.server } : {}
@@ -256,7 +274,7 @@ const secret = defineCommand({
256
274
  async run({ args }) {
257
275
  await runCommand(args, async (mode) => {
258
276
  const cwd = await setup();
259
- const { executeSecretPut, readStdin } = await import("./secret-DTH-dXnP.mjs");
277
+ const { executeSecretPut, readStdin } = await import("./secret-C4unwCEw.mjs");
260
278
  const value = mode === "json" ? await readStdin() : void 0;
261
279
  if (mode === "json" && !value) throw new CliError("no_input", "No value provided", "Pipe secret value to stdin");
262
280
  return executeSecretPut(cwd, args.name, value, args.server);
@@ -280,7 +298,7 @@ const secret = defineCommand({
280
298
  async run({ args }) {
281
299
  await runCommand(args, async () => {
282
300
  const cwd = await setup();
283
- const { executeSecretDelete } = await import("./secret-DTH-dXnP.mjs");
301
+ const { executeSecretDelete } = await import("./secret-C4unwCEw.mjs");
284
302
  return executeSecretDelete(cwd, args.name, args.server);
285
303
  });
286
304
  }
@@ -297,7 +315,7 @@ const secret = defineCommand({
297
315
  async run({ args }) {
298
316
  await runCommand(args, async () => {
299
317
  const cwd = await setup();
300
- const { executeSecretList } = await import("./secret-DTH-dXnP.mjs");
318
+ const { executeSecretList } = await import("./secret-C4unwCEw.mjs");
301
319
  return executeSecretList(cwd, args.server);
302
320
  });
303
321
  }
@@ -332,7 +350,7 @@ const storage = defineCommand({
332
350
  },
333
351
  async run({ args }) {
334
352
  await runCommand(args, async () => {
335
- const { executeStorageStatus } = await import("./storage-DOtoALnS.mjs");
353
+ const { executeStorageStatus } = await import("./storage-tZM1J8qj.mjs");
336
354
  return executeStorageStatus(resolveStorageCwd(args.dir), args.server);
337
355
  });
338
356
  }
@@ -349,7 +367,7 @@ const storage = defineCommand({
349
367
  },
350
368
  async run({ args }) {
351
369
  await runCommand(args, async () => {
352
- const { executeStorageEnable } = await import("./storage-DOtoALnS.mjs");
370
+ const { executeStorageEnable } = await import("./storage-tZM1J8qj.mjs");
353
371
  return executeStorageEnable(resolveStorageCwd(args.dir), args.server);
354
372
  });
355
373
  }
@@ -371,7 +389,7 @@ const storage = defineCommand({
371
389
  },
372
390
  async run({ args }) {
373
391
  await runCommand(args, async () => {
374
- const { executeStorageDisable } = await import("./storage-DOtoALnS.mjs");
392
+ const { executeStorageDisable } = await import("./storage-tZM1J8qj.mjs");
375
393
  return executeStorageDisable(resolveStorageCwd(args.dir), {
376
394
  server: args.server,
377
395
  force: args.force
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { n as log, u as ok } from "./_ui-CKEIHAtB.mjs";
3
- import { n as getServerInfo } from "./_agent-dJAeDb38.mjs";
2
+ import { n as log, u as ok } from "./_ui-DXZ9prrM.mjs";
3
+ import { n as getServerInfo } from "./_agent-D39qatZJ.mjs";
4
4
  import { t as apiRequest } from "./_api-client-a6cPMebU.mjs";
5
5
  //#region delete.ts
6
6
  async function runDelete(opts) {
@@ -1,22 +1,23 @@
1
1
  #!/usr/bin/env node
2
- import { n as log, t as fmtUrl, u as ok } from "./_ui-CKEIHAtB.mjs";
2
+ import { n as log, t as fmtUrl, u as ok } from "./_ui-DXZ9prrM.mjs";
3
3
  import { r as errorMessage } from "./_utils-C502jKo8.mjs";
4
- import { buildAgentBundle } from "./_bundler-DnZnKsIU.mjs";
5
- import { o as writeProjectConfig } from "./_config-B7Ywbpfb.mjs";
6
- import { t as resolveServerEnv } from "./_server-common-CpVD57yn.mjs";
7
- import { i as resolveDeployTarget } from "./_agent-dJAeDb38.mjs";
4
+ import { buildAgentBundle } from "./_bundler-DdmuZzeR.mjs";
5
+ import { o as writeProjectConfig } from "./_config-Czjhvaax.mjs";
6
+ import { t as resolveServerEnv } from "./_server-common-B75oco06.mjs";
7
+ import { i as resolveDeployTarget } from "./_agent-D39qatZJ.mjs";
8
+ import { assertTypechecks } from "./_typecheck-gate-B1GcKZX-.mjs";
8
9
  import { t as apiRequest } from "./_api-client-a6cPMebU.mjs";
9
10
  import { gzipSync } from "node:zlib";
10
11
  //#region _deploy.ts
11
12
  async function runDeploy(opts) {
12
13
  const body = gzipSync(JSON.stringify({
13
14
  ...opts.slug ? { slug: opts.slug } : {},
15
+ ...opts.allowMissingSecrets ? { credentialPolicy: "warn" } : {},
14
16
  env: opts.env,
15
17
  worker: opts.bundle.worker,
16
- clientFiles: opts.bundle.clientFiles,
17
- agentConfig: opts.bundle.agentConfig
18
+ clientFiles: opts.bundle.clientFiles
18
19
  }));
19
- return { slug: (await apiRequest(`${opts.url}/deploy`, {
20
+ const data = await apiRequest(`${opts.url}/deploy`, {
20
21
  method: "POST",
21
22
  body,
22
23
  headers: {
@@ -29,13 +30,18 @@ async function runDeploy(opts) {
29
30
  ...opts.slug ? {} : { retry: 0 },
30
31
  ...opts.retryDelay !== void 0 ? { retryDelay: opts.retryDelay } : {},
31
32
  ...opts.fetch ? { fetch: opts.fetch } : {}
32
- })).slug };
33
+ });
34
+ return {
35
+ slug: data.slug,
36
+ ...data.warnings ? { warnings: data.warnings } : {}
37
+ };
33
38
  }
34
39
  //#endregion
35
40
  //#region deploy.ts
36
41
  async function executeDeploy(opts) {
37
42
  const { cwd } = opts;
38
43
  const { config: projectConfig, serverUrl, apiKey } = await resolveDeployTarget(cwd, opts.server);
44
+ if (!opts.skipTypecheck) await assertTypechecks(cwd);
39
45
  const bundle = await buildAgentBundle(cwd, { minify: true });
40
46
  const slug = projectConfig?.slug;
41
47
  const env = await resolveServerEnv(cwd);
@@ -48,6 +54,7 @@ async function executeDeploy(opts) {
48
54
  ...env
49
55
  },
50
56
  ...slug ? { slug } : {},
57
+ ...opts.allowMissingSecrets ? { allowMissingSecrets: true } : {},
51
58
  apiKey
52
59
  });
53
60
  const agentUrl = `${serverUrl}/${deployed.slug}`;
@@ -63,10 +70,12 @@ async function executeDeploy(opts) {
63
70
  serverUrl
64
71
  })}`);
65
72
  }
73
+ for (const warning of deployed.warnings ?? []) log.warn(warning);
66
74
  log.success(`Deployed ${fmtUrl(agentUrl)}`);
67
75
  return ok({
68
76
  slug: deployed.slug,
69
- url: agentUrl
77
+ url: agentUrl,
78
+ ...deployed.warnings ? { warnings: deployed.warnings } : {}
70
79
  });
71
80
  }
72
81
  //#endregion
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { n as log, r as parsePort, t as fmtUrl, u as ok } from "./_ui-CKEIHAtB.mjs";
2
+ import { n as log, r as parsePort, t as fmtUrl, u as ok } from "./_ui-DXZ9prrM.mjs";
3
3
  import { n as errorDetail } from "./_utils-C502jKo8.mjs";
4
4
  import path from "node:path";
5
- import pc from "picocolors";
5
+ import { styleText } from "node:util";
6
6
  //#region dev.ts
7
7
  /**
8
8
  * Start the dev server and return the result.
@@ -11,7 +11,7 @@ import pc from "picocolors";
11
11
  async function executeDev(opts) {
12
12
  const port = parsePort(opts.port);
13
13
  const agentName = path.basename(path.resolve(opts.cwd));
14
- const { startDevServer } = await import("./_dev-server-DvcbFgos.mjs");
14
+ const { startDevServer } = await import("./_dev-server-BCbEUhmv.mjs");
15
15
  let cleanup;
16
16
  let shuttingDown = false;
17
17
  const onSignal = () => {
@@ -30,7 +30,7 @@ async function executeDev(opts) {
30
30
  port
31
31
  });
32
32
  const url = `http://localhost:${port}`;
33
- log.success(`${pc.bold(agentName)} running at ${fmtUrl(url)}`);
33
+ log.success(`${styleText("bold", agentName)} running at ${fmtUrl(url)}`);
34
34
  log.info("Press Ctrl-C to stop");
35
35
  process.on("unhandledRejection", (err) => {
36
36
  log.error(`Unhandled rejection: ${errorDetail(err)}`);
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { a as unwrapCancel, n as log, u as ok } from "./_ui-CKEIHAtB.mjs";
2
+ import { a as unwrapCancel, n as log, u as ok } from "./_ui-DXZ9prrM.mjs";
3
3
  import { i as fileExists, o as readJson, r as errorMessage, s as resolveCwd } from "./_utils-C502jKo8.mjs";
4
- import { r as isDevMode, t as getMonorepoRoot } from "./_agent-dJAeDb38.mjs";
4
+ import { r as isDevMode, t as getMonorepoRoot } from "./_agent-D39qatZJ.mjs";
5
5
  import path from "node:path";
6
+ import { styleText } from "node:util";
6
7
  import * as p from "@clack/prompts";
7
- import pc from "picocolors";
8
8
  import { execa } from "execa";
9
9
  //#region init.ts
10
10
  const DEFAULT_PROJECT_NAME = "my-voice-agent";
@@ -74,7 +74,7 @@ function resolveTargetDir(dir) {
74
74
  }
75
75
  /** Run deploy after init and return deploy metadata if successful. */
76
76
  async function tryDeploy(cwd, server) {
77
- const { executeDeploy } = await import("./deploy-DzGsORJx.mjs");
77
+ const { executeDeploy } = await import("./deploy-D-dnt9L8.mjs");
78
78
  try {
79
79
  const result = await executeDeploy({
80
80
  cwd,
@@ -92,7 +92,7 @@ async function tryDeploy(cwd, server) {
92
92
  }
93
93
  /** Scaffold the project, optionally showing a spinner. */
94
94
  async function scaffoldProject(dir, cwd, template, silent) {
95
- const { runInit } = await import("./_init-B-Fd6Lm4.mjs");
95
+ const { runInit } = await import("./_init-H-lw7czL.mjs");
96
96
  if (silent) {
97
97
  await runInit({
98
98
  targetDir: cwd,
@@ -116,11 +116,11 @@ function printPostInitInfo(cwd, monorepoRoot) {
116
116
  }
117
117
  async function executeInit(opts, extra) {
118
118
  const suppressUi = extra?.silent;
119
- if (!suppressUi) p.intro(pc.cyanBright("Create a new voice agent"));
119
+ if (!suppressUi) p.intro(styleText("cyanBright", "Create a new voice agent"));
120
120
  const dir = opts.dir ?? await promptProjectName(opts.yes);
121
121
  const monorepoRoot = getMonorepoRoot();
122
122
  const cwd = resolveTargetDir(dir);
123
- if (!opts.force && await fileExists(path.join(cwd, "agent.ts"))) throw new Error(`agent.ts already exists in this directory. Use ${pc.cyanBright("--force")} to overwrite.`);
123
+ if (!opts.force && await fileExists(path.join(cwd, "agent.ts"))) throw new Error(`agent.ts already exists in this directory. Use ${styleText("cyanBright", "--force")} to overwrite.`);
124
124
  const template = opts.template ?? "simple";
125
125
  await scaffoldProject(dir, cwd, template, suppressUi);
126
126
  const installed = await installDeps(cwd, suppressUi);
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { a as unwrapCancel, n as log, s as fail, u as ok } from "./_ui-CKEIHAtB.mjs";
3
- import { n as getServerInfo } from "./_agent-dJAeDb38.mjs";
2
+ import { a as unwrapCancel, n as log, s as fail, u as ok } from "./_ui-DXZ9prrM.mjs";
3
+ import { n as getServerInfo } from "./_agent-D39qatZJ.mjs";
4
4
  import { t as apiRequest } from "./_api-client-a6cPMebU.mjs";
5
5
  import * as p from "@clack/prompts";
6
6
  import { text } from "node:stream/consumers";
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { n as log, s as fail, u as ok } from "./_ui-CKEIHAtB.mjs";
3
- import { n as getServerInfo } from "./_agent-dJAeDb38.mjs";
2
+ import { n as log, s as fail, u as ok } from "./_ui-DXZ9prrM.mjs";
3
+ import { n as getServerInfo } from "./_agent-D39qatZJ.mjs";
4
4
  import { t as apiRequest } from "./_api-client-a6cPMebU.mjs";
5
5
  import * as p from "@clack/prompts";
6
6
  //#region storage.ts
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { n as log, s as fail, u as ok } from "./_ui-CKEIHAtB.mjs";
2
+ import { n as log, s as fail, u as ok } from "./_ui-DXZ9prrM.mjs";
3
3
  import { r as errorMessage, t as errorCode } from "./_utils-C502jKo8.mjs";
4
4
  import { createRequire } from "node:module";
5
5
  import { existsSync, readFileSync } from "node:fs";
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { readFile } from "node:fs/promises";
5
+ import { spawn } from "node:child_process";
6
+ //#region typecheck.ts
7
+ /**
8
+ * Project typechecking — `tsc --noEmit` over the project's own tsconfig.
9
+ *
10
+ * Public (no `_` prefix) for the same reason as the bundlers: the guest
11
+ * sandbox runs the same check before `test_agent` builds, so the studio's
12
+ * coding agent sees type errors as build feedback instead of shipping
13
+ * runtime-working-but-wrong code (the bundlers strip types unchecked —
14
+ * excess-property bugs like `send`/`state` shipped exactly that way).
15
+ *
16
+ * Gated on `tsconfig.json`: a project that declares its type discipline is
17
+ * checked with it; one that doesn't isn't (scaffolded projects and studio
18
+ * workspaces always have one). TypeScript itself resolves from the
19
+ * PROJECT'S node_modules — the user's pinned compiler, not ours.
20
+ */
21
+ /** Bound on one typecheck run — a hung compiler must not wedge a deploy. */
22
+ const TYPECHECK_TIMEOUT_MS = 12e4;
23
+ /** Diagnostics tail kept for the failure message. */
24
+ const OUTPUT_CAP = 16e3;
25
+ /**
26
+ * The project's own TypeScript package root — `node_modules/typescript`,
27
+ * walking up from `cwd` exactly as a bare import would.
28
+ *
29
+ * Deliberately NOT `require.resolve("typescript")`. Node appends
30
+ * `Module.globalPaths` — `NODE_PATH`, `~/.node_modules`, the install prefix —
31
+ * to EVERY lookup, and the `paths` option does not suppress them, so an
32
+ * ambient TypeScript anywhere on the host silently satisfies a project that
33
+ * never declared one. That breaks the promise in this module's header twice
34
+ * over: the gate would check a user's build with a compiler their project
35
+ * doesn't pin, and the "TypeScript is not installed" branch below would be
36
+ * unreachable on any host that sets NODE_PATH — which is how vitest runs its
37
+ * workers (it points NODE_PATH at pnpm's hidden store), so the test for that
38
+ * branch could not fail honestly either.
39
+ *
40
+ * The walk-up follows symlinks, which is what pnpm's `node_modules/typescript
41
+ * -> .pnpm/typescript@x/node_modules/typescript` link needs, and what lets a
42
+ * guest sandbox workspace reach the toolchain baked next to the harness.
43
+ */
44
+ function findTypescriptPackage(cwd) {
45
+ let dir = path.resolve(cwd);
46
+ for (;;) {
47
+ const candidate = path.join(dir, "node_modules", "typescript");
48
+ if (existsSync(path.join(candidate, "package.json"))) return candidate;
49
+ const parent = path.dirname(dir);
50
+ if (parent === dir) return;
51
+ dir = parent;
52
+ }
53
+ }
54
+ /** Resolve the project's TypeScript compiler entry (its own `tsc` bin). */
55
+ function resolveTscEntry(cwd) {
56
+ const dir = findTypescriptPackage(cwd);
57
+ if (dir === void 0) throw new Error("no typescript package in the project's node_modules");
58
+ const pkg = JSON.parse(readFileSync(path.join(dir, "package.json"), "utf-8"));
59
+ const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.tsc;
60
+ if (!bin) throw new Error("installed typescript package declares no tsc bin");
61
+ return path.join(dir, bin);
62
+ }
63
+ /**
64
+ * Typecheck the project at `cwd` with its own tsconfig + compiler.
65
+ * Skips (ok, skipped: true) when the project has no tsconfig.json.
66
+ */
67
+ async function typecheckProject(cwd) {
68
+ if (!await readFile(path.join(cwd, "tsconfig.json"), "utf-8").then(() => true, () => false)) return {
69
+ ok: true,
70
+ skipped: true
71
+ };
72
+ let tscEntry;
73
+ try {
74
+ tscEntry = resolveTscEntry(cwd);
75
+ } catch (err) {
76
+ return {
77
+ ok: false,
78
+ output: `tsconfig.json is present but TypeScript is not installed — add it (npm install -D typescript) or remove tsconfig.json: ${errMessage(err)}`
79
+ };
80
+ }
81
+ const result = await new Promise((resolve, reject) => {
82
+ const child = spawn(process.execPath, [
83
+ tscEntry,
84
+ "--noEmit",
85
+ "--pretty",
86
+ "false"
87
+ ], {
88
+ cwd,
89
+ timeout: TYPECHECK_TIMEOUT_MS,
90
+ stdio: [
91
+ "ignore",
92
+ "pipe",
93
+ "pipe"
94
+ ]
95
+ });
96
+ let output = "";
97
+ const keep = (s) => s.length > OUTPUT_CAP ? `…${s.slice(-16e3)}` : s;
98
+ child.stdout.on("data", (chunk) => {
99
+ output = keep(output + chunk.toString());
100
+ });
101
+ child.stderr.on("data", (chunk) => {
102
+ output = keep(output + chunk.toString());
103
+ });
104
+ child.on("error", reject);
105
+ child.on("close", (code, signal) => {
106
+ if (signal) {
107
+ reject(/* @__PURE__ */ new Error(`tsc killed by ${signal} after ${TYPECHECK_TIMEOUT_MS}ms`));
108
+ return;
109
+ }
110
+ resolve({
111
+ code,
112
+ output
113
+ });
114
+ });
115
+ });
116
+ if (result.code === 0) return {
117
+ ok: true,
118
+ skipped: false
119
+ };
120
+ return {
121
+ ok: false,
122
+ output: `Type check failed:\n${result.output.trim()}`
123
+ };
124
+ }
125
+ function errMessage(err) {
126
+ return err instanceof Error ? err.message : String(err);
127
+ }
128
+ //#endregion
129
+ export { typecheckProject };
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { t as withPreservedNodeEnv } from "./_vite-env-Dg_QlVv0.mjs";
3
3
  import path from "node:path";
4
+ import fs from "node:fs/promises";
4
5
  import { build } from "vite";
5
6
  //#region worker-bundler.ts
6
7
  /**
@@ -12,26 +13,48 @@ import { build } from "vite";
12
13
  * worker published from the browser comes out of the same Vite/Rollup pass as
13
14
  * one from `aai deploy`.
14
15
  *
16
+ * Every worker is built through a generated wrapper entry that re-exports the
17
+ * agent *and* its extracted config as `__aaiConfig` (via the dependency-free
18
+ * `@alexkroman1/aai/manifest` helpers, bundled in). The guest harness returns
19
+ * that export from `bundle/load`, which is how the platform obtains an
20
+ * agent's config without ever evaluating tenant code on the host — the
21
+ * `POST /deploy` route and the studio's sandbox inspection both rely on it.
22
+ *
23
+ * **The worker ships its own runtime.** Deploy builds also export
24
+ * `__aaiCreateRuntime` — a factory over the *user's installed* SDK's
25
+ * `createRuntime`, bundled in alongside the provider SDKs. The guest harness
26
+ * builds the session runtime through it, so a deployed agent runs exactly the
27
+ * runtime version it was built and tested against (identical to `aai dev`),
28
+ * instead of whatever SDK the platform's harness image was baked with. The
29
+ * harness↔bundle contract is deliberately tiny: the factory takes
30
+ * `{ env, db?, runCode? }` and returns `{ startSession, shutdown }`.
31
+ *
15
32
  * What the studio supplies via options, because a workspace is not a project:
16
33
  *
17
- * - **`root` + `entry`.** The studio builds a generated entry that re-exports
18
- * the agent *and* its extracted config, so the guest can report the config
19
- * back without the host ever evaluating agent code.
20
34
  * - **`configFile: false`.** Workspace files are untrusted and a Vite config
21
35
  * is executable host code.
22
36
  * - **`plugins`.** The studio adds its import allowlist; without it, Vite
23
37
  * would happily resolve any package in the server's `node_modules`.
24
38
  */
25
39
  /**
26
- * Transform `.md` imports into raw string exports so templates that do
27
- * `import systemPrompt from "./system-prompt.md"` bundle correctly.
40
+ * Generated wrapper entry, written under `.aai/` for the duration of the
41
+ * build (the CLI's own scratch dir — dot-paths are ignored by the dev
42
+ * watcher, and the studio's workspace materialization never writes there).
28
43
  */
29
- const rawMdPlugin = {
30
- name: "raw-md",
31
- transform(code, id) {
32
- if (id.endsWith(".md")) return `export default ${JSON.stringify(code)}`;
33
- }
44
+ const WRAPPER_ENTRY_REL = path.join(".aai", "worker-entry.ts");
45
+ function wrapperEntrySource(runtime) {
46
+ return `import def from "../agent.ts";
47
+ import { agentToolsToSchemas, toAgentConfig } from "@alexkroman1/aai/manifest";
48
+ ${runtime ? `import { createRuntime } from "@alexkroman1/aai/runtime";` : ""}
49
+ export default def;
50
+ export const __aaiConfig = {
51
+ ...toAgentConfig(def),
52
+ toolSchemas: agentToolsToSchemas(def.tools ?? {}),
34
53
  };
54
+ ${runtime ? `export const __aaiCreateRuntime = (opts: Record<string, unknown>) =>
55
+ createRuntime({ ...opts, agent: def });
56
+ ` : ""}`;
57
+ }
35
58
  /**
36
59
  * Bundle agent.ts into a single ESM string for the sandbox worker.
37
60
  *
@@ -39,25 +62,36 @@ const rawMdPlugin = {
39
62
  * and gracefully degrades in restricted environments like Deno.
40
63
  */
41
64
  async function buildWorker(cwd, opts = {}) {
42
- const entry = opts.entry ?? "agent.ts";
43
- const agentEntry = path.isAbsolute(entry) ? entry : path.join(cwd, entry);
44
- const result = await withPreservedNodeEnv(() => build({
45
- root: cwd,
46
- logLevel: "silent",
47
- ...opts.configFile === false && { configFile: false },
48
- plugins: [rawMdPlugin, ...opts.plugins ?? []],
49
- build: {
50
- lib: {
51
- entry: agentEntry,
52
- formats: ["es"],
53
- fileName: "worker"
54
- },
55
- target: "node20",
56
- minify: opts.minify ? "oxc" : false,
57
- write: false,
58
- rollupOptions: { output: { entryFileNames: "[name].js" } }
59
- }
60
- }));
65
+ const wrapperPath = path.join(cwd, WRAPPER_ENTRY_REL);
66
+ await fs.mkdir(path.dirname(wrapperPath), { recursive: true });
67
+ await fs.writeFile(wrapperPath, wrapperEntrySource(opts.runtime !== false), "utf-8");
68
+ let result;
69
+ try {
70
+ result = await withPreservedNodeEnv(() => build({
71
+ root: cwd,
72
+ logLevel: "silent",
73
+ ...opts.configFile === false && { configFile: false },
74
+ ...opts.plugins && { plugins: opts.plugins },
75
+ ssr: { noExternal: true },
76
+ build: {
77
+ ssr: true,
78
+ lib: {
79
+ entry: wrapperPath,
80
+ formats: ["es"],
81
+ fileName: "worker"
82
+ },
83
+ target: "node20",
84
+ minify: opts.minify ? "oxc" : false,
85
+ write: false,
86
+ rollupOptions: { output: {
87
+ entryFileNames: "[name].js",
88
+ codeSplitting: false
89
+ } }
90
+ }
91
+ }));
92
+ } finally {
93
+ await fs.rm(wrapperPath, { force: true }).catch(() => void 0);
94
+ }
61
95
  const output = Array.isArray(result) ? result[0] : result;
62
96
  if (!output) throw new Error("Vite produced no output for agent.ts");
63
97
  const chunk = output.output.find((o) => o.type === "chunk" && o.isEntry);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alexkroman1/aai-cli",
3
- "version": "4.0.0",
3
+ "version": "5.0.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "aai": "dist/cli.mjs"
@@ -13,6 +13,10 @@
13
13
  "./worker-bundler": {
14
14
  "@dev/source": "./worker-bundler.ts",
15
15
  "import": "./dist/worker-bundler.mjs"
16
+ },
17
+ "./typecheck": {
18
+ "@dev/source": "./typecheck.ts",
19
+ "import": "./dist/typecheck.mjs"
16
20
  }
17
21
  },
18
22
  "files": [
@@ -28,12 +32,11 @@
28
32
  "giget": "^3.3.0",
29
33
  "ofetch": "^1.5.1",
30
34
  "p-debounce": "^5.1.0",
31
- "picocolors": "^1.1.1",
32
- "rolldown": "~1.1.4",
35
+ "p-timeout": "^7.0.1",
33
36
  "vite": "^8.1.5",
34
37
  "zod": "^4.4.3",
35
- "@alexkroman1/aai": "4.0.0",
36
- "@alexkroman1/aai-ui": "4.0.0"
38
+ "@alexkroman1/aai-ui": "5.0.0",
39
+ "@alexkroman1/aai": "5.0.0"
37
40
  },
38
41
  "devDependencies": {
39
42
  "playwright": "^1.61.1",
@@ -63,6 +66,8 @@
63
66
  "build": "tsdown",
64
67
  "typecheck": "tsc --noEmit",
65
68
  "lint": "biome check .",
69
+ "check:publint": "publint",
70
+ "check:attw": "attw --pack --profile esm-only",
66
71
  "test:e2e": "VITEST_PROFILE=e2e VITEST_INCLUDE=e2e*.test.ts vitest run -c ../../vitest.slow.config.ts",
67
72
  "check:e2e": "pnpm run test:e2e"
68
73
  }
@@ -1,96 +0,0 @@
1
- #!/usr/bin/env node
2
- import { n as log, u as ok } from "./_ui-CKEIHAtB.mjs";
3
- import { c as validateAgentExport, r as errorMessage } from "./_utils-C502jKo8.mjs";
4
- import { t as buildClient } from "./client-bundler-DO3GgO6p.mjs";
5
- import { buildWorker } from "./worker-bundler.mjs";
6
- import path from "node:path";
7
- import { pathToFileURL } from "node:url";
8
- import fs from "node:fs/promises";
9
- import { createHash } from "node:crypto";
10
- import { agentToolsToSchemas, toAgentConfig } from "@alexkroman1/aai/manifest";
11
- //#region _bundler.ts
12
- /**
13
- * Bundle an agent directory: build agent.ts into worker ESM + extract config.
14
- *
15
- * - agent.ts is the single entry point: `export default agent({...})`
16
- * - A single Vite build produces the worker ESM (all deps bundled in).
17
- * The AgentDef is extracted from that bundle via dynamic import, avoiding a
18
- * second build pass.
19
- */
20
- async function buildAgentBundle(cwd, opts = {}) {
21
- const [[worker, agentDef], clientFiles] = await Promise.all([buildWorker(cwd, opts).then(async (code) => [code, await evalWorkerBundle(code, cwd)]), buildClient(cwd)]);
22
- log.step(`Bundling ${agentDef.name}`);
23
- const config = toAgentConfig(agentDef);
24
- const toolSchemas = agentToolsToSchemas(agentDef.tools ?? {});
25
- return {
26
- worker,
27
- clientFiles,
28
- agentConfig: {
29
- ...config,
30
- toolSchemas
31
- }
32
- };
33
- }
34
- /**
35
- * Write the worker ESM to a temp file and dynamic-import it, returning
36
- * the AgentDef default export. All dependencies are bundled in, so the
37
- * file can be evaluated from any directory.
38
- *
39
- * Each call imports a uniquely-named file, and Node's ESM registry never
40
- * evicts — so every call retains one bundle for the process lifetime. That is
41
- * fine for one-shot commands (`aai build`/`aai deploy`); long-lived callers
42
- * must go through `createWorkerEvaluator` to at least dedupe identical
43
- * builds. Evaluating in a discardable context is not an option: tool
44
- * `execute` functions from the returned AgentDef are called in-process by the
45
- * dev runtime, which rules out worker threads, and `node:vm` ESM evaluation
46
- * is still flagged experimental.
47
- */
48
- async function evalWorkerBundle(code, cwd) {
49
- const evalDir = path.join(cwd, ".aai", "eval");
50
- const tmpPath = path.join(evalDir, `agent-${Date.now()}-${Math.random().toString(36).slice(2)}.mjs`);
51
- try {
52
- await fs.mkdir(evalDir, { recursive: true });
53
- await fs.writeFile(tmpPath, code);
54
- } catch (err) {
55
- await fs.rm(tmpPath, { force: true }).catch(() => void 0);
56
- throw new Error(`Failed to write the eval bundle under ${evalDir} — is the project directory writable? (${errorMessage(err)})`, { cause: err });
57
- }
58
- try {
59
- const mod = await import(pathToFileURL(tmpPath).href);
60
- const agentDef = mod.default ?? mod;
61
- validateAgentExport(agentDef);
62
- return agentDef;
63
- } finally {
64
- await fs.rm(tmpPath).catch(() => {});
65
- }
66
- }
67
- /**
68
- * Memoizing wrapper around `evalWorkerBundle` for long-lived callers (the
69
- * dev server): byte-identical worker code returns the previously evaluated
70
- * AgentDef without touching the ESM registry. No-op saves and formatter
71
- * churn are the common watcher events, so this caps the registry leak (see
72
- * `evalWorkerBundle`) to genuinely-new bundles — the residual one-module-per-
73
- * distinct-build leak is accepted for the reasons documented there.
74
- */
75
- function createWorkerEvaluator(cwd) {
76
- let lastHash;
77
- let lastAgentDef;
78
- return async (code) => {
79
- const hash = createHash("sha256").update(code).digest("hex");
80
- if (lastAgentDef && hash === lastHash) return lastAgentDef;
81
- const agentDef = await evalWorkerBundle(code, cwd);
82
- lastHash = hash;
83
- lastAgentDef = agentDef;
84
- return agentDef;
85
- };
86
- }
87
- async function executeBuild(cwd) {
88
- const bundle = await buildAgentBundle(cwd, { minify: true });
89
- log.success("Build complete");
90
- return ok({
91
- name: bundle.agentConfig.name,
92
- workerBytes: bundle.worker.length
93
- });
94
- }
95
- //#endregion
96
- export { buildAgentBundle, createWorkerEvaluator, executeBuild };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import path from "node:path";
3
- import fs from "node:fs/promises";
4
3
  import { parseEnv } from "node:util";
4
+ import fs from "node:fs/promises";
5
5
  //#region _server-common.ts
6
6
  /**
7
7
  * Build the `ctx.env` record that agent tools will see at runtime.