@alexkroman1/aai-cli 5.5.1 → 5.7.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/README.md +16 -7
- package/dist/{_agent-DMyOab9_.mjs → _agent-2nVugrN3.mjs} +22 -3
- package/dist/_agent.d.ts +3 -1
- package/dist/_api-client-B-upMGkc.mjs +104 -0
- package/dist/_api-client.d.ts +16 -0
- package/dist/_cli-common.d.ts +65 -0
- package/dist/{_config-5AEqhh-O.mjs → _config-Y5V-5Krn.mjs} +58 -9
- package/dist/_config.d.ts +23 -1
- package/dist/_deploy.d.ts +7 -0
- package/dist/{_dev-server-vV05Fnki.mjs → _dev-server-BB5N8kdh.mjs} +2 -2
- package/dist/{_init-BZ9t_Kz-.mjs → _init-D7JIT-IJ.mjs} +3 -3
- package/dist/{_slug-api-DaqQJHk8.mjs → _slug-api-CGJSST9B.mjs} +2 -2
- package/dist/_studio-commands.d.ts +73 -0
- package/dist/_studio.d.ts +64 -0
- package/dist/{_templates-Bv8CR800.mjs → _templates-Bt6u9_68.mjs} +19 -8
- package/dist/_templates.d.ts +9 -0
- package/dist/{_typecheck-gate-9IHWDnl1.mjs → _typecheck-gate-DvE8S3aQ.mjs} +1 -1
- package/dist/{build-D_PgQOD4.mjs → build-BXwDB78d.mjs} +1 -1
- package/dist/cli.mjs +264 -32
- package/dist/delete-DRNfvczK.mjs +53 -0
- package/dist/delete.d.ts +9 -2
- package/dist/{deploy-1eaXcfUw.mjs → deploy-Cp-wgME3.mjs} +7 -5
- package/dist/deploy.d.ts +2 -0
- package/dist/{dev-gVNdGFYY.mjs → dev-C4KyxouE.mjs} +1 -1
- package/dist/{init-DoU4_txp.mjs → init-BT-IU9AR.mjs} +15 -14
- package/dist/login-AA_UdRI-.mjs +172 -0
- package/dist/login.d.ts +33 -20
- package/dist/scaffold/package.json +4 -4
- package/dist/{secret-CGAIAbUx.mjs → secret-Dr0qnxeb.mjs} +1 -1
- package/dist/{storage-CnhOayhm.mjs → storage-CoQB8d-u.mjs} +1 -1
- package/dist/studio-sXvYUxr5.mjs +325 -0
- package/dist/studio.d.ts +54 -0
- package/package.json +4 -4
- package/dist/_api-client-MenP4-O7.mjs +0 -49
- package/dist/delete-DXilFBb1.mjs +0 -29
- package/dist/login-C59ZHzuO.mjs +0 -109
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @alexkroman1/aai-cli
|
|
2
2
|
|
|
3
|
-
The `aai` command — scaffold, run, test, and
|
|
3
|
+
The `aai` command — scaffold, run, test, and publish aai voice agents.
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
6
|
npm i -g @alexkroman1/aai-cli # or: npx @alexkroman1/aai-cli@latest
|
|
@@ -15,7 +15,10 @@ npm i -g @alexkroman1/aai-cli # or: npx @alexkroman1/aai-cli@latest
|
|
|
15
15
|
| `aai dev` | Local dev server: loads `agent.ts`, rebuilds on change, serves the browser client (`--port`) |
|
|
16
16
|
| `aai test` | Run the project's vitest suite |
|
|
17
17
|
| `aai build` | Bundle without deploying (type-checks first; `--skipTypecheck` opts out) |
|
|
18
|
-
| `aai
|
|
18
|
+
| `aai list` | List your studio projects |
|
|
19
|
+
| `aai pull <project>` | Pull a studio project into a local directory, ready for `aai dev` |
|
|
20
|
+
| `aai push` | Sync this project's source to its studio workspace (fast-forward-checked; `--force` overwrites) |
|
|
21
|
+
| `aai publish` | Push, then deploy to production — the studio's Publish button from the terminal (`.env` syncs as agent secrets) |
|
|
19
22
|
| `aai delete` | Remove a deployed agent |
|
|
20
23
|
| `aai secret put\|delete\|list` | Manage a deployed agent's secrets |
|
|
21
24
|
| `aai storage status\|enable\|disable` | Manage the agent's opt-in SQL database (`ctx.db`) |
|
|
@@ -30,16 +33,22 @@ aai init my-agent --template pizza-ordering
|
|
|
30
33
|
cd my-agent
|
|
31
34
|
# put ASSEMBLYAI_API_KEY=... in .env
|
|
32
35
|
aai dev # talk to it at the printed URL
|
|
33
|
-
aai
|
|
36
|
+
aai publish # ship it; .env values become the agent's secrets
|
|
34
37
|
```
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
agent
|
|
39
|
+
Every published agent is also a **studio project**: publishing prints a
|
|
40
|
+
studio link where the same source can be edited in the browser (with the
|
|
41
|
+
coding agent), and `aai pull` brings those edits back to your machine.
|
|
42
|
+
`push`/`pull` are fast-forward-only — an edit made in the studio since your
|
|
43
|
+
last pull surfaces as a conflict instead of being overwritten.
|
|
44
|
+
|
|
45
|
+
Publishes type-check the project locally, then build and deploy inside the
|
|
46
|
+
project's sandbox — byte-for-byte the studio's Publish path — preflight
|
|
47
|
+
required credentials, and print the agent's public URL.
|
|
39
48
|
|
|
40
49
|
## Notes
|
|
41
50
|
|
|
42
|
-
- A bare `aai` in a project directory offers to
|
|
51
|
+
- A bare `aai` in a project directory offers to publish (TTY-confirmed);
|
|
43
52
|
outside one it runs `init`.
|
|
44
53
|
- `--server <url>` targets a self-hosted platform; the origin is remembered
|
|
45
54
|
in your user config after explicit approval.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as readProjectConfig, i as readGlobalConfig, n as ensureApiKey, o as serverOrigin, t as approveServer } from "./_config-
|
|
2
|
+
import { a as readProjectConfig, i as readGlobalConfig, n as ensureApiKey, o as serverOrigin, t as approveServer } from "./_config-Y5V-5Krn.mjs";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -67,6 +67,25 @@ function resolveServerUrl(explicit, configUrl, approvedOrigins = []) {
|
|
|
67
67
|
If you do intend to use that server, re-run with --server ${origin} to approve it.`);
|
|
68
68
|
}
|
|
69
69
|
/**
|
|
70
|
+
* Reject a repo-supplied slug that isn't the platform's slug shape.
|
|
71
|
+
*
|
|
72
|
+
* Enforced before a slug is ever interpolated into a URL path.
|
|
73
|
+
* `.aai/project.json` is part of the working tree, so a cloned repo controls
|
|
74
|
+
* this value, and callers pair it with the user's API key — `aai publish`
|
|
75
|
+
* hands it to `syncEnvSecrets`, which PUTs the whole `.env` to
|
|
76
|
+
* `${serverUrl}/${slug}/secret`. A hostile `"slug": "x/../admin"` must not
|
|
77
|
+
* steer that request to a path of the repo's choosing.
|
|
78
|
+
*
|
|
79
|
+
* Lives here, at the single point where repo-controlled config becomes a
|
|
80
|
+
* credentialed target, rather than at each call site: the check used to
|
|
81
|
+
* exist only in `getServerInfo` (secret/storage/delete), so `publish` — the
|
|
82
|
+
* command users actually run — had no guard at all.
|
|
83
|
+
*/
|
|
84
|
+
function assertValidConfigSlug(slug) {
|
|
85
|
+
if (slug === void 0 || VALID_SLUG_RE.test(slug)) return;
|
|
86
|
+
throw new Error(`Invalid slug in .aai/project.json: ${JSON.stringify(slug)}\n Expected lowercase letters, digits, \`-\`, \`_\` (2-64 chars). Fix or delete the file — \`aai publish\` will create a fresh deployment.`);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
70
89
|
* Resolve everything needed to talk to the platform: project config (null if
|
|
71
90
|
* the project has never been deployed), server URL, and API key.
|
|
72
91
|
*/
|
|
@@ -74,6 +93,7 @@ async function resolveDeployTarget(cwd, explicitServer) {
|
|
|
74
93
|
const [config, globalConfig] = await Promise.all([readProjectConfig(cwd), readGlobalConfig()]);
|
|
75
94
|
const serverUrl = resolveServerUrl(explicitServer, config?.serverUrl, globalConfig.approvedServers ?? []);
|
|
76
95
|
if (explicitServer) await approveServer(serverUrl);
|
|
96
|
+
assertValidConfigSlug(config?.slug);
|
|
77
97
|
return {
|
|
78
98
|
config,
|
|
79
99
|
serverUrl,
|
|
@@ -83,8 +103,7 @@ async function resolveDeployTarget(cwd, explicitServer) {
|
|
|
83
103
|
/** Like resolveDeployTarget, but requires an existing deployment (project config). */
|
|
84
104
|
async function getServerInfo(cwd, explicitServer) {
|
|
85
105
|
const { config, serverUrl, apiKey } = await resolveDeployTarget(cwd, explicitServer);
|
|
86
|
-
if (!config) throw new Error("
|
|
87
|
-
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.`);
|
|
106
|
+
if (!config?.slug) throw new Error("This project has no deployed agent — run `aai publish` first");
|
|
88
107
|
return {
|
|
89
108
|
serverUrl,
|
|
90
109
|
slug: config.slug,
|
package/dist/_agent.d.ts
CHANGED
|
@@ -25,8 +25,10 @@ export declare function resolveServerUrl(explicit?: string, configUrl?: string,
|
|
|
25
25
|
*/
|
|
26
26
|
export declare function resolveDeployTarget(cwd: string, explicitServer?: string): Promise<{
|
|
27
27
|
config: {
|
|
28
|
-
slug
|
|
28
|
+
slug?: string | undefined;
|
|
29
29
|
serverUrl: string;
|
|
30
|
+
studioProject?: string | undefined;
|
|
31
|
+
studioSourceHash?: string | undefined;
|
|
30
32
|
} | null;
|
|
31
33
|
serverUrl: string;
|
|
32
34
|
apiKey: string;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { FetchError, ofetch } from "ofetch";
|
|
3
|
+
//#region _api-client.ts
|
|
4
|
+
/**
|
|
5
|
+
* Shared HTTP helper for platform API calls (deploy, delete, secrets).
|
|
6
|
+
*
|
|
7
|
+
* Built on ofetch: JSON bodies are serialized (with Content-Type set) and
|
|
8
|
+
* responses parsed automatically, and transient failures (network errors,
|
|
9
|
+
* 5xx/429) are retried before surfacing an error.
|
|
10
|
+
*/
|
|
11
|
+
const HINT_INVALID_API_KEY = "Your API key may be invalid. Run `aai` to re-enter your AssemblyAI API key.";
|
|
12
|
+
/** 404 hint for requests scoped to a deployed agent's slug. */
|
|
13
|
+
const HINT_NOT_DEPLOYED = "The agent may not be deployed. Check `.aai/project.json` for the correct slug.";
|
|
14
|
+
/**
|
|
15
|
+
* Send an authenticated request to the platform API and return the parsed
|
|
16
|
+
* JSON response. Throws a descriptive error with status-specific hints on
|
|
17
|
+
* failure (the 401 hint is always included; pass more via `hints`).
|
|
18
|
+
*/
|
|
19
|
+
async function apiRequest(url, opts) {
|
|
20
|
+
const client = opts.fetch ? ofetch.create({}, { fetch: opts.fetch }) : ofetch;
|
|
21
|
+
try {
|
|
22
|
+
return await client(url, {
|
|
23
|
+
method: opts.method ?? "GET",
|
|
24
|
+
headers: {
|
|
25
|
+
Authorization: `Bearer ${opts.apiKey}`,
|
|
26
|
+
...opts.headers
|
|
27
|
+
},
|
|
28
|
+
...opts.body !== void 0 ? { body: opts.body } : {},
|
|
29
|
+
retry: opts.retry ?? 2,
|
|
30
|
+
retryDelay: opts.retryDelay ?? 300
|
|
31
|
+
});
|
|
32
|
+
} catch (err) {
|
|
33
|
+
if (opts.allow404 && err instanceof FetchError && err.statusCode === 404) return null;
|
|
34
|
+
throw toApiError(err, url, opts);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Collapse `{"error": "..."}` payloads embedded in a message into their own
|
|
39
|
+
* text. Studio Publish runs the real `aai deploy` inside the sandbox, so its
|
|
40
|
+
* failures arrive wrapped twice and stringifying them produced a
|
|
41
|
+
* triple-escaped wall of JSON around one actionable sentence.
|
|
42
|
+
*/
|
|
43
|
+
function unwrapEmbeddedErrors(message, depth = 0) {
|
|
44
|
+
if (depth > 3) return message;
|
|
45
|
+
const start = message.indexOf("{\"error\"");
|
|
46
|
+
if (start === -1) return message;
|
|
47
|
+
const json = message.slice(start);
|
|
48
|
+
try {
|
|
49
|
+
const inner = JSON.parse(json).error;
|
|
50
|
+
if (typeof inner !== "string") return message;
|
|
51
|
+
return unwrapEmbeddedErrors(message.slice(0, start) + inner, depth + 1);
|
|
52
|
+
} catch {
|
|
53
|
+
return message;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** The messages of a Zod issue tree, deduped and flattened. */
|
|
57
|
+
function zodIssueMessages(value) {
|
|
58
|
+
if (Array.isArray(value)) return value.flatMap(zodIssueMessages);
|
|
59
|
+
if (value === null || typeof value !== "object") return [];
|
|
60
|
+
const node = value;
|
|
61
|
+
const nested = zodIssueMessages(node.issues);
|
|
62
|
+
if (nested.length > 0) return nested;
|
|
63
|
+
return typeof node.message === "string" ? [node.message] : [];
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* A human-readable one-liner for a server error body.
|
|
67
|
+
*
|
|
68
|
+
* Servers answer with `{ error }`, or with a serialized ZodError whose useful
|
|
69
|
+
* part is buried several levels down. Dumping the raw JSON turned a
|
|
70
|
+
* one-character mistake (`aai secret put MY-KEY`) into a 515-character escaped
|
|
71
|
+
* blob, so the shapes we actually emit are unwrapped here and anything else
|
|
72
|
+
* falls back to the raw body rather than being dropped.
|
|
73
|
+
*/
|
|
74
|
+
function describeErrorBody(data) {
|
|
75
|
+
if (typeof data === "string") return data;
|
|
76
|
+
if (data === null || typeof data !== "object") return JSON.stringify(data ?? "");
|
|
77
|
+
const error = data.error;
|
|
78
|
+
if (typeof error === "string") return unwrapEmbeddedErrors(error);
|
|
79
|
+
if (error !== null && typeof error === "object") {
|
|
80
|
+
const { message } = error;
|
|
81
|
+
if (typeof message === "string") {
|
|
82
|
+
try {
|
|
83
|
+
const issues = zodIssueMessages(JSON.parse(message));
|
|
84
|
+
if (issues.length > 0) return [...new Set(issues)].join("; ");
|
|
85
|
+
} catch {}
|
|
86
|
+
return message;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return JSON.stringify(data);
|
|
90
|
+
}
|
|
91
|
+
/** Format an ofetch failure into a descriptive, action-centric error. */
|
|
92
|
+
function toApiError(err, url, opts) {
|
|
93
|
+
if (err instanceof FetchError && err.statusCode !== void 0) {
|
|
94
|
+
const status = err.statusCode;
|
|
95
|
+
const body = describeErrorBody(err.data);
|
|
96
|
+
const hint = status === 401 ? HINT_INVALID_API_KEY : opts.hints?.[status];
|
|
97
|
+
return /* @__PURE__ */ new Error(`${opts.action} failed (HTTP ${status}): ${body}${hint ? `\n ${hint}` : ""}`);
|
|
98
|
+
}
|
|
99
|
+
const hint = "Check your network connection and verify the server URL is correct.";
|
|
100
|
+
const cause = err instanceof FetchError && err.cause !== void 0 ? err.cause : err;
|
|
101
|
+
return new Error(`${opts.action} failed: could not reach ${url}\n ${hint}`, { cause });
|
|
102
|
+
}
|
|
103
|
+
//#endregion
|
|
104
|
+
export { apiRequest as n, HINT_NOT_DEPLOYED as t };
|
package/dist/_api-client.d.ts
CHANGED
|
@@ -28,6 +28,12 @@ export type ApiRequestOptions = {
|
|
|
28
28
|
retryDelay?: number;
|
|
29
29
|
/** Optional fetch implementation for testing. Defaults to globalThis.fetch. */
|
|
30
30
|
fetch?: typeof globalThis.fetch;
|
|
31
|
+
/**
|
|
32
|
+
* Resolve `null` on a 404 instead of throwing — for existence probes
|
|
33
|
+
* ("is there a studio project with this name?") where absence is an
|
|
34
|
+
* answer, not a failure.
|
|
35
|
+
*/
|
|
36
|
+
allow404?: boolean;
|
|
31
37
|
};
|
|
32
38
|
/**
|
|
33
39
|
* Send an authenticated request to the platform API and return the parsed
|
|
@@ -35,3 +41,13 @@ export type ApiRequestOptions = {
|
|
|
35
41
|
* failure (the 401 hint is always included; pass more via `hints`).
|
|
36
42
|
*/
|
|
37
43
|
export declare function apiRequest<T = unknown>(url: string, opts: ApiRequestOptions): Promise<T>;
|
|
44
|
+
/**
|
|
45
|
+
* A human-readable one-liner for a server error body.
|
|
46
|
+
*
|
|
47
|
+
* Servers answer with `{ error }`, or with a serialized ZodError whose useful
|
|
48
|
+
* part is buried several levels down. Dumping the raw JSON turned a
|
|
49
|
+
* one-character mistake (`aai secret put MY-KEY`) into a 515-character escaped
|
|
50
|
+
* blob, so the shapes we actually emit are unwrapped here and anything else
|
|
51
|
+
* falls back to the raw body rather than being dropped.
|
|
52
|
+
*/
|
|
53
|
+
export declare function describeErrorBody(data: unknown): string;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { ArgsDef, CommandDef } from "citty";
|
|
2
|
+
import { type CommandResult, type OutputMode } from "./_output.ts";
|
|
3
|
+
/** Shared arg definitions for citty commands. */
|
|
4
|
+
export declare const sharedArgs: {
|
|
5
|
+
readonly server: {
|
|
6
|
+
readonly type: "string";
|
|
7
|
+
readonly alias: "s";
|
|
8
|
+
readonly description: "Platform server URL";
|
|
9
|
+
};
|
|
10
|
+
readonly yes: {
|
|
11
|
+
readonly type: "boolean";
|
|
12
|
+
readonly alias: "y";
|
|
13
|
+
readonly description: "Accept defaults (no prompts)";
|
|
14
|
+
};
|
|
15
|
+
readonly json: {
|
|
16
|
+
readonly type: "boolean";
|
|
17
|
+
readonly description: "Output JSON (auto-detected in non-TTY)";
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Flags in `rawArgs` that `argsDef` doesn't declare, in the form the user
|
|
22
|
+
* typed them.
|
|
23
|
+
*
|
|
24
|
+
* citty silently drops an unrecognized flag, so `aai push --serverr=http://x`
|
|
25
|
+
* exited 0 having pushed to the DEFAULT server — production, for an installed
|
|
26
|
+
* CLI — as if the flag had been honoured. Since `--server` is what decides
|
|
27
|
+
* where the API key and secret values are sent, a typo quietly retargeting it
|
|
28
|
+
* is worth failing on.
|
|
29
|
+
*/
|
|
30
|
+
export declare function findUnknownFlags(rawArgs: string[], argsDef: ArgsDef): string[];
|
|
31
|
+
/**
|
|
32
|
+
* Any command in the tree, regardless of its args shape — the walk below only
|
|
33
|
+
* reads `subCommands` and `args`, and the concrete generics differ per command.
|
|
34
|
+
*/
|
|
35
|
+
type AnyCommandDef = CommandDef<ArgsDef>;
|
|
36
|
+
/**
|
|
37
|
+
* Unknown flags in `argv` for whichever (possibly nested) subcommand it
|
|
38
|
+
* selects — `[]` when everything is declared.
|
|
39
|
+
*
|
|
40
|
+
* Walks the real command tree rather than re-listing flags, so this cannot
|
|
41
|
+
* drift from what the commands accept. An unknown SUBCOMMAND is not reported:
|
|
42
|
+
* citty already answers that with usage text and a non-zero exit.
|
|
43
|
+
*/
|
|
44
|
+
export declare function unknownFlagsForArgv(root: AnyCommandDef, argv: string[]): Promise<string[]>;
|
|
45
|
+
/** Shared command setup: resolve cwd, optionally require agent.ts. */
|
|
46
|
+
export declare function setup(opts?: {
|
|
47
|
+
agent?: boolean;
|
|
48
|
+
}): Promise<string>;
|
|
49
|
+
/**
|
|
50
|
+
* Run a command body with standard output-mode resolution, error handling,
|
|
51
|
+
* and result emission.
|
|
52
|
+
*
|
|
53
|
+
* - API key acquisition is owned by `resolveDeployTarget`/`getServerInfo`
|
|
54
|
+
* inside the commands that talk to the platform — after the server-trust
|
|
55
|
+
* check, so an untrusted `serverUrl` is refused without prompting for a
|
|
56
|
+
* key, and commands with no platform traffic never prompt at all.
|
|
57
|
+
*
|
|
58
|
+
* A thrown error and a returned `fail(...)` converge here on one emitter:
|
|
59
|
+
* human mode logs the message, JSON mode writes exactly one result line,
|
|
60
|
+
* and both exit 1.
|
|
61
|
+
*/
|
|
62
|
+
export declare function runCommand(args: {
|
|
63
|
+
json?: boolean | undefined;
|
|
64
|
+
}, fn: (mode: OutputMode) => Promise<CommandResult<unknown>>): Promise<void>;
|
|
65
|
+
export {};
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { n as log, o as CliError } from "./_ui-8kOEB-JH.mjs";
|
|
3
3
|
import { a as errorMessage, c as readJson, d as writeJson } from "./_utils-Ch0J4s6a.mjs";
|
|
4
|
+
import { mkdtempSync } from "node:fs";
|
|
4
5
|
import path from "node:path";
|
|
5
|
-
import
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
6
7
|
import envPaths from "env-paths";
|
|
7
8
|
import { z } from "zod";
|
|
8
9
|
//#region _config.ts
|
|
@@ -18,8 +19,17 @@ import { z } from "zod";
|
|
|
18
19
|
* that isn't an approved http(s) origin.
|
|
19
20
|
*/
|
|
20
21
|
const ProjectConfigSchema = z.object({
|
|
21
|
-
slug
|
|
22
|
-
|
|
22
|
+
/** Deployed agent slug — absent for a pulled project never published. */
|
|
23
|
+
slug: z.string().optional(),
|
|
24
|
+
serverUrl: z.string(),
|
|
25
|
+
/** Studio project this directory is linked to (`aai pull`/`aai push`). */
|
|
26
|
+
studioProject: z.string().optional(),
|
|
27
|
+
/**
|
|
28
|
+
* The workspace files hash at the last pull/push — `aai push` sends it
|
|
29
|
+
* back as the fast-forward token, so an edit made in the studio since
|
|
30
|
+
* then surfaces as a 409 instead of being silently overwritten.
|
|
31
|
+
*/
|
|
32
|
+
studioSourceHash: z.string().optional()
|
|
23
33
|
});
|
|
24
34
|
/**
|
|
25
35
|
* Resolve the global config directory (the platform-conventional env-paths
|
|
@@ -33,8 +43,19 @@ const ProjectConfigSchema = z.object({
|
|
|
33
43
|
function getConfigDir() {
|
|
34
44
|
const override = process.env.AAI_CONFIG_DIR?.trim();
|
|
35
45
|
if (override) return override;
|
|
46
|
+
if (process.env.VITEST) return testConfigDir();
|
|
36
47
|
return envPaths("aai", { suffix: "" }).config;
|
|
37
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Per-process throwaway config dir used only under vitest. Memoized: callers
|
|
51
|
+
* read-modify-write the same config across calls, so a fresh dir per call
|
|
52
|
+
* would silently drop what the previous one wrote.
|
|
53
|
+
*/
|
|
54
|
+
let _testConfigDir;
|
|
55
|
+
function testConfigDir() {
|
|
56
|
+
_testConfigDir ??= mkdtempSync(path.join(tmpdir(), "aai-vitest-config-"));
|
|
57
|
+
return _testConfigDir;
|
|
58
|
+
}
|
|
38
59
|
async function readProjectConfig(agentDir) {
|
|
39
60
|
const file = path.join(agentDir, ".aai", "project.json");
|
|
40
61
|
let data;
|
|
@@ -53,6 +74,23 @@ async function writeProjectConfig(agentDir, data) {
|
|
|
53
74
|
await writeJson(path.join(agentDir, ".aai", "project.json"), data);
|
|
54
75
|
}
|
|
55
76
|
/**
|
|
77
|
+
* Merge `patch` into the existing project config rather than replacing the
|
|
78
|
+
* file — a publish recording its `slug` must not drop the studio link
|
|
79
|
+
* fields a pull wrote, and vice versa.
|
|
80
|
+
*/
|
|
81
|
+
async function updateProjectConfig(agentDir, patch) {
|
|
82
|
+
let existing = null;
|
|
83
|
+
try {
|
|
84
|
+
existing = await readProjectConfig(agentDir);
|
|
85
|
+
} catch {}
|
|
86
|
+
const merged = {
|
|
87
|
+
...existing,
|
|
88
|
+
...patch
|
|
89
|
+
};
|
|
90
|
+
await writeProjectConfig(agentDir, merged);
|
|
91
|
+
return merged;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
56
94
|
* Origin of `url`, or `null` when it is not an absolute http(s) URL.
|
|
57
95
|
*
|
|
58
96
|
* Non-HTTP schemes are rejected rather than returned: `new URL()` yields the
|
|
@@ -106,6 +144,20 @@ async function trySaveApiKey(dir, config, apiKey) {
|
|
|
106
144
|
log.warn(`Couldn't save your API key to ${path.join(dir, "config.json")}: ${errorMessage(err)} — you'll be prompted again next run.`);
|
|
107
145
|
}
|
|
108
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* The credential every platform command runs on.
|
|
149
|
+
*
|
|
150
|
+
* Two sources, in order: the key `aai login` saved, then
|
|
151
|
+
* `ASSEMBLYAI_API_KEY` for non-interactive callers (CI, scripts, the eval
|
|
152
|
+
* harnesses).
|
|
153
|
+
*
|
|
154
|
+
* There is deliberately NO "paste a key" prompt. Pasting one produced a
|
|
155
|
+
* half-configured CLI — able to push and publish while linked to no account
|
|
156
|
+
* the user could see in the studio — and it made `aai login`, which is the
|
|
157
|
+
* real onboarding path, optional in practice. It was also the riskier code
|
|
158
|
+
* path: a hidden password prompt reads stdin, so a piped invocation could
|
|
159
|
+
* have its input eaten and persisted as the API key.
|
|
160
|
+
*/
|
|
109
161
|
async function ensureApiKey(configDir) {
|
|
110
162
|
const dir = configDir ?? getConfigDir();
|
|
111
163
|
const config = await readGlobalConfig(dir);
|
|
@@ -115,10 +167,7 @@ async function ensureApiKey(configDir) {
|
|
|
115
167
|
await trySaveApiKey(dir, config, envKey);
|
|
116
168
|
return envKey;
|
|
117
169
|
}
|
|
118
|
-
|
|
119
|
-
const apiKey = unwrapCancel(await p.password({ message: "Enter your AssemblyAI API key" }), "Setup cancelled");
|
|
120
|
-
await trySaveApiKey(dir, config, apiKey);
|
|
121
|
-
return apiKey;
|
|
170
|
+
throw new CliError("not_logged_in", "You're not logged in.", "Run `aai login` to link your account, or set ASSEMBLYAI_API_KEY for non-interactive use.");
|
|
122
171
|
}
|
|
123
172
|
//#endregion
|
|
124
|
-
export { readProjectConfig as a,
|
|
173
|
+
export { readProjectConfig as a, writeGlobalConfig as c, readGlobalConfig as i, writeProjectConfig as l, ensureApiKey as n, serverOrigin as o, getConfigDir as r, updateProjectConfig as s, approveServer as t };
|
package/dist/_config.d.ts
CHANGED
|
@@ -11,8 +11,10 @@ import { z } from "zod";
|
|
|
11
11
|
* that isn't an approved http(s) origin.
|
|
12
12
|
*/
|
|
13
13
|
declare const ProjectConfigSchema: z.ZodObject<{
|
|
14
|
-
slug: z.ZodString
|
|
14
|
+
slug: z.ZodOptional<z.ZodString>;
|
|
15
15
|
serverUrl: z.ZodString;
|
|
16
|
+
studioProject: z.ZodOptional<z.ZodString>;
|
|
17
|
+
studioSourceHash: z.ZodOptional<z.ZodString>;
|
|
16
18
|
}, z.core.$strip>;
|
|
17
19
|
/**
|
|
18
20
|
* Resolve the global config directory (the platform-conventional env-paths
|
|
@@ -27,6 +29,12 @@ export declare function getConfigDir(): string;
|
|
|
27
29
|
export type ProjectConfig = z.infer<typeof ProjectConfigSchema>;
|
|
28
30
|
export declare function readProjectConfig(agentDir: string): Promise<ProjectConfig | null>;
|
|
29
31
|
export declare function writeProjectConfig(agentDir: string, data: ProjectConfig): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Merge `patch` into the existing project config rather than replacing the
|
|
34
|
+
* file — a publish recording its `slug` must not drop the studio link
|
|
35
|
+
* fields a pull wrote, and vice versa.
|
|
36
|
+
*/
|
|
37
|
+
export declare function updateProjectConfig(agentDir: string, patch: Partial<ProjectConfig> & Pick<ProjectConfig, "serverUrl">): Promise<ProjectConfig>;
|
|
30
38
|
export type GlobalConfig = {
|
|
31
39
|
apiKey?: string;
|
|
32
40
|
/**
|
|
@@ -53,5 +61,19 @@ export declare function serverOrigin(url: string): string | null;
|
|
|
53
61
|
export declare function approveServer(url: string, configDir?: string): Promise<void>;
|
|
54
62
|
export declare function readGlobalConfig(configDir?: string): Promise<GlobalConfig>;
|
|
55
63
|
export declare function writeGlobalConfig(configDir: string, data: GlobalConfig): Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* The credential every platform command runs on.
|
|
66
|
+
*
|
|
67
|
+
* Two sources, in order: the key `aai login` saved, then
|
|
68
|
+
* `ASSEMBLYAI_API_KEY` for non-interactive callers (CI, scripts, the eval
|
|
69
|
+
* harnesses).
|
|
70
|
+
*
|
|
71
|
+
* There is deliberately NO "paste a key" prompt. Pasting one produced a
|
|
72
|
+
* half-configured CLI — able to push and publish while linked to no account
|
|
73
|
+
* the user could see in the studio — and it made `aai login`, which is the
|
|
74
|
+
* real onboarding path, optional in practice. It was also the riskier code
|
|
75
|
+
* path: a hidden password prompt reads stdin, so a piped invocation could
|
|
76
|
+
* have its input eaten and persisted as the API key.
|
|
77
|
+
*/
|
|
56
78
|
export declare function ensureApiKey(configDir?: string): Promise<string>;
|
|
57
79
|
export {};
|
package/dist/_deploy.d.ts
CHANGED
|
@@ -15,6 +15,13 @@ export type DeployOpts = {
|
|
|
15
15
|
* panel needs a deployed slug to attach secrets to).
|
|
16
16
|
*/
|
|
17
17
|
allowMissingSecrets?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Ask the server to permit a `-preview`-suffixed slug (`aai deploy
|
|
20
|
+
* --allow-preview-slug`). That suffix is reserved for the studio's
|
|
21
|
+
* auto-preview deploys — the server rejects it otherwise — and this is set
|
|
22
|
+
* by the studio's own in-guest deploy, not by ordinary users.
|
|
23
|
+
*/
|
|
24
|
+
allowPreviewSlug?: boolean;
|
|
18
25
|
/** Retry delay override for tests (0 = no real sleeps on retry paths). */
|
|
19
26
|
retryDelay?: number;
|
|
20
27
|
/** Optional fetch implementation for testing. Defaults to globalThis.fetch. */
|
|
@@ -4,7 +4,7 @@ import { a as errorMessage, r as errorCode } from "./_utils-Ch0J4s6a.mjs";
|
|
|
4
4
|
import { n as fallbackHtmlPlugin } from "./client-bundler-yiWoXrgb.mjs";
|
|
5
5
|
import { buildWorker } from "./worker-bundler.mjs";
|
|
6
6
|
import { n as createWorkerEvaluator } from "./_bundler-Cjaxa2wi.mjs";
|
|
7
|
-
import { n as ensureApiKey } from "./_config-
|
|
7
|
+
import { n as ensureApiKey } from "./_config-Y5V-5Krn.mjs";
|
|
8
8
|
import { t as resolveServerEnv } from "./_server-common-CnaP_Urf.mjs";
|
|
9
9
|
import { createRequire } from "node:module";
|
|
10
10
|
import { existsSync } from "node:fs";
|
|
@@ -44,7 +44,7 @@ function agentEnvWarnings(agentDef, env, shellEnv = process.env) {
|
|
|
44
44
|
const missing = required.filter((name) => !(env[name] || shellEnv[name]));
|
|
45
45
|
if (missing.length > 0) warnings.push(`Missing provider credential${s(missing)}: ${missing.join(", ")}. Set ${them(missing)} in .env or the environment.`);
|
|
46
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
|
|
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 publish\`.`);
|
|
48
48
|
const declared = (agentDef.requiredEnv ?? []).filter((name) => !env[name]);
|
|
49
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
50
|
return warnings;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { c as readJson, d as writeJson, s as isEexist } from "./_utils-Ch0J4s6a.mjs";
|
|
3
|
-
import { r as isDevMode, t as getMonorepoRoot } from "./_agent-
|
|
4
|
-
import { REPO_URL, downloadAndMergeTemplate } from "./_templates-
|
|
3
|
+
import { r as isDevMode, t as getMonorepoRoot } from "./_agent-2nVugrN3.mjs";
|
|
4
|
+
import { REPO_URL, downloadAndMergeTemplate } from "./_templates-Bt6u9_68.mjs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import fs from "node:fs/promises";
|
|
7
7
|
//#region _init.ts
|
|
@@ -15,7 +15,7 @@ A voice agent built with [aai](${REPO_URL}).
|
|
|
15
15
|
\`\`\`sh
|
|
16
16
|
npm install # Install dependencies
|
|
17
17
|
aai dev # Run locally (opens browser)
|
|
18
|
-
aai
|
|
18
|
+
aai publish # Publish to production (and sync to the studio)
|
|
19
19
|
\`\`\`
|
|
20
20
|
|
|
21
21
|
## Secrets
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { n as getServerInfo } from "./_agent-
|
|
3
|
-
import { n as apiRequest, t as HINT_NOT_DEPLOYED } from "./_api-client-
|
|
2
|
+
import { n as getServerInfo } from "./_agent-2nVugrN3.mjs";
|
|
3
|
+
import { n as apiRequest, t as HINT_NOT_DEPLOYED } from "./_api-client-B-upMGkc.mjs";
|
|
4
4
|
//#region _slug-api.ts
|
|
5
5
|
/**
|
|
6
6
|
* Authenticated request against a deployed agent's slug-scoped resource
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export declare const list: import("citty").CommandDef<{
|
|
2
|
+
readonly server: {
|
|
3
|
+
readonly type: "string";
|
|
4
|
+
readonly alias: "s";
|
|
5
|
+
readonly description: "Platform server URL";
|
|
6
|
+
};
|
|
7
|
+
readonly json: {
|
|
8
|
+
readonly type: "boolean";
|
|
9
|
+
readonly description: "Output JSON (auto-detected in non-TTY)";
|
|
10
|
+
};
|
|
11
|
+
}>;
|
|
12
|
+
export declare const pull: import("citty").CommandDef<{
|
|
13
|
+
readonly project: {
|
|
14
|
+
readonly type: "positional";
|
|
15
|
+
readonly description: "Studio project name (see `aai list`)";
|
|
16
|
+
readonly required: true;
|
|
17
|
+
};
|
|
18
|
+
readonly dir: {
|
|
19
|
+
readonly type: "positional";
|
|
20
|
+
readonly description: "Target directory (default: the project name)";
|
|
21
|
+
readonly required: false;
|
|
22
|
+
};
|
|
23
|
+
readonly force: {
|
|
24
|
+
readonly type: "boolean";
|
|
25
|
+
readonly alias: "f";
|
|
26
|
+
readonly description: "Overwrite files in a non-empty directory";
|
|
27
|
+
};
|
|
28
|
+
readonly server: {
|
|
29
|
+
readonly type: "string";
|
|
30
|
+
readonly alias: "s";
|
|
31
|
+
readonly description: "Platform server URL";
|
|
32
|
+
};
|
|
33
|
+
readonly json: {
|
|
34
|
+
readonly type: "boolean";
|
|
35
|
+
readonly description: "Output JSON (auto-detected in non-TTY)";
|
|
36
|
+
};
|
|
37
|
+
}>;
|
|
38
|
+
export declare const push: import("citty").CommandDef<{
|
|
39
|
+
readonly force: {
|
|
40
|
+
readonly type: "boolean";
|
|
41
|
+
readonly alias: "f";
|
|
42
|
+
readonly description: "Overwrite studio-side changes instead of failing the fast-forward check";
|
|
43
|
+
};
|
|
44
|
+
readonly server: {
|
|
45
|
+
readonly type: "string";
|
|
46
|
+
readonly alias: "s";
|
|
47
|
+
readonly description: "Platform server URL";
|
|
48
|
+
};
|
|
49
|
+
readonly json: {
|
|
50
|
+
readonly type: "boolean";
|
|
51
|
+
readonly description: "Output JSON (auto-detected in non-TTY)";
|
|
52
|
+
};
|
|
53
|
+
}>;
|
|
54
|
+
export declare const publish: import("citty").CommandDef<{
|
|
55
|
+
readonly force: {
|
|
56
|
+
readonly type: "boolean";
|
|
57
|
+
readonly alias: "f";
|
|
58
|
+
readonly description: "Overwrite studio-side changes instead of failing the fast-forward check";
|
|
59
|
+
};
|
|
60
|
+
readonly server: {
|
|
61
|
+
readonly type: "string";
|
|
62
|
+
readonly alias: "s";
|
|
63
|
+
readonly description: "Platform server URL";
|
|
64
|
+
};
|
|
65
|
+
readonly json: {
|
|
66
|
+
readonly type: "boolean";
|
|
67
|
+
readonly description: "Output JSON (auto-detected in non-TTY)";
|
|
68
|
+
};
|
|
69
|
+
readonly skipTypecheck: {
|
|
70
|
+
readonly type: "boolean";
|
|
71
|
+
readonly description: "Skip type checking before publishing";
|
|
72
|
+
};
|
|
73
|
+
}>;
|