@alexkroman1/aai-cli 4.0.0 → 5.0.1
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/{_agent-dJAeDb38.mjs → _agent-D39qatZJ.mjs} +1 -1
- package/dist/_bundler-DdmuZzeR.mjs +92 -0
- package/dist/{_config-B7Ywbpfb.mjs → _config-Czjhvaax.mjs} +1 -1
- package/dist/{_dev-server-DvcbFgos.mjs → _dev-server-BCbEUhmv.mjs} +42 -140
- package/dist/{_init-B-Fd6Lm4.mjs → _init-H-lw7czL.mjs} +1 -1
- package/dist/_typecheck-gate-B1GcKZX-.mjs +17 -0
- package/dist/{_ui-CKEIHAtB.mjs → _ui-DXZ9prrM.mjs} +2 -2
- package/dist/cli.mjs +34 -16
- package/dist/{delete-Br2Jj28e.mjs → delete-K24hW1ab.mjs} +2 -2
- package/dist/{deploy-DzGsORJx.mjs → deploy-D-dnt9L8.mjs} +19 -10
- package/dist/{dev-CQPY9Cfx.mjs → dev-C8o-p13B.mjs} +4 -4
- package/dist/{init-Dn3RNhsR.mjs → init-B53rR3yD.mjs} +7 -7
- package/dist/{secret-DTH-dXnP.mjs → secret-C4unwCEw.mjs} +2 -2
- package/dist/{storage-DOtoALnS.mjs → storage-tZM1J8qj.mjs} +2 -2
- package/dist/{test-B3BkLNn_.mjs → test-CgWhus_Z.mjs} +1 -1
- package/dist/typecheck.mjs +129 -0
- package/dist/worker-bundler.mjs +63 -29
- package/package.json +10 -5
- package/dist/_bundler-DnZnKsIU.mjs +0 -96
- package/dist/{_server-common-CpVD57yn.mjs → _server-common-B75oco06.mjs} +1 -1
|
@@ -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-
|
|
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-
|
|
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-
|
|
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-
|
|
7
|
-
import { n as ensureApiKey } from "./_config-
|
|
8
|
-
import { t as resolveServerEnv } from "./_server-common-
|
|
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
|
|
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
|
-
|
|
139
|
-
|
|
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
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
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
|
|
182
|
-
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
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
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
3
|
-
import { n as getServerInfo } from "./_agent-
|
|
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-
|
|
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-
|
|
5
|
-
import { o as writeProjectConfig } from "./_config-
|
|
6
|
-
import { t as resolveServerEnv } from "./_server-common-
|
|
7
|
-
import { i as resolveDeployTarget } from "./_agent-
|
|
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
|
-
|
|
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
|
-
})
|
|
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-
|
|
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
|
|
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-
|
|
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(`${
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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(
|
|
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 ${
|
|
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-
|
|
3
|
-
import { n as getServerInfo } from "./_agent-
|
|
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-
|
|
3
|
-
import { n as getServerInfo } from "./_agent-
|
|
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-
|
|
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 };
|
package/dist/worker-bundler.mjs
CHANGED
|
@@ -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
|
-
*
|
|
27
|
-
*
|
|
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
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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": "
|
|
3
|
+
"version": "5.0.1",
|
|
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
|
-
"
|
|
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": "
|
|
36
|
-
"@alexkroman1/aai-ui": "
|
|
38
|
+
"@alexkroman1/aai": "5.0.1",
|
|
39
|
+
"@alexkroman1/aai-ui": "5.0.1"
|
|
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.
|