@alexkroman1/aai-cli 5.11.0 → 5.13.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.
Files changed (51) hide show
  1. package/dist/{_agent-DS2PUJcl.mjs → _agent-C8Jk-ZcP.mjs} +3 -2
  2. package/dist/_agent.d.ts +1 -0
  3. package/dist/{_api-client-B-upMGkc.mjs → _api-client-BX8Qv51n.mjs} +2 -1
  4. package/dist/{_bundler-DC17suWN.mjs → _bundler-BuIyDdvs.mjs} +30 -11
  5. package/dist/_bundler.d.ts +16 -2
  6. package/dist/{_config-DMyolIk9.mjs → _config-CzwAWOWr.mjs} +3 -3
  7. package/dist/_deploy.d.ts +0 -10
  8. package/dist/_dev-restart.d.ts +59 -0
  9. package/dist/{_dev-server-B3ivqyEd.mjs → _dev-server-Ch7K8e8i.mjs} +181 -92
  10. package/dist/_dev-server.d.ts +17 -0
  11. package/dist/{_init-5hHkHe-V.mjs → _init-CbkrE2wq.mjs} +3 -3
  12. package/dist/_preflight-BtfaYtbE.mjs +46 -0
  13. package/dist/_preflight.d.ts +57 -0
  14. package/dist/{_server-common-DX8Bfrf5.mjs → _server-common-61fL__39.mjs} +1 -1
  15. package/dist/_slug-api-D30zPmsn.mjs +50 -0
  16. package/dist/_slug-api.d.ts +16 -0
  17. package/dist/_studio-D2ciIhbi.mjs +94 -0
  18. package/dist/_studio.d.ts +19 -16
  19. package/dist/{_templates-DmdG-voA.mjs → _templates-B1kt3BGt.mjs} +2 -2
  20. package/dist/{_typecheck-gate-DB-PY0A3.mjs → _typecheck-gate-4v9UBZHh.mjs} +1 -1
  21. package/dist/{_ui-DfwfDbT-.mjs → _ui-u7T4YooX.mjs} +5 -1
  22. package/dist/_ui.d.ts +2 -0
  23. package/dist/{_utils-8KKw-bzi.mjs → _utils-0DHbcqUb.mjs} +2 -2
  24. package/dist/{build-CACFbdQ4.mjs → build-DQJ1ZSm9.mjs} +4 -4
  25. package/dist/cli.d.ts +2 -1
  26. package/dist/cli.mjs +70 -34
  27. package/dist/{client-bundler-DONm-khu.mjs → client-bundler-C9qXL8A2.mjs} +1 -1
  28. package/dist/client-bundler.mjs +1 -1
  29. package/dist/{delete-DEZ7u3u4.mjs → delete-DCRX3d96.mjs} +4 -4
  30. package/dist/{deploy-Ch0d_jje.mjs → deploy-DjsEV-40.mjs} +27 -26
  31. package/dist/deploy.d.ts +0 -2
  32. package/dist/{dev-CdeRcYiQ.mjs → dev-CvPH5dbF.mjs} +3 -3
  33. package/dist/{eject-DSrAp8PL.mjs → eject-LgBRfnbH.mjs} +3 -3
  34. package/dist/{init-BBAoRI_f.mjs → init-BppxMR8r.mjs} +5 -5
  35. package/dist/{login-BeFUiU6M.mjs → login-BQQaxJhL.mjs} +4 -15
  36. package/dist/login.d.ts +0 -9
  37. package/dist/project-config.d.ts +23 -0
  38. package/dist/project-config.mjs +3 -0
  39. package/dist/scaffold/CLAUDE.md +60 -10
  40. package/dist/scaffold/package.json +3 -3
  41. package/dist/{secret-4_dYyrpA.mjs → secret-Ds9dvqAj.mjs} +7 -7
  42. package/dist/{storage-BTfErOOW.mjs → storage-DbrQSHFk.mjs} +2 -2
  43. package/dist/{studio-DPDFKZ9i.mjs → studio-GSFd4s5O.mjs} +12 -179
  44. package/dist/templates/dispatch-center/shared.ts +13 -15
  45. package/dist/templates/pizza-ordering/agent.test.ts +3 -0
  46. package/dist/templates/retail/store.ts +11 -17
  47. package/dist/templates/solo-rpg/agent.test.ts +3 -0
  48. package/dist/{test-C-V98oC-.mjs → test-2LWBcXvY.mjs} +2 -2
  49. package/dist/typecheck.mjs +1 -1
  50. package/package.json +8 -3
  51. package/dist/_slug-api-fRNNR8tz.mjs +0 -27
