@alexkroman1/aai-cli 5.6.0 → 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/dist/{_agent-BB1ZaZsX.mjs → _agent-2nVugrN3.mjs} +21 -2
- package/dist/{_api-client-B6_uvuLs.mjs → _api-client-B-upMGkc.mjs} +55 -1
- package/dist/_api-client.d.ts +10 -0
- package/dist/_cli-common.d.ts +27 -0
- package/dist/{_config-PIC0nzRu.mjs → _config-Y5V-5Krn.mjs} +30 -7
- package/dist/_config.d.ts +14 -0
- package/dist/{_dev-server-CGptLIAm.mjs → _dev-server-BB5N8kdh.mjs} +1 -1
- package/dist/{_init-BPFM4uBH.mjs → _init-D7JIT-IJ.mjs} +2 -2
- package/dist/{_slug-api-dcaQ_Uho.mjs → _slug-api-CGJSST9B.mjs} +2 -2
- package/dist/_studio.d.ts +12 -3
- package/dist/{_templates-jmjgdgcd.mjs → _templates-Bt6u9_68.mjs} +1 -1
- package/dist/cli.mjs +120 -17
- package/dist/{delete-BaHgd9cN.mjs → delete-DRNfvczK.mjs} +4 -2
- package/dist/{deploy-D-O-Q_mW.mjs → deploy-Cp-wgME3.mjs} +3 -3
- package/dist/{dev-C5P8amSg.mjs → dev-C4KyxouE.mjs} +1 -1
- package/dist/{init-BGlOhIOl.mjs → init-BT-IU9AR.mjs} +3 -3
- package/dist/{login-B6IhXski.mjs → login-AA_UdRI-.mjs} +74 -19
- package/dist/scaffold/package.json +3 -3
- package/dist/{secret-_bHo4rqt.mjs → secret-Dr0qnxeb.mjs} +1 -1
- package/dist/{storage-MWiVx_mV.mjs → storage-CoQB8d-u.mjs} +1 -1
- package/dist/{studio-CsRn2J1a.mjs → studio-sXvYUxr5.mjs} +53 -12
- package/dist/studio.d.ts +2 -0
- package/package.json +3 -3
|
@@ -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,
|
|
@@ -84,7 +104,6 @@ async function resolveDeployTarget(cwd, explicitServer) {
|
|
|
84
104
|
async function getServerInfo(cwd, explicitServer) {
|
|
85
105
|
const { config, serverUrl, apiKey } = await resolveDeployTarget(cwd, explicitServer);
|
|
86
106
|
if (!config?.slug) throw new Error("This project has no deployed agent — run `aai publish` first");
|
|
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 publish\` to create a fresh deployment.`);
|
|
88
107
|
return {
|
|
89
108
|
serverUrl,
|
|
90
109
|
slug: config.slug,
|
|
@@ -34,11 +34,65 @@ async function apiRequest(url, opts) {
|
|
|
34
34
|
throw toApiError(err, url, opts);
|
|
35
35
|
}
|
|
36
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
|
+
}
|
|
37
91
|
/** Format an ofetch failure into a descriptive, action-centric error. */
|
|
38
92
|
function toApiError(err, url, opts) {
|
|
39
93
|
if (err instanceof FetchError && err.statusCode !== void 0) {
|
|
40
94
|
const status = err.statusCode;
|
|
41
|
-
const body =
|
|
95
|
+
const body = describeErrorBody(err.data);
|
|
42
96
|
const hint = status === 401 ? HINT_INVALID_API_KEY : opts.hints?.[status];
|
|
43
97
|
return /* @__PURE__ */ new Error(`${opts.action} failed (HTTP ${status}): ${body}${hint ? `\n ${hint}` : ""}`);
|
|
44
98
|
}
|
package/dist/_api-client.d.ts
CHANGED
|
@@ -41,3 +41,13 @@ export type ApiRequestOptions = {
|
|
|
41
41
|
* failure (the 401 hint is always included; pass more via `hints`).
|
|
42
42
|
*/
|
|
43
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;
|
package/dist/_cli-common.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ArgsDef, CommandDef } from "citty";
|
|
1
2
|
import { type CommandResult, type OutputMode } from "./_output.ts";
|
|
2
3
|
/** Shared arg definitions for citty commands. */
|
|
3
4
|
export declare const sharedArgs: {
|
|
@@ -16,6 +17,31 @@ export declare const sharedArgs: {
|
|
|
16
17
|
readonly description: "Output JSON (auto-detected in non-TTY)";
|
|
17
18
|
};
|
|
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[]>;
|
|
19
45
|
/** Shared command setup: resolve cwd, optionally require agent.ts. */
|
|
20
46
|
export declare function setup(opts?: {
|
|
21
47
|
agent?: boolean;
|
|
@@ -36,3 +62,4 @@ export declare function setup(opts?: {
|
|
|
36
62
|
export declare function runCommand(args: {
|
|
37
63
|
json?: boolean | undefined;
|
|
38
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
|
|
@@ -42,8 +43,19 @@ const ProjectConfigSchema = z.object({
|
|
|
42
43
|
function getConfigDir() {
|
|
43
44
|
const override = process.env.AAI_CONFIG_DIR?.trim();
|
|
44
45
|
if (override) return override;
|
|
46
|
+
if (process.env.VITEST) return testConfigDir();
|
|
45
47
|
return envPaths("aai", { suffix: "" }).config;
|
|
46
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
|
+
}
|
|
47
59
|
async function readProjectConfig(agentDir) {
|
|
48
60
|
const file = path.join(agentDir, ".aai", "project.json");
|
|
49
61
|
let data;
|
|
@@ -132,6 +144,20 @@ async function trySaveApiKey(dir, config, apiKey) {
|
|
|
132
144
|
log.warn(`Couldn't save your API key to ${path.join(dir, "config.json")}: ${errorMessage(err)} — you'll be prompted again next run.`);
|
|
133
145
|
}
|
|
134
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
|
+
*/
|
|
135
161
|
async function ensureApiKey(configDir) {
|
|
136
162
|
const dir = configDir ?? getConfigDir();
|
|
137
163
|
const config = await readGlobalConfig(dir);
|
|
@@ -141,10 +167,7 @@ async function ensureApiKey(configDir) {
|
|
|
141
167
|
await trySaveApiKey(dir, config, envKey);
|
|
142
168
|
return envKey;
|
|
143
169
|
}
|
|
144
|
-
|
|
145
|
-
const apiKey = unwrapCancel(await p.password({ message: "Enter your AssemblyAI API key" }), "Setup cancelled");
|
|
146
|
-
await trySaveApiKey(dir, config, apiKey);
|
|
147
|
-
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.");
|
|
148
171
|
}
|
|
149
172
|
//#endregion
|
|
150
|
-
export { readProjectConfig as a, writeGlobalConfig as c, readGlobalConfig as i, ensureApiKey as n, serverOrigin as o, getConfigDir as r, updateProjectConfig as s, approveServer as t };
|
|
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
|
@@ -61,5 +61,19 @@ export declare function serverOrigin(url: string): string | null;
|
|
|
61
61
|
export declare function approveServer(url: string, configDir?: string): Promise<void>;
|
|
62
62
|
export declare function readGlobalConfig(configDir?: string): Promise<GlobalConfig>;
|
|
63
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
|
+
*/
|
|
64
78
|
export declare function ensureApiKey(configDir?: string): Promise<string>;
|
|
65
79
|
export {};
|
|
@@ -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";
|
|
@@ -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
|
|
@@ -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
|
package/dist/_studio.d.ts
CHANGED
|
@@ -17,14 +17,23 @@ export declare const MAX_STUDIO_FILES = 100;
|
|
|
17
17
|
/**
|
|
18
18
|
* Walk a local project into the path→content record a workspace stores —
|
|
19
19
|
* the CLI-side twin of the guest's `snapshotWorkspace`: same ignored
|
|
20
|
-
* directories, same caps, oversized files skipped with a
|
|
21
|
-
* than failing the whole push.
|
|
20
|
+
* directories, same caps, oversized and non-text files skipped with a
|
|
21
|
+
* warning rather than failing the whole push.
|
|
22
22
|
*/
|
|
23
23
|
export declare function collectSourceFiles(dir: string): Promise<{
|
|
24
24
|
files: Record<string, string>;
|
|
25
25
|
warnings: string[];
|
|
26
26
|
}>;
|
|
27
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* A studio project name derived from a directory name, or null if unusable.
|
|
29
|
+
*
|
|
30
|
+
* A `-preview` suffix is deliberately unusable. Publishing a project deploys
|
|
31
|
+
* it under the project's own name, so a `*-preview` project would claim a
|
|
32
|
+
* slug the studio's orphan-preview sweep reaps hourly — deleting the agent,
|
|
33
|
+
* its app-database schema, and its secrets on a schedule the user never
|
|
34
|
+
* asked for. Refusing the name is recoverable (rename the directory); losing
|
|
35
|
+
* a published agent to the reaper is not.
|
|
36
|
+
*/
|
|
28
37
|
export declare function projectNameFromDir(dir: string): string | null;
|
|
29
38
|
/** The shareable studio URL for a project — what every command prints. */
|
|
30
39
|
export declare function studioProjectUrl(serverUrl: string, project: string): string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { a as errorMessage } from "./_utils-Ch0J4s6a.mjs";
|
|
3
|
-
import { t as getMonorepoRoot } from "./_agent-
|
|
3
|
+
import { t as getMonorepoRoot } from "./_agent-2nVugrN3.mjs";
|
|
4
4
|
import { existsSync } from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import fs from "node:fs/promises";
|
package/dist/cli.mjs
CHANGED
|
@@ -23,6 +23,95 @@ const sharedArgs = {
|
|
|
23
23
|
description: "Output JSON (auto-detected in non-TTY)"
|
|
24
24
|
}
|
|
25
25
|
};
|
|
26
|
+
/** Flags citty handles itself, so no command declares them. */
|
|
27
|
+
const BUILTIN_FLAGS = /* @__PURE__ */ new Set([
|
|
28
|
+
"help",
|
|
29
|
+
"h",
|
|
30
|
+
"version",
|
|
31
|
+
"v"
|
|
32
|
+
]);
|
|
33
|
+
/**
|
|
34
|
+
* Flags in `rawArgs` that `argsDef` doesn't declare, in the form the user
|
|
35
|
+
* typed them.
|
|
36
|
+
*
|
|
37
|
+
* citty silently drops an unrecognized flag, so `aai push --serverr=http://x`
|
|
38
|
+
* exited 0 having pushed to the DEFAULT server — production, for an installed
|
|
39
|
+
* CLI — as if the flag had been honoured. Since `--server` is what decides
|
|
40
|
+
* where the API key and secret values are sent, a typo quietly retargeting it
|
|
41
|
+
* is worth failing on.
|
|
42
|
+
*/
|
|
43
|
+
function findUnknownFlags(rawArgs, argsDef) {
|
|
44
|
+
const declared = declaredFlagNames(argsDef);
|
|
45
|
+
const unknown = [];
|
|
46
|
+
for (const raw of rawArgs) {
|
|
47
|
+
if (raw === "--") break;
|
|
48
|
+
const token = flagToken(raw);
|
|
49
|
+
if (token && !declared.has(canonicalFlag(token))) unknown.push(token);
|
|
50
|
+
}
|
|
51
|
+
return unknown;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A flag's comparison key: dashes dropped and lower-cased, with a leading
|
|
55
|
+
* `no` negation removed.
|
|
56
|
+
*
|
|
57
|
+
* citty accepts both `--allowMissingSecrets` and `--allow-missing-secrets` for
|
|
58
|
+
* an arg declared as `allowMissingSecrets`, and the guest's in-sandbox Publish
|
|
59
|
+
* spawns the kebab-case spelling. Comparing the literal text rejected
|
|
60
|
+
* `--allow-missing-secrets` as unknown and broke Publish for every studio
|
|
61
|
+
* user, so both sides are normalized to one key.
|
|
62
|
+
*/
|
|
63
|
+
function canonicalFlag(flag) {
|
|
64
|
+
return flag.replace(/^--?/, "").replace(/^no-/, "").replace(/-/g, "").toLowerCase();
|
|
65
|
+
}
|
|
66
|
+
/** Every flag name and alias `argsDef` declares, plus citty's built-ins. */
|
|
67
|
+
function declaredFlagNames(argsDef) {
|
|
68
|
+
const declared = new Set([...BUILTIN_FLAGS].map(canonicalFlag));
|
|
69
|
+
for (const [name, def] of Object.entries(argsDef)) {
|
|
70
|
+
const { type, alias } = def;
|
|
71
|
+
if (type === "positional") continue;
|
|
72
|
+
declared.add(canonicalFlag(name));
|
|
73
|
+
if (typeof alias === "string") declared.add(canonicalFlag(alias));
|
|
74
|
+
else if (Array.isArray(alias)) for (const a of alias) declared.add(canonicalFlag(a));
|
|
75
|
+
}
|
|
76
|
+
return declared;
|
|
77
|
+
}
|
|
78
|
+
/** The flag part of `raw` (`--server` from `--server=x`), or null if positional. */
|
|
79
|
+
function flagToken(raw) {
|
|
80
|
+
if (!raw.startsWith("-") || raw === "-" || /^-\d/.test(raw)) return null;
|
|
81
|
+
const eq = raw.indexOf("=");
|
|
82
|
+
return eq === -1 ? raw : raw.slice(0, eq);
|
|
83
|
+
}
|
|
84
|
+
/** Resolve a possibly-nested `Resolvable` citty field. */
|
|
85
|
+
async function resolve(value) {
|
|
86
|
+
return typeof value === "function" ? await value() : await value;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Unknown flags in `argv` for whichever (possibly nested) subcommand it
|
|
90
|
+
* selects — `[]` when everything is declared.
|
|
91
|
+
*
|
|
92
|
+
* Walks the real command tree rather than re-listing flags, so this cannot
|
|
93
|
+
* drift from what the commands accept. An unknown SUBCOMMAND is not reported:
|
|
94
|
+
* citty already answers that with usage text and a non-zero exit.
|
|
95
|
+
*/
|
|
96
|
+
async function unknownFlagsForArgv(root, argv) {
|
|
97
|
+
let cmd = root;
|
|
98
|
+
let i = 0;
|
|
99
|
+
for (; i < argv.length; i++) {
|
|
100
|
+
const token = argv[i];
|
|
101
|
+
if (token === void 0 || token.startsWith("-")) break;
|
|
102
|
+
const subCommands = await resolve(cmd.subCommands);
|
|
103
|
+
const next = subCommands?.[token];
|
|
104
|
+
if (next === void 0) {
|
|
105
|
+
if (subCommands && Object.keys(subCommands).length > 0) return [];
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
const resolved = await resolve(next);
|
|
109
|
+
if (resolved === void 0) break;
|
|
110
|
+
cmd = resolved;
|
|
111
|
+
}
|
|
112
|
+
const argsDef = await resolve(cmd.args) ?? {};
|
|
113
|
+
return findUnknownFlags(argv.slice(i), argsDef);
|
|
114
|
+
}
|
|
26
115
|
/** Shared command setup: resolve cwd, optionally require agent.ts. */
|
|
27
116
|
async function setup(opts) {
|
|
28
117
|
const cwd = resolveCwd();
|
|
@@ -76,7 +165,7 @@ const list = defineCommand({
|
|
|
76
165
|
async run({ args }) {
|
|
77
166
|
await runCommand(args, async () => {
|
|
78
167
|
const cwd = resolveCwd();
|
|
79
|
-
const { executeList } = await import("./studio-
|
|
168
|
+
const { executeList } = await import("./studio-sXvYUxr5.mjs");
|
|
80
169
|
return executeList({
|
|
81
170
|
cwd,
|
|
82
171
|
server: args.server
|
|
@@ -111,7 +200,7 @@ const pull = defineCommand({
|
|
|
111
200
|
async run({ args }) {
|
|
112
201
|
await runCommand(args, async () => {
|
|
113
202
|
const cwd = resolveCwd();
|
|
114
|
-
const { executePull } = await import("./studio-
|
|
203
|
+
const { executePull } = await import("./studio-sXvYUxr5.mjs");
|
|
115
204
|
return executePull({
|
|
116
205
|
cwd,
|
|
117
206
|
project: args.project,
|
|
@@ -139,7 +228,7 @@ const push = defineCommand({
|
|
|
139
228
|
async run({ args }) {
|
|
140
229
|
await runCommand(args, async () => {
|
|
141
230
|
const cwd = await setup({ agent: true });
|
|
142
|
-
const { executePush } = await import("./studio-
|
|
231
|
+
const { executePush } = await import("./studio-sXvYUxr5.mjs");
|
|
143
232
|
return executePush({
|
|
144
233
|
cwd,
|
|
145
234
|
server: args.server,
|
|
@@ -169,7 +258,7 @@ const publish = defineCommand({
|
|
|
169
258
|
async run({ args }) {
|
|
170
259
|
await runCommand(args, async () => {
|
|
171
260
|
const cwd = await setup({ agent: true });
|
|
172
|
-
const { executePublish } = await import("./studio-
|
|
261
|
+
const { executePublish } = await import("./studio-sXvYUxr5.mjs");
|
|
173
262
|
return executePublish({
|
|
174
263
|
cwd,
|
|
175
264
|
server: args.server,
|
|
@@ -227,7 +316,7 @@ const init = defineCommand({
|
|
|
227
316
|
},
|
|
228
317
|
async run({ args }) {
|
|
229
318
|
await runCommand(args, async (mode) => {
|
|
230
|
-
const { executeInit } = await import("./init-
|
|
319
|
+
const { executeInit } = await import("./init-BT-IU9AR.mjs");
|
|
231
320
|
return executeInit({
|
|
232
321
|
dir: args.dir,
|
|
233
322
|
force: args.force,
|
|
@@ -256,7 +345,7 @@ const dev = defineCommand({
|
|
|
256
345
|
async run({ args }) {
|
|
257
346
|
await runCommand(args, async () => {
|
|
258
347
|
const cwd = await setup({ agent: true });
|
|
259
|
-
const { executeDev } = await import("./dev-
|
|
348
|
+
const { executeDev } = await import("./dev-C4KyxouE.mjs");
|
|
260
349
|
return executeDev({
|
|
261
350
|
cwd,
|
|
262
351
|
port: args.port
|
|
@@ -331,7 +420,7 @@ const deploy = defineCommand({
|
|
|
331
420
|
async run({ args }) {
|
|
332
421
|
await runCommand(args, async () => {
|
|
333
422
|
const cwd = await setup({ agent: true });
|
|
334
|
-
const { executeDeploy } = await import("./deploy-
|
|
423
|
+
const { executeDeploy } = await import("./deploy-Cp-wgME3.mjs");
|
|
335
424
|
return executeDeploy({
|
|
336
425
|
cwd,
|
|
337
426
|
server: args.server,
|
|
@@ -354,7 +443,7 @@ const del = defineCommand({
|
|
|
354
443
|
async run({ args }) {
|
|
355
444
|
await runCommand(args, async () => {
|
|
356
445
|
const cwd = await setup();
|
|
357
|
-
const { executeDelete } = await import("./delete-
|
|
446
|
+
const { executeDelete } = await import("./delete-DRNfvczK.mjs");
|
|
358
447
|
return executeDelete({
|
|
359
448
|
cwd,
|
|
360
449
|
server: args.server
|
|
@@ -385,7 +474,7 @@ const secret = defineCommand({
|
|
|
385
474
|
async run({ args }) {
|
|
386
475
|
await runCommand(args, async (mode) => {
|
|
387
476
|
const cwd = await setup();
|
|
388
|
-
const { executeSecretPut, NO_INPUT, readStdin } = await import("./secret-
|
|
477
|
+
const { executeSecretPut, NO_INPUT, readStdin } = await import("./secret-Dr0qnxeb.mjs");
|
|
389
478
|
const value = mode === "json" ? await readStdin() : void 0;
|
|
390
479
|
if (mode === "json" && !value) throw new CliError(...NO_INPUT);
|
|
391
480
|
return executeSecretPut(cwd, args.name, value, args.server);
|
|
@@ -409,7 +498,7 @@ const secret = defineCommand({
|
|
|
409
498
|
async run({ args }) {
|
|
410
499
|
await runCommand(args, async () => {
|
|
411
500
|
const cwd = await setup();
|
|
412
|
-
const { executeSecretDelete } = await import("./secret-
|
|
501
|
+
const { executeSecretDelete } = await import("./secret-Dr0qnxeb.mjs");
|
|
413
502
|
return executeSecretDelete(cwd, args.name, args.server);
|
|
414
503
|
});
|
|
415
504
|
}
|
|
@@ -426,7 +515,7 @@ const secret = defineCommand({
|
|
|
426
515
|
async run({ args }) {
|
|
427
516
|
await runCommand(args, async () => {
|
|
428
517
|
const cwd = await setup();
|
|
429
|
-
const { executeSecretList } = await import("./secret-
|
|
518
|
+
const { executeSecretList } = await import("./secret-Dr0qnxeb.mjs");
|
|
430
519
|
return executeSecretList(cwd, args.server);
|
|
431
520
|
});
|
|
432
521
|
}
|
|
@@ -461,7 +550,7 @@ const storage = defineCommand({
|
|
|
461
550
|
},
|
|
462
551
|
async run({ args }) {
|
|
463
552
|
await runCommand(args, async () => {
|
|
464
|
-
const { executeStorageStatus } = await import("./storage-
|
|
553
|
+
const { executeStorageStatus } = await import("./storage-CoQB8d-u.mjs");
|
|
465
554
|
return executeStorageStatus(resolveStorageCwd(args.dir), args.server);
|
|
466
555
|
});
|
|
467
556
|
}
|
|
@@ -478,7 +567,7 @@ const storage = defineCommand({
|
|
|
478
567
|
},
|
|
479
568
|
async run({ args }) {
|
|
480
569
|
await runCommand(args, async () => {
|
|
481
|
-
const { executeStorageEnable } = await import("./storage-
|
|
570
|
+
const { executeStorageEnable } = await import("./storage-CoQB8d-u.mjs");
|
|
482
571
|
return executeStorageEnable(resolveStorageCwd(args.dir), args.server);
|
|
483
572
|
});
|
|
484
573
|
}
|
|
@@ -500,7 +589,7 @@ const storage = defineCommand({
|
|
|
500
589
|
},
|
|
501
590
|
async run({ args }) {
|
|
502
591
|
await runCommand(args, async () => {
|
|
503
|
-
const { executeStorageDisable } = await import("./storage-
|
|
592
|
+
const { executeStorageDisable } = await import("./storage-CoQB8d-u.mjs");
|
|
504
593
|
return executeStorageDisable(resolveStorageCwd(args.dir), {
|
|
505
594
|
server: args.server,
|
|
506
595
|
force: args.force
|
|
@@ -521,7 +610,7 @@ const login = defineCommand({
|
|
|
521
610
|
},
|
|
522
611
|
async run({ args }) {
|
|
523
612
|
await runCommand(args, async () => {
|
|
524
|
-
const { executeLogin } = await import("./login-
|
|
613
|
+
const { executeLogin } = await import("./login-AA_UdRI-.mjs");
|
|
525
614
|
return executeLogin({ server: args.server });
|
|
526
615
|
});
|
|
527
616
|
}
|
|
@@ -534,7 +623,7 @@ const templates = defineCommand({
|
|
|
534
623
|
args: { json: sharedArgs.json },
|
|
535
624
|
async run({ args }) {
|
|
536
625
|
await runCommand(args, async (mode) => {
|
|
537
|
-
const { listTemplates } = await import("./_templates-
|
|
626
|
+
const { listTemplates } = await import("./_templates-Bt6u9_68.mjs");
|
|
538
627
|
const names = await listTemplates();
|
|
539
628
|
if (mode === "human") {
|
|
540
629
|
for (const name of names) log.message(name);
|
|
@@ -586,7 +675,21 @@ if (process.env.VITEST !== "true") {
|
|
|
586
675
|
}
|
|
587
676
|
process.argv.splice(2, 0, "publish");
|
|
588
677
|
};
|
|
589
|
-
|
|
678
|
+
/**
|
|
679
|
+
* Refuse an unrecognized flag instead of ignoring it.
|
|
680
|
+
*
|
|
681
|
+
* citty drops one silently, so `aai push --serverr=http://x` exited 0 having
|
|
682
|
+
* pushed to the DEFAULT server. `--server` decides where the API key and
|
|
683
|
+
* secret values go, so a typo that quietly retargets it is worth failing on.
|
|
684
|
+
*/
|
|
685
|
+
const assertKnownFlags = async () => {
|
|
686
|
+
const unknown = await unknownFlagsForArgv(mainCommand, process.argv.slice(2));
|
|
687
|
+
if (unknown.length === 0) return;
|
|
688
|
+
log.error(`Unknown ${unknown.length === 1 ? "option" : "options"}: ${unknown.join(", ")}`);
|
|
689
|
+
log.info("Run `aai <command> --help` to see the options it accepts.");
|
|
690
|
+
process.exit(1);
|
|
691
|
+
};
|
|
692
|
+
runDefault().then(assertKnownFlags).then(() => runMain(mainCommand)).catch((err) => {
|
|
590
693
|
log.error(errorMessage(err));
|
|
591
694
|
process.exitCode = 1;
|
|
592
695
|
});
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { n as log, u as ok } from "./_ui-8kOEB-JH.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { l as writeProjectConfig } from "./_config-Y5V-5Krn.mjs";
|
|
4
|
+
import { i as resolveDeployTarget, n as getServerInfo } from "./_agent-2nVugrN3.mjs";
|
|
5
|
+
import { n as apiRequest, t as HINT_NOT_DEPLOYED } from "./_api-client-B-upMGkc.mjs";
|
|
5
6
|
//#region delete.ts
|
|
6
7
|
async function runDelete(opts) {
|
|
7
8
|
await apiRequest(`${opts.url}/${opts.slug}`, {
|
|
@@ -32,6 +33,7 @@ async function executeDelete(opts) {
|
|
|
32
33
|
hints: { 404: "Run `aai list` to see your projects." }
|
|
33
34
|
});
|
|
34
35
|
log.success(`Deleted ${project}`);
|
|
36
|
+
await writeProjectConfig(cwd, { serverUrl });
|
|
35
37
|
return ok({
|
|
36
38
|
project,
|
|
37
39
|
...config.slug ? { slug: config.slug } : {}
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import { n as log, t as fmtUrl, u as ok } from "./_ui-8kOEB-JH.mjs";
|
|
3
3
|
import { a as errorMessage } from "./_utils-Ch0J4s6a.mjs";
|
|
4
4
|
import { t as buildAgentBundle } from "./_bundler-Cjaxa2wi.mjs";
|
|
5
|
-
import { s as updateProjectConfig } from "./_config-
|
|
5
|
+
import { s as updateProjectConfig } from "./_config-Y5V-5Krn.mjs";
|
|
6
6
|
import { t as resolveServerEnv } from "./_server-common-CnaP_Urf.mjs";
|
|
7
|
-
import { i as resolveDeployTarget } from "./_agent-
|
|
7
|
+
import { i as resolveDeployTarget } from "./_agent-2nVugrN3.mjs";
|
|
8
8
|
import { assertTypechecks } from "./_typecheck-gate-DvE8S3aQ.mjs";
|
|
9
|
-
import { n as apiRequest } from "./_api-client-
|
|
9
|
+
import { n as apiRequest } from "./_api-client-B-upMGkc.mjs";
|
|
10
10
|
import { gzipSync } from "node:zlib";
|
|
11
11
|
//#region _deploy.ts
|
|
12
12
|
async function runDeploy(opts) {
|
|
@@ -11,7 +11,7 @@ import { styleText } from "node:util";
|
|
|
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-BB5N8kdh.mjs");
|
|
15
15
|
let cleanup;
|
|
16
16
|
let shuttingDown = false;
|
|
17
17
|
const onSignal = () => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { a as unwrapCancel, n as log, u as ok } from "./_ui-8kOEB-JH.mjs";
|
|
3
3
|
import { a as errorMessage, c as readJson, l as resolveCwd, o as fileExists, t as AGENT_ENTRY } from "./_utils-Ch0J4s6a.mjs";
|
|
4
|
-
import { r as isDevMode, t as getMonorepoRoot } from "./_agent-
|
|
4
|
+
import { r as isDevMode, t as getMonorepoRoot } from "./_agent-2nVugrN3.mjs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { styleText } from "node:util";
|
|
7
7
|
import * as p from "@clack/prompts";
|
|
@@ -74,7 +74,7 @@ function resolveTargetDir(dir) {
|
|
|
74
74
|
}
|
|
75
75
|
/** Publish after init and return deploy metadata if successful. */
|
|
76
76
|
async function tryPublish(cwd, server) {
|
|
77
|
-
const { executePublish } = await import("./studio-
|
|
77
|
+
const { executePublish } = await import("./studio-sXvYUxr5.mjs");
|
|
78
78
|
try {
|
|
79
79
|
const result = await executePublish({
|
|
80
80
|
cwd,
|
|
@@ -93,7 +93,7 @@ async function tryPublish(cwd, server) {
|
|
|
93
93
|
}
|
|
94
94
|
/** Scaffold the project, optionally showing a spinner. */
|
|
95
95
|
async function scaffoldProject(dir, cwd, template, silent) {
|
|
96
|
-
const { runInit } = await import("./_init-
|
|
96
|
+
const { runInit } = await import("./_init-D7JIT-IJ.mjs");
|
|
97
97
|
const s = silent ? void 0 : p.spinner();
|
|
98
98
|
s?.start(`Creating ${dir}`);
|
|
99
99
|
await runInit({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { n as log, o as CliError, u as ok } from "./_ui-8kOEB-JH.mjs";
|
|
3
|
-
import { c as writeGlobalConfig, i as readGlobalConfig, r as getConfigDir, t as approveServer } from "./_config-
|
|
4
|
-
import { a as resolveServerUrl } from "./_agent-
|
|
3
|
+
import { c as writeGlobalConfig, i as readGlobalConfig, r as getConfigDir, t as approveServer } from "./_config-Y5V-5Krn.mjs";
|
|
4
|
+
import { a as resolveServerUrl } from "./_agent-2nVugrN3.mjs";
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import { setTimeout } from "node:timers/promises";
|
|
7
7
|
import { randomBytes } from "node:crypto";
|
|
@@ -35,6 +35,73 @@ async function jsonBody(res, what) {
|
|
|
35
35
|
if (body === null) throw new CliError("login_failed", `${what} returned an invalid response`);
|
|
36
36
|
return body;
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Fetch, turning a transport failure into an error that names the server.
|
|
40
|
+
*
|
|
41
|
+
* A connection failure here is undici's bare `TypeError: fetch failed` — no
|
|
42
|
+
* URL, no cause worth printing — and `aai login` is exactly where it is least
|
|
43
|
+
* diagnosable: the target is resolved (dev mode pins `localhost:8080`
|
|
44
|
+
* whenever the CLI itself lives in the monorepo, whatever the cwd), so the
|
|
45
|
+
* user cannot tell which server was unreachable, or that a local one was
|
|
46
|
+
* expected at all. `apiRequest` has always said "could not reach <url>";
|
|
47
|
+
* login used raw fetch and said nothing.
|
|
48
|
+
*/
|
|
49
|
+
async function reachable(fetchFn, url, serverUrl) {
|
|
50
|
+
try {
|
|
51
|
+
return await fetchFn(url);
|
|
52
|
+
} catch (err) {
|
|
53
|
+
throw unreachableError(serverUrl, err);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* "Could not reach <server>", with advice chosen by the kind of host and the
|
|
58
|
+
* original transport failure preserved as the cause.
|
|
59
|
+
*/
|
|
60
|
+
function unreachableError(serverUrl, cause) {
|
|
61
|
+
const hint = isLoopback(serverUrl) ? "That's a local server — start it with `pnpm dev:aai-server`, or pass `--server <url>` to log in elsewhere. (A CLI installed from the monorepo targets localhost by default; set AAI_NO_DEV=1 to use the hosted platform.)" : "Check your network connection and verify the server URL is correct.";
|
|
62
|
+
return new CliError("login_unreachable", `Could not reach ${serverUrl}.`, hint, { cause });
|
|
63
|
+
}
|
|
64
|
+
/** Whether `url`'s host is loopback — used only to pick a better hint. */
|
|
65
|
+
function isLoopback(url) {
|
|
66
|
+
try {
|
|
67
|
+
const { hostname } = new URL(url);
|
|
68
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
|
|
69
|
+
} catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Poll the exchange until the browser approves the link, or the deadline.
|
|
75
|
+
*
|
|
76
|
+
* A transport failure is retried rather than fatal: the user is off in a
|
|
77
|
+
* browser approving, and a dev server reloading in that window would
|
|
78
|
+
* otherwise lose a login that was about to succeed. The last one is
|
|
79
|
+
* remembered, so a server that never comes back is reported as unreachable
|
|
80
|
+
* rather than as "you didn't approve in time" — which would blame the user
|
|
81
|
+
* for someone else's outage.
|
|
82
|
+
*/
|
|
83
|
+
async function pollForGrant(fetchFn, serverUrl, code, opts) {
|
|
84
|
+
let lastTransportError;
|
|
85
|
+
for (;;) {
|
|
86
|
+
let res = null;
|
|
87
|
+
try {
|
|
88
|
+
res = await fetchFn(`${serverUrl}/studio/cli-link/exchange`, {
|
|
89
|
+
method: "POST",
|
|
90
|
+
headers: { "Content-Type": "application/json" },
|
|
91
|
+
body: JSON.stringify({ code })
|
|
92
|
+
});
|
|
93
|
+
lastTransportError = void 0;
|
|
94
|
+
} catch (err) {
|
|
95
|
+
lastTransportError = err;
|
|
96
|
+
}
|
|
97
|
+
if (res && res.status !== 404) return await jsonBody(res, "Linking your account");
|
|
98
|
+
if (Date.now() >= opts.deadline) {
|
|
99
|
+
if (lastTransportError !== void 0) throw unreachableError(serverUrl, lastTransportError);
|
|
100
|
+
throw new CliError("login_timeout", "Timed out waiting for the link to be approved in the browser.", "Run `aai login` again and approve the link within five minutes.");
|
|
101
|
+
}
|
|
102
|
+
await setTimeout(opts.intervalMs);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
38
105
|
function requireTty() {
|
|
39
106
|
if (!process.stdin.isTTY) throw new CliError("login_interactive", "`aai login` is interactive and needs a TTY.", "Non-interactive setups can set the ASSEMBLYAI_API_KEY environment variable instead.");
|
|
40
107
|
}
|
|
@@ -77,29 +144,17 @@ async function executeLogin(opts, deps = {}) {
|
|
|
77
144
|
const globalConfig = await readGlobalConfig();
|
|
78
145
|
const serverUrl = resolveServerUrl(opts.server, void 0, globalConfig.approvedServers ?? []);
|
|
79
146
|
if (opts.server) await approveServer(serverUrl);
|
|
80
|
-
if ((await jsonBody(await fetchFn
|
|
147
|
+
if ((await jsonBody(await reachable(fetchFn, `${serverUrl}/studio/auth`, serverUrl), "Reading the server's login configuration")).mode === "none") throw new CliError("login_unavailable", "This server has no browser login configured, so there is no account to link.", "Set the ASSEMBLYAI_API_KEY environment variable instead — it's the only other way to authenticate.");
|
|
81
148
|
const code = randomBytes(32).toString("base64url");
|
|
82
149
|
const linkUrl = `${serverUrl}/?cli-link=${code}`;
|
|
83
150
|
log.info(`Opening the browser to link your account…\n ${linkUrl}`);
|
|
84
151
|
log.info(`Confirmation code: ${linkConfirmationCode(code)}`);
|
|
85
152
|
log.info("Approve the link in the browser (sign in there first if you need to) — the approval page shows the same code.");
|
|
86
153
|
(deps.openBrowser ?? defaultOpenBrowser)(linkUrl);
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const res = await fetchFn(`${serverUrl}/studio/cli-link/exchange`, {
|
|
92
|
-
method: "POST",
|
|
93
|
-
headers: { "Content-Type": "application/json" },
|
|
94
|
-
body: JSON.stringify({ code })
|
|
95
|
-
});
|
|
96
|
-
if (res.status !== 404) {
|
|
97
|
-
granted = await jsonBody(res, "Linking your account");
|
|
98
|
-
break;
|
|
99
|
-
}
|
|
100
|
-
if (Date.now() >= deadline) throw new CliError("login_timeout", "Timed out waiting for the link to be approved in the browser.", "Run `aai login` again and approve the link within five minutes.");
|
|
101
|
-
await setTimeout(pollInterval);
|
|
102
|
-
}
|
|
154
|
+
const granted = await pollForGrant(fetchFn, serverUrl, code, {
|
|
155
|
+
intervalMs: deps.pollIntervalMs ?? LINK_POLL_INTERVAL_MS,
|
|
156
|
+
deadline: Date.now() + (deps.timeoutMs ?? LINK_TIMEOUT_MS)
|
|
157
|
+
});
|
|
103
158
|
if (!granted.apiKey) throw new CliError("login_failed", "Linking your account did not return an API key.");
|
|
104
159
|
const dir = getConfigDir();
|
|
105
160
|
await writeGlobalConfig(dir, {
|
|
@@ -11,15 +11,15 @@
|
|
|
11
11
|
"publish:agent": "aai publish"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@alexkroman1/aai": "^5.
|
|
15
|
-
"@alexkroman1/aai-ui": "^5.
|
|
14
|
+
"@alexkroman1/aai": "^5.7.0",
|
|
15
|
+
"@alexkroman1/aai-ui": "^5.7.0",
|
|
16
16
|
"react": "^19.2.8",
|
|
17
17
|
"react-dom": "^19.2.8",
|
|
18
18
|
"tailwindcss": "^4.0.0",
|
|
19
19
|
"zod": "^4.4.3"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
|
-
"@alexkroman1/aai-cli": "^5.
|
|
22
|
+
"@alexkroman1/aai-cli": "^5.7.0",
|
|
23
23
|
"@tailwindcss/vite": "^4.3.3",
|
|
24
24
|
"@types/node": "^26.1.1",
|
|
25
25
|
"@types/react": "^19.2.17",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { a as unwrapCancel, n as log, s as fail, u as ok } from "./_ui-8kOEB-JH.mjs";
|
|
3
|
-
import { t as slugRequest } from "./_slug-api-
|
|
3
|
+
import { t as slugRequest } from "./_slug-api-CGJSST9B.mjs";
|
|
4
4
|
import * as p from "@clack/prompts";
|
|
5
5
|
import { text } from "node:stream/consumers";
|
|
6
6
|
//#region secret.ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { n as log, s as fail, u as ok } from "./_ui-8kOEB-JH.mjs";
|
|
3
|
-
import { t as slugRequest } from "./_slug-api-
|
|
3
|
+
import { t as slugRequest } from "./_slug-api-CGJSST9B.mjs";
|
|
4
4
|
import * as p from "@clack/prompts";
|
|
5
5
|
//#region storage.ts
|
|
6
6
|
async function storageRequest(cwd, init, server) {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { n as log, o as CliError, t as fmtUrl, u as ok } from "./_ui-8kOEB-JH.mjs";
|
|
3
|
-
import { s as updateProjectConfig } from "./_config-
|
|
3
|
+
import { s as updateProjectConfig } from "./_config-Y5V-5Krn.mjs";
|
|
4
4
|
import { t as resolveServerEnv } from "./_server-common-CnaP_Urf.mjs";
|
|
5
|
-
import { i as resolveDeployTarget } from "./_agent-
|
|
6
|
-
import { layerScaffold } from "./_templates-
|
|
7
|
-
import { n as apiRequest } from "./_api-client-
|
|
5
|
+
import { i as resolveDeployTarget } from "./_agent-2nVugrN3.mjs";
|
|
6
|
+
import { layerScaffold } from "./_templates-Bt6u9_68.mjs";
|
|
7
|
+
import { n as apiRequest } from "./_api-client-B-upMGkc.mjs";
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
10
|
-
import { VALID_SLUG_RE } from "@alexkroman1/aai/utils";
|
|
10
|
+
import { PREVIEW_SLUG_SUFFIX, VALID_SLUG_RE } from "@alexkroman1/aai/utils";
|
|
11
11
|
//#region _studio.ts
|
|
12
12
|
/**
|
|
13
13
|
* Internals of the studio-workspace commands (`aai list/pull/push/publish`):
|
|
@@ -60,10 +60,32 @@ async function walkProject(dir, current = dir) {
|
|
|
60
60
|
return out.sort((a, b) => a.localeCompare(b));
|
|
61
61
|
}
|
|
62
62
|
/**
|
|
63
|
+
* Decode `buf` as UTF-8, or null when it isn't valid UTF-8.
|
|
64
|
+
*
|
|
65
|
+
* `fatal` makes an invalid sequence throw instead of becoming U+FFFD, which
|
|
66
|
+
* is the whole point: a workspace is a JSON path→string map and cannot carry
|
|
67
|
+
* arbitrary bytes, so a lossy read turned a pushed PNG into replacement
|
|
68
|
+
* characters while reporting success — and a later `aai pull` wrote the
|
|
69
|
+
* mangled version back over the local original. `ignoreBOM` keeps a leading
|
|
70
|
+
* U+FEFF in the string; without it the decoder strips the BOM and the check
|
|
71
|
+
* meant to stop corruption would quietly perform some of its own.
|
|
72
|
+
*/
|
|
73
|
+
const UTF8_STRICT = new TextDecoder("utf-8", {
|
|
74
|
+
fatal: true,
|
|
75
|
+
ignoreBOM: true
|
|
76
|
+
});
|
|
77
|
+
function decodeUtf8(buf) {
|
|
78
|
+
try {
|
|
79
|
+
return UTF8_STRICT.decode(buf);
|
|
80
|
+
} catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
63
85
|
* Walk a local project into the path→content record a workspace stores —
|
|
64
86
|
* the CLI-side twin of the guest's `snapshotWorkspace`: same ignored
|
|
65
|
-
* directories, same caps, oversized files skipped with a
|
|
66
|
-
* than failing the whole push.
|
|
87
|
+
* directories, same caps, oversized and non-text files skipped with a
|
|
88
|
+
* warning rather than failing the whole push.
|
|
67
89
|
*/
|
|
68
90
|
async function collectSourceFiles(dir) {
|
|
69
91
|
const paths = await walkProject(dir);
|
|
@@ -77,16 +99,31 @@ async function collectSourceFiles(dir) {
|
|
|
77
99
|
warnings.push(`${rel} is ${st.size} bytes (max ${MAX_STUDIO_FILE_BYTES}) — not synced.`);
|
|
78
100
|
continue;
|
|
79
101
|
}
|
|
80
|
-
|
|
102
|
+
const content = decodeUtf8(await readFile(abs));
|
|
103
|
+
if (content === null) {
|
|
104
|
+
warnings.push(`${rel} is not valid UTF-8 (binary file?) — not synced.`);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
files[rel.split(path.sep).join("/")] = content;
|
|
81
108
|
}
|
|
82
109
|
return {
|
|
83
110
|
files,
|
|
84
111
|
warnings
|
|
85
112
|
};
|
|
86
113
|
}
|
|
87
|
-
/**
|
|
114
|
+
/**
|
|
115
|
+
* A studio project name derived from a directory name, or null if unusable.
|
|
116
|
+
*
|
|
117
|
+
* A `-preview` suffix is deliberately unusable. Publishing a project deploys
|
|
118
|
+
* it under the project's own name, so a `*-preview` project would claim a
|
|
119
|
+
* slug the studio's orphan-preview sweep reaps hourly — deleting the agent,
|
|
120
|
+
* its app-database schema, and its secrets on a schedule the user never
|
|
121
|
+
* asked for. Refusing the name is recoverable (rename the directory); losing
|
|
122
|
+
* a published agent to the reaper is not.
|
|
123
|
+
*/
|
|
88
124
|
function projectNameFromDir(dir) {
|
|
89
125
|
const name = path.basename(dir).toLowerCase().replace(/[^a-z0-9-_]+/g, "-").replace(/-{2,}/g, "-").replace(/^[-_]+|[-_]+$/g, "").slice(0, 64);
|
|
126
|
+
if (name.endsWith(PREVIEW_SLUG_SUFFIX)) return null;
|
|
90
127
|
return VALID_SLUG_RE.test(name) ? name : null;
|
|
91
128
|
}
|
|
92
129
|
/** The shareable studio URL for a project — what every command prints. */
|
|
@@ -217,7 +254,8 @@ async function pushProject(opts) {
|
|
|
217
254
|
created: result.created,
|
|
218
255
|
serverUrl,
|
|
219
256
|
apiKey,
|
|
220
|
-
slug
|
|
257
|
+
slug,
|
|
258
|
+
warnings
|
|
221
259
|
};
|
|
222
260
|
}
|
|
223
261
|
async function executePush(opts) {
|
|
@@ -227,7 +265,8 @@ async function executePush(opts) {
|
|
|
227
265
|
return ok({
|
|
228
266
|
project: pushed.project,
|
|
229
267
|
created: pushed.created,
|
|
230
|
-
url
|
|
268
|
+
url,
|
|
269
|
+
...pushed.warnings.length > 0 ? { warnings: pushed.warnings } : {}
|
|
231
270
|
});
|
|
232
271
|
}
|
|
233
272
|
/**
|
|
@@ -260,6 +299,7 @@ async function executePublish(opts) {
|
|
|
260
299
|
if (pushed.slug) await syncEnvSecrets(opts.cwd, serverUrl, apiKey, pushed.slug);
|
|
261
300
|
log.step(`Publishing ${project} (builds in the project's sandbox)…`);
|
|
262
301
|
const result = await publishStudioProject(serverUrl, apiKey, project);
|
|
302
|
+
if (typeof result?.slug !== "string" || typeof result?.output !== "string") throw new CliError("bad_publish_response", `Unexpected response from the publish route at ${serverUrl}.`, "Check that --server points at an aai platform server, then try again.");
|
|
263
303
|
if (result.output.trim()) log.message(result.output.trim());
|
|
264
304
|
await updateProjectConfig(opts.cwd, {
|
|
265
305
|
serverUrl,
|
|
@@ -277,7 +317,8 @@ async function executePublish(opts) {
|
|
|
277
317
|
slug: result.slug,
|
|
278
318
|
url: agentUrl,
|
|
279
319
|
studioUrl,
|
|
280
|
-
output: result.output
|
|
320
|
+
output: result.output,
|
|
321
|
+
...pushed.warnings.length > 0 ? { warnings: pushed.warnings } : {}
|
|
281
322
|
});
|
|
282
323
|
}
|
|
283
324
|
//#endregion
|
package/dist/studio.d.ts
CHANGED
|
@@ -37,6 +37,7 @@ export declare function executePush(opts: {
|
|
|
37
37
|
project: string;
|
|
38
38
|
created: boolean;
|
|
39
39
|
url: string;
|
|
40
|
+
warnings?: string[];
|
|
40
41
|
}>>;
|
|
41
42
|
export declare function executePublish(opts: {
|
|
42
43
|
cwd: string;
|
|
@@ -49,4 +50,5 @@ export declare function executePublish(opts: {
|
|
|
49
50
|
url: string;
|
|
50
51
|
studioUrl: string;
|
|
51
52
|
output: string;
|
|
53
|
+
warnings?: string[];
|
|
52
54
|
}>>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alexkroman1/aai-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"aai": "dist/cli.mjs"
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"p-timeout": "^7.0.1",
|
|
38
38
|
"vite": "^8.1.5",
|
|
39
39
|
"zod": "^4.4.3",
|
|
40
|
-
"@alexkroman1/aai": "5.
|
|
41
|
-
"@alexkroman1/aai-ui": "5.
|
|
40
|
+
"@alexkroman1/aai": "5.7.0",
|
|
41
|
+
"@alexkroman1/aai-ui": "5.7.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"playwright": "^1.61.1",
|