@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.
- package/dist/_artifacts-BJOYGQPp.mjs +21 -0
- package/dist/_artifacts.d.ts +16 -0
- package/dist/_build-target.d.ts +172 -0
- package/dist/{_bundler-DolUCMxu.mjs → _bundler-DM0d0M7m.mjs} +1 -1
- package/dist/{_dev-server-CSMqF8PN.mjs → _dev-server-BzWB6-4y.mjs} +9 -6
- package/dist/_e2e-test-utils.d.ts +1 -1
- package/dist/{_init-CQ8idAwo.mjs → _init-Bsi3DZNJ.mjs} +1 -1
- package/dist/_server-common-De0haHr9.mjs +70 -0
- package/dist/_server-common.d.ts +20 -1
- package/dist/{_templates-CK4oKoeX.mjs → _templates-CIlJ3Vay.mjs} +1 -1
- package/dist/_templates.d.ts +1 -1
- package/dist/_vercel-output.d.ts +63 -0
- package/dist/build-BhEaxBPu.mjs +481 -0
- package/dist/build.d.ts +18 -10
- package/dist/cli.mjs +49 -14
- package/dist/{client-bundler-BJgREAh6.mjs → client-bundler-6mTLs6ny.mjs} +4 -4
- package/dist/client-bundler.d.ts +1 -1
- package/dist/client-bundler.mjs +1 -1
- package/dist/{deploy-uAJ4NukN.mjs → deploy-CGqPU5U-.mjs} +2 -2
- package/dist/{dev-DApPSaE_.mjs → dev-Bx9gYBHM.mjs} +1 -1
- package/dist/{eval-BK47A_K5.mjs → eval-B3I7FqN9.mjs} +1 -1
- package/dist/{init-DukDxECd.mjs → init-CFyusRbq.mjs} +39 -3
- package/dist/init.d.ts +14 -0
- package/dist/scaffold/CLAUDE.md +73 -22
- package/dist/scaffold/package.json +6 -6
- package/dist/start.d.ts +112 -0
- package/dist/start.mjs +156 -0
- package/dist/{studio-CpHlNHUZ.mjs → studio-C_zuRC_z.mjs} +2 -2
- package/dist/templates/briefing-desk/agent.eval.test.ts +156 -0
- package/dist/templates/code-interpreter/agent.test.ts +103 -0
- package/dist/templates/link-digest/client.tsx +55 -3
- package/dist/templates/math-buddy/agent.test.ts +126 -0
- package/dist/templates/personal-finance/agent.test.ts +127 -0
- package/dist/templates/support-line/agent.ts +8 -0
- package/dist/templates/travel-concierge/routing.ts +64 -55
- package/dist/templates/travel-concierge/tools/cancel_action.ts +3 -1
- package/dist/templates/travel-concierge/tools/complete_or_escalate.ts +3 -1
- package/dist/templates/travel-concierge/tools/confirm_action.ts +3 -1
- package/dist/templates/web-researcher/agent.test.ts +130 -0
- package/dist/worker-bundler.d.ts +1 -1
- package/dist/worker-bundler.mjs +7 -7
- package/package.json +9 -4
- package/dist/_server-common-vILJp3it.mjs +0 -43
- package/dist/build-Mxk8gWvX.mjs +0 -108
- 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-
|
|
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-
|
|
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-
|
|
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-
|
|
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 (
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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,
|
|
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 =
|
|
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
|
-
...
|
|
97
|
-
...omitUndefined({ plugins:
|
|
96
|
+
...options.configFile === false && { configFile: false },
|
|
97
|
+
...omitUndefined({ plugins: options.plugins }),
|
|
98
98
|
resolve: { dedupe: DEDUPED_PEERS },
|
|
99
99
|
build: {
|
|
100
100
|
outDir,
|
package/dist/client-bundler.d.ts
CHANGED
|
@@ -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,
|
|
30
|
+
export declare function buildClient(cwd: string, options?: BuildClientOptions): Promise<Record<string, string>>;
|
package/dist/client-bundler.mjs
CHANGED
|
@@ -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 {
|
|
7
|
-
import { t as
|
|
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-
|
|
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 {
|
|
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-
|
|
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-
|
|
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 ??
|
|
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;
|
package/dist/scaffold/CLAUDE.md
CHANGED
|
@@ -133,8 +133,8 @@ gate in front of `aai build`.
|
|
|
133
133
|
|
|
134
134
|
## Running it yourself (`npm start`)
|
|
135
135
|
|
|
136
|
-
`
|
|
137
|
-
|
|
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: `
|
|
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
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
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
|
|
1215
|
-
|
|
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
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
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": "
|
|
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": "^
|
|
19
|
-
"@alexkroman1/aai-
|
|
20
|
-
"@alexkroman1/aai-
|
|
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": "^
|
|
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",
|
package/dist/start.d.ts
ADDED
|
@@ -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>;
|