@alexkroman1/aai-cli 13.3.0 → 15.0.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 (158) hide show
  1. package/dist/{_agent-Clbq3a4h.mjs → _agent-BzUeqOdj.mjs} +32 -9
  2. package/dist/_agent.d.ts +23 -0
  3. package/dist/_artifacts-BJOYGQPp.mjs +21 -0
  4. package/dist/_artifacts.d.ts +16 -0
  5. package/dist/_build-target.d.ts +172 -0
  6. package/dist/{_bundler-DEkjO1d8.mjs → _bundler-DM0d0M7m.mjs} +8 -7
  7. package/dist/_cli-common.d.ts +45 -6
  8. package/dist/{_config-ZY2euhdh.mjs → _config-DYzC6WMD.mjs} +2 -2
  9. package/dist/_dev-env.d.ts +1 -1
  10. package/dist/{_dev-server-C13xuGB1.mjs → _dev-server-BzWB6-4y.mjs} +25 -24
  11. package/dist/_dev-vite-config.d.ts +3 -3
  12. package/dist/_e2e-test-utils.d.ts +1 -1
  13. package/dist/{_init-Ci4QGjp8.mjs → _init-Bsi3DZNJ.mjs} +9 -23
  14. package/dist/{_output-DbJt9gsl.mjs → _output-DBP9Op_d.mjs} +22 -13
  15. package/dist/_output.d.ts +10 -9
  16. package/dist/{_preflight-B2laWPdU.mjs → _preflight-CcAR577V.mjs} +2 -2
  17. package/dist/_resource-commands.d.ts +9 -9
  18. package/dist/_server-common-De0haHr9.mjs +70 -0
  19. package/dist/_server-common.d.ts +20 -1
  20. package/dist/{_slug-api-DlKpj91X.mjs → _slug-api-CGvc2YSQ.mjs} +18 -9
  21. package/dist/_slug-api.d.ts +19 -0
  22. package/dist/{_studio-DTi3EdXQ.mjs → _studio-DnR_BqFp.mjs} +7 -3
  23. package/dist/_studio-commands.d.ts +13 -13
  24. package/dist/{_templates-V4l5sZJy.mjs → _templates-CIlJ3Vay.mjs} +4 -4
  25. package/dist/_templates.d.ts +1 -1
  26. package/dist/{_typecheck-gate-sV4N5F93.mjs → _typecheck-gate-U3jcil8L.mjs} +3 -2
  27. package/dist/_typecheck-gate.d.ts +3 -1
  28. package/dist/{_utils-Dhd058D9.mjs → _utils-D5JGcjiW.mjs} +43 -1
  29. package/dist/_utils.d.ts +35 -0
  30. package/dist/_vercel-output.d.ts +63 -0
  31. package/dist/{_workflow-determinism-C9l8uC2R.mjs → _workflow-determinism-CvpqMpdh.mjs} +18 -26
  32. package/dist/_workflow-determinism.d.ts +3 -3
  33. package/dist/build-BhEaxBPu.mjs +481 -0
  34. package/dist/build.d.ts +23 -10
  35. package/dist/cli.d.ts +1 -2
  36. package/dist/cli.mjs +155 -138
  37. package/dist/{client-bundler-CT-_26c5.mjs → client-bundler-6mTLs6ny.mjs} +5 -5
  38. package/dist/client-bundler.d.ts +1 -1
  39. package/dist/client-bundler.mjs +1 -1
  40. package/dist/{delete-RqlQZ6Y_.mjs → delete-BdFnbnVR.mjs} +4 -4
  41. package/dist/{deploy-B7f3CK7v.mjs → deploy-CGqPU5U-.mjs} +11 -11
  42. package/dist/{dev-DamXieW_.mjs → dev-Bx9gYBHM.mjs} +3 -3
  43. package/dist/{eval-h3DTVfRw.mjs → eval-B3I7FqN9.mjs} +3 -3
  44. package/dist/{init-D-vFrV8h.mjs → init-CFyusRbq.mjs} +48 -20
  45. package/dist/init.d.ts +14 -0
  46. package/dist/{login-CDiemNmC.mjs → login-DaLrPOFg.mjs} +4 -6
  47. package/dist/{logs-DGwPNCzB.mjs → logs-7BRwq0cG.mjs} +21 -14
  48. package/dist/project-config.mjs +1 -1
  49. package/dist/scaffold/CLAUDE.md +155 -76
  50. package/dist/scaffold/package.json +6 -6
  51. package/dist/{secret-V1w7y9-j.mjs → secret-Cjp912yF.mjs} +5 -5
  52. package/dist/secret.d.ts +8 -6
  53. package/dist/start.d.ts +112 -0
  54. package/dist/start.mjs +156 -0
  55. package/dist/{studio-BpMCfUNx.mjs → studio-C_zuRC_z.mjs} +13 -15
  56. package/dist/templates/briefing-desk/agent.eval.test.ts +156 -0
  57. package/dist/templates/call-audit/agent.test.ts +11 -6
  58. package/dist/templates/call-audit/agent.ts +2 -2
  59. package/dist/templates/call-audit/client.tsx +27 -42
  60. package/dist/templates/call-audit/workflows/audit.ts +7 -5
  61. package/dist/templates/call-audit/workflows/ingest.ts +7 -7
  62. package/dist/templates/call-audit/workflows/media.ts +12 -37
  63. package/dist/templates/call-audit/workflows/summarize.ts +8 -8
  64. package/dist/templates/call-audit/workflows/sync-api.ts +3 -3
  65. package/dist/templates/code-interpreter/agent.test.ts +103 -0
  66. package/dist/templates/dispatch-center/agent.test.ts +7 -7
  67. package/dist/templates/dispatch-center/client.tsx +53 -61
  68. package/dist/templates/dispatch-center/shared.ts +0 -8
  69. package/dist/templates/dispatch-center/tools/ops_dashboard.ts +4 -1
  70. package/dist/templates/embedded-assets/agent.eval.test.ts +4 -2
  71. package/dist/templates/health-assistant/agent.eval.test.ts +4 -2
  72. package/dist/templates/infocom-adventure/client.tsx +40 -36
  73. package/dist/templates/link-digest/agent.test.ts +3 -3
  74. package/dist/templates/link-digest/agent.ts +2 -2
  75. package/dist/templates/link-digest/client.tsx +81 -19
  76. package/dist/templates/link-digest/workflows/digest.ts +13 -16
  77. package/dist/templates/math-buddy/agent.test.ts +126 -0
  78. package/dist/templates/night-owl/agent.eval.test.ts +4 -2
  79. package/dist/templates/night-owl/client.tsx +8 -2
  80. package/dist/templates/personal-finance/agent.test.ts +127 -0
  81. package/dist/templates/pipeline-simple/agent.test.ts +2 -2
  82. package/dist/templates/pizza-ordering/client.tsx +5 -4
  83. package/dist/templates/pizza-ordering/shared.ts +3 -8
  84. package/dist/templates/pizza-ordering/tools/add_pizza.ts +3 -2
  85. package/dist/templates/pizza-ordering/tools/place_order.ts +3 -2
  86. package/dist/templates/pizza-ordering/tools/remove_pizza.ts +3 -2
  87. package/dist/templates/pizza-ordering/tools/update_pizza.ts +3 -2
  88. package/dist/templates/pizza-ordering/tools/view_order.ts +3 -2
  89. package/dist/templates/plan-and-execute/agent.test.ts +10 -5
  90. package/dist/templates/plan-and-execute/client.tsx +2 -2
  91. package/dist/templates/podcast-digest/agent.test.ts +20 -11
  92. package/dist/templates/podcast-digest/client.tsx +16 -22
  93. package/dist/templates/podcast-digest/workflows/digest.ts +21 -19
  94. package/dist/templates/podcast-digest/workflows/feeds.ts +61 -33
  95. package/dist/templates/podcast-digest/workflows/slack.ts +7 -7
  96. package/dist/templates/recap-workflow/agent.eval.test.ts +21 -13
  97. package/dist/templates/recap-workflow/agent.test.ts +27 -23
  98. package/dist/templates/recap-workflow/workflows/recap.ts +24 -28
  99. package/dist/templates/redline/agent.eval.test.ts +5 -8
  100. package/dist/templates/redline/agent.test.ts +1 -1
  101. package/dist/templates/redline/client.tsx +36 -34
  102. package/dist/templates/redline/workflows/redline.ts +17 -17
  103. package/dist/templates/research-workflow/agent.eval.test.ts +5 -5
  104. package/dist/templates/research-workflow/agent.test.ts +10 -7
  105. package/dist/templates/research-workflow/workflows/research.ts +28 -30
  106. package/dist/templates/retail/agent.test.ts +17 -15
  107. package/dist/templates/retail/cancel.ts +7 -6
  108. package/dist/templates/retail/client.tsx +39 -58
  109. package/dist/templates/retail/payment.ts +4 -3
  110. package/dist/templates/retail/pending.test.ts +19 -19
  111. package/dist/templates/retail/store.test.ts +5 -5
  112. package/dist/templates/retail/swap.ts +8 -11
  113. package/dist/templates/solo-rpg/agent.eval.test.ts +2 -16
  114. package/dist/templates/solo-rpg/agent.test.ts +12 -12
  115. package/dist/templates/solo-rpg/client.tsx +3 -3
  116. package/dist/templates/spoken-summary/agent.test.ts +9 -9
  117. package/dist/templates/spoken-summary/agent.ts +3 -3
  118. package/dist/templates/spoken-summary/client.tsx +23 -30
  119. package/dist/templates/spoken-summary/workflows/summarize.ts +11 -11
  120. package/dist/templates/spoken-summary/workflows/transcribe.ts +14 -14
  121. package/dist/templates/support-line/agent.ts +8 -0
  122. package/dist/templates/support-line/client.tsx +4 -3
  123. package/dist/templates/support-line/prompts.ts +0 -2
  124. package/dist/templates/transcription-workflow/agent.test.ts +20 -12
  125. package/dist/templates/transcription-workflow/agent.ts +2 -2
  126. package/dist/templates/transcription-workflow/client.tsx +15 -14
  127. package/dist/templates/transcription-workflow/recover.ts +1 -1
  128. package/dist/templates/transcription-workflow/run-panel.tsx +34 -14
  129. package/dist/templates/transcription-workflow/total-latency.tsx +46 -20
  130. package/dist/templates/transcription-workflow/workflows/batch.ts +15 -15
  131. package/dist/templates/transcription-workflow/workflows/downsample.ts +1 -1
  132. package/dist/templates/transcription-workflow/workflows/normalize.ts +15 -19
  133. package/dist/templates/transcription-workflow/workflows/stream.ts +12 -12
  134. package/dist/templates/transcription-workflow/workflows/sync-api.ts +3 -3
  135. package/dist/templates/transcription-workflow/workflows/transcribe.ts +23 -34
  136. package/dist/templates/travel-concierge/agent.eval.test.ts +2 -2
  137. package/dist/templates/travel-concierge/agent.test.ts +15 -7
  138. package/dist/templates/travel-concierge/client.tsx +2 -2
  139. package/dist/templates/travel-concierge/routing.ts +64 -55
  140. package/dist/templates/travel-concierge/shared.ts +10 -14
  141. package/dist/templates/travel-concierge/tools/cancel_action.ts +3 -1
  142. package/dist/templates/travel-concierge/tools/complete_or_escalate.ts +3 -1
  143. package/dist/templates/travel-concierge/tools/confirm_action.ts +3 -1
  144. package/dist/templates/travel-concierge/tools/lookup_booking.ts +4 -3
  145. package/dist/templates/travel-concierge/tools/search_car_rentals.ts +3 -2
  146. package/dist/templates/travel-concierge/tools/search_excursions.ts +3 -2
  147. package/dist/templates/travel-concierge/tools/search_flights.ts +3 -2
  148. package/dist/templates/travel-concierge/tools/search_hotels.ts +3 -2
  149. package/dist/templates/web-researcher/agent.test.ts +130 -0
  150. package/dist/{test-guenJUxi.mjs → test-D032V2cA.mjs} +4 -17
  151. package/dist/typecheck.mjs +1 -1
  152. package/dist/worker-bundler.d.ts +1 -1
  153. package/dist/worker-bundler.mjs +8 -8
  154. package/dist/{workflow-BZyKLkhV.mjs → workflow-CVioyO3V.mjs} +6 -5
  155. package/package.json +9 -4
  156. package/dist/_server-common-BV4_CrP3.mjs +0 -43
  157. package/dist/build-CWyFGe-W.mjs +0 -69
  158. package/dist/scaffold/server.mjs +0 -204
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { c as isEexist, l as readJson, p as writeJson } from "./_utils-Dhd058D9.mjs";
3
- import { r as isDevMode, t as getMonorepoRoot } from "./_agent-Clbq3a4h.mjs";
4
- import { REPO_URL, downloadAndMergeTemplate } from "./_templates-V4l5sZJy.mjs";
2
+ import { d as readJson, g as writeJson, i as compareCodeUnits, u as isEexist } from "./_utils-D5JGcjiW.mjs";
3
+ import { r as isDevMode, t as getMonorepoRoot } from "./_agent-BzUeqOdj.mjs";
4
+ import { REPO_URL, downloadAndMergeTemplate } from "./_templates-CIlJ3Vay.mjs";
5
5
  import path from "node:path";
