@alexkroman1/aai-cli 14.0.0 → 15.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.
Files changed (45) hide show
  1. package/dist/_artifacts-BJOYGQPp.mjs +21 -0
  2. package/dist/_artifacts.d.ts +16 -0
  3. package/dist/_build-target.d.ts +172 -0
  4. package/dist/{_bundler-DolUCMxu.mjs → _bundler-DM0d0M7m.mjs} +1 -1
  5. package/dist/{_dev-server-CSMqF8PN.mjs → _dev-server-BzWB6-4y.mjs} +9 -6
  6. package/dist/_e2e-test-utils.d.ts +1 -1
  7. package/dist/{_init-CQ8idAwo.mjs → _init-Bsi3DZNJ.mjs} +1 -1
  8. package/dist/_server-common-De0haHr9.mjs +70 -0
  9. package/dist/_server-common.d.ts +20 -1
  10. package/dist/{_templates-CK4oKoeX.mjs → _templates-CIlJ3Vay.mjs} +1 -1
  11. package/dist/_templates.d.ts +1 -1
  12. package/dist/_vercel-output.d.ts +63 -0
  13. package/dist/build-BhEaxBPu.mjs +481 -0
  14. package/dist/build.d.ts +18 -10
  15. package/dist/cli.mjs +49 -14
  16. package/dist/{client-bundler-BJgREAh6.mjs → client-bundler-6mTLs6ny.mjs} +4 -4
  17. package/dist/client-bundler.d.ts +1 -1
  18. package/dist/client-bundler.mjs +1 -1
  19. package/dist/{deploy-uAJ4NukN.mjs → deploy-CGqPU5U-.mjs} +2 -2
  20. package/dist/{dev-DApPSaE_.mjs → dev-Bx9gYBHM.mjs} +1 -1
  21. package/dist/{eval-BK47A_K5.mjs → eval-B3I7FqN9.mjs} +1 -1
  22. package/dist/{init-DukDxECd.mjs → init-CFyusRbq.mjs} +39 -3
  23. package/dist/init.d.ts +14 -0
  24. package/dist/scaffold/CLAUDE.md +73 -22
  25. package/dist/scaffold/package.json +6 -6
  26. package/dist/start.d.ts +112 -0
  27. package/dist/start.mjs +156 -0
  28. package/dist/{studio-CpHlNHUZ.mjs → studio-C_zuRC_z.mjs} +2 -2
  29. package/dist/templates/briefing-desk/agent.eval.test.ts +156 -0
  30. package/dist/templates/code-interpreter/agent.test.ts +103 -0
  31. package/dist/templates/link-digest/client.tsx +55 -3
  32. package/dist/templates/math-buddy/agent.test.ts +126 -0
  33. package/dist/templates/personal-finance/agent.test.ts +127 -0
  34. package/dist/templates/support-line/agent.ts +8 -0
  35. package/dist/templates/travel-concierge/routing.ts +64 -55
  36. package/dist/templates/travel-concierge/tools/cancel_action.ts +3 -1
  37. package/dist/templates/travel-concierge/tools/complete_or_escalate.ts +3 -1
  38. package/dist/templates/travel-concierge/tools/confirm_action.ts +3 -1
  39. package/dist/templates/web-researcher/agent.test.ts +130 -0
  40. package/dist/worker-bundler.d.ts +1 -1
  41. package/dist/worker-bundler.mjs +7 -7
  42. package/package.json +9 -4
  43. package/dist/_server-common-vILJp3it.mjs +0 -43
  44. package/dist/build-Mxk8gWvX.mjs +0 -108
  45. package/dist/scaffold/server.mjs +0 -308
package/dist/cli.mjs CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { i as installStdoutGuard, n as fail, o as writeLine, r as getOutputMode, t as CliError } from "./_output-DBP9Op_d.mjs";
3
- import { n as log, o as silenceOutput } from "./_ui-DCt4qZrk.mjs";
3
+ import { a as parsePort, n as log, o as silenceOutput } from "./_ui-DCt4qZrk.mjs";
4
4
  import { c as fileExists, f as readPackageJson, p as resolveCwd, s as errorMessage$1, t as AGENT_ENTRY } from "./_utils-D5JGcjiW.mjs";
5
5
  import { existsSync } from "node:fs";
6
6
  import path from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
- import { isRecord, plural } from "@alexkroman1/aai/utils";
8
+ import { isRecord, omitUndefined, plural } from "@alexkroman1/aai/utils";
9
9
  import { defineCommand, runMain, showUsage } from "citty";
10
10
  //#region src/_cli-common.ts
11
11
  /** Shared arg definitions for citty commands. */