@@ -0,0 +1,57 @@
1
+ /**
2
+ * The deploy-time credential preflight — the classic dev/prod failure, caught
3
+ * before the upload instead of at the deployed agent's first session.
4
+ *
5
+ * An agent that ran locally on shell-exported provider keys dies after deploy
6
+ * with what looks like a provider outage: `aai dev` falls back to the shell
7
+ * (`withHostCredentialFallback`), and the platform never will. Naming the key
8
+ * here turns that into one line of output while the developer is still in the
9
+ * directory that owns the `.env`.
10
+ *
11
+ * **This lives in the CLI because the agent's config does.** The platform
12
+ * stores no description of a bundle and never evaluates one (see "The
13
+ * platform stores no agent config" in packages/aai-server/CLAUDE.md), so the
14
+ * only place that knows an agent needs `CARTESIA_API_KEY` is the process that
15
+ * just built it.
16
+ *
17
+ * **It WARNS, and must not be turned into a hard failure.** The CLI sees the
18
+ * env it is about to upload; it cannot see what is already stored against the
19
+ * slug from an earlier `aai secret put`, so a key the platform holds looks
20
+ * missing from here. The server-side check this replaced could see both and
21
+ * so could reject — moving the config to where it is authored costs that
22
+ * accuracy, and a false rejection is worse than a false warning: it blocks a
23
+ * deploy that would have worked.
24
+ */
25
+ import { requiredProviderEnvVars } from "@alexkroman1/aai/runtime";
26
+ /**
27
+ * The config shape read out of a bundle's `__aaiConfig` export: the provider
28
+ * descriptors the credential derivation needs, plus the agent's declared
29
+ * `requiredEnv`. Deliberately structural rather than the SDK's `AgentConfig` —
30
+ * the export comes from the USER's installed SDK, which may be older or newer
31
+ * than this CLI's, so anything beyond these is not ours to assume.
32
+ *
33
+ * **The `__aaiConfig` export, not the bundle's default export.** They look
34
+ * interchangeable — `evalWorkerBundle` already returns the `AgentDef`, and
35
+ * `agentEnvWarnings` (`_dev-server.ts`) derives the same key set from one —
36
+ * but `__aaiConfig` is `toAgentConfig(def)`, which has run
37
+ * `normalizeAgentConveniences` and `defaultProviders`. A def written with an
38
+ * author shorthand (`llm: "gpt-5"`) still carries a STRING there, and
39
+ * `descriptorKind` reads a string as no kind at all, so deriving from the raw
40
+ * def silently omits that provider's key. The normalized config is what the
41
+ * runtime will actually resolve against, so it is what the preflight checks.
42
+ */
43
+ export type PreflightConfig = Parameters<typeof requiredProviderEnvVars>[0] & {
44
+ requiredEnv?: readonly string[] | undefined;
45
+ };
46
+ /**
47
+ * Env var names the agent needs that the env being uploaded doesn't supply.
48
+ * Empty values count as missing — an empty credential authenticates nothing.
49
+ *
50
+ * Two sources: provider credentials derived from the stt/llm/tts/s2s
51
+ * descriptors (the same registry-backed derivation the runtime resolves keys
52
+ * with), and the agent's own declared `requiredEnv` — an `agent()` field for
53
+ * custom keys tools read from `ctx.env`, which no static derivation can see.
54
+ */
55
+ export declare function missingCredentials(config: PreflightConfig, env: Record<string, string>): string[];
56
+ /** One line naming the missing keys and what to do about them. */
57
+ export declare function missingCredentialMessage(missing: string[]): string;
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { r as errorCode } from "./_utils-8KKw-bzi.mjs";
2
+ import { r as errorCode } from "./_utils-0DHbcqUb.mjs";
3
3
  import path from "node:path";
4
4
  import { parseEnv } from "node:util";
5
5
  import fs from "node:fs/promises";
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+ import { n as getServerInfo } from "./_agent-C8Jk-ZcP.mjs";
3
+ import { n as apiRequest, t as HINT_NOT_DEPLOYED } from "./_api-client-BX8Qv51n.mjs";
4
+ //#region _slug-api.ts
5
+ /**
6
+ * Authenticated request against a deployed agent's slug-scoped resource
7
+ * (`${serverUrl}/${slug}${resourcePath}`) — the one shape every per-agent
8
+ * command (secret, storage) shares, including the standard "not deployed"
9
+ * 404 hint.
10
+ *
11
+ * Its own module (rather than living beside `getServerInfo` in `_agent.ts`)
12
+ * so tests can mock `_agent.ts`/`_api-client.ts` while this composition
13
+ * stays real — an intra-module call would bypass those mocks.
14
+ */
15
+ /**
16
+ * A SECRET request, routed to the project when this directory is linked to
17
+ * one and to the bare slug otherwise.
18
+ *
19
+ * A studio project deploys TWO agents — production and preview — and a
20
+ * secret has to reach both or the preview fails at its first session while
21
+ * production works. The project route fans out server-side
22
+ * (`aai-studio-server/studio-secrets.ts`); this CLI used to write the
23
+ * production slug alone, so a key set here was missing from the preview the
24
+ * user's own `aai publish` had created. An unlinked directory has one agent
25
+ * and keeps the per-slug route, which is the platform primitive underneath.
26
+ */
27
+ async function secretRequest(cwd, resourcePath, init, server) {
28
+ const { serverUrl, slug, apiKey, studioProject } = await getServerInfo(cwd, server);
29
+ return {
30
+ data: await apiRequest(studioProject ? `${serverUrl}/studio/projects/${encodeURIComponent(studioProject)}/secret${resourcePath}` : `${serverUrl}/${slug}/secret${resourcePath}`, {
31
+ ...init,
32
+ apiKey,
33
+ hints: { 404: HINT_NOT_DEPLOYED }
34
+ }),
35
+ target: studioProject ?? slug
36
+ };
37
+ }
38
+ async function slugRequest(cwd, resourcePath, init, server) {
39
+ const { serverUrl, slug, apiKey } = await getServerInfo(cwd, server);
40
+ return {
41
+ data: await apiRequest(`${serverUrl}/${slug}${resourcePath}`, {
42
+ ...init,
43
+ apiKey,
44
+ hints: { 404: HINT_NOT_DEPLOYED }
45
+ }),
46
+ slug
47
+ };
48
+ }
49
+ //#endregion
50
+ export { slugRequest as n, secretRequest as t };
@@ -9,6 +9,22 @@ import { type ApiRequestOptions } from "./_api-client.ts";
9
9
  * so tests can mock `_agent.ts`/`_api-client.ts` while this composition