6
6
  import fs from "node:fs/promises";
7
7
  //#region src/_init.ts
@@ -119,17 +119,6 @@ async function patchPackageJsonForWorkspace(targetDir) {
119
119
  await pinSharedDeps(targetDir, packagesDir, pkgJson);
120
120
  }
121
121
  /**
122
- * Code-unit ordering, never `localeCompare`.
123
- *
124
- * With no explicit locale that answers to the runtime's ICU default, so the same
125
- * project would scaffold a differently-ordered file on a different machine —
126
- * which is the reason the repo's own generated artifacts sort this way too.
127
- */
128
- function compareNames(a, b) {
129
- if (a === b) return 0;
130
- return a < b ? -1 : 1;
131
- }
132
- /**
133
122
  * Pin every THIRD-PARTY dependency this project shares with a linked workspace
134
123
  * package to the copy that workspace package resolved.
135
124
  *
@@ -162,17 +151,14 @@ async function pinSharedDeps(targetDir, packagesDir, pkgJson) {
162
151
  ...pkgJson.devDependencies
163
152
  };
164
153
  const linked = Object.entries(WORKSPACE_PKG_DIRS).filter(([name]) => name in declared).map(([, dir]) => dir);
165
- const pins = /* @__PURE__ */ new Map();
166
- for (const name of Object.keys(declared)) {
167
- if (name in WORKSPACE_PKG_DIRS) continue;
154
+ const resolved = await Promise.all(Object.keys(declared).filter((name) => !(name in WORKSPACE_PKG_DIRS)).map(async (name) => {
168
155
  for (const dir of linked) {
169
156
  const manifest = await readJson(path.join(packagesDir, dir, "node_modules", name, "package.json"));
170
- if (typeof manifest?.version === "string") {
171
- pins.set(name, manifest.version);
172
- break;
173
- }
157
+ if (typeof manifest?.version === "string") return [name, manifest.version];
174
158
  }
175
- }
159
+ return null;
160
+ }));
161
+ const pins = new Map(resolved.filter((entry) => entry !== null));
176
162
  if (pins.size === 0) return;