@@ -395,7 +395,7 @@ const list = defineExec({
395
395
  args: { ...platformArgs },
396
396
  cwd: "any",
397
397
  async run({ args, cwd }) {
398
- const { executeList } = await import("./studio-CpHlNHUZ.mjs");
398
+ const { executeList } = await import("./studio-C_zuRC_z.mjs");
399
399
  return executeList({
400
400
  cwd,
401
401
  server: args.server
@@ -427,7 +427,7 @@ const pull = defineExec({
427
427
  },
428
428
  cwd: "any",
429
429
  async run({ args, cwd }) {
430
- const { executePull } = await import("./studio-CpHlNHUZ.mjs");
430
+ const { executePull } = await import("./studio-C_zuRC_z.mjs");
431
431
  return executePull({
432
432
  cwd,
433
433
  project: args.project,
@@ -448,7 +448,7 @@ const push = defineExec({
448
448
  },
449
449
  cwd: "agent",
450
450
  async run({ args, cwd }) {
451
- const { executePush } = await import("./studio-CpHlNHUZ.mjs");
451
+ const { executePush } = await import("./studio-C_zuRC_z.mjs");
452
452
  return executePush({
453
453
  cwd,
454
454
  server: args.server,
@@ -471,7 +471,7 @@ const publish = defineExec({
471
471
  },
472
472
  cwd: "agent",
473
473
  async run({ args, cwd }) {
474
- const { executePublish } = await import("./studio-CpHlNHUZ.mjs");
474
+ const { executePublish } = await import("./studio-C_zuRC_z.mjs");
475
475
  return executePublish({
476
476
  cwd,
477
477
  server: args.server,
@@ -693,7 +693,7 @@ const init = defineExec({
693
693
  template: {
694
694
  type: "string",
695
695
  alias: "t",
696
- description: "Template to use (run `aai templates` for the list)"
696
+ description: "Template to use (prompted when omitted)"
697
697
  },
698
698
  server: sharedArgs.server,
699
699
  yes: sharedArgs.yes,
@@ -705,7 +705,7 @@ const init = defineExec({
705
705
  },
706
706
  cwd: "none",
707
707
  async run({ args, mode }) {
708
- const { executeInit } = await import("./init-DukDxECd.mjs");
708
+ const { executeInit } = await import("./init-CFyusRbq.mjs");
709
709
  return executeInit({
710
710
  dir: args.dir,
711
711
  force: args.force,
@@ -736,7 +736,7 @@ const dev = defineExec({
736
736
  },
737
737
  cwd: "agent",
738
738
  async run({ args, cwd }) {
739
- const { executeDev } = await import("./dev-DApPSaE_.mjs");
739
+ const { executeDev } = await import("./dev-Bx9gYBHM.mjs");
740
740
  return executeDev({
741
741
  cwd,
742
742
  port: args.port,
@@ -744,6 +744,35 @@ const dev = defineExec({
744
744
  });
745
745
  }
746
746
  });
747
+ const start = defineExec({
748
+ meta: {
749
+ name: "start",
750
+ description: "Serve the built agent (production)"
751
+ },
752
+ args: {
753
+ port: {
754
+ type: "string",
755
+ alias: "p",
756
+ description: "Port to listen on"
757
+ },
758
+ host: {
759
+ type: "string",
760
+ description: "Address to bind (default: loopback)"
761
+ },
762
+ json: sharedArgs.json
763
+ },
764
+ cwd: "agent",
765
+ async run({ args, cwd }) {
766
+ const { executeStart } = await import("./start.mjs");
767
+ return executeStart({
768
+ cwd,
769
+ ...omitUndefined({
770
+ port: args.port === void 0 ? void 0 : parsePort(args.port),
771
+ host: args.host?.trim() || void 0
772
+ })
773
+ });
774
+ }
775
+ });
747
776
  const test = defineExec({
748
777
  meta: {
749
778
  name: "test",
@@ -770,7 +799,7 @@ const evalCommand = defineExec({
770
799
  args: { json: sharedArgs.json },
771
800
  cwd: "agent",
772
801
  async run({ cwd }) {
773
- const { executeEval } = await import("./eval-BK47A_K5.mjs");
802
+ const { executeEval } = await import("./eval-B3I7FqN9.mjs");
774
803
  return executeEval(cwd);
775
804
  }
776
805
  });
@@ -788,15 +817,20 @@ const build = defineExec({
788
817
  skipTypecheck: {
789
818
  type: "boolean",
790
819
  description: "Skip type checking before build"
820
+ },
821
+ target: {
822
+ type: "string",
823
+ description: "Deployment shape to emit (node, vercel; default: detected)"
791
824
  }
792
825
  },
793
826
  cwd: "agent",
794
827
  async run({ args, cwd }) {
795
- const { executeBuild } = await import("./build-Mxk8gWvX.mjs");
828
+ const { executeBuild } = await import("./build-BhEaxBPu.mjs");
796
829
  return executeBuild({
797
830
  cwd,
798
831
  skipTests: args.skipTests,
799
- skipTypecheck: args.skipTypecheck
832
+ skipTypecheck: args.skipTypecheck,
833
+ target: args.target
800
834
  });
801
835
  }
802
836
  });
@@ -819,7 +853,7 @@ const deploy = defineExec({
819
853
  },
820
854
  cwd: "agent",
821
855
  async run({ args, cwd }) {
822
- const { executeDeploy } = await import("./deploy-uAJ4NukN.mjs");
856
+ const { executeDeploy } = await import("./deploy-CGqPU5U-.mjs");
823
857
  return executeDeploy({
824
858
  cwd,
825
859
  server: args.server,
@@ -863,7 +897,7 @@ const templates = defineExec({
863
897
  args: { json: sharedArgs.json },
864
898
  cwd: "none",
865
899
  async run({ mode }) {
866
- const { listTemplates } = await import("./_templates-CK4oKoeX.mjs");
900
+ const { listTemplates } = await import("./_templates-CIlJ3Vay.mjs");
867
901
  const names = await listTemplates();
868
902
  if (mode === "human") {
869
903
  for (const name of names) log.message(name);
@@ -880,6 +914,7 @@ const mainCommand = defineCommand({
880
914
  subCommands: {
881
915
  init,
882
916
  dev,
917
+ start,
883
918
  test,
884
919
  eval: evalCommand,
885
920
  build,
@@ -81,10 +81,10 @@ const DEFAULT_OUT_DIR = ".aai/client";
81
81
  * @internal — build hook for aai-server/the studio; not a supported public
82
82
  * API and not covered by semver.
83
83
  */
84
- async function buildClient(cwd, opts = {}) {
84
+ async function buildClient(cwd, options = {}) {
85
85
  const clientEntry = path.join(cwd, "client.tsx");
86
86
  if (!await fileExists(clientEntry)) return {};
87
- const outDir = opts.outDir ?? DEFAULT_OUT_DIR;
87
+ const outDir = options.outDir ?? DEFAULT_OUT_DIR;
88
88
  const clientDir = path.join(cwd, outDir);
89
89
  let cleanupHtml = () => {};
90
90
  try {
@@ -93,8 +93,8 @@ async function buildClient(cwd, opts = {}) {
93
93
  root: cwd,
94
94
  base: "./",
95
95
  logLevel: "silent",
96
- ...opts.configFile === false && { configFile: false },
97
- ...omitUndefined({ plugins: opts.plugins }),
96
+ ...options.configFile === false && { configFile: false },
97
+ ...omitUndefined({ plugins: options.plugins }),
98
98
  resolve: { dedupe: DEDUPED_PEERS },
99
99
  build: {
100
100
  outDir,
@@ -27,4 +27,4 @@ export type BuildClientOptions = {
27
27
  * @internal — build hook for aai-server/the studio; not a supported public
28
28
  * API and not covered by semver.
29
29
  */
30
- export declare function buildClient(cwd: string, opts?: BuildClientOptions): Promise<Record<string, string>>;
30
+ export declare function buildClient(cwd: string, options?: BuildClientOptions): Promise<Record<string, string>>;
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import { t as buildClient } from "./client-bundler-BJgREAh6.mjs";
2
+ import { t as buildClient } from "./client-bundler-6mTLs6ny.mjs";
3
3
  export { buildClient };
@@ -3,8 +3,8 @@ import { a as ok } from "./_output-DBP9Op_d.mjs";
3
3
  import { n as log, r as notify, t as fmtUrl } from "./_ui-DCt4qZrk.mjs";
4
4
  import { s as errorMessage$1 } from "./_utils-D5JGcjiW.mjs";
5
5
  import { s as updateProjectConfig } from "./_config-DYzC6WMD.mjs";
6
- import { i as evalWorkerConfig, t as buildAgentBundle } from "./_bundler-DolUCMxu.mjs";
7
- import { t as resolveServerEnv } from "./_server-common-vILJp3it.mjs";
6
+ import { n as resolveServerEnv } from "./_server-common-De0haHr9.mjs";
7
+ import { i as evalWorkerConfig, t as buildAgentBundle } from "./_bundler-DM0d0M7m.mjs";
8
8
  import { o as resolveDeployTarget } from "./_agent-BzUeqOdj.mjs";
9
9
  import { assertTypechecks } from "./_typecheck-gate-U3jcil8L.mjs";
10
10
  import { n as scanWorkflowDeterminism, t as determinismWarnings } from "./_workflow-determinism-CvpqMpdh.mjs";
@@ -12,7 +12,7 @@ import { styleText } from "node:util";
12
12
  async function executeDev(opts) {
13
13
  const port = parsePort(opts.port);
14
14
  const agentName = path.basename(path.resolve(opts.cwd));
15
- const { startDevServer } = await import("./_dev-server-CSMqF8PN.mjs");
15
+ const { startDevServer } = await import("./_dev-server-BzWB6-4y.mjs");
16
16
  let cleanup;
17
17
  let shuttingDown = false;
18
18
  const onSignal = () => {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as ok, n as fail } from "./_output-DBP9Op_d.mjs";
3
3
  import { n as log } from "./_ui-DCt4qZrk.mjs";
4
- import { t as resolveServerEnv } from "./_server-common-vILJp3it.mjs";
4
+ import { n as resolveServerEnv } from "./_server-common-De0haHr9.mjs";
5
5
  import { classifyVitestError, runVitest } from "./test-D032V2cA.mjs";
6
6
  //#region src/eval.ts
7
7
  /**
@@ -3,6 +3,7 @@ import { a as ok } from "./_output-DBP9Op_d.mjs";
3
3
  import { n as log, s as unwrapCancel } from "./_ui-DCt4qZrk.mjs";
4
4
  import { c as fileExists, d as readJson, p as resolveCwd, s as errorMessage$1, t as AGENT_ENTRY } from "./_utils-D5JGcjiW.mjs";
5
5
  import { r as isDevMode, t as getMonorepoRoot } from "./_agent-BzUeqOdj.mjs";
6
+ import { listTemplates } from "./_templates-CIlJ3Vay.mjs";
6
7
  import path from "node:path";
7
8
  import { omitUndefined } from "@alexkroman1/aai/utils";
8
9
  import { styleText } from "node:util";
@@ -39,6 +40,41 @@ async function promptProjectName(yes) {
39
40
  defaultValue: DEFAULT_PROJECT_NAME
40
41
  }), "Setup cancelled") || DEFAULT_PROJECT_NAME;
41
42
  }
43
+ /**
44
+ * The template a non-interactive `init` gets, and the entry the selector opens
45
+ * on — so an author who just presses Enter lands where they used to.
46
+ */
47
+ const DEFAULT_TEMPLATE = "simple";
48
+ /**
49
+ * Ask which template to scaffold, listing what this CLI actually ships.
50
+ *
51
+ * The options are derived from {@link listTemplates} rather than a roster kept
52
+ * here: that function already backs `aai templates` AND the unknown-template
53
+ * error, so a template added to the package shows up in the picker with no
54
+ * second list to update. {@link DEFAULT_TEMPLATE} is hoisted to the top and
55
+ * pre-selected, which is what keeps a bare `aai init` a single Enter away from
56
+ * the project it produced before there was a picker.
57
+ *
58
+ * Callers must not reach here when there is nobody to answer — `--yes` and
59
+ * JSON mode (auto-detected on a pipe) resolve the default without prompting.
60
+ */
61
+ async function promptTemplate(list = listTemplates) {
62
+ const [first, ...rest] = await list();
63
+ if (first === void 0) return DEFAULT_TEMPLATE;
64
+ if (rest.length === 0) return first;
65
+ const names = [first, ...rest];
66
+ const hasDefault = names.includes(DEFAULT_TEMPLATE);
67
+ const ordered = hasDefault ? [DEFAULT_TEMPLATE, ...names.filter((name) => name !== DEFAULT_TEMPLATE)] : names;
68
+ return unwrapCancel(await p.select({
69
+ message: "Which template?",
70
+ maxItems: 12,
71
+ initialValue: hasDefault ? DEFAULT_TEMPLATE : first,
72
+ options: ordered.map((name) => name === DEFAULT_TEMPLATE ? {
73
+ value: name,
74
+ hint: "the default starting point"
75
+ } : { value: name })
76
+ }), "Setup cancelled");
77
+ }
42
78
  /** Best-effort corepack enable so pnpm is available (scaffold declares packageManager: pnpm). */
43
79
  async function ensurePnpm() {
44
80
  await execa("corepack", ["enable"], { reject: false });
@@ -106,7 +142,7 @@ function collectWarnings() {
106
142
  }
107
143
  /** Publish after init and return deploy metadata if successful. */
108
144
  async function tryPublish(cwd, server, warn) {
109
- const { executePublish } = await import("./studio-CpHlNHUZ.mjs");
145
+ const { executePublish } = await import("./studio-C_zuRC_z.mjs");
110
146
  try {
111
147
  const result = await executePublish({
112
148
  cwd,
@@ -127,7 +163,7 @@ async function tryPublish(cwd, server, warn) {
127
163
  }
128
164
  /** Scaffold the project, optionally showing a spinner. */
129
165
  async function scaffoldProject(dir, cwd, template, silent) {
130
- const { runInit } = await import("./_init-CQ8idAwo.mjs");
166
+ const { runInit } = await import("./_init-Bsi3DZNJ.mjs");
131
167
  await withSpinner(silent, {
132
168
  start: `Creating ${dir}`,
133
169
  done: "Project created",
@@ -150,7 +186,7 @@ async function executeInit(opts, extra) {
150
186
  const monorepoRoot = getMonorepoRoot();
151
187
  const cwd = resolveTargetDir(dir);
152
188
  if (!opts.force && await fileExists(path.join(cwd, "agent.ts"))) throw new Error(`${AGENT_ENTRY} already exists in this directory. Use ${styleText("cyanBright", "--force")} to overwrite.`);
153
- const template = opts.template ?? "simple";
189
+ const template = opts.template ?? (opts.yes || suppressUi ? DEFAULT_TEMPLATE : await promptTemplate());
154
190
  const { warn, warnings } = collectWarnings();
155
191
  await scaffoldProject(dir, cwd, template, suppressUi);
156
192
  const installed = await installDeps(cwd, warn, suppressUi);
package/dist/init.d.ts CHANGED
@@ -17,6 +17,20 @@ type InitData = {
17
17
  */
18
18
  warnings?: string[];
19
19
  };
20
+ /**
21
+ * Ask which template to scaffold, listing what this CLI actually ships.
22
+ *
23
+ * The options are derived from {@link listTemplates} rather than a roster kept
24
+ * here: that function already backs `aai templates` AND the unknown-template
25
+ * error, so a template added to the package shows up in the picker with no
26
+ * second list to update. {@link DEFAULT_TEMPLATE} is hoisted to the top and
27
+ * pre-selected, which is what keeps a bare `aai init` a single Enter away from
28
+ * the project it produced before there was a picker.
29
+ *
30
+ * Callers must not reach here when there is nobody to answer — `--yes` and
31
+ * JSON mode (auto-detected on a pipe) resolve the default without prompting.
32
+ */
33
+ export declare function promptTemplate(list?: () => Promise<string[]>): Promise<string>;
20
34
  /** Build the command + args for running pnpm, routing through safe-chain when available. */
21
35
  export declare function resolvePnpmCommand(checkSafeChain?: () => Promise<boolean>): Promise<{
22
36
  cmd: string;
@@ -133,8 +133,8 @@ gate in front of `aai build`.
133
133
 
134
134
  ## Running it yourself (`npm start`)
135
135
 
136
- `server.mjs` serves this agent from a plain Node process — no platform
137
- account, nothing managed. It is the deployment counterpart of `aai dev`:
136
+ `aai start` serves this agent from a plain Node process — no platform account,
137
+ nothing managed. It is the deployment counterpart of `aai dev`:
138
138
 
139
139
  ```sh
140
140
  npm start # http://127.0.0.1:3000
@@ -142,11 +142,18 @@ PORT=8080 HOST=0.0.0.0 npm start # bind every interface, e.g. in a container
142
142
  ```
143
143
 
144
144
  `npm start` **builds first** (that is the `prestart` script) and then serves
145
- the result: `server.mjs` boots `.aai/worker.mjs`, the same artifact
145
+ the result: `aai start` boots `.aai/worker.mjs`, the same artifact
146
146
  `aai publish` uploads. It serves your own `client.tsx` build when there is one
147
- and falls back to `defaultClientDir()` (`@alexkroman1/aai-ui/client-dir`), the
148
- prebuilt default UI shipped inside the package — the only export of `aai-ui`
149
- that runs on Node rather than in the browser.
147
+ and falls back to the prebuilt default UI shipped inside `@alexkroman1/aai-ui`.
148
+
149
+ There is no server file in your project, and that is deliberate — the boot
150
+ belongs to the framework, so it improves when you update rather than being
151
+ frozen at the moment you scaffolded. When you need to own it, import
152
+ `createProjectServer` from `@alexkroman1/aai-cli/start`: it builds the server
153
+ and binds nothing, so you decide how it is served. Building one from scratch
154
+ instead, `defaultClientDir()` (`@alexkroman1/aai-ui/client-dir`) is where that
155
+ prebuilt UI lives — the only export of `aai-ui` that runs on Node rather than
156
+ in the browser.
150
157
 
151
158
  The build is what makes `tools/` work — a tool is registered by existing, and
152
159
  the enumeration happens where the bundle is assembled, so a server that loaded
@@ -161,9 +168,31 @@ One thing to know: it binds **loopback by default**, because this server has
161
168
  no request authentication of its own; set `HOST=0.0.0.0` only behind your own
162
169
  proxy or auth.
163
170
 
164
- Deleting `server.mjs` costs nothing: `aai dev`, `aai publish` and the managed
165
- platform never read it. `run_code` is the one feature that does not follow —
166
- it needs the platform's sandbox and refuses outside one.
171
+ `run_code` is the one feature that does not follow — it needs the platform's
172
+ sandbox and refuses outside one.
173
+
174
+ ### Deploying to a host that wants its own entry file
175
+
176
+ `aai build --target <host>` writes the deployment that host expects into the
177
+ build output. Nothing host-specific lives in your project: the files are
178
+ generated, gitignored, and rewritten by the host's own build.
179
+
180
+ ```sh
181
+ aai build --target vercel # writes .vercel/output/ (Build Output API v3)
182
+ ```
183
+
184
+ You rarely type it. The target is detected from the host's own build
185
+ environment, so `vercel deploy` (or a git push) picks it up with nothing
186
+ configured; `--target node`, the default everywhere else, emits nothing extra
187
+ and is what `npm start` runs.
188
+
189
+ One thing to know before deploying a VOICE agent to a serverless host: the
190
+ session is a WebSocket, so the host has to support one. Vercel does — it hands
191
+ the function the raw upgrade, and the emitted entry passes it to the same
192
+ server `aai dev` runs. A host that serves only request/response still runs the
193
+ HTTP surface — `/health`, `/client-config`,
194
+ `/workflows/*` and your static assets — which is everything a workflow app
195
+ needs and none of what a voice agent needs.
167
196
 
168
197
  ## Project structure
169
198
 
@@ -1199,11 +1228,23 @@ statement about pipeline mode, not about the SDK.
1199
1228
 
1200
1229
  ### Answering a phone call
1201
1230
 
1202
- A deployed voice agent already serves carrier media streams there is nothing
1203
- to switch on. `createRuntimeServer` mounts `WS /phone` whenever the agent is a voice
1204
- agent (`telephony` defaults to `true`, and to `false` for a `page: "static"`
1205
- workflow app, which has no stages to put on a call). Point the carrier at it
1206
- with a `carrier` query parameter naming who is dialling:
1231
+ **Say which carriers may call, and `WS /phone` is served for exactly those.**
1232
+ `telephony` is an allow-list on `agent()`, and it is the whole of the wiring:
1233
+
1234
+ ```ts
1235
+ import { agent } from "@alexkroman1/aai";
1236
+
1237
+ export default agent({
1238
+ name: "Support",
1239
+ greeting: "Support line — what's happened?",
1240
+ // The carrier this agent's number is with. `true` admits every carrier this
1241
+ // build decodes; omit the field and `/phone` is not served at all.
1242
+ telephony: ["twilio"],
1243
+ });
1244
+ ```
1245
+
1246
+ Point the carrier at the deployed agent with a `carrier` query parameter naming
1247
+ who is dialling:
1207
1248
 
1208
1249
  ```text
1209
1250
  wss://<your-agent-url>/phone?carrier=twilio
@@ -1211,15 +1252,25 @@ wss://<your-agent-url>/phone?carrier=telnyx
1211
1252
  ```
1212
1253
 
1213
1254
  Twilio and Telnyx are the two carriers this build decodes (`CARRIER_CODECS`);
1214
- an unknown `carrier` is declined at the upgrade. Both speak 8 kHz mu-law, which
1215
- the bridge transcodes in both directions, so the agent, its tools and its slots
1255
+ an unknown `carrier` is declined at the upgrade with a `400`, and a real one
1256
+ this agent did not declare with a `404`. Both speak 8 kHz mu-law, which the
1257
+ bridge transcodes in both directions, so the agent, its tools and its slots
1216
1258
  behave exactly as they do in the browser — a phone call is a transport, not a
1217
- mode. Nothing about `agent.ts` changes to support one.
1218
-
1219
- Turn the route off with `telephony: false` on `createRuntimeServer`. If you are
1220
- embedding the runtime yourself rather than deploying, the pieces are
1221
- `createTelephonyBridge`, `startTelephonySession`, `TELEPHONY_PATH` and
1222
- `carrierByName`, all on `@alexkroman1/aai-runtime`.
1259
+ mode. Nothing else about `agent.ts` changes to support one.
1260
+
1261
+ **An agent that declares nothing answers no carrier**, which is a change from
1262
+ earlier releases: every voice agent used to serve both carriers' framing from
1263
+ the moment it booted, whether or not it had a phone number. `/phone` is the one
1264
+ door dialled from OUTSIDE your deployment, by a carrier following a number, so
1265
+ it is opened by a sentence in `agent.ts` rather than inherited. `aai dev` and a
1266
+ deployed sandbox honour the same declaration, so a call refused after a deploy
1267
+ is refused on your laptop too.
1268
+
1269
+ `telephony: false` is the same refusal stated out loud, and an operator can pass
1270
+ `telephony` to `createAgentServer` to override one deployment of an agent that
1271
+ does declare a carrier. If you are embedding the runtime yourself rather than
1272
+ deploying, the pieces are `createTelephonyBridge`, `startTelephonySession`,
1273
+ `TELEPHONY_PATH` and `carrierByName`, all on `@alexkroman1/aai-runtime`.
1223
1274
 
1224
1275
  **Silence nudge (pipeline only):** set `silenceTimeoutMs` to make the
1225
1276
  assistant proactively take a turn after that much user silence (e.g.
@@ -7,7 +7,7 @@
7
7
  "scripts": {
8
8
  "dev": "aai dev",
9
9
  "prestart": "aai build --skip-tests",
10
- "start": "node server.mjs",
10
+ "start": "aai start",
11
11
  "build": "aai build",
12
12
  "test": "vitest run --exclude \"**/*.eval.test.*\"",
13
13
  "test:agent": "aai test",
@@ -15,9 +15,10 @@
15
15
  "publish:agent": "aai publish"
16
16
  },
17
17
  "dependencies": {
18
- "@alexkroman1/aai": "^14.0.0",
19
- "@alexkroman1/aai-runtime": "^14.0.0",
20
- "@alexkroman1/aai-ui": "^14.0.0",
18
+ "@alexkroman1/aai": "^15.0.0",
19
+ "@alexkroman1/aai-cli": "^15.0.0",
20
+ "@alexkroman1/aai-runtime": "^15.0.0",
21
+ "@alexkroman1/aai-ui": "^15.0.0",
21
22
  "react": "^19.2.8",
22
23
  "react-dom": "^19.2.8",
23
24
  "tailwindcss": "^4.0.0",
@@ -25,9 +26,8 @@
25
26
  "zod": "^4.5.4"
26
27
  },
27
28
  "devDependencies": {
28
- "@alexkroman1/aai-cli": "^14.0.0",
29
29
  "@tailwindcss/vite": "^4.3.3",
30
- "@types/node": "^26.4.1",
30
+ "@types/node": "^24.10.1",
31
31
  "@types/react": "^19.2.18",
32
32
  "@types/react-dom": "^19.2.5",
33
33
  "@vitejs/plugin-react": "^6.1.1",
@@ -0,0 +1,112 @@
1
+ /**
2
+ * `aai start` — serve a BUILT agent from a plain Node process.
3
+ *
4
+ * The deployment counterpart of `aai dev`: no file watching, no Vite, no
5
+ * typecheck, no source evaluation. It loads the artifact `aai build` left at
6
+ * {@link WORKER_ARTIFACT_REL} — the same one `aai publish` uploads and the
7
+ * managed platform runs — so a self-hosted agent and a deployed one cannot
8
+ * behave differently.
9
+ *
10
+ * ## Why this is a command rather than a file in every project
11
+ *
12
+ * It used to be `scaffold/server.mjs`, ~300 lines of boot that `aai init`
13
+ * copied into every scaffolded project: worker load, env resolution, schema
14
+ * DDL, client-directory probing, error classification, listen, signal
15
+ * handlers. Shipping that as source made each of those a fact a USER's
16
+ * repository asserted, so improving any of them reached only projects
17
+ * scaffolded afterwards, and an existing project silently kept the old
18
+ * behaviour with nothing to report the drift.
19
+ *
20
+ * Every framework that solved this solved it the same way: the boot belongs to
21
+ * the framework and the project holds none of it. Next has `next start` and
22
+ * generates a `server.js` for `output: "standalone"` rather than asking anyone
23
+ * to write one. Nitro's default production preset is `node-server`, emitted
24
+ * into the build directory. The custom server is a documented opt-out, not the
25
+ * default everyone inherits — and here that opt-out is
26
+ * {@link createProjectServer}, which builds the server and binds nothing.
27
+ *
28
+ * ## Why the CLI rather than the runtime
29
+ *
30
+ * Booting a project needs three things at once: the runtime, the project's
31
+ * `.aai/` layout, and the prebuilt browser client. Only this package depends on
32
+ * all three — `aai-runtime` may not import `@alexkroman1/aai-ui`, and
33
+ * konsistent's `runtime-package-boundary` carries the install-weight argument
34
+ * for why that stays true. `client-dir.ts`'s own module doc records that this
35
+ * composition has always lived in an ENTRY POINT; this is that entry point,
36
+ * owned by the framework instead of copied into each project.
37
+ *
38
+ * The cost, stated because it is real: `npm start` needs `@alexkroman1/aai-cli`
39
+ * installed, so a production image carries a build toolchain it does not run.
40
+ * Next makes the same trade — `next` is a `dependency`, not a `devDependency` —
41
+ * and it is why the scaffold moves this package to `dependencies`.
42
+ */
43
+ import type { AgentDef } from "@alexkroman1/aai";
44
+ import { type AgentServer } from "@alexkroman1/aai-runtime";
45
+ export { CLIENT_ARTIFACT_REL } from "./_artifacts.ts";
46
+ /** The port `aai start` binds when neither an argument nor `PORT` says otherwise. */
47
+ export declare const DEFAULT_START_PORT = 3000;
48
+ /** Options for {@link createProjectServer} and {@link executeStart}. */
49
+ export interface StartOptions {
50
+ /** Project root — the directory holding `agent.ts` and `.aai/`. */
51
+ cwd: string;
52
+ /** Port to bind. Defaults to `PORT`, then {@link DEFAULT_START_PORT}. */
53
+ port?: number | undefined;
54
+ /**
55
+ * Address to bind. Defaults to `HOST`, then loopback.
56
+ *
57
+ * Loopback is deliberate: this server has no request authentication of its
58
+ * own, so exposing it is an explicit act, and `HOST=0.0.0.0` is how a
59
+ * container says so. An EMPTY `HOST` means unset, not "every interface".
60
+ */
61
+ host?: string | undefined;
62
+ }
63
+ /**
64
+ * Load the built agent, or fail saying what to run.
65
+ *
66
+ * A `file:` URL rather than a relative specifier, because on Windows a bare
67
+ * POSIX-looking path is not a valid module specifier.
68
+ */
69
+ export declare function loadBuiltAgent(cwd: string): Promise<AgentDef>;
70
+ /**
71
+ * Build this project's {@link AgentServer} WITHOUT binding a socket.
72
+ *
73
+ * The seam a custom server is written against, and the one a serverless host
74
+ * needs: Vercel documents `export default <http.Server>` as its Node WebSocket
75
+ * shape and binds the socket itself, so such a host takes `AgentServer.node`
76
+ * and never calls `listen()`. `aai build --target vercel` emits an entry that
77
+ * does exactly this, so nothing host-specific is committed to a project.
78
+ *
79
+ * @example
80
+ * ```ts no-check
81
+ * // The entry `aai build --target vercel` bundles into
82
+ * // `.vercel/output/functions/index.func/` — `no-check` because this file
83
+ * // lives in the USER's project, where `@alexkroman1/aai-cli/start` resolves;
84
+ * // it cannot resolve from inside this package.
85
+ * import { createProjectServer } from "@alexkroman1/aai-cli/start";
86
+ *
87
+ * const server = (await createProjectServer({ cwd: import.meta.dirname })).node;
88
+ *
89
+ * export default function handler(req, res) {
90
+ * server.emit("request", req, res);
91
+ * }
92
+ * ```
93
+ */
94
+ export declare function createProjectServer(options: StartOptions): Promise<AgentServer>;
95
+ /** What {@link executeStart} answers, for `--json` and for tests. */
96
+ export interface StartResult {
97
+ ok: true;
98
+ data: {
99
+ name: string;
100
+ port: number | undefined;
101
+ };
102
+ }
103
+ /**
104
+ * Serve the built agent and keep serving it: bind, announce, and shut down
105
+ * cleanly on a signal.
106
+ *
107
+ * The signal listeners are SYNCHRONOUS. An `async` one hands its promise to
108
+ * `process`, which discards what a listener returns — so a `close()` that
109
+ * rejected would surface as an unhandled rejection, i.e. a crash with a stack
110
+ * trace on Ctrl-C, instead of the non-zero exit a failed shutdown should be.
111
+ */
112
+ export declare function executeStart(options: StartOptions): Promise<StartResult>;