10
10
  * stays real — an intra-module call would bypass those mocks.
11
11
  */
12
+ /**
13
+ * A SECRET request, routed to the project when this directory is linked to
14
+ * one and to the bare slug otherwise.
15
+ *
16
+ * A studio project deploys TWO agents — production and preview — and a
17
+ * secret has to reach both or the preview fails at its first session while
18
+ * production works. The project route fans out server-side
19
+ * (`aai-studio-server/studio-secrets.ts`); this CLI used to write the
20
+ * production slug alone, so a key set here was missing from the preview the
21
+ * user's own `aai publish` had created. An unlinked directory has one agent
22
+ * and keeps the per-slug route, which is the platform primitive underneath.
23
+ */
24
+ export declare function secretRequest<T = unknown>(cwd: string, resourcePath: string, init: Pick<ApiRequestOptions, "method" | "body" | "action">, server?: string): Promise<{
25
+ data: T;
26
+ target: string;
27
+ }>;
12
28
  export declare function slugRequest<T = unknown>(cwd: string, resourcePath: string, init: Pick<ApiRequestOptions, "method" | "body" | "action">, server?: string): Promise<{
13
29
  data: T;
14
30
  slug: string;
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env node
2
+ import { n as apiRequest } from "./_api-client-BX8Qv51n.mjs";
3
+ import path from "node:path";
4
+ import { MAX_SLUG_LENGTH, PREVIEW_SLUG_SUFFIX, VALID_SLUG_RE } from "@alexkroman1/aai/utils";
5
+ import { slugifyName } from "@alexkroman1/aai/slugify";
6
+ import { isLocalOnlyFile, snapshotWorkspaceFiles } from "@alexkroman1/aai/workspace-files";
7
+ //#region _studio.ts
8
+ /**
9
+ * Internals of the studio-workspace commands (`aai list/pull/push/publish`):
10
+ * the local source-file walk and the thin clients for the platform's
11
+ * `/studio/projects` routes. The workspace is the single source of truth —
12
+ * these helpers only move files between a local directory and the project's
13
+ * workspace row; production deploys happen exclusively through the studio's
14
+ * Publish route (which runs the deploy machinery in the project's sandbox).
15
+ */
16
+ /**
17
+ * Snapshot the local project into a workspace file map.
18
+ *
19
+ * The walk, the caps, the skip rules and the strict UTF-8 decode all come
20
+ * from the SDK (`@alexkroman1/aai/workspace-files`), which is also what the
21
+ * guest's end-of-turn sync uses — the two write the same map from opposite
22
+ * ends, and a disagreement between them is not an error but a file silently
23
+ * dropped here and resurrected there.
24
+ *
25
+ * `isLocalOnlyFile` is the one rule this side adds: `.env` rides the secret
26
+ * routes and lockfiles are install output, so neither belongs in a row.
27
+ */
28
+ function collectSourceFiles(dir) {
29
+ return snapshotWorkspaceFiles(dir, {
30
+ subject: "Project",
31
+ skipFile: isLocalOnlyFile
32
+ });
33
+ }
34
+ /**
35
+ * A studio project name derived from a directory name, or null if unusable.
36
+ *
37
+ * The normalization is the platform's own (`slugifyName`), not a local
38
+ * regex. This used to strip `[^a-z0-9-_]` by hand, which differs from the
39
+ * studio's slugifier on exactly the names people give agents: `Café
40
+ * Ordering` reduced to `caf-ordering` here and `cafe-ordering` there, so the
41
+ * same directory produced a different project depending on whether it was
42
+ * created by `aai push` or by typing the name into the studio.
43
+ *
44
+ * A `-preview` suffix is deliberately unusable. Publishing a project deploys
45
+ * it under the project's own name, so a `*-preview` project would claim a
46
+ * slug the studio's orphan-preview sweep reaps hourly — deleting the agent,
47
+ * its app-database schema, and its secrets on a schedule the user never
48
+ * asked for. Refusing the name is recoverable (rename the directory); losing
49
+ * a published agent to the reaper is not.
50
+ */
51
+ function projectNameFromDir(dir) {
52
+ const name = slugifyName(path.basename(dir), MAX_SLUG_LENGTH);
53
+ if (name.endsWith(PREVIEW_SLUG_SUFFIX)) return null;
54
+ return VALID_SLUG_RE.test(name) ? name : null;
55
+ }
56
+ /** The shareable studio URL for a project — what every command prints. */
57
+ function studioProjectUrl(serverUrl, project) {
58
+ return `${serverUrl}/studio/chat/${project}`;
59
+ }
60
+ function listStudioProjects(serverUrl, apiKey) {
61
+ return apiRequest(`${serverUrl}/studio/projects`, {
62
+ apiKey,
63
+ action: "list"
64
+ }).then((res) => res.projects);
65
+ }
66
+ /** Fetch a project, or null when it doesn't exist (the push existence probe). */
67
+ function fetchStudioProject(serverUrl, apiKey, project) {
68
+ return apiRequest(`${serverUrl}/studio/projects/${encodeURIComponent(project)}`, {
69
+ apiKey,
70
+ action: "pull",
71
+ allow404: true
72
+ });
73
+ }
74
+ /** `PUT /studio/projects/:project/source` — the atomic whole-tree push. */
75
+ function pushStudioSource(serverUrl, apiKey, project, body) {
76
+ return apiRequest(`${serverUrl}/studio/projects/${encodeURIComponent(project)}/source`, {
77
+ apiKey,
78
+ action: "push",
79
+ method: "PUT",
80
+ body,
81
+ hints: { 409: "The studio has newer changes. Run `aai pull` to fetch them, or `aai push --force` to overwrite." }
82
+ });
83
+ }
84
+ /** `POST /studio/projects/:project/deploy` — Publish, in the project's sandbox. */
85
+ function publishStudioProject(serverUrl, apiKey, project) {
86
+ return apiRequest(`${serverUrl}/studio/projects/${encodeURIComponent(project)}/deploy`, {
87
+ apiKey,
88
+ action: "publish",
89
+ method: "POST",
90
+ retry: 0
91
+ });
92
+ }
93
+ //#endregion
94
+ export { publishStudioProject as a, projectNameFromDir as i, fetchStudioProject as n, pushStudioSource as o, listStudioProjects as r, studioProjectUrl as s, collectSourceFiles as t };
package/dist/_studio.d.ts CHANGED
@@ -6,27 +6,30 @@
6
6
  * workspace row; production deploys happen exclusively through the studio's
7
7
  * Publish route (which runs the deploy machinery in the project's sandbox).
8
8
  */
9
+ import { type WorkspaceSnapshot } from "@alexkroman1/aai/workspace-files";
9
10
  /**
10
- * Mirrors of the studio workspace caps (`aai-studio-server/studio-limits.ts`)
11
- * — by value, like `aai-guest/limits.ts`, since the CLI cannot depend on the
12
- * private server package. The server re-validates every push; these exist so
13
- * an oversized file is a named warning locally instead of a rejected upload.
11
+ * Snapshot the local project into a workspace file map.
12
+ *
13
+ * The walk, the caps, the skip rules and the strict UTF-8 decode all come
14
+ * from the SDK (`@alexkroman1/aai/workspace-files`), which is also what the
15
+ * guest's end-of-turn sync uses — the two write the same map from opposite
16
+ * ends, and a disagreement between them is not an error but a file silently
17
+ * dropped here and resurrected there.
18
+ *
19
+ * `isLocalOnlyFile` is the one rule this side adds: `.env` rides the secret
20
+ * routes and lockfiles are install output, so neither belongs in a row.
14
21
  */
15
- export declare const MAX_STUDIO_FILE_BYTES = 256000;
16
- export declare const MAX_STUDIO_FILES = 100;
17
- /**
18
- * Walk a local project into the path→content record a workspace stores —
19
- * the CLI-side twin of the guest's `snapshotWorkspace`: same ignored
20
- * directories, same caps, oversized and non-text files skipped with a
21
- * warning rather than failing the whole push.
22
- */
23
- export declare function collectSourceFiles(dir: string): Promise<{
24
- files: Record<string, string>;
25
- warnings: string[];
26
- }>;
22
+ export declare function collectSourceFiles(dir: string): Promise<WorkspaceSnapshot>;
27
23
  /**
28
24
  * A studio project name derived from a directory name, or null if unusable.
29
25
  *
26
+ * The normalization is the platform's own (`slugifyName`), not a local
27
+ * regex. This used to strip `[^a-z0-9-_]` by hand, which differs from the
28
+ * studio's slugifier on exactly the names people give agents: `Café
29
+ * Ordering` reduced to `caf-ordering` here and `cafe-ordering` there, so the
30
+ * same directory produced a different project depending on whether it was
31
+ * created by `aai push` or by typing the name into the studio.
32
+ *
30
33
  * A `-preview` suffix is deliberately unusable. Publishing a project deploys
31
34
  * it under the project's own name, so a `*-preview` project would claim a
32
35
  * slug the studio's orphan-preview sweep reaps hourly — deleting the agent,
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { a as errorMessage } from "./_utils-8KKw-bzi.mjs";
3
- import { t as getMonorepoRoot } from "./_agent-DS2PUJcl.mjs";
2
+ import { a as errorMessage } from "./_utils-0DHbcqUb.mjs";
3
+ import { t as getMonorepoRoot } from "./_agent-C8Jk-ZcP.mjs";
4
4
  import { existsSync } from "node:fs";
5
5
  import path from "node:path";
6
6
  import fs from "node:fs/promises";
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { t as CliError } from "./_output-CC300DzW.mjs";
3
- import { n as log } from "./_ui-DfwfDbT-.mjs";
3
+ import { n as log } from "./_ui-u7T4YooX.mjs";
4
4
  import { typecheckProject } from "./typecheck.mjs";
5
5
  //#region _typecheck-gate.ts
6
6
  /**
@@ -37,6 +37,10 @@ function notify(level, message) {
37
37
  }
38
38
  process.stderr.write(`${message}\n`);
39
39
  }
40
+ /** Whether `silenceOutput()` has been called — i.e. we are in JSON mode. */
41
+ function outputSilenced() {
42
+ return silenced;
43
+ }
40
44
  /**
41
45
  * Unwrap a clack prompt result, exiting cleanly if the user cancelled.
42
46
  * `message` lets the caller name what was cancelled (e.g. "Setup cancelled").
@@ -65,4 +69,4 @@ function parsePort(raw) {
65
69
  return port;
66
70
  }
67
71
  //#endregion
68
- export { silenceOutput as a, parsePort as i, log as n, unwrapCancel as o, notify as r, fmtUrl as t };
72
+ export { parsePort as a, outputSilenced as i, log as n, silenceOutput as o, notify as r, unwrapCancel as s, fmtUrl as t };
package/dist/_ui.d.ts CHANGED
@@ -23,6 +23,8 @@ export declare function silenceOutput(): void;
23
23
  * line — while a human tailing the log sees what happened.
24
24
  */
25
25
  export declare function notify(level: "error" | "warn" | "info" | "success", message: string): void;
26
+ /** Whether `silenceOutput()` has been called — i.e. we are in JSON mode. */
27
+ export declare function outputSilenced(): boolean;
26
28
  /**
27
29
  * Unwrap a clack prompt result, exiting cleanly if the user cancelled.
28
30
  * `message` lets the caller name what was cancelled (e.g. "Setup cancelled").
@@ -2,7 +2,7 @@
2
2
  import { readFileSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import fs from "node:fs/promises";
5
- import { errorDetail, errorMessage, errorMessage as errorMessage$1 } from "@alexkroman1/aai/utils";
5
+ import { errorDetail, errorMessage, errorMessage as errorMessage$1, omitUndefined } from "@alexkroman1/aai/utils";
6
6
  //#region _utils.ts
7
7
  /**
8
8
  * The file that marks a directory as an agent project — the single source
@@ -93,7 +93,7 @@ async function writeJson(filePath, data, opts = {}) {
93
93
  ...opts.mode !== void 0 ? { mode: 448 } : {}
94
94
  });
95
95
  const tmpPath = `${filePath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
96
- await fs.writeFile(tmpPath, `${JSON.stringify(data, null, 2)}\n`, { ...opts.mode !== void 0 ? { mode: opts.mode } : {} });
96
+ await fs.writeFile(tmpPath, `${JSON.stringify(data, null, 2)}\n`, omitUndefined({ mode: opts.mode }));
97
97
  try {
98
98
  await fs.rename(tmpPath, filePath);
99
99
  } catch (err) {
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as ok, t as CliError } from "./_output-CC300DzW.mjs";
3
- import { n as log } from "./_ui-DfwfDbT-.mjs";
4
- import { r as evalWorkerBundle, t as buildAgentBundle } from "./_bundler-DC17suWN.mjs";
5
- import { assertTypechecks } from "./_typecheck-gate-DB-PY0A3.mjs";
6
- import { classifyVitestError, runVitest } from "./test-C-V98oC-.mjs";
3
+ import { n as log } from "./_ui-u7T4YooX.mjs";
4
+ import { r as evalWorkerBundle, t as buildAgentBundle } from "./_bundler-BuIyDdvs.mjs";
5
+ import { assertTypechecks } from "./_typecheck-gate-4v9UBZHh.mjs";
6
+ import { classifyVitestError, runVitest } from "./test-2LWBcXvY.mjs";
7
7
  //#region build.ts
8
8
  /**
9
9
  * `aai build` — bundle the agent without deploying, behind the same gates
package/dist/cli.d.ts CHANGED
@@ -1 +1,2 @@
1
- export declare const mainCommand: import("citty").CommandDef<import("citty").ArgsDef>;
1
+ import { type ArgsDef, type CommandDef } from "citty";
2
+ export declare const mainCommand: CommandDef<ArgsDef>;
package/dist/cli.mjs CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { i as installStdoutGuard, n as fail, o as writeLine, r as getOutputMode, t as CliError } from "./_output-CC300DzW.mjs";
3
- import { a as silenceOutput, n as log } from "./_ui-DfwfDbT-.mjs";
4
- import { a as errorMessage, l as resolveCwd, o as fileExists, t as AGENT_ENTRY } from "./_utils-8KKw-bzi.mjs";
3
+ import { n as log, o as silenceOutput } from "./_ui-u7T4YooX.mjs";
4
+ import { a as errorMessage, l as resolveCwd, o as fileExists, t as AGENT_ENTRY } from "./_utils-0DHbcqUb.mjs";
5
5
  import { existsSync, readFileSync } from "node:fs";
6
6
  import path from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
- import { defineCommand, runMain } from "citty";
8
+ import { defineCommand, runMain, showUsage } from "citty";
9
9
  //#region _cli-common.ts
10
10
  /** Shared arg definitions for citty commands. */
11
11
  const sharedArgs = {
@@ -55,11 +55,11 @@ function findUnknownFlags(rawArgs, argsDef) {
55
55
  * A flag's comparison key: dashes dropped and lower-cased, with a leading
56
56
  * `no` negation removed.
57
57
  *
58
- * citty accepts both `--allowMissingSecrets` and `--allow-missing-secrets` for
59
- * an arg declared as `allowMissingSecrets`, and the guest's in-sandbox Publish
60
- * spawns the kebab-case spelling. Comparing the literal text rejected
61
- * `--allow-missing-secrets` as unknown and broke Publish for every studio
62
- * user, so both sides are normalized to one key.
58
+ * citty accepts both `--allowPreviewSlug` and `--allow-preview-slug` for an
59
+ * arg declared as `allowPreviewSlug`, and the guest's in-sandbox Publish
60
+ * spawns the kebab-case spelling. Comparing the literal text rejected the
61
+ * kebab-case form as unknown and broke Publish for every studio user, so both
62
+ * sides are normalized to one key.
63
63
  */
64
64
  function canonicalFlag(flag) {
65
65
  return flag.replace(/^--?/, "").replace(/^no-/, "").replace(/-/g, "").toLowerCase();
@@ -166,7 +166,7 @@ const list = defineCommand({
166
166
  async run({ args }) {
167
167
  await runCommand(args, async () => {
168
168
  const cwd = resolveCwd();
169
- const { executeList } = await import("./studio-DPDFKZ9i.mjs");
169
+ const { executeList } = await import("./studio-GSFd4s5O.mjs");
170
170
  return executeList({
171
171
  cwd,
172
172
  server: args.server
@@ -201,7 +201,7 @@ const pull = defineCommand({
201
201
  async run({ args }) {
202
202
  await runCommand(args, async () => {
203
203
  const cwd = resolveCwd();
204
- const { executePull } = await import("./studio-DPDFKZ9i.mjs");
204
+ const { executePull } = await import("./studio-GSFd4s5O.mjs");
205
205
  return executePull({
206
206
  cwd,
207
207
  project: args.project,
@@ -229,7 +229,7 @@ const push = defineCommand({
229
229
  async run({ args }) {
230
230
  await runCommand(args, async () => {
231
231
  const cwd = await setup({ agent: true });
232
- const { executePush } = await import("./studio-DPDFKZ9i.mjs");
232
+ const { executePush } = await import("./studio-GSFd4s5O.mjs");
233
233
  return executePush({
234
234
  cwd,
235
235
  server: args.server,
@@ -259,7 +259,7 @@ const publish = defineCommand({
259
259
  async run({ args }) {
260
260
  await runCommand(args, async () => {
261
261
  const cwd = await setup({ agent: true });
262
- const { executePublish } = await import("./studio-DPDFKZ9i.mjs");
262
+ const { executePublish } = await import("./studio-GSFd4s5O.mjs");
263
263
  return executePublish({
264
264
  cwd,
265
265
  server: args.server,
@@ -286,6 +286,22 @@ function readCliVersion(dir) {
286
286
  return "unknown";
287
287
  }
288
288
  const VERSION = readCliVersion(cliDir);
289
+ /**
290
+ * `aai secret put` for a subcommand, `aai` for the root — the name to put in
291
+ * front of `--help` when telling someone which usage to read.
292
+ *
293
+ * `meta` is a `Resolvable`, so it can be a function or a promise; only the
294
+ * plain-object form carries a name synchronously and anything else degrades
295
+ * to the binary name rather than blocking the error path on a resolve.
296
+ */
297
+ function commandPath(cmd, parent) {
298
+ const nameOf = (c) => {
299
+ const meta = c?.meta;
300
+ return meta && typeof meta === "object" && "name" in meta && typeof meta.name === "string" ? meta.name : void 0;
301
+ };
302
+ const path = [nameOf(parent), nameOf(cmd)].filter((n) => n !== void 0).join(" ");
303
+ return path.startsWith("aai") ? path : `aai ${path}`.trim();
304
+ }
289
305
  const init = defineCommand({
290
306
  meta: {
291
307
  name: "init",
@@ -317,7 +333,7 @@ const init = defineCommand({
317
333
  },
318
334
  async run({ args }) {
319
335
  await runCommand(args, async (mode) => {
320
- const { executeInit } = await import("./init-BBAoRI_f.mjs");
336
+ const { executeInit } = await import("./init-BppxMR8r.mjs");
321
337
  return executeInit({
322
338
  dir: args.dir,
323
339
  force: args.force,
@@ -346,7 +362,7 @@ const dev = defineCommand({
346
362
  async run({ args }) {
347
363
  await runCommand(args, async () => {
348
364
  const cwd = await setup({ agent: true });
349
- const { executeDev } = await import("./dev-CdeRcYiQ.mjs");
365
+ const { executeDev } = await import("./dev-CvPH5dbF.mjs");
350
366
  return executeDev({
351
367
  cwd,
352
368
  port: args.port
@@ -362,8 +378,8 @@ const test = defineCommand({
362
378
  args: { json: sharedArgs.json },
363
379
  async run({ args }) {
364
380
  await runCommand(args, async () => {
365
- const cwd = await setup();
366
- const { executeTest } = await import("./test-C-V98oC-.mjs");
381
+ const cwd = await setup({ agent: true });
382
+ const { executeTest } = await import("./test-2LWBcXvY.mjs");
367
383
  return executeTest(cwd);
368
384
  });
369
385
  }
@@ -387,7 +403,7 @@ const build = defineCommand({
387
403
  async run({ args }) {
388
404
  await runCommand(args, async () => {
389
405
  const cwd = await setup({ agent: true });
390
- const { executeBuild } = await import("./build-CACFbdQ4.mjs");
406
+ const { executeBuild } = await import("./build-DQJ1ZSm9.mjs");
391
407
  return executeBuild({
392
408
  cwd,
393
409
  skipTests: args.skipTests,
@@ -412,7 +428,7 @@ const eject = defineCommand({
412
428
  async run({ args }) {
413
429
  await runCommand(args, async () => {
414
430
  const cwd = await setup({ agent: true });
415
- const { executeEject } = await import("./eject-DSrAp8PL.mjs");
431
+ const { executeEject } = await import("./eject-LgBRfnbH.mjs");
416
432
  return executeEject({
417
433
  cwd,
418
434
  force: args.force
@@ -429,10 +445,6 @@ const deploy = defineCommand({
429
445
  args: {
430
446
  server: sharedArgs.server,
431
447
  json: sharedArgs.json,
432
- allowMissingSecrets: {
433
- type: "boolean",
434
- description: "Deploy even when the agent's providers are missing credentials (the server warns instead of rejecting; set them afterwards with `aai secret put`)"
435
- },
436
448
  allowPreviewSlug: {
437
449
  type: "boolean",
438
450
  description: "Permit a `-preview`-suffixed slug (reserved for studio auto-previews; studio-internal — a slug you claim this way is subject to the preview reaper)"
@@ -445,11 +457,10 @@ const deploy = defineCommand({
445
457
  async run({ args }) {
446
458
  await runCommand(args, async () => {
447
459
  const cwd = await setup({ agent: true });
448
- const { executeDeploy } = await import("./deploy-Ch0d_jje.mjs");
460
+ const { executeDeploy } = await import("./deploy-DjsEV-40.mjs");
449
461
  return executeDeploy({
450
462
  cwd,
451
463
  server: args.server,
452
- allowMissingSecrets: args.allowMissingSecrets,
453
464
  allowPreviewSlug: args.allowPreviewSlug,
454
465
  skipTypecheck: args.skipTypecheck
455
466
  });
@@ -468,7 +479,7 @@ const del = defineCommand({
468
479
  async run({ args }) {
469
480
  await runCommand(args, async () => {
470
481
  const cwd = await setup();
471
- const { executeDelete } = await import("./delete-DEZ7u3u4.mjs");
482
+ const { executeDelete } = await import("./delete-DCRX3d96.mjs");
472
483
  return executeDelete({
473
484
  cwd,
474
485
  server: args.server
@@ -499,7 +510,7 @@ const secret = defineCommand({
499
510
  async run({ args }) {
500
511
  await runCommand(args, async (mode) => {
501
512
  const cwd = await setup();
502
- const { executeSecretPut, NO_INPUT, readStdin } = await import("./secret-4_dYyrpA.mjs");
513
+ const { executeSecretPut, NO_INPUT, readStdin } = await import("./secret-Ds9dvqAj.mjs");
503
514
  const value = mode === "json" ? await readStdin() : void 0;
504
515
  if (mode === "json" && !value) throw new CliError(...NO_INPUT);
505
516
  return executeSecretPut(cwd, args.name, value, args.server);
@@ -523,7 +534,7 @@ const secret = defineCommand({
523
534
  async run({ args }) {
524
535
  await runCommand(args, async () => {
525
536
  const cwd = await setup();
526
- const { executeSecretDelete } = await import("./secret-4_dYyrpA.mjs");
537
+ const { executeSecretDelete } = await import("./secret-Ds9dvqAj.mjs");
527
538
  return executeSecretDelete(cwd, args.name, args.server);
528
539
  });
529
540
  }
@@ -540,7 +551,7 @@ const secret = defineCommand({
540
551
  async run({ args }) {
541
552
  await runCommand(args, async () => {
542
553
  const cwd = await setup();
543
- const { executeSecretList } = await import("./secret-4_dYyrpA.mjs");
554
+ const { executeSecretList } = await import("./secret-Ds9dvqAj.mjs");
544
555
  return executeSecretList(cwd, args.server);
545
556
  });
546
557
  }
@@ -575,7 +586,7 @@ const storage = defineCommand({
575
586
  },
576
587
  async run({ args }) {
577
588
  await runCommand(args, async () => {
578
- const { executeStorageStatus } = await import("./storage-BTfErOOW.mjs");
589
+ const { executeStorageStatus } = await import("./storage-DbrQSHFk.mjs");
579
590
  return executeStorageStatus(resolveStorageCwd(args.dir), args.server);
580
591
  });
581
592
  }
@@ -592,7 +603,7 @@ const storage = defineCommand({
592
603
  },
593
604
  async run({ args }) {
594
605
  await runCommand(args, async () => {
595
- const { executeStorageEnable } = await import("./storage-BTfErOOW.mjs");
606
+ const { executeStorageEnable } = await import("./storage-DbrQSHFk.mjs");
596
607
  return executeStorageEnable(resolveStorageCwd(args.dir), args.server);
597
608
  });
598
609
  }
@@ -614,7 +625,7 @@ const storage = defineCommand({
614
625
  },
615
626
  async run({ args }) {
616
627
  await runCommand(args, async () => {
617
- const { executeStorageDisable } = await import("./storage-BTfErOOW.mjs");
628
+ const { executeStorageDisable } = await import("./storage-DbrQSHFk.mjs");
618
629
  return executeStorageDisable(resolveStorageCwd(args.dir), {
619
630
  server: args.server,
620
631
  force: args.force
@@ -635,7 +646,7 @@ const login = defineCommand({
635
646
  },
636
647
  async run({ args }) {
637
648
  await runCommand(args, async () => {
638
- const { executeLogin } = await import("./login-BeFUiU6M.mjs");
649
+ const { executeLogin } = await import("./login-BQQaxJhL.mjs");
639
650
  return executeLogin({ server: args.server });
640
651
  });
641
652
  }
@@ -648,7 +659,7 @@ const templates = defineCommand({
648
659
  args: { json: sharedArgs.json },
649
660
  async run({ args }) {
650
661
  await runCommand(args, async (mode) => {
651
- const { listTemplates } = await import("./_templates-DmdG-voA.mjs");
662
+ const { listTemplates } = await import("./_templates-B1kt3BGt.mjs");
652
663
  const names = await listTemplates();
653
664
  if (mode === "human") {
654
665
  for (const name of names) log.message(name);
@@ -702,6 +713,31 @@ if (process.env.VITEST !== "true") {
702
713
  process.argv.splice(2, 0, "publish");
703
714
  };
704
715
  /**
716
+ * Render usage the way the active output mode allows.
717
+ *
718
+ * citty calls `showUsage` from two places: the `--help` path, and its
719
+ * `catch` for a `CLIError` (a missing positional, an unknown subcommand) —
720
+ * where it writes the usage block to STDOUT and the reason to stderr. That
721
+ * second one breaks JSON mode's contract of exactly one result line on
722
+ * stdout, and JSON mode is auto-detected on a pipe: `aai secret put --json`
723
+ * put a usage block where a script's `jq` expected a result, with no JSON
724
+ * emitted at all. Every other failure path in this CLI converges on one
725
+ * emitter and gets this right.
726
+ *
727
+ * `--help` is explicitly still the human block whatever the mode — piping
728
+ * it into a pager is the normal way to read it, and it is not an error.
729
+ * The specific reason keeps going to stderr as citty already writes it, so
730
+ * nothing is lost to a human watching the terminal.
731
+ */
732
+ const usageForMode = async (cmd, parent) => {
733
+ const argv = process.argv.slice(2);
734
+ if (argv.includes("--help") || argv.includes("-h") || getOutputMode({}) === "human") {
735
+ await showUsage(cmd, parent);
736
+ return;
737
+ }
738
+ await writeLine(`${JSON.stringify(fail("usage", `Invalid arguments for \`${commandPath(cmd, parent)}\`.`, `Run \`${commandPath(cmd, parent)} --help\` to see the arguments it accepts.`))}\n`);
739
+ };
740
+ /**
705
741
  * Refuse an unrecognized flag instead of ignoring it.
706
742
  *
707
743
  * citty drops one silently, so `aai push --serverr=http://x` exited 0 having
@@ -715,7 +751,7 @@ if (process.env.VITEST !== "true") {
715
751
  log.info("Run `aai <command> --help` to see the options it accepts.");
716
752
  process.exit(1);
717
753
  };
718
- runDefault().then(assertKnownFlags).then(() => runMain(mainCommand)).catch((err) => {
754
+ runDefault().then(assertKnownFlags).then(() => runMain(mainCommand, { showUsage: usageForMode })).catch((err) => {
719
755
  log.error(errorMessage(err));
720
756
  process.exitCode = 1;
721
757
  });
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as errorMessage$1, o as fileExists } from "./_utils-8KKw-bzi.mjs";
2
+ import { a as errorMessage$1, o as fileExists } from "./_utils-0DHbcqUb.mjs";
3
3
  import { t as withPreservedNodeEnv } from "./_vite-env-Dg_QlVv0.mjs";
4
4
  import { existsSync, unlinkSync, writeFileSync } from "node:fs";
5
5
  import path from "node:path";
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import { t as buildClient } from "./client-bundler-DONm-khu.mjs";
2
+ import { t as buildClient } from "./client-bundler-C9qXL8A2.mjs";
3
3
  export { buildClient };