177
163
  const file = path.join(targetDir, "pnpm-workspace.yaml");
178
164
  let existing;
@@ -181,7 +167,7 @@ async function pinSharedDeps(targetDir, packagesDir, pkgJson) {
181
167
  } catch {
182
168
  return;
183
169
  }
184
- const block = [...pins].sort(([a], [b]) => compareNames(a, b)).map(([name, version]) => ` "${name}": "${version}"`).join("\n");
170
+ const block = [...pins].sort(([a], [b]) => compareCodeUnits(a, b)).map(([name, version]) => ` "${name}": "${version}"`).join("\n");
185
171
  await fs.writeFile(file, `${existing.trimEnd()}\n\n# Added by \`aai init\` in DEV MODE only — see pinSharedDeps in\n# packages/aai-cli/src/_init.ts. The @alexkroman1/* packages above are LINKED to
186
172
  # this checkout, so anything they and this project both depend on has to be
187
173
  # ONE copy: two copies of xstate are two incompatible sets of types, and the
@@ -1,6 +1,13 @@
1
1
  #!/usr/bin/env node
2
+ import { stripVTControlCharacters } from "node:util";
2
3
  //#region src/_output.ts
3
4
  /**
5
+ * Structured output support for CLI commands.
6
+ *
7
+ * In JSON mode (non-TTY or --json), commands emit exactly one JSON line to
8
+ * stdout. In human mode (TTY, default), commands use @clack/prompts as before.
9
+ */
10
+ /**
4
11
  * Determine output mode from CLI flags and TTY state.
5
12
  *
6
13
  * Priority: --json flag > --no-json flag > TTY auto-detection.
@@ -11,27 +18,29 @@ function getOutputMode(args, isTTY = Boolean(process.stdout.isTTY)) {
11
18
  return isTTY ? "human" : "json";
12
19
  }
13
20
  /**
14
- * A CSI sequence `ESC [ params intermediates final` — which covers every SGR
15
- * colour pair a diagnostic can arrive wearing (`\u001B[31m` `\u001B[0m`,
16
- * `\u001B[38;5;246m`) as well as cursor moves.
17
- */
18
- const ANSI_RAW = new RegExp(`${String.fromCharCode(27)}\\[[0-?]*[ -/]*[@-~]`, "g");
19
- /**
20
- * The same sequence AFTER `JSON.stringify`, which escapes the ESC byte as the
21
- * six characters `\u001b`. Both forms are needed because a caller stringifies
22
- * a whole result and hands the line here — see {@link writeLine}.
21
+ * A terminal escape sequence AFTER `JSON.stringify`, which escapes the ESC
22
+ * byte as the six characters `\u001b`. Needed alongside the raw form because a
23
+ * caller stringifies a whole result and hands the line here — see
24
+ * {@link writeLine} — and no builtin reads that spelling.
23
25
  */
24
26
  const ANSI_JSON = /\\u001[bB]\[[0-?]*[ -/]*[@-~]/g;
25
27
  /**
26
28
  * Remove ANSI escape sequences, in raw or JSON-escaped form.
27
29
  *
28
30
  * A bundler colours its own diagnostics unconditionally, so `aai build`'s
29
- * failure reached the JSON envelope as per-character SGR pairs
30
- * (`[38;5;246m1 │[0m [38;5;249mi[0m[38;5;249mm[0m…`) illegible in a CI log
31
- * and meaningless to a `jq` consumer, which is the audience JSON mode has.
31
+ * failure reached the JSON envelope as per-character SGR pairs — illegible in a
32
+ * CI log and meaningless to a `jq` consumer, which is the audience JSON mode
33
+ * has.
34
+ *
35
+ * The raw half is `node:util`'s, not a local CSI regex. A/B'd on Node 26: the
36
+ * two agree on every SGR pair and cursor move the hand-built pattern was
37
+ * written for, and the builtin ALSO strips OSC-8 hyperlinks (`ESC ] 8 ;; url`)
38
+ * and charset selects (`ESC ( B`), which that pattern left in the envelope — so
39
+ * its claim to cover what a diagnostic arrives wearing was the incomplete part.
40
+ * The JSON-escaped half has no builtin and stays.
32
41
  */
33
42
  function stripAnsi(text) {
34
- return text.replace(ANSI_RAW, "").replace(ANSI_JSON, "");
43
+ return stripVTControlCharacters(text).replace(ANSI_JSON, "");
35
44
  }
36
45
  /**
37
46
  * Write a machine-readable line to stdout, resolving only once it has been
package/dist/_output.d.ts CHANGED
@@ -1,9 +1,3 @@
1
- /**
2
- * Structured output support for CLI commands.
3
- *
4
- * In JSON mode (non-TTY or --json), commands emit exactly one JSON line to
5
- * stdout. In human mode (TTY, default), commands use @clack/prompts as before.
6
- */
7
1
  export type OutputMode = "json" | "human";
8
2
  export type CommandResult<T> = {
9
3
  ok: true;
@@ -26,9 +20,16 @@ export declare function getOutputMode(args: {
26
20
  * Remove ANSI escape sequences, in raw or JSON-escaped form.
27
21
  *
28
22
  * A bundler colours its own diagnostics unconditionally, so `aai build`'s
29
- * failure reached the JSON envelope as per-character SGR pairs
30
- * (`[38;5;246m1 │[0m [38;5;249mi[0m[38;5;249mm[0m…`) illegible in a CI log
31
- * and meaningless to a `jq` consumer, which is the audience JSON mode has.
23
+ * failure reached the JSON envelope as per-character SGR pairs — illegible in a
24
+ * CI log and meaningless to a `jq` consumer, which is the audience JSON mode
25
+ * has.
26
+ *
27
+ * The raw half is `node:util`'s, not a local CSI regex. A/B'd on Node 26: the
28
+ * two agree on every SGR pair and cursor move the hand-built pattern was
29
+ * written for, and the builtin ALSO strips OSC-8 hyperlinks (`ESC ] 8 ;; url`)
30
+ * and charset selects (`ESC ( B`), which that pattern left in the envelope — so
31
+ * its claim to cover what a diagnostic arrives wearing was the incomplete part.
32
+ * The JSON-escaped half has no builtin and stays.
32
33
  */
33
34
  export declare function stripAnsi(text: string): string;
34
35
  /**
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { plural } from "@alexkroman1/aai/utils";
2
3
  import { requiredProviderEnvVars } from "@alexkroman1/aai-runtime";
3
4
  //#region src/_preflight.ts
4
5
  /**
@@ -39,8 +40,7 @@ function missingCredentials(config, env) {
39
40
  }
40
41
  /** One line naming the missing keys and what to do about them. */
41
42
  function missingCredentialMessage(missing) {
42
- const plural = missing.length > 1;
43
- return `Missing credential${plural ? "s" : ""} the agent needs to start: ${missing.join(", ")}. Declare ${plural ? "them" : "it"} in .env and redeploy (already set on the platform with \`aai secret put\`? then this is already handled).`;
43
+ return `Missing ${plural(missing.length, "credential")} the agent needs to start: ${missing.join(", ")}. Declare ${plural(missing.length, "it", "them")} in .env and redeploy (already set on the platform with \`aai secret put\`? then this is already handled).`;
44
44
  }
45
45
  //#endregion
46
46
  export { missingCredentialMessage, missingCredentials };
@@ -1,5 +1,14 @@
1
1
  export declare const secret: import("citty").CommandDef<import("citty").ArgsDef>;
2
2
  export declare const logs: import("citty").CommandDef<{
3
+ readonly server: {
4
+ readonly type: "string";
5
+ readonly alias: "s";
6
+ readonly description: "Platform server URL";
7
+ };
8
+ readonly json: {
9
+ readonly type: "boolean";
10
+ readonly description: "Output JSON (auto-detected in non-TTY)";
11
+ };
3
12
  readonly dir: {
4
13
  readonly type: "positional";
5
14
  readonly description: "Project directory";
@@ -10,13 +19,4 @@ export declare const logs: import("citty").CommandDef<{
10
19
  readonly alias: "f";
11
20
  readonly description: "Keep printing new output";
12
21
  };
13
- readonly server: {
14
- readonly type: "string";
15
- readonly alias: "s";
16
- readonly description: "Platform server URL";
17
- };
18
- readonly json: {
19
- readonly type: "boolean";
20
- readonly description: "Output JSON (auto-detected in non-TTY)";
21
- };
22
22
  }>;
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env node
2
+ import { a as errorCode } from "./_utils-D5JGcjiW.mjs";
3
+ import path from "node:path";
4
+ import { parseEnv } from "node:util";
5
+ import fs from "node:fs/promises";
6
+ //#region src/_server-common.ts
7
+ /**
8
+ * One dotenv file's entries, or `{}` when it is not there.
9
+ *
10
+ * An ABSENT file is normal — `.env` is gitignored and a container usually ships
11
+ * neither — while an UNREADABLE one is not: the agent would boot with no
12
+ * secrets and fail later as an opaque provider auth error, so that one throws.
13
+ */
14
+ async function readDotenv(file) {
15
+ try {
16
+ return parseEnv(await fs.readFile(file, "utf-8"));
17
+ } catch (err) {
18
+ if (errorCode(err) !== "ENOENT") throw err;
19
+ return {};
20
+ }
21
+ }
22
+ /**
23
+ * The dotenv files a DEPLOYMENT reads, in precedence order.
24
+ *
25
+ * `.env.example` counts as a DECLARATION here and nowhere else, which is what
26
+ * lets a container run with no `.env` at all: that file is committed, it names
27
+ * the secrets the agent needs, and `docker run -e MY_API_KEY=...` supplies the
28
+ * values — the shell always winning over a file entry, and a declared-but-empty
29
+ * value being dropped rather than kept (see below). Without it a deployment
30
+ * that ships no `.env` — which is every correctly-built image — resolves an
31
+ * empty agent env and fails later as an opaque provider auth error.
32
+ *
33
+ * `aai dev` deliberately reads `.env` alone. There the developer HAS the file,
34
+ * and treating the example as a declaration would surface a key they have not
35
+ * filled in yet as one they have.
36
+ */
37
+ const DEPLOY_ENV_FILES = [".env.example", ".env"];
38
+ /**
39
+ * Build the `ctx.env` record that agent tools will see at runtime.
40
+ *
41
+ * Only variables explicitly declared in `.env` are included — matching
42
+ * the platform sandbox behavior where `ctx.env`
43
+ * contains only secrets set via `aai secret put`. This prevents agents
44
+ * from accidentally depending on shell-level vars (PATH, HOME, etc.) that
45
+ * won't exist in production.
46
+ *
47
+ * Values are resolved by merging the `.env` file with the current
48
+ * environment — existing shell exports take precedence over `.env`
49
+ * defaults, without mutating `process.env`.
50
+ *
51
+ * @param cwd - Project directory containing `.env` (optional).
52
+ * @param baseEnv - Override the environment to read values from (tests only).
53
+ * @param files - Which dotenv files to read, later entries winning over
54
+ * earlier. Defaults to `.env` alone; {@link DEPLOY_ENV_FILES} is what a
55
+ * DEPLOYMENT reads, and its own doc says why the two differ.
56
+ */
57
+ async function resolveServerEnv(cwd, baseEnv, files = [".env"]) {
58
+ const fileEntries = {};
59
+ const root = cwd;
60
+ if (root !== void 0) for (const file of files) Object.assign(fileEntries, await readDotenv(path.join(root, file)));
61
+ const source = baseEnv ?? process.env;
62
+ const env = {};
63
+ for (const [key, fileVal] of Object.entries(fileEntries)) {
64
+ const val = source[key] ?? fileVal;
65
+ if (val !== void 0 && val !== "") env[key] = val;
66
+ }
67
+ return env;
68
+ }
69
+ //#endregion
70
+ export { resolveServerEnv as n, DEPLOY_ENV_FILES as t };
@@ -1,3 +1,19 @@
1
+ /**
2
+ * The dotenv files a DEPLOYMENT reads, in precedence order.
3
+ *
4
+ * `.env.example` counts as a DECLARATION here and nowhere else, which is what
5
+ * lets a container run with no `.env` at all: that file is committed, it names
6
+ * the secrets the agent needs, and `docker run -e MY_API_KEY=...` supplies the
7
+ * values — the shell always winning over a file entry, and a declared-but-empty
8
+ * value being dropped rather than kept (see below). Without it a deployment
9
+ * that ships no `.env` — which is every correctly-built image — resolves an
10
+ * empty agent env and fails later as an opaque provider auth error.
11
+ *
12
+ * `aai dev` deliberately reads `.env` alone. There the developer HAS the file,
13
+ * and treating the example as a declaration would surface a key they have not
14
+ * filled in yet as one they have.
15
+ */
16
+ export declare const DEPLOY_ENV_FILES: readonly [".env.example", ".env"];
1
17
  /**
2
18
  * Build the `ctx.env` record that agent tools will see at runtime.
3
19
  *
@@ -13,5 +29,8 @@
13
29
  *
14
30
  * @param cwd - Project directory containing `.env` (optional).
15
31
  * @param baseEnv - Override the environment to read values from (tests only).
32
+ * @param files - Which dotenv files to read, later entries winning over
33
+ * earlier. Defaults to `.env` alone; {@link DEPLOY_ENV_FILES} is what a
34
+ * DEPLOYMENT reads, and its own doc says why the two differ.
16
35
  */
17
- export declare function resolveServerEnv(cwd?: string, baseEnv?: Record<string, string | undefined>): Promise<Record<string, string>>;
36
+ export declare function resolveServerEnv(cwd?: string, baseEnv?: Record<string, string | undefined>, files?: readonly string[]): Promise<Record<string, string>>;
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { n as getServerInfo } from "./_agent-Clbq3a4h.mjs";
3
- import { l as HINT_NOT_DEPLOYED, s as studioProjectApiUrl, u as apiRequest } from "./_studio-DTi3EdXQ.mjs";
2
+ import { n as getServerInfo } from "./_agent-BzUeqOdj.mjs";
3
+ import { l as HINT_NOT_DEPLOYED, s as studioProjectApiUrl, u as apiRequest } from "./_studio-DnR_BqFp.mjs";
4
4
  //#region src/_slug-api.ts
5
5
  /**
6
6
  * Authenticated request against a deployed agent's slug-scoped resource
@@ -43,12 +43,21 @@ async function secretRequest(cwd, resourcePath, init, server) {
43
43
  target: studioProject ?? slug
44
44
  };
45
45
  }
46
- async function slugRequest(cwd, resourcePath, init, server) {
47
- const { serverUrl, slug, apiKey } = await getServerInfo(cwd, server);
48
- return {
49
- data: await deployedAgentRequest(`${serverUrl}/${slug}${resourcePath}`, init, apiKey),
50
- slug
51
- };
46
+ /**
47
+ * A slug-scoped request against an ALREADY-resolved target.
48
+ *
49
+ * {@link slugRequest} resolves per call, which is right for a command that
50
+ * issues one request and wrong for one that POLLS. `aai logs --follow` re-entered
51
+ * `getServerInfo` on every tick — a read and zod parse of `.aai/project.json`, a
52
+ * read of the global config, a `new URL` trust check, a slug-shape test, and with
53
+ * `--server` an `approveServer` that takes the CROSS-PROCESS config lock — once a
54
+ * second for the life of the follow, all of it re-deriving an immutable answer.
55
+ * `delete.ts` documents the same cost and answers it the same way, by hand; this
56
+ * is the general version, so the next polling command inherits it.
57
+ */
58
+ async function slugRequestOn(target, resourcePath, init) {
59
+ const { serverUrl, slug, apiKey } = target;
60
+ return deployedAgentRequest(`${serverUrl}/${slug}${resourcePath}`, init, apiKey);
52
61
  }
53
62
  //#endregion
54
- export { slugRequest as n, secretRequest as t };
63
+ export { slugRequestOn as n, secretRequest as t };
@@ -27,6 +27,25 @@ export declare function secretRequest<T = unknown>(cwd: string, resourcePath: st
27
27
  data: T;
28
28
  target: string;
29
29
  }>;
30
+ /** An already-resolved deployed target — what {@link slugRequestOn} needs. */
31
+ export type SlugTarget = {
32
+ serverUrl: string;
33
+ slug: string;
34
+ apiKey: string;
35
+ };
36
+ /**
37
+ * A slug-scoped request against an ALREADY-resolved target.
38
+ *
39
+ * {@link slugRequest} resolves per call, which is right for a command that
40
+ * issues one request and wrong for one that POLLS. `aai logs --follow` re-entered
41
+ * `getServerInfo` on every tick — a read and zod parse of `.aai/project.json`, a
42
+ * read of the global config, a `new URL` trust check, a slug-shape test, and with
43
+ * `--server` an `approveServer` that takes the CROSS-PROCESS config lock — once a
44
+ * second for the life of the follow, all of it re-deriving an immutable answer.
45
+ * `delete.ts` documents the same cost and answers it the same way, by hand; this
46
+ * is the general version, so the next polling command inherits it.
47
+ */
48
+ export declare function slugRequestOn<T = unknown>(target: SlugTarget, resourcePath: string, init: SlugRequestInit): Promise<T>;
30
49
  export declare function slugRequest<T = unknown>(cwd: string, resourcePath: string, init: SlugRequestInit, server?: string): Promise<{
31
50
  data: T;
32
51
  slug: string;
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as CliError } from "./_output-DbJt9gsl.mjs";
2
+ import { t as CliError } from "./_output-DBP9Op_d.mjs";
3
3
  import path from "node:path";
4
4
  import { isRecord, omitUndefined } from "@alexkroman1/aai/utils";
5
5
  import { MAX_SLUG_LENGTH, PREVIEW_SLUG_SUFFIX, VALID_SLUG_RE } from "@alexkroman1/aai/internal";
@@ -221,7 +221,11 @@ function fetchStudioProject(serverUrl, apiKey, project) {
221
221
  apiKey,
222
222
  action: "pull",
223
223
  allow404: true
224
- });
224
+ }).then((res) => res === null ? null : checkedResponse(res, isStudioProject, `the studio project ${project} at ${serverUrl}`));
225
+ }
226
+ /** A pulled workspace, as the wire must actually carry it. */
227
+ function isStudioProject(value) {
228
+ return isRecord(value) && isRecord(value.files) && Object.values(value.files).every((v) => typeof v === "string") && typeof value.sourceHash === "string";
225
229
  }
226
230
  /** `PUT /studio/projects/:project/source` — the atomic whole-tree push. */
227
231
  function pushStudioSource(serverUrl, apiKey, project, body) {
@@ -231,7 +235,7 @@ function pushStudioSource(serverUrl, apiKey, project, body) {
231
235
  method: "PUT",
232
236
  body,
233
237
  hints: { 409: "The studio has newer changes. Run `aai pull` to fetch them, or `aai push --force` to overwrite." }
234
- });
238
+ }).then((res) => checkedResponse(res, (value) => isRecord(value) && typeof value.sourceHash === "string" && typeof value.created === "boolean", `the studio push route at ${serverUrl}`));
235
239
  }
236
240
  /** `POST /studio/projects/:project/deploy` — Publish, in the project's sandbox. */
237
241
  function publishStudioProject(serverUrl, apiKey, project, opts = {}) {
@@ -10,6 +10,15 @@ export declare const list: import("citty").CommandDef<{
10
10
  };
11
11
  }>;
12
12
  export declare const pull: import("citty").CommandDef<{
13
+ readonly server: {
14
+ readonly type: "string";
15
+ readonly alias: "s";
16
+ readonly description: "Platform server URL";
17
+ };
18
+ readonly json: {
19
+ readonly type: "boolean";
20
+ readonly description: "Output JSON (auto-detected in non-TTY)";
21
+ };
13
22
  readonly project: {
14
23
  readonly type: "positional";
15
24
  readonly description: "Studio project name (see `aai list`)";
@@ -25,6 +34,8 @@ export declare const pull: import("citty").CommandDef<{
25
34
  readonly alias: "f";
26
35
  readonly description: "Overwrite files in a non-empty directory";
27
36
  };
37
+ }>;
38
+ export declare const push: import("citty").CommandDef<{
28
39
  readonly server: {
29
40
  readonly type: "string";
30
41
  readonly alias: "s";
@@ -34,13 +45,13 @@ export declare const pull: import("citty").CommandDef<{
34
45
  readonly type: "boolean";
35
46
  readonly description: "Output JSON (auto-detected in non-TTY)";
36
47
  };
37
- }>;
38
- export declare const push: import("citty").CommandDef<{
39
48
  readonly force: {
40
49
  readonly type: "boolean";
41
50
  readonly alias: "f";
42
51
  readonly description: "Overwrite studio-side changes instead of failing the fast-forward check";
43
52
  };
53
+ }>;
54
+ export declare const publish: import("citty").CommandDef<{
44
55
  readonly server: {
45
56
  readonly type: "string";
46
57
  readonly alias: "s";
@@ -50,22 +61,11 @@ export declare const push: import("citty").CommandDef<{
50
61
  readonly type: "boolean";
51
62
  readonly description: "Output JSON (auto-detected in non-TTY)";
52
63
  };
53
- }>;
54
- export declare const publish: import("citty").CommandDef<{
55
64
  readonly force: {
56
65
  readonly type: "boolean";
57
66
  readonly alias: "f";
58
67
  readonly description: "Overwrite studio-side changes instead of failing the fast-forward check";
59
68
  };
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
69
  readonly skipTypecheck: {
70
70
  readonly type: "boolean";
71
71
  readonly description: "Skip type checking before publishing";
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { o as errorMessage$1 } from "./_utils-Dhd058D9.mjs";
3
- import { t as getMonorepoRoot } from "./_agent-Clbq3a4h.mjs";
2
+ import { g as writeJson, s as errorMessage$1 } from "./_utils-D5JGcjiW.mjs";
3
+ import { t as getMonorepoRoot } from "./_agent-BzUeqOdj.mjs";
4
4
  import { existsSync } from "node:fs";
5
5
  import path from "node:path";
6
6
  import { isRecord } from "@alexkroman1/aai/utils";
@@ -106,7 +106,7 @@ async function layerScaffoldManifest(scaffoldDir, targetDir) {
106
106
  const [mine, theirs] = await Promise.all([readJsonFile(target), readJsonFile(path.join(scaffoldDir, "package.json"))]);
107
107
  if (!(mine && theirs)) return;
108
108
  const merged = mergeScaffoldManifest(mine, theirs);
109
- if (merged) await fs.writeFile(target, `${JSON.stringify(merged, null, 2)}\n`, "utf-8");
109
+ if (merged) await writeJson(target, merged);
110
110
  }
111
111
  /** Parse a JSON file, or null when it is missing or unparseable. */
112
112
  async function readJsonFile(file) {
@@ -119,7 +119,7 @@ async function readJsonFile(file) {
119
119
  }
120
120
  /**
121
121
  * Directory holding the base scaffold — the files every project gets
122
- * underneath its template (package.json, tsconfig, `server.mjs`, …).
122
+ * underneath its template (package.json, tsconfig, `.gitignore`, …).
123
123
  *
124
124
  * The scaffold is the single definition of the self-hosted entrypoint, so a
125
125
  * caller needing one of its files resolves it here rather than carrying a
@@ -37,7 +37,7 @@ type Manifest = Record<string, unknown>;
37
37
  export declare function mergeScaffoldManifest(existing: Manifest, scaffold: Manifest): Manifest | null;
38
38
  /**
39
39
  * Directory holding the base scaffold — the files every project gets
40
- * underneath its template (package.json, tsconfig, `server.mjs`, …).
40
+ * underneath its template (package.json, tsconfig, `.gitignore`, …).
41
41
  *
42
42
  * The scaffold is the single definition of the self-hosted entrypoint, so a
43
43
  * caller needing one of its files resolves it here rather than carrying a
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as CliError } from "./_output-DbJt9gsl.mjs";
2
+ import { t as CliError } from "./_output-DBP9Op_d.mjs";
3
3
  import { n as log } from "./_ui-DCt4qZrk.mjs";
4
4
  import { typecheckProject } from "./typecheck.mjs";
5
5
  //#region src/_typecheck-gate.ts
@@ -9,7 +9,8 @@ import { typecheckProject } from "./typecheck.mjs";
9
9
  * bundlers strip types unchecked, so without this a type-broken agent
10
10
  * ships and misbehaves at runtime instead of failing here.
11
11
  */
12
- async function assertTypechecks(cwd) {
12
+ async function assertTypechecks(cwd, opts = {}) {
13
+ if (opts.skip) return;
13
14
  log.step("Type checking…");
14
15
  const result = await typecheckProject(cwd);
15
16
  if (!result.ok) throw new CliError("typecheck_failed", result.output, "Fix the type errors, or pass --skipTypecheck to build anyway");
@@ -4,4 +4,6 @@
4
4
  * bundlers strip types unchecked, so without this a type-broken agent
5
5
  * ships and misbehaves at runtime instead of failing here.
6
6
  */
7
- export declare function assertTypechecks(cwd: string): Promise<void>;
7
+ export declare function assertTypechecks(cwd: string, opts?: {
8
+ skip?: boolean | undefined;
9
+ }): Promise<void>;
@@ -115,5 +115,47 @@ async function writeJson(filePath, data, opts = {}) {
115
115
  throw err;
116
116
  }
117
117
  }
118
+ /**
119
+ * Code-unit ordering, never `localeCompare`.
120
+ *
121
+ * With no explicit locale `localeCompare` answers to the runtime's ICU default,
122
+ * so the same project would produce a differently-ordered artifact on a
123
+ * different machine — which is why the repo's own generated files sort this way.
124
+ * The reproducibility this protects is real in both callers: the scaffold's
125
+ * `pnpm-workspace.yaml` overrides, and the unrun-spec report.
126
+ */
127
+ function compareCodeUnits(a, b) {
128
+ if (a === b) return 0;
129
+ return a < b ? -1 : 1;
130
+ }
131
+ /**
132
+ * Strip trailing slashes, so a `${base}/...` join cannot produce `//path`.
133
+ *
134
+ * Here rather than beside its first caller because BOTH callers need it and
135
+ * `_agent.ts` — where it lived — is a module `workflow.test.ts` mocks whole, so
136
+ * importing a pure helper from there breaks the mock. `workflow.ts`'s local copy
137
+ * took `/\/$/` — ONE slash — against this `/\/+$/`, so the two disagreed on the
138
+ * only input either was written for.
139
+ */
140
+ function stripTrailingSlash(url) {
141
+ return url.replace(/\/+$/, "");
142
+ }
143
+ /**
144
+ * A list as one phrase — the first {@link MAX_NAMED_ITEMS} names, then a count.
145
+ *
146
+ * One definition because a user reads this wording from two different commands:
147
+ * the unrun-spec report (`aai test`) and the "no such project" hint
148
+ * (`aai pull`). Both open-coded the same slice, the same arithmetic and the same
149
+ * `, and N more`, so the cap could drift between two messages one CLI prints.
150
+ *
151
+ * Here rather than in `_ui.ts` because this is a pure formatter and that module
152
+ * is the OUTPUT seam specs mock — `studio.test.ts` replaces it with a bare
153
+ * factory, which drops every export the factory does not list.
154
+ */
155
+ function formatCappedList(items, max = 10) {
156
+ const named = items.slice(0, max).join(", ");
157
+ const rest = items.length - max;
158
+ return rest > 0 ? `${named}, and ${rest} more` : named;
159
+ }
118
160
  //#endregion
119
- export { errorDetail as a, isEexist as c, resolveCwd as d, validateAgentExport as f, errorCode as i, readJson as l, binFromManifest as n, errorMessage$1 as o, writeJson as p, binFromPackageJson as r, fileExists as s, AGENT_ENTRY as t, readPackageJson as u };
161
+ export { errorCode as a, fileExists as c, readJson as d, readPackageJson as f, writeJson as g, validateAgentExport as h, compareCodeUnits as i, formatCappedList as l, stripTrailingSlash as m, binFromManifest as n, errorDetail as o, resolveCwd as p, binFromPackageJson as r, errorMessage$1 as s, AGENT_ENTRY as t, isEexist as u };
package/dist/_utils.d.ts CHANGED
@@ -69,3 +69,38 @@ export declare function readJson(filePath: string): Promise<unknown>;
69
69
  export declare function writeJson(filePath: string, data: unknown, opts?: {
70
70
  mode?: number;
71
71
  }): Promise<void>;
72
+ /**
73
+ * Code-unit ordering, never `localeCompare`.
74
+ *
75
+ * With no explicit locale `localeCompare` answers to the runtime's ICU default,
76
+ * so the same project would produce a differently-ordered artifact on a
77
+ * different machine — which is why the repo's own generated files sort this way.
78
+ * The reproducibility this protects is real in both callers: the scaffold's
79
+ * `pnpm-workspace.yaml` overrides, and the unrun-spec report.
80
+ */
81
+ export declare function compareCodeUnits(a: string, b: string): number;
82
+ /**
83
+ * Strip trailing slashes, so a `${base}/...` join cannot produce `//path`.
84
+ *
85
+ * Here rather than beside its first caller because BOTH callers need it and
86
+ * `_agent.ts` — where it lived — is a module `workflow.test.ts` mocks whole, so
87
+ * importing a pure helper from there breaks the mock. `workflow.ts`'s local copy
88
+ * took `/\/$/` — ONE slash — against this `/\/+$/`, so the two disagreed on the
89
+ * only input either was written for.
90
+ */
91
+ export declare function stripTrailingSlash(url: string): string;
92
+ /** How many names a capped list prints before it starts counting. */
93
+ export declare const MAX_NAMED_ITEMS = 10;
94
+ /**
95
+ * A list as one phrase — the first {@link MAX_NAMED_ITEMS} names, then a count.
96
+ *
97
+ * One definition because a user reads this wording from two different commands:
98
+ * the unrun-spec report (`aai test`) and the "no such project" hint
99
+ * (`aai pull`). Both open-coded the same slice, the same arithmetic and the same
100
+ * `, and N more`, so the cap could drift between two messages one CLI prints.
101
+ *
102
+ * Here rather than in `_ui.ts` because this is a pure formatter and that module
103
+ * is the OUTPUT seam specs mock — `studio.test.ts` replaces it with a bare
104
+ * factory, which drops every export the factory does not list.
105
+ */
106
+ export declare function formatCappedList(items: readonly string[], max?: number): string;