@alexkroman1/aai-cli 1.9.2 → 1.10.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-Ba5Ykp05.mjs} +22 -26
- package/dist/{_api-client-yJD7xGfd.mjs → _api-client-a6cPMebU.mjs} +2 -2
- package/dist/_bundler-CKz9Q15i.mjs +96 -0
- package/dist/{_config-zV70t71V.mjs → _config-BMjZt5hG.mjs} +58 -43
- package/dist/_dev-server-DZl-xNwH.mjs +411 -0
- package/dist/{_init-y7K-dzni.mjs → _init-BznoJC91.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-ECl2je7-.mjs +97 -0
- package/dist/_vite-env-DNb9R8xq.mjs +48 -0
- package/dist/cli.mjs +75 -66
- package/dist/{client-bundler-Bki_Rned.mjs → client-bundler-VdcgFcos.mjs} +22 -24
- package/dist/client-bundler.mjs +1 -1
- package/dist/{delete-DE35s3d5.mjs → delete-xZ8PzM_4.mjs} +4 -7
- package/dist/{deploy-DOy6wOCd.mjs → deploy-Ds1spTPC.mjs} +23 -13
- package/dist/{dev-DGzDKzGZ.mjs → dev-CtE0d-Gs.mjs} +19 -12
- package/dist/{init-BDhs6-jB.mjs → init-CFwZf8q1.mjs} +22 -17
- package/dist/{secret-D-90QtDQ.mjs → secret-B4LRmUhF.mjs} +5 -7
- package/dist/{test-C6jwCnFu.mjs → test-DXPY9Bqq.mjs} +24 -11
- package/dist/worker-bundler.mjs +1 -1
- 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,18 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as
|
|
2
|
+
import { a as serverOrigin, i as readProjectConfig, n as ensureApiKey, r as readGlobalConfig, t as approveServer } from "./_config-BMjZt5hG.mjs";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
//#region _agent.ts
|
|
7
|
-
var _agent_exports = /* @__PURE__ */ __exportAll({
|
|
8
|
-
DEFAULT_DEV_SERVER: () => DEFAULT_DEV_SERVER,
|
|
9
|
-
DEFAULT_SERVER: () => DEFAULT_SERVER,
|
|
10
|
-
getMonorepoRoot: () => getMonorepoRoot,
|
|
11
|
-
getServerInfo: () => getServerInfo,
|
|
12
|
-
isDevMode: () => isDevMode,
|
|
13
|
-
resolveDeployTarget: () => resolveDeployTarget,
|
|
14
|
-
resolveServerUrl: () => resolveServerUrl
|
|
15
|
-
});
|
|
16
7
|
const DEFAULT_SERVER = "https://aai-agent.fly.dev";
|
|
17
8
|
const DEFAULT_DEV_SERVER = "http://localhost:8080";
|
|
18
9
|
let _cachedMonorepoRoot;
|
|
@@ -33,24 +24,19 @@ function isDevMode() {
|
|
|
33
24
|
function stripTrailingSlash(url) {
|
|
34
25
|
return url.replace(/\/+$/, "");
|
|
35
26
|
}
|
|
36
|
-
/** Hostnames that are always safe to target — the request never leaves the machine. */
|
|
37
|
-
const LOOPBACK_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
38
|
-
"localhost",
|
|
39
|
-
"127.0.0.1",
|
|
40
|
-
"::1",
|
|
41
|
-
"[::1]"
|
|
42
|
-
]);
|
|
43
27
|
/**
|
|
44
|
-
* Whether `origin` may receive a credential without prior user approval
|
|
45
|
-
*
|
|
28
|
+
* Whether `origin` may receive a credential without prior user approval.
|
|
29
|
+
*
|
|
30
|
+
* Only the shipped platform qualifies. Loopback origins from
|
|
31
|
+
* `.aai/project.json` used to be implicitly trusted too, but that let a
|
|
32
|
+
* cloned repo steer a credentialed request (API key + `aai secret` values)
|
|
33
|
+
* to any process listening on a local port — the exact input class this
|
|
34
|
+
* trust model exists to gate. Dev mode still targets `DEFAULT_DEV_SERVER`
|
|
35
|
+
* before the project config is ever consulted, and `--server` approves a
|
|
36
|
+
* local origin like any other.
|
|
46
37
|
*/
|
|
47
38
|
function isImplicitlyTrusted(origin) {
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
return LOOPBACK_HOSTNAMES.has(new URL(origin).hostname);
|
|
51
|
-
} catch {
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
39
|
+
return origin === serverOrigin(DEFAULT_SERVER);
|
|
54
40
|
}
|
|
55
41
|
/**
|
|
56
42
|
* Resolve which platform server to talk to.
|
|
@@ -94,10 +80,20 @@ async function resolveDeployTarget(cwd, explicitServer) {
|
|
|
94
80
|
apiKey: await ensureApiKey()
|
|
95
81
|
};
|
|
96
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Slug shape accepted by the platform (kept in sync with `VALID_SLUG_RE` in
|
|
85
|
+
* aai-server's schemas.ts — the server package is private, so the pattern is
|
|
86
|
+
* duplicated here). Enforced before a slug is ever interpolated into a URL
|
|
87
|
+
* path: `.aai/project.json` is repo-controlled, so a hostile
|
|
88
|
+
* `"slug": "x/../admin"` must not steer a credentialed request to an
|
|
89
|
+
* arbitrary path on an approved origin.
|
|
90
|
+
*/
|
|
91
|
+
const VALID_SLUG_RE = /^[a-z0-9][a-z0-9_-]{0,62}[a-z0-9]$/;
|
|
97
92
|
/** Like resolveDeployTarget, but requires an existing deployment (project config). */
|
|
98
93
|
async function getServerInfo(cwd, explicitServer) {
|
|
99
94
|
const { config, serverUrl, apiKey } = await resolveDeployTarget(cwd, explicitServer);
|
|
100
95
|
if (!config) throw new Error("No .aai/project.json found — run `aai deploy` first");
|
|
96
|
+
if (!VALID_SLUG_RE.test(config.slug)) throw new Error(`Invalid slug in .aai/project.json: ${JSON.stringify(config.slug)}\n Expected lowercase letters, digits, \`-\`, \`_\` (2-64 chars). Fix the file or run \`aai deploy\` to create a fresh deployment.`);
|
|
101
97
|
return {
|
|
102
98
|
serverUrl,
|
|
103
99
|
slug: config.slug,
|
|
@@ -105,4 +101,4 @@ async function getServerInfo(cwd, explicitServer) {
|
|
|
105
101
|
};
|
|
106
102
|
}
|
|
107
103
|
//#endregion
|
|
108
|
-
export { resolveDeployTarget as
|
|
104
|
+
export { resolveDeployTarget as i, getServerInfo as n, isDevMode as r, getMonorepoRoot as t };
|
|
@@ -24,8 +24,8 @@ async function apiRequest(url, opts) {
|
|
|
24
24
|
...opts.headers
|
|
25
25
|
},
|
|
26
26
|
...opts.body !== void 0 ? { body: opts.body } : {},
|
|
27
|
-
retry: 2,
|
|
28
|
-
retryDelay: 300
|
|
27
|
+
retry: opts.retry ?? 2,
|
|
28
|
+
retryDelay: opts.retryDelay ?? 300
|
|
29
29
|
});
|
|
30
30
|
} catch (err) {
|
|
31
31
|
throw toApiError(err, url, opts);
|
|
@@ -0,0 +1,96 @@
|
|
|
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-ECl2je7-.mjs";
|
|
4
|
+
import { t as buildClient } from "./client-bundler-VdcgFcos.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,35 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as unwrapCancel } from "./_ui-
|
|
3
|
-
import {
|
|
4
|
-
import "node:
|
|
2
|
+
import { a as unwrapCancel, n as log, o as CliError } from "./_ui-CKEIHAtB.mjs";
|
|
3
|
+
import { l as writeJson, o as readJson, r as errorMessage } from "./_utils-ECl2je7-.mjs";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import * as p from "@clack/prompts";
|
|
7
7
|
import os from "node:os";
|
|
8
|
-
import
|
|
8
|
+
import envPaths from "env-paths";
|
|
9
9
|
import { z } from "zod";
|
|
10
|
-
//#region \0rolldown/runtime.js
|
|
11
|
-
var __defProp = Object.defineProperty;
|
|
12
|
-
var __exportAll = (all, no_symbols) => {
|
|
13
|
-
let target = {};
|
|
14
|
-
for (var name in all) __defProp(target, name, {
|
|
15
|
-
get: all[name],
|
|
16
|
-
enumerable: true
|
|
17
|
-
});
|
|
18
|
-
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
19
|
-
return target;
|
|
20
|
-
};
|
|
21
|
-
//#endregion
|
|
22
10
|
//#region _config.ts
|
|
23
|
-
var _config_exports = /* @__PURE__ */ __exportAll({
|
|
24
|
-
approveServer: () => approveServer,
|
|
25
|
-
ensureApiKey: () => ensureApiKey,
|
|
26
|
-
getConfigDir: () => getConfigDir,
|
|
27
|
-
readGlobalConfig: () => readGlobalConfig,
|
|
28
|
-
readProjectConfig: () => readProjectConfig,
|
|
29
|
-
serverOrigin: () => serverOrigin,
|
|
30
|
-
writeGlobalConfig: () => writeGlobalConfig,
|
|
31
|
-
writeProjectConfig: () => writeProjectConfig
|
|
32
|
-
});
|
|
33
11
|
/**
|
|
34
12
|
* `.aai/project.json` lives in the working tree, so everything in it is
|
|
35
13
|
* untrusted input — a cloned repo can supply any value.
|
|
@@ -43,20 +21,48 @@ var _config_exports = /* @__PURE__ */ __exportAll({
|
|
|
43
21
|
*/
|
|
44
22
|
const ProjectConfigSchema = z.object({
|
|
45
23
|
slug: z.string(),
|
|
46
|
-
serverUrl: z.string()
|
|
47
|
-
sessionId: z.string().optional()
|
|
24
|
+
serverUrl: z.string()
|
|
48
25
|
});
|
|
49
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Config dir the CLI used before switching to env-paths. On Linux it matches
|
|
28
|
+
* env-paths exactly; on macOS (XDG-style vs ~/Library/Preferences/aai) and
|
|
29
|
+
* Windows (no trailing `Config` segment) it does not.
|
|
30
|
+
*/
|
|
31
|
+
function legacyConfigDir() {
|
|
50
32
|
if (process.platform === "win32") return path.join(process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming"), "aai");
|
|
51
33
|
return path.join(process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config"), "aai");
|
|
52
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the global config directory, preferring the platform-conventional
|
|
37
|
+
* env-paths location while staying backward compatible: an existing config at
|
|
38
|
+
* the legacy path keeps winning so already-authenticated users are not
|
|
39
|
+
* silently logged out. Injectable for tests.
|
|
40
|
+
*
|
|
41
|
+
* `AAI_CONFIG_DIR` overrides everything — it exists so tests (and unusual
|
|
42
|
+
* setups) can redirect ALL global-config reads and writes away from the
|
|
43
|
+
* user's real config. The test suite's `approveServer` calls used to
|
|
44
|
+
* permanently pollute `~/.config/aai/config.json` with approved origins.
|
|
45
|
+
*/
|
|
46
|
+
function getConfigDir(dirs = {
|
|
47
|
+
legacy: legacyConfigDir(),
|
|
48
|
+
modern: envPaths("aai", { suffix: "" }).config
|
|
49
|
+
}, exists = existsSync) {
|
|
50
|
+
const override = process.env.AAI_CONFIG_DIR?.trim();
|
|
51
|
+
if (override) return override;
|
|
52
|
+
return exists(path.join(dirs.legacy, "config.json")) ? dirs.legacy : dirs.modern;
|
|
53
|
+
}
|
|
53
54
|
async function readProjectConfig(agentDir) {
|
|
54
55
|
const file = path.join(agentDir, ".aai", "project.json");
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
let data;
|
|
57
|
+
try {
|
|
58
|
+
data = await readJson(file);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
const reason = errorMessage(err).replace(`Invalid JSON in ${file}: `, "");
|
|
61
|
+
throw new Error(`project.json is corrupted at ${file}: ${reason}\n Fix or delete the file — deploying without it would create a new agent under a fresh slug.`, { cause: err });
|
|
59
62
|
}
|
|
63
|
+
if (data === null) return null;
|
|
64
|
+
const parsed = ProjectConfigSchema.safeParse(data);
|
|
65
|
+
if (!parsed.success) return null;
|
|
60
66
|
return parsed.data;
|
|
61
67
|
}
|
|
62
68
|
async function writeProjectConfig(agentDir, data) {
|
|
@@ -100,7 +106,21 @@ async function readGlobalConfig(configDir) {
|
|
|
100
106
|
return await readJson(path.join(dir, "config.json")) ?? {};
|
|
101
107
|
}
|
|
102
108
|
async function writeGlobalConfig(configDir, data) {
|
|
103
|
-
await writeJson(path.join(configDir, "config.json"), data);
|
|
109
|
+
await writeJson(path.join(configDir, "config.json"), data, { mode: 384 });
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Persist the API key to the global config, warning (not failing) when the
|
|
113
|
+
* config dir is unwritable — the key in hand still works for this run.
|
|
114
|
+
*/
|
|
115
|
+
async function trySaveApiKey(dir, config, apiKey) {
|
|
116
|
+
try {
|
|
117
|
+
await writeGlobalConfig(dir, {
|
|
118
|
+
...config,
|
|
119
|
+
apiKey
|
|
120
|
+
});
|
|
121
|
+
} catch (err) {
|
|
122
|
+
log.warn(`Couldn't save your API key to ${path.join(dir, "config.json")}: ${errorMessage(err)} — you'll be prompted again next run.`);
|
|
123
|
+
}
|
|
104
124
|
}
|
|
105
125
|
async function ensureApiKey(configDir) {
|
|
106
126
|
const dir = configDir ?? getConfigDir();
|
|
@@ -108,18 +128,13 @@ async function ensureApiKey(configDir) {
|
|
|
108
128
|
if (config.apiKey) return config.apiKey;
|
|
109
129
|
const envKey = process.env.ASSEMBLYAI_API_KEY;
|
|
110
130
|
if (envKey) {
|
|
111
|
-
await
|
|
112
|
-
...config,
|
|
113
|
-
apiKey: envKey
|
|
114
|
-
});
|
|
131
|
+
await trySaveApiKey(dir, config, envKey);
|
|
115
132
|
return envKey;
|
|
116
133
|
}
|
|
134
|
+
if (!process.stdin.isTTY) throw new CliError("no_api_key", "No API key configured and no TTY to prompt for one.", "Set the ASSEMBLYAI_API_KEY environment variable, or run `aai` interactively once to save a key.");
|
|
117
135
|
const apiKey = unwrapCancel(await p.password({ message: "Enter your AssemblyAI API key" }));
|
|
118
|
-
await
|
|
119
|
-
...config,
|
|
120
|
-
apiKey
|
|
121
|
-
});
|
|
136
|
+
await trySaveApiKey(dir, config, apiKey);
|
|
122
137
|
return apiKey;
|
|
123
138
|
}
|
|
124
139
|
//#endregion
|
|
125
|
-
export {
|
|
140
|
+
export { serverOrigin as a, readProjectConfig as i, ensureApiKey as n, writeProjectConfig as o, readGlobalConfig as r, approveServer as t };
|