@alexkroman1/aai-cli 1.9.2 → 1.11.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/{_agent-cHGzbDVG.mjs → _agent-Ds5LyAq5.mjs} +14 -26
- package/dist/{_api-client-yJD7xGfd.mjs → _api-client-a6cPMebU.mjs} +2 -2
- package/dist/_bundler-z6wTRMX8.mjs +96 -0
- package/dist/{_config-zV70t71V.mjs → _config-BWYgLjiI.mjs} +58 -43
- package/dist/_dev-server-DgjcUw14.mjs +406 -0
- package/dist/{_init-y7K-dzni.mjs → _init-BxI5nkzm.mjs} +57 -25
- package/dist/{_server-common-QpOTRZgi.mjs → _server-common-Bv-4Rskq.mjs} +2 -2
- package/dist/_ui-CKEIHAtB.mjs +111 -0
- package/dist/_utils-C502jKo8.mjs +82 -0
- package/dist/_vite-env-DNb9R8xq.mjs +48 -0
- package/dist/cli.mjs +75 -66
- package/dist/{client-bundler-Bki_Rned.mjs → client-bundler-BO1Wm1wT.mjs} +22 -24
- package/dist/client-bundler.mjs +1 -1
- package/dist/{delete-DE35s3d5.mjs → delete-CKNJodJg.mjs} +4 -7
- package/dist/{deploy-DOy6wOCd.mjs → deploy-b-3G8tdt.mjs} +23 -13
- package/dist/{dev-DGzDKzGZ.mjs → dev-BkOmi66P.mjs} +19 -12
- package/dist/{init-BDhs6-jB.mjs → init-BC7sUpmo.mjs} +22 -17
- package/dist/{secret-D-90QtDQ.mjs → secret-Daqz2l7g.mjs} +5 -7
- package/dist/{test-C6jwCnFu.mjs → test-B3BkLNn_.mjs} +24 -11
- package/dist/worker-bundler.mjs +2 -2
- package/package.json +7 -11
- package/dist/_bundler-CpLVjRzc.mjs +0 -62
- package/dist/_dev-server-8pf2tqvC.mjs +0 -214
- package/dist/_output-fy2bXRNb.mjs +0 -63
- package/dist/_ui-YqQ6Fi8K.mjs +0 -34
- package/dist/_utils-BeU10C7O.mjs +0 -55
- package/dist/_vite-env-CmXG4ZAu.mjs +0 -31
- package/dist/types.mjs +0 -2
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { i as isDevMode, n as getMonorepoRoot } from "./_agent-cHGzbDVG.mjs";
|
|
2
|
+
import { a as unwrapCancel, n as log, u as ok } from "./_ui-CKEIHAtB.mjs";
|
|
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-Ds5LyAq5.mjs";
|
|
6
5
|
import path from "node:path";
|
|
7
6
|
import * as p from "@clack/prompts";
|
|
8
|
-
import
|
|
7
|
+
import pc from "picocolors";
|
|
9
8
|
import { execa } from "execa";
|
|
10
9
|
//#region init.ts
|
|
11
10
|
const DEFAULT_PROJECT_NAME = "my-voice-agent";
|
|
@@ -75,19 +74,25 @@ function resolveTargetDir(dir) {
|
|
|
75
74
|
}
|
|
76
75
|
/** Run deploy after init and return deploy metadata if successful. */
|
|
77
76
|
async function tryDeploy(cwd, server) {
|
|
78
|
-
const { executeDeploy } = await import("./deploy-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
77
|
+
const { executeDeploy } = await import("./deploy-b-3G8tdt.mjs");
|
|
78
|
+
try {
|
|
79
|
+
const result = await executeDeploy({
|
|
80
|
+
cwd,
|
|
81
|
+
...server ? { server } : {}
|
|
82
|
+
});
|
|
83
|
+
return result.ok ? {
|
|
84
|
+
slug: result.data.slug,
|
|
85
|
+
url: result.data.url
|
|
86
|
+
} : null;
|
|
87
|
+
} catch (err) {
|
|
88
|
+
log.warn(`Deploy failed: ${errorMessage(err)}`);
|
|
89
|
+
log.warn("Your project was still created — run `aai deploy` in it to retry.");
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
87
92
|
}
|
|
88
93
|
/** Scaffold the project, optionally showing a spinner. */
|
|
89
94
|
async function scaffoldProject(dir, cwd, template, silent) {
|
|
90
|
-
const { runInit } = await import("./_init-
|
|
95
|
+
const { runInit } = await import("./_init-BxI5nkzm.mjs");
|
|
91
96
|
if (silent) {
|
|
92
97
|
await runInit({
|
|
93
98
|
targetDir: cwd,
|
|
@@ -111,11 +116,11 @@ function printPostInitInfo(cwd, monorepoRoot) {
|
|
|
111
116
|
}
|
|
112
117
|
async function executeInit(opts, extra) {
|
|
113
118
|
const suppressUi = extra?.silent;
|
|
114
|
-
if (!suppressUi) p.intro(
|
|
119
|
+
if (!suppressUi) p.intro(pc.cyanBright("Create a new voice agent"));
|
|
115
120
|
const dir = opts.dir ?? await promptProjectName(opts.yes);
|
|
116
121
|
const monorepoRoot = getMonorepoRoot();
|
|
117
122
|
const cwd = resolveTargetDir(dir);
|
|
118
|
-
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 ${pc.cyanBright("--force")} to overwrite.`);
|
|
119
124
|
const template = opts.template ?? "simple";
|
|
120
125
|
await scaffoldProject(dir, cwd, template, suppressUi);
|
|
121
126
|
const installed = await installDeps(cwd, suppressUi);
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { n as
|
|
4
|
-
import {
|
|
5
|
-
import { t as apiRequest } from "./_api-client-yJD7xGfd.mjs";
|
|
2
|
+
import { a as unwrapCancel, n as log, s as fail, u as ok } from "./_ui-CKEIHAtB.mjs";
|
|
3
|
+
import { n as getServerInfo } from "./_agent-Ds5LyAq5.mjs";
|
|
4
|
+
import { t as apiRequest } from "./_api-client-a6cPMebU.mjs";
|
|
6
5
|
import * as p from "@clack/prompts";
|
|
7
6
|
import { text } from "node:stream/consumers";
|
|
8
7
|
//#region secret.ts
|
|
@@ -28,8 +27,7 @@ async function readStdin() {
|
|
|
28
27
|
async function executeSecretPut(cwd, name, value, server) {
|
|
29
28
|
let secretValue = value;
|
|
30
29
|
if (!secretValue) {
|
|
31
|
-
const result = await p.password({ message: `Enter value for ${name}` });
|
|
32
|
-
if (p.isCancel(result)) process.exit(0);
|
|
30
|
+
const result = unwrapCancel(await p.password({ message: `Enter value for ${name}` }));
|
|
33
31
|
if (!result) return fail("no_input", "No value provided", "Pipe secret value to stdin");
|
|
34
32
|
secretValue = result;
|
|
35
33
|
}
|
|
@@ -41,7 +39,7 @@ async function executeSecretPut(cwd, name, value, server) {
|
|
|
41
39
|
return ok({ name });
|
|
42
40
|
}
|
|
43
41
|
async function executeSecretDelete(cwd, name, server) {
|
|
44
|
-
const { slug } = await secretRequest(cwd, `/${name}`, { method: "DELETE" }, server);
|
|
42
|
+
const { slug } = await secretRequest(cwd, `/${encodeURIComponent(name)}`, { method: "DELETE" }, server);
|
|
45
43
|
log.success(`Deleted ${name} from ${slug}`);
|
|
46
44
|
return ok({ name });
|
|
47
45
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { n as log, s as fail, u as ok } from "./_ui-CKEIHAtB.mjs";
|
|
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";
|
|
6
6
|
import path from "node:path";
|
|
7
|
-
import {
|
|
7
|
+
import { execaSync } from "execa";
|
|
8
8
|
//#region test.ts
|
|
9
9
|
/**
|
|
10
10
|
* `aai test` — run agent tests via vitest.
|
|
@@ -44,7 +44,7 @@ function runVitest(cwd) {
|
|
|
44
44
|
else if (existsSync(path.join(cwd, "agent.test.js"))) testFile = "agent.test.js";
|
|
45
45
|
if (!testFile) return false;
|
|
46
46
|
const { cmd, args } = resolveVitestCommand(cwd);
|
|
47
|
-
|
|
47
|
+
execaSync(cmd, [
|
|
48
48
|
...args,
|
|
49
49
|
"run",
|
|
50
50
|
"--root",
|
|
@@ -53,13 +53,25 @@ function runVitest(cwd) {
|
|
|
53
53
|
], {
|
|
54
54
|
cwd,
|
|
55
55
|
stdio: "inherit",
|
|
56
|
-
env: {
|
|
57
|
-
...process.env,
|
|
58
|
-
NODE_OPTIONS: "--experimental-strip-types"
|
|
59
|
-
}
|
|
56
|
+
env: { NODE_OPTIONS: "--experimental-strip-types" }
|
|
60
57
|
});
|
|
61
58
|
return true;
|
|
62
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Classify a {@link runVitest} failure. execaSync throws an ENOENT-coded
|
|
62
|
+
* error when the binary itself couldn't be spawned (infrastructure problem)
|
|
63
|
+
* and an exit-code error when vitest ran and the tests failed.
|
|
64
|
+
*/
|
|
65
|
+
function classifyVitestError(err) {
|
|
66
|
+
if (errorCode(err) === "ENOENT") return {
|
|
67
|
+
code: "spawn_failed",
|
|
68
|
+
message: `Could not launch the test runner: ${errorMessage(err)} — is the binary on your PATH?`
|
|
69
|
+
};
|
|
70
|
+
return {
|
|
71
|
+
code: "test_failed",
|
|
72
|
+
message: `Tests failed: ${errorMessage(err)}`
|
|
73
|
+
};
|
|
74
|
+
}
|
|
63
75
|
/** Execute agent tests and return structured result. */
|
|
64
76
|
async function executeTest(cwd) {
|
|
65
77
|
log.step("Running agent tests");
|
|
@@ -73,9 +85,10 @@ async function executeTest(cwd) {
|
|
|
73
85
|
}
|
|
74
86
|
log.success("Tests passed");
|
|
75
87
|
return ok({ passed: true });
|
|
76
|
-
} catch {
|
|
77
|
-
|
|
88
|
+
} catch (err) {
|
|
89
|
+
const { code, message } = classifyVitestError(err);
|
|
90
|
+
return fail(code, message);
|
|
78
91
|
}
|
|
79
92
|
}
|
|
80
93
|
//#endregion
|
|
81
|
-
export { executeTest, runVitest };
|
|
94
|
+
export { classifyVitestError, executeTest, runVitest };
|
package/dist/worker-bundler.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as withPreservedNodeEnv } from "./_vite-env-
|
|
2
|
+
import { t as withPreservedNodeEnv } from "./_vite-env-DNb9R8xq.mjs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { build } from "vite";
|
|
5
5
|
//#region worker-bundler.ts
|
|
@@ -53,7 +53,7 @@ async function buildWorker(cwd, opts = {}) {
|
|
|
53
53
|
fileName: "worker"
|
|
54
54
|
},
|
|
55
55
|
target: "node20",
|
|
56
|
-
minify: opts.minify ? "
|
|
56
|
+
minify: opts.minify ? "oxc" : false,
|
|
57
57
|
write: false,
|
|
58
58
|
rollupOptions: { output: { entryFileNames: "[name].js" } }
|
|
59
59
|
}
|
package/package.json
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alexkroman1/aai-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"aai": "dist/cli.mjs"
|
|
7
7
|
},
|
|
8
8
|
"exports": {
|
|
9
|
-
"./types": {
|
|
10
|
-
"@dev/source": "./types.ts",
|
|
11
|
-
"import": "./dist/types.mjs"
|
|
12
|
-
},
|
|
13
9
|
"./client-bundler": {
|
|
14
10
|
"@dev/source": "./client-bundler.ts",
|
|
15
11
|
"import": "./dist/client-bundler.mjs"
|
|
@@ -26,21 +22,21 @@
|
|
|
26
22
|
"@clack/prompts": "^1.7.0",
|
|
27
23
|
"chokidar": "^5.0.0",
|
|
28
24
|
"citty": "^0.2.2",
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"execa": "^9.6.1",
|
|
25
|
+
"env-paths": "^4.0.0",
|
|
26
|
+
"execa": "^10.0.0",
|
|
32
27
|
"get-port": "^7.2.0",
|
|
33
28
|
"giget": "^3.3.0",
|
|
34
29
|
"ofetch": "^1.5.1",
|
|
35
30
|
"p-debounce": "^5.1.0",
|
|
31
|
+
"picocolors": "^1.1.1",
|
|
32
|
+
"rolldown": "~1.1.4",
|
|
36
33
|
"vite": "^8.1.5",
|
|
37
34
|
"zod": "^4.4.3",
|
|
38
|
-
"@alexkroman1/aai": "1.
|
|
39
|
-
"@alexkroman1/aai-ui": "1.
|
|
35
|
+
"@alexkroman1/aai": "1.11.0",
|
|
36
|
+
"@alexkroman1/aai-ui": "1.11.0"
|
|
40
37
|
},
|
|
41
38
|
"devDependencies": {
|
|
42
39
|
"playwright": "^1.61.1",
|
|
43
|
-
"tree-kill": "^1.2.2",
|
|
44
40
|
"tsdown": "^0.22.13",
|
|
45
41
|
"verdaccio": "^6.8.0",
|
|
46
42
|
"vitest": "^4.1.10"
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { i as ok } from "./_output-fy2bXRNb.mjs";
|
|
3
|
-
import { n as log } from "./_ui-YqQ6Fi8K.mjs";
|
|
4
|
-
import { s as validateAgentExport } from "./_utils-BeU10C7O.mjs";
|
|
5
|
-
import { t as buildClient } from "./client-bundler-Bki_Rned.mjs";
|
|
6
|
-
import { buildWorker } from "./worker-bundler.mjs";
|
|
7
|
-
import path from "node:path";
|
|
8
|
-
import { pathToFileURL } from "node:url";
|
|
9
|
-
import fs from "node:fs/promises";
|
|
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
|
-
async function evalWorkerBundle(code, cwd) {
|
|
40
|
-
const evalDir = path.join(cwd, ".aai", "eval");
|
|
41
|
-
await fs.mkdir(evalDir, { recursive: true });
|
|
42
|
-
const tmpPath = path.join(evalDir, `agent-${Date.now()}-${Math.random().toString(36).slice(2)}.mjs`);
|
|
43
|
-
try {
|
|
44
|
-
await fs.writeFile(tmpPath, code);
|
|
45
|
-
const mod = await import(pathToFileURL(tmpPath).href);
|
|
46
|
-
const agentDef = mod.default ?? mod;
|
|
47
|
-
validateAgentExport(agentDef);
|
|
48
|
-
return agentDef;
|
|
49
|
-
} finally {
|
|
50
|
-
await fs.rm(tmpPath).catch(() => {});
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
async function executeBuild(cwd) {
|
|
54
|
-
const bundle = await buildAgentBundle(cwd, { minify: true });
|
|
55
|
-
log.success("Build complete");
|
|
56
|
-
return ok({
|
|
57
|
-
name: bundle.agentConfig.name,
|
|
58
|
-
workerBytes: bundle.worker.length
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
//#endregion
|
|
62
|
-
export { buildAgentBundle, evalWorkerBundle, executeBuild };
|
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { r as ensureApiKey } from "./_config-zV70t71V.mjs";
|
|
3
|
-
import { n as log } from "./_ui-YqQ6Fi8K.mjs";
|
|
4
|
-
import { n as errorMessage } from "./_utils-BeU10C7O.mjs";
|
|
5
|
-
import { n as fallbackHtmlPlugin } from "./client-bundler-Bki_Rned.mjs";
|
|
6
|
-
import { buildWorker } from "./worker-bundler.mjs";
|
|
7
|
-
import { evalWorkerBundle } from "./_bundler-CpLVjRzc.mjs";
|
|
8
|
-
import { t as resolveServerEnv } from "./_server-common-QpOTRZgi.mjs";
|
|
9
|
-
import { createRequire } from "node:module";
|
|
10
|
-
import { existsSync } from "node:fs";
|
|
11
|
-
import path from "node:path";
|
|
12
|
-
import { requiredProviderEnvVars, withHostCredentialFallback } from "@alexkroman1/aai/runtime";
|
|
13
|
-
import { watch } from "chokidar";
|
|
14
|
-
import getPort, { portNumbers } from "get-port";
|
|
15
|
-
import pDebounce from "p-debounce";
|
|
16
|
-
//#region _dev-server.ts
|
|
17
|
-
/**
|
|
18
|
-
* Dev server for directory-based agents.
|
|
19
|
-
*
|
|
20
|
-
* Imports agent.ts directly for the full agent definition,
|
|
21
|
-
* builds a runtime, and starts an HTTP+WebSocket server. Watches for
|
|
22
|
-
* file changes and restarts automatically. Optionally runs Vite for
|
|
23
|
-
* client SPA HMR.
|
|
24
|
-
*/
|
|
25
|
-
async function resolveAgentEnv(root, agentDef) {
|
|
26
|
-
const env = await resolveServerEnv(root);
|
|
27
|
-
const required = requiredProviderEnvVars(agentDef);
|
|
28
|
-
if (required.includes("ASSEMBLYAI_API_KEY") && !env.ASSEMBLYAI_API_KEY) env.ASSEMBLYAI_API_KEY = await ensureApiKey();
|
|
29
|
-
const missing = required.filter((name) => !(env[name] || process.env[name]));
|
|
30
|
-
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.`);
|
|
31
|
-
return env;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* The env handed to `createServer` for host-mode connections: provider
|
|
35
|
-
* credentials plus the `AAI_ALLOW_HOST` gate read straight from the shell
|
|
36
|
-
* (it is a control variable, not something an agent declares in `.env`).
|
|
37
|
-
*/
|
|
38
|
-
function hostModeEnv(providerEnv) {
|
|
39
|
-
const gate = process.env.AAI_ALLOW_HOST;
|
|
40
|
-
return gate === void 0 ? providerEnv : {
|
|
41
|
-
...providerEnv,
|
|
42
|
-
AAI_ALLOW_HOST: gate
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Explicit bind host for the dev server, or `undefined` to take the
|
|
47
|
-
* loopback default. An empty `AAI_DEV_HOST` means "unset", not "every
|
|
48
|
-
* interface" — Node treats `listen(port, "")` as 0.0.0.0, which would quietly
|
|
49
|
-
* undo the loopback default this exists to guard.
|
|
50
|
-
*/
|
|
51
|
-
function devBindHost() {
|
|
52
|
-
const host = process.env.AAI_DEV_HOST?.trim();
|
|
53
|
-
return host ? host : void 0;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Load the agent definition by bundling agent.ts (and all its local imports)
|
|
57
|
-
* into a single ESM file, then importing that. A raw `import(agent.ts?t=...)`
|
|
58
|
-
* only cache-busts agent.ts itself — transitive imports (./tools.ts, etc.)
|
|
59
|
-
* stay in Node's ESM registry, so edits to them are ignored on reload.
|
|
60
|
-
* Bundling picks them up and matches the deploy path exactly.
|
|
61
|
-
*/
|
|
62
|
-
async function loadAgentDef(cwd) {
|
|
63
|
-
return evalWorkerBundle(await buildWorker(cwd), cwd);
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* True for paths that should never trigger a restart: anything inside
|
|
67
|
-
* `node_modules/` and any dot-entry (`.git/`, `.aai/`, `.DS_Store`, …).
|
|
68
|
-
* `.git/` especially matters — commits and status checks churn the index
|
|
69
|
-
* and would otherwise cause spurious full backend restarts.
|
|
70
|
-
*
|
|
71
|
-
* Exception: `.env` / `.env.*` files stay watched — env edits should
|
|
72
|
-
* restart the server with the new values.
|
|
73
|
-
*/
|
|
74
|
-
function isIgnoredPath(dir, filePath) {
|
|
75
|
-
const rel = path.relative(dir, filePath);
|
|
76
|
-
if (!rel || rel.startsWith("..")) return false;
|
|
77
|
-
return rel.split(path.sep).some((segment) => {
|
|
78
|
-
if (segment === "node_modules") return true;
|
|
79
|
-
if (segment === ".env" || segment.startsWith(".env.")) return false;
|
|
80
|
-
return segment.startsWith(".");
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Watch the agent directory for changes and call `onChange` when detected.
|
|
85
|
-
* Debounces to avoid rapid restarts. Uses chokidar for reliable recursive
|
|
86
|
-
* watching across platforms (raw `fs.watch` misses events on Linux).
|
|
87
|
-
*/
|
|
88
|
-
function watchDirectory(dir, onChange) {
|
|
89
|
-
const debouncedChange = pDebounce(() => {
|
|
90
|
-
log.info("File change detected, restarting...");
|
|
91
|
-
onChange();
|
|
92
|
-
}, 300);
|
|
93
|
-
const watcher = watch(dir, {
|
|
94
|
-
ignored: (filePath) => isIgnoredPath(dir, filePath),
|
|
95
|
-
ignoreInitial: true,
|
|
96
|
-
persistent: false
|
|
97
|
-
});
|
|
98
|
-
watcher.on("all", () => void debouncedChange());
|
|
99
|
-
return watcher;
|
|
100
|
-
}
|
|
101
|
-
/** Locate the pre-built default aai-ui client (served when no custom client.tsx). */
|
|
102
|
-
function resolveDefaultClientDir() {
|
|
103
|
-
const pkgPath = createRequire(import.meta.url).resolve("@alexkroman1/aai-ui/package.json");
|
|
104
|
-
return path.join(path.dirname(pkgPath), "dist", "default-client");
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Start the dev server for a directory-based agent.
|
|
108
|
-
*
|
|
109
|
-
* Returns a cleanup function to shut down the server and watchers.
|
|
110
|
-
*/
|
|
111
|
-
async function startDevServer(opts) {
|
|
112
|
-
const { cwd, port } = opts;
|
|
113
|
-
const { createRuntime, createServer } = await import("@alexkroman1/aai/runtime");
|
|
114
|
-
const hasClient = existsSync(path.join(cwd, "client.tsx"));
|
|
115
|
-
const backendPort = hasClient ? await getPort({ port: portNumbers(port + 1, port + 100) }) : port;
|
|
116
|
-
const vitePort = port;
|
|
117
|
-
const clientDirOpt = hasClient ? {} : { clientDir: resolveDefaultClientDir() };
|
|
118
|
-
/** Full build sequence, shared by initial startup and every restart. */
|
|
119
|
-
async function buildServer() {
|
|
120
|
-
const agentDef = await loadAgentDef(cwd);
|
|
121
|
-
const env = await resolveAgentEnv(cwd, agentDef);
|
|
122
|
-
const providerEnv = withHostCredentialFallback(env);
|
|
123
|
-
const runtime = createRuntime({
|
|
124
|
-
agent: agentDef,
|
|
125
|
-
env,
|
|
126
|
-
providerEnv
|
|
127
|
-
});
|
|
128
|
-
return createServer({
|
|
129
|
-
runtime,
|
|
130
|
-
name: agentDef.name,
|
|
131
|
-
env: hostModeEnv(providerEnv),
|
|
132
|
-
hostBaseAgent: agentDef,
|
|
133
|
-
...clientDirOpt
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
const agentServer = await buildServer();
|
|
137
|
-
await agentServer.listen(backendPort, devBindHost());
|
|
138
|
-
let viteServer;
|
|
139
|
-
if (hasClient) {
|
|
140
|
-
const { createServer: createViteServer } = await import("vite");
|
|
141
|
-
const target = `http://localhost:${backendPort}`;
|
|
142
|
-
viteServer = await createViteServer({
|
|
143
|
-
root: cwd,
|
|
144
|
-
plugins: [fallbackHtmlPlugin(cwd)],
|
|
145
|
-
server: {
|
|
146
|
-
port: vitePort,
|
|
147
|
-
proxy: {
|
|
148
|
-
"/health": target,
|
|
149
|
-
"/websocket": {
|
|
150
|
-
target,
|
|
151
|
-
ws: true
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
});
|
|
156
|
-
await viteServer.listen();
|
|
157
|
-
}
|
|
158
|
-
let restarting = false;
|
|
159
|
-
let pendingRestart = false;
|
|
160
|
-
let closed = false;
|
|
161
|
-
let currentServer = agentServer;
|
|
162
|
-
const watcher = watchDirectory(cwd, () => {
|
|
163
|
-
if (restarting) {
|
|
164
|
-
pendingRestart = true;
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
restarting = true;
|
|
168
|
-
restart().finally(() => {
|
|
169
|
-
restarting = false;
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
async function restart() {
|
|
173
|
-
do {
|
|
174
|
-
pendingRestart = false;
|
|
175
|
-
await restartOnce();
|
|
176
|
-
} while (pendingRestart && !closed);
|
|
177
|
-
}
|
|
178
|
-
async function restartOnce() {
|
|
179
|
-
let newServer;
|
|
180
|
-
try {
|
|
181
|
-
newServer = await buildServer();
|
|
182
|
-
} catch (err) {
|
|
183
|
-
log.error(`Restart failed: ${errorMessage(err)} (previous server still running)`);
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
if (closed) {
|
|
187
|
-
await newServer.close().catch(() => void 0);
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
try {
|
|
191
|
-
await currentServer.close();
|
|
192
|
-
} catch {}
|
|
193
|
-
try {
|
|
194
|
-
await newServer.listen(backendPort, devBindHost());
|
|
195
|
-
currentServer = newServer;
|
|
196
|
-
if (closed) {
|
|
197
|
-
await newServer.close().catch(() => void 0);
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
log.success("Restarted");
|
|
201
|
-
} catch (err) {
|
|
202
|
-
log.error(`Restart failed: ${errorMessage(err)}`);
|
|
203
|
-
await newServer.close().catch(() => void 0);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
return async () => {
|
|
207
|
-
closed = true;
|
|
208
|
-
await watcher.close();
|
|
209
|
-
await viteServer?.close();
|
|
210
|
-
await currentServer.close();
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
//#endregion
|
|
214
|
-
export { startDevServer };
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
//#region _output.ts
|
|
3
|
-
/**
|
|
4
|
-
* Determine output mode from CLI flags and TTY state.
|
|
5
|
-
*
|
|
6
|
-
* Priority: --json flag > --no-json flag > TTY auto-detection.
|
|
7
|
-
*/
|
|
8
|
-
function getOutputMode(args, isTTY = Boolean(process.stdout.isTTY)) {
|
|
9
|
-
if (args.json === true) return "json";
|
|
10
|
-
if (args.json === false) return "human";
|
|
11
|
-
return isTTY ? "human" : "json";
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Wrap a command function to handle output formatting.
|
|
15
|
-
*
|
|
16
|
-
* - `fn` does the work and returns a `CommandResult<T>`. Human-readable
|
|
17
|
-
* output is printed inside `fn` itself.
|
|
18
|
-
* - In JSON mode, writes exactly one JSON line to stdout.
|
|
19
|
-
*/
|
|
20
|
-
async function withOutput(mode, fn) {
|
|
21
|
-
const result = await fn();
|
|
22
|
-
if (mode === "json") await writeLine(`${JSON.stringify(result)}\n`);
|
|
23
|
-
if (!result.ok) process.exit(1);
|
|
24
|
-
}
|
|
25
|
-
/** Write a line to stdout, resolving only once it has been flushed. */
|
|
26
|
-
function writeLine(line) {
|
|
27
|
-
return new Promise((resolve) => {
|
|
28
|
-
process.stdout.write(line, () => resolve());
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
/** Create an ok result. */
|
|
32
|
-
function ok(data) {
|
|
33
|
-
return {
|
|
34
|
-
ok: true,
|
|
35
|
-
data
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
/** Create an error result. */
|
|
39
|
-
function fail(code, error, hint) {
|
|
40
|
-
return hint ? {
|
|
41
|
-
ok: false,
|
|
42
|
-
error,
|
|
43
|
-
code,
|
|
44
|
-
hint
|
|
45
|
-
} : {
|
|
46
|
-
ok: false,
|
|
47
|
-
error,
|
|
48
|
-
code
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
/** Typed CLI error that carries a structured error code and optional hint. */
|
|
52
|
-
var CliError = class extends Error {
|
|
53
|
-
code;
|
|
54
|
-
hint;
|
|
55
|
-
constructor(code, message, hint) {
|
|
56
|
-
super(message);
|
|
57
|
-
this.name = "CliError";
|
|
58
|
-
this.code = code;
|
|
59
|
-
if (hint !== void 0) this.hint = hint;
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
//#endregion
|
|
63
|
-
export { withOutput as a, ok as i, fail as n, writeLine as o, getOutputMode as r, CliError as t };
|
package/dist/_ui-YqQ6Fi8K.mjs
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import * as p from "@clack/prompts";
|
|
3
|
-
import { colorize } from "consola/utils";
|
|
4
|
-
//#region _ui.ts
|
|
5
|
-
const noop = () => {};
|
|
6
|
-
let silenced = false;
|
|
7
|
-
/** Log instance that delegates to clack (human mode) or no-ops (JSON mode). */
|
|
8
|
-
const log = new Proxy(p.log, { get(target, prop, receiver) {
|
|
9
|
-
return silenced ? noop : Reflect.get(target, prop, receiver);
|
|
10
|
-
} });
|
|
11
|
-
/** Replace all log methods with no-ops. Call once in JSON mode. */
|
|
12
|
-
function silenceOutput() {
|
|
13
|
-
silenced = true;
|
|
14
|
-
}
|
|
15
|
-
/** Unwrap a clack prompt result, exiting cleanly if the user cancelled. */
|
|
16
|
-
function unwrapCancel(result) {
|
|
17
|
-
if (p.isCancel(result)) {
|
|
18
|
-
p.cancel("Setup cancelled");
|
|
19
|
-
process.exit(0);
|
|
20
|
-
}
|
|
21
|
-
return result;
|
|
22
|
-
}
|
|
23
|
-
/** Format a URL for display. */
|
|
24
|
-
function fmtUrl(url) {
|
|
25
|
-
return colorize("cyanBright", url);
|
|
26
|
-
}
|
|
27
|
-
/** Parse and validate a port string. Returns the numeric port or throws. */
|
|
28
|
-
function parsePort(raw) {
|
|
29
|
-
const port = Number.parseInt(raw, 10);
|
|
30
|
-
if (Number.isNaN(port) || port < 0 || port > 65535) throw new Error(`Invalid port: ${raw}. Must be a number between 0 and 65535.`);
|
|
31
|
-
return port;
|
|
32
|
-
}
|
|
33
|
-
//#endregion
|
|
34
|
-
export { unwrapCancel as a, silenceOutput as i, log as n, parsePort as r, fmtUrl as t };
|
package/dist/_utils-BeU10C7O.mjs
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import fs from "node:fs/promises";
|
|
4
|
-
//#region _utils.ts
|
|
5
|
-
/** Resolve the working directory from INIT_CWD or process.cwd(). */
|
|
6
|
-
function resolveCwd() {
|
|
7
|
-
return process.env.INIT_CWD || process.cwd();
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
10
|
-
* Extract a message from an unknown error. Local copy of `errorMessage` from
|
|
11
|
-
* `@alexkroman1/aai` — importing the root barrel pulls zod into every CLI
|
|
12
|
-
* invocation (including `aai --help`), so the one-liner lives here instead.
|
|
13
|
-
*/
|
|
14
|
-
function errorMessage(err) {
|
|
15
|
-
return err instanceof Error ? err.message : String(err);
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Extract a stack (falling back to the message) from an unknown error. Local
|
|
19
|
-
* copy of `errorDetail` from `@alexkroman1/aai` for the same reason as
|
|
20
|
-
* {@link errorMessage} above.
|
|
21
|
-
*/
|
|
22
|
-
function errorDetail(err) {
|
|
23
|
-
return err instanceof Error ? err.stack ?? err.message : String(err);
|
|
24
|
-
}
|
|
25
|
-
/** True when `err` is a filesystem EEXIST error (target already exists). */
|
|
26
|
-
function isEexist(err) {
|
|
27
|
-
return err instanceof Error && "code" in err && err.code === "EEXIST";
|
|
28
|
-
}
|
|
29
|
-
/** Validate that a module's default export is a valid agent definition. Throws if invalid. */
|
|
30
|
-
function validateAgentExport(mod) {
|
|
31
|
-
if (!mod?.name || typeof mod.name !== "string") throw new Error("agent.ts must export default agent({ name: ... })");
|
|
32
|
-
}
|
|
33
|
-
async function fileExists(p) {
|
|
34
|
-
try {
|
|
35
|
-
await fs.access(p);
|
|
36
|
-
return true;
|
|
37
|
-
} catch {
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
/** Read and parse a JSON file. Returns null if the file is missing or malformed. */
|
|
42
|
-
async function readJson(filePath) {
|
|
43
|
-
try {
|
|
44
|
-
return JSON.parse(await fs.readFile(filePath, "utf-8"));
|
|
45
|
-
} catch {
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
/** Write `data` as pretty-printed JSON (+ trailing newline), creating parent dirs. */
|
|
50
|
-
async function writeJson(filePath, data) {
|
|
51
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
52
|
-
await fs.writeFile(filePath, `${JSON.stringify(data, null, 2)}\n`);
|
|
53
|
-
}
|
|
54
|
-
//#endregion
|
|
55
|
-
export { readJson as a, writeJson as c, isEexist as i, errorMessage as n, resolveCwd as o, fileExists as r, validateAgentExport as s, errorDetail as t };
|