@alexkroman1/aai-cli 13.2.0 → 14.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 (152) hide show
  1. package/bin.mjs +1 -1
  2. package/dist/{_agent-BTwZJu4P.mjs → _agent-BzUeqOdj.mjs} +33 -10
  3. package/dist/_agent.d.ts +23 -0
  4. package/dist/{_bundler-C31rlcYz.mjs → _bundler-DolUCMxu.mjs} +9 -8
  5. package/dist/_cli-common.d.ts +45 -6
  6. package/dist/{_config-CmJOFsAP.mjs → _config-DYzC6WMD.mjs} +7 -4
  7. package/dist/_config.d.ts +25 -1
  8. package/dist/_dev-env.d.ts +1 -1
  9. package/dist/{_dev-server-DWvkNoIS.mjs → _dev-server-CSMqF8PN.mjs} +28 -30
  10. package/dist/_dev-vite-config.d.ts +3 -3
  11. package/dist/_e2e-test-utils.d.ts +15 -7
  12. package/dist/{_init-BURbdHNM.mjs → _init-CQ8idAwo.mjs} +64 -30
  13. package/dist/{_output-CKkmWs7i.mjs → _output-DBP9Op_d.mjs} +43 -3
  14. package/dist/_output.d.ts +24 -7
  15. package/dist/{_preflight-Co1G9Jww.mjs → _preflight-CcAR577V.mjs} +3 -3
  16. package/dist/_resource-commands.d.ts +9 -9
  17. package/dist/{_server-common-6e0QI4mq.mjs → _server-common-vILJp3it.mjs} +3 -3
  18. package/dist/{_slug-api-CfGQq1Tt.mjs → _slug-api-CGvc2YSQ.mjs} +19 -10
  19. package/dist/_slug-api.d.ts +19 -0
  20. package/dist/{_studio-B1waXMuR.mjs → _studio-DnR_BqFp.mjs} +9 -5
  21. package/dist/_studio-commands.d.ts +13 -13
  22. package/dist/{_templates-0Zr2z6yA.mjs → _templates-CK4oKoeX.mjs} +3 -3
  23. package/dist/{_typecheck-gate-BXvUNnfs.mjs → _typecheck-gate-U3jcil8L.mjs} +5 -4
  24. package/dist/_typecheck-gate.d.ts +3 -1
  25. package/dist/{_ui-u7T4YooX.mjs → _ui-DCt4qZrk.mjs} +1 -1
  26. package/dist/{_utils-B8QmtFhK.mjs → _utils-D5JGcjiW.mjs} +44 -2
  27. package/dist/_utils.d.ts +35 -0
  28. package/dist/{_vite-env-D9Qj6KPX.mjs → _vite-env-kejF0p2L.mjs} +1 -1
  29. package/dist/{_workflow-determinism-f9J-5Zlj.mjs → _workflow-determinism-CvpqMpdh.mjs} +19 -27
  30. package/dist/_workflow-determinism.d.ts +3 -3
  31. package/dist/build-Mxk8gWvX.mjs +108 -0
  32. package/dist/build.d.ts +5 -0
  33. package/dist/cli.mjs +172 -116
  34. package/dist/{client-bundler-j5c46x0G.mjs → client-bundler-BJgREAh6.mjs} +4 -4
  35. package/dist/client-bundler.mjs +1 -1
  36. package/dist/{delete-X_CRgCf6.mjs → delete-BdFnbnVR.mjs} +6 -6
  37. package/dist/{deploy-BzZ-r4iM.mjs → deploy-uAJ4NukN.mjs} +14 -14
  38. package/dist/{dev-BDykFvcc.mjs → dev-DApPSaE_.mjs} +5 -5
  39. package/dist/{eval-0qEOdm0l.mjs → eval-BK47A_K5.mjs} +27 -11
  40. package/dist/eval.d.ts +2 -0
  41. package/dist/{init-BtJI306C.mjs → init-DukDxECd.mjs} +13 -21
  42. package/dist/{login-DeUETobb.mjs → login-DaLrPOFg.mjs} +6 -8
  43. package/dist/{logs-hWnJ66Bl.mjs → logs-7BRwq0cG.mjs} +22 -15
  44. package/dist/project-config.mjs +1 -1
  45. package/dist/scaffold/.env.example +17 -1
  46. package/dist/scaffold/CLAUDE.md +84 -56
  47. package/dist/scaffold/package.json +6 -5
  48. package/dist/scaffold/server.mjs +129 -25
  49. package/dist/secret-Cjp912yF.mjs +162 -0
  50. package/dist/secret.d.ts +48 -13
  51. package/dist/{studio-BxAS_FQQ.mjs → studio-CpHlNHUZ.mjs} +15 -17
  52. package/dist/templates/briefing-desk/agent.test.ts +7 -3
  53. package/dist/templates/call-audit/agent.test.ts +25 -9
  54. package/dist/templates/call-audit/agent.ts +2 -2
  55. package/dist/templates/call-audit/client.tsx +40 -58
  56. package/dist/templates/call-audit/workflows/audit.ts +7 -5
  57. package/dist/templates/call-audit/workflows/ingest.ts +7 -7
  58. package/dist/templates/call-audit/workflows/media.ts +12 -37
  59. package/dist/templates/call-audit/workflows/summarize.ts +8 -8
  60. package/dist/templates/call-audit/workflows/sync-api.ts +3 -3
  61. package/dist/templates/dispatch-center/agent.test.ts +7 -7
  62. package/dist/templates/dispatch-center/client.tsx +53 -61
  63. package/dist/templates/dispatch-center/shared.ts +0 -8
  64. package/dist/templates/dispatch-center/tools/ops_dashboard.ts +4 -1
  65. package/dist/templates/embedded-assets/agent.eval.test.ts +4 -2
  66. package/dist/templates/embedded-assets/agent.test.ts +7 -1
  67. package/dist/templates/health-assistant/agent.eval.test.ts +4 -2
  68. package/dist/templates/health-assistant/agent.test.ts +8 -4
  69. package/dist/templates/infocom-adventure/client.tsx +40 -36
  70. package/dist/templates/link-digest/agent.test.ts +10 -4
  71. package/dist/templates/link-digest/agent.ts +2 -2
  72. package/dist/templates/link-digest/client.tsx +42 -36
  73. package/dist/templates/link-digest/workflows/digest.ts +13 -16
  74. package/dist/templates/night-owl/agent.eval.test.ts +4 -2
  75. package/dist/templates/night-owl/agent.test.ts +6 -1
  76. package/dist/templates/night-owl/client.tsx +8 -2
  77. package/dist/templates/pipeline-simple/agent.test.ts +33 -16
  78. package/dist/templates/pizza-ordering/client.tsx +5 -4
  79. package/dist/templates/pizza-ordering/shared.ts +3 -8
  80. package/dist/templates/pizza-ordering/tools/add_pizza.ts +3 -2
  81. package/dist/templates/pizza-ordering/tools/place_order.ts +3 -2
  82. package/dist/templates/pizza-ordering/tools/remove_pizza.ts +3 -2
  83. package/dist/templates/pizza-ordering/tools/update_pizza.ts +3 -2
  84. package/dist/templates/pizza-ordering/tools/view_order.ts +3 -2
  85. package/dist/templates/plan-and-execute/agent.test.ts +10 -5
  86. package/dist/templates/plan-and-execute/client.tsx +2 -2
  87. package/dist/templates/podcast-digest/agent.test.ts +33 -14
  88. package/dist/templates/podcast-digest/client.tsx +27 -36
  89. package/dist/templates/podcast-digest/workflows/digest.ts +21 -19
  90. package/dist/templates/podcast-digest/workflows/feeds.ts +61 -33
  91. package/dist/templates/podcast-digest/workflows/slack.ts +7 -7
  92. package/dist/templates/recap-workflow/agent.eval.test.ts +21 -13
  93. package/dist/templates/recap-workflow/agent.test.ts +49 -32
  94. package/dist/templates/recap-workflow/workflows/recap.ts +24 -28
  95. package/dist/templates/redline/agent.eval.test.ts +5 -8
  96. package/dist/templates/redline/agent.test.ts +8 -2
  97. package/dist/templates/redline/client.tsx +47 -48
  98. package/dist/templates/redline/workflows/redline.ts +17 -17
  99. package/dist/templates/research-workflow/agent.eval.test.ts +5 -5
  100. package/dist/templates/research-workflow/agent.test.ts +17 -8
  101. package/dist/templates/research-workflow/workflows/research.ts +28 -30
  102. package/dist/templates/retail/agent.test.ts +17 -15
  103. package/dist/templates/retail/cancel.ts +7 -6
  104. package/dist/templates/retail/client.tsx +39 -58
  105. package/dist/templates/retail/payment.ts +4 -3
  106. package/dist/templates/retail/pending.test.ts +19 -19
  107. package/dist/templates/retail/registry.test.ts +99 -58
  108. package/dist/templates/retail/store.test.ts +5 -5
  109. package/dist/templates/retail/swap.ts +8 -11
  110. package/dist/templates/simple/agent.test.ts +39 -6
  111. package/dist/templates/solo-rpg/agent.eval.test.ts +2 -16
  112. package/dist/templates/solo-rpg/agent.test.ts +12 -12
  113. package/dist/templates/solo-rpg/client.tsx +3 -3
  114. package/dist/templates/spoken-summary/agent.test.ts +21 -12
  115. package/dist/templates/spoken-summary/agent.ts +3 -3
  116. package/dist/templates/spoken-summary/client.tsx +36 -47
  117. package/dist/templates/spoken-summary/workflows/summarize.ts +11 -11
  118. package/dist/templates/spoken-summary/workflows/transcribe.ts +14 -14
  119. package/dist/templates/support-line/client.tsx +4 -3
  120. package/dist/templates/support-line/prompts.ts +0 -2
  121. package/dist/templates/transcription-workflow/agent.test.ts +36 -20
  122. package/dist/templates/transcription-workflow/agent.ts +2 -2
  123. package/dist/templates/transcription-workflow/client.tsx +34 -40
  124. package/dist/templates/transcription-workflow/recover.ts +1 -1
  125. package/dist/templates/transcription-workflow/run-panel.tsx +34 -14
  126. package/dist/templates/transcription-workflow/total-latency.tsx +46 -20
  127. package/dist/templates/transcription-workflow/workflows/batch.ts +15 -15
  128. package/dist/templates/transcription-workflow/workflows/downsample.ts +1 -1
  129. package/dist/templates/transcription-workflow/workflows/normalize.ts +15 -19
  130. package/dist/templates/transcription-workflow/workflows/stream.ts +12 -12
  131. package/dist/templates/transcription-workflow/workflows/sync-api.ts +3 -3
  132. package/dist/templates/transcription-workflow/workflows/transcribe.ts +23 -34
  133. package/dist/templates/travel-concierge/agent.eval.test.ts +2 -2
  134. package/dist/templates/travel-concierge/agent.test.ts +15 -7
  135. package/dist/templates/travel-concierge/client.tsx +2 -2
  136. package/dist/templates/travel-concierge/shared.ts +10 -14
  137. package/dist/templates/travel-concierge/tools/lookup_booking.ts +4 -3
  138. package/dist/templates/travel-concierge/tools/search_car_rentals.ts +3 -2
  139. package/dist/templates/travel-concierge/tools/search_excursions.ts +3 -2
  140. package/dist/templates/travel-concierge/tools/search_flights.ts +3 -2
  141. package/dist/templates/travel-concierge/tools/search_hotels.ts +3 -2
  142. package/dist/test-D032V2cA.mjs +242 -0
  143. package/dist/test.d.ts +97 -31
  144. package/dist/typecheck.mjs +2 -2
  145. package/dist/worker-bundler.mjs +3 -3
  146. package/dist/{workflow-BBu5oEDw.mjs → workflow-CVioyO3V.mjs} +8 -7
  147. package/package.json +10 -10
  148. package/dist/bin.d.mts +0 -2
  149. package/dist/build-BLlLOrng.mjs +0 -66
  150. package/dist/secret-B-3T5tvr.mjs +0 -61
  151. package/dist/test-CiLab-AA.mjs +0 -182
  152. package/dist/tsdown.config.d.ts +0 -2
@@ -1,10 +1,25 @@
1
1
  #!/usr/bin/env node
2
- import { c as isEexist, l as readJson, p as writeJson } from "./_utils-B8QmtFhK.mjs";
3
- import { r as isDevMode, t as getMonorepoRoot } from "./_agent-BTwZJu4P.mjs";
4
- import { REPO_URL, downloadAndMergeTemplate } from "./_templates-0Zr2z6yA.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-CK4oKoeX.mjs";
5
5
  import path from "node:path";
6
6
  import fs from "node:fs/promises";
7
- //#region _init.ts
7
+ //#region src/_init.ts
8
+ /**
9
+ * The README every scaffolded project gets, and the first thing a new author
10
+ * reads. Three things in it are corrections rather than prose:
11
+ *
12
+ * - **`npm run dev`, never a bare `aai dev`.** The CLI is a devDependency, so
13
+ * after `npm install` the binary is in `node_modules/.bin` and NOT on
14
+ * `PATH`. The old quickstart said `aai dev`, which fails with
15
+ * `command not found` for everyone who has not installed the CLI globally.
16
+ * - **The key `aai dev` needs is named, with the two LOCAL ways first.** No
17
+ * step of local development needs a platform account; a twenty-persona DX
18
+ * audit read the account-shaped failure it used to get and concluded the
19
+ * opposite, then stopped using the loop this file documents.
20
+ * - **`aai login` appears where it is actually required.** It was in no
21
+ * user-facing doc at all, while the quickstart's own publish step needs it.
22
+ */
8
23
  function readmeContent(slug) {
9
24
  return `# ${slug}
10
25
 
@@ -14,8 +29,41 @@ A voice agent built with [aai](${REPO_URL}).
14
29
 
15
30
  \`\`\`sh
16
31
  npm install # Install dependencies
17
- aai dev # Run locally (opens browser)
18
- aai publish # Publish to production (and sync to the studio)
32
+ npm run dev # Run locally on http://localhost:3000 (opens browser)
33
+ \`\`\`
34
+
35
+ The \`aai\` CLI is a devDependency of this project, so it lives in
36
+ \`node_modules/.bin\` rather than on your \`PATH\`. Run it through npm
37
+ (\`npm run dev\`, \`npm test\`, \`npm run build\`) or with \`npx aai <command>\`.
38
+
39
+ ### The one key local development needs
40
+
41
+ The default pipeline (speech-to-text → LLM → text-to-speech) runs on a single
42
+ AssemblyAI key. **Any one of these is enough, and the first two need no aai
43
+ account** — nothing about running this agent locally is gated on one:
44
+
45
+ 1. Put \`ASSEMBLYAI_API_KEY=<your key>\` in \`.env\` (this project's \`.env.example\`
46
+ documents it, and it is the same file \`aai publish\` uploads as secrets).
47
+ 2. Or export it in your shell: \`export ASSEMBLYAI_API_KEY=<your key>\`.
48
+ 3. Or run \`npx aai login\`, and \`aai dev\` will use your account's key.
49
+
50
+ Get a key at <https://www.assemblyai.com/dashboard>.
51
+
52
+ ## Publishing
53
+
54
+ Publishing (and the studio it syncs to) is the one part that does need an
55
+ account:
56
+
57
+ \`\`\`sh
58
+ npx aai login # Link your account — once per machine
59
+ npm run publish:agent # Publish to production (and sync to the studio)
60
+ \`\`\`
61
+
62
+ You can also run this agent as a plain Node server, with no aai account and
63
+ nothing managed:
64
+
65
+ \`\`\`sh
66
+ npm start # Builds, then serves on http://127.0.0.1:3000
19
67
  \`\`\`
20
68
 
21
69
  ## Secrets
@@ -32,9 +80,9 @@ MY_API_KEY=secret-value
32
80
  **Production** — set secrets on the server:
33
81
 
34
82
  \`\`\`sh
35
- aai secret put MY_KEY # Set a secret (prompts for value)
36
- aai secret list # List secret names
37
- aai secret delete MY_KEY # Remove a secret
83
+ npx aai secret put MY_KEY # Set a secret (prompts for value)
84
+ npx aai secret list # List secret names
85
+ npx aai secret delete MY_KEY # Remove a secret
38
86
  \`\`\`
39
87
 
40
88
  `;
@@ -71,17 +119,6 @@ async function patchPackageJsonForWorkspace(targetDir) {
71
119
  await pinSharedDeps(targetDir, packagesDir, pkgJson);
72
120
  }
73
121
  /**
74
- * Code-unit ordering, never `localeCompare`.
75
- *
76
- * With no explicit locale that answers to the runtime's ICU default, so the same
77
- * project would scaffold a differently-ordered file on a different machine —
78
- * which is the reason the repo's own generated artifacts sort this way too.
79
- */
80
- function compareNames(a, b) {
81
- if (a === b) return 0;
82
- return a < b ? -1 : 1;
83
- }
84
- /**
85
122
  * Pin every THIRD-PARTY dependency this project shares with a linked workspace
86
123
  * package to the copy that workspace package resolved.
87
124
  *
@@ -114,17 +151,14 @@ async function pinSharedDeps(targetDir, packagesDir, pkgJson) {
114
151
  ...pkgJson.devDependencies
115
152
  };
116
153
  const linked = Object.entries(WORKSPACE_PKG_DIRS).filter(([name]) => name in declared).map(([, dir]) => dir);
117
- const pins = /* @__PURE__ */ new Map();
118
- for (const name of Object.keys(declared)) {
119
- 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) => {
120
155
  for (const dir of linked) {
121
156
  const manifest = await readJson(path.join(packagesDir, dir, "node_modules", name, "package.json"));
122
- if (typeof manifest?.version === "string") {
123
- pins.set(name, manifest.version);
124
- break;
125
- }
157
+ if (typeof manifest?.version === "string") return [name, manifest.version];
126
158
  }
127
- }
159
+ return null;
160
+ }));
161
+ const pins = new Map(resolved.filter((entry) => entry !== null));
128
162
  if (pins.size === 0) return;
129
163
  const file = path.join(targetDir, "pnpm-workspace.yaml");
130
164
  let existing;
@@ -133,8 +167,8 @@ async function pinSharedDeps(targetDir, packagesDir, pkgJson) {
133
167
  } catch {
134
168
  return;
135
169
  }
136
- const block = [...pins].sort(([a], [b]) => compareNames(a, b)).map(([name, version]) => ` "${name}": "${version}"`).join("\n");
137
- await fs.writeFile(file, `${existing.trimEnd()}\n\n# Added by \`aai init\` in DEV MODE only — see pinSharedDeps in\n# packages/aai-cli/_init.ts. The @alexkroman1/* packages above are LINKED to
170
+ const block = [...pins].sort(([a], [b]) => compareCodeUnits(a, b)).map(([name, version]) => ` "${name}": "${version}"`).join("\n");
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
138
172
  # this checkout, so anything they and this project both depend on has to be
139
173
  # ONE copy: two copies of xstate are two incompatible sets of types, and the
140
174
  # typecheck gate refuses the deploy.\noverrides:\n${block}\n`);
@@ -1,5 +1,12 @@
1
1
  #!/usr/bin/env node
2
- //#region _output.ts
2
+ import { stripVTControlCharacters } from "node:util";
3
+ //#region src/_output.ts
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
+ */
3
10
  /**
4
11
  * Determine output mode from CLI flags and TTY state.
5
12
  *
@@ -11,7 +18,39 @@ function getOutputMode(args, isTTY = Boolean(process.stdout.isTTY)) {
11
18
  return isTTY ? "human" : "json";
12
19
  }
13
20
  /**
14
- * Write a line to stdout, resolving only once it has been flushed.
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.
25
+ */
26
+ const ANSI_JSON = /\\u001[bB]\[[0-?]*[ -/]*[@-~]/g;
27
+ /**
28
+ * Remove ANSI escape sequences, in raw or JSON-escaped form.
29
+ *
30
+ * A bundler colours its own diagnostics unconditionally, so `aai build`'s
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.
41
+ */
42
+ function stripAnsi(text) {
43
+ return stripVTControlCharacters(text).replace(ANSI_JSON, "");
44
+ }
45
+ /**
46
+ * Write a machine-readable line to stdout, resolving only once it has been
47
+ * flushed.
48
+ *
49
+ * ANSI escapes are stripped on the way out. This function carries JSON mode's
50
+ * one result line and nothing else — human output goes through `log.*` in
51
+ * `_ui.ts`, which is where colour belongs — so stripping here is what keeps
52
+ * the envelope clean no matter which module built the string inside it, and
53
+ * leaves a TTY's coloured diagnostic intact.
15
54
  *
16
55
  * Resolves (never rejects) even when the write fails: the common failure is
17
56
  * EPIPE — the consumer closed the pipe (`aai … --json | head -1`) — and there
@@ -19,8 +58,9 @@ function getOutputMode(args, isTTY = Boolean(process.stdout.isTTY)) {
19
58
  * Stream-level `'error'` events are handled by {@link installStdoutGuard}.
20
59
  */
21
60
  function writeLine(line) {
61
+ const clean = stripAnsi(line);
22
62
  return new Promise((resolve) => {
23
- process.stdout.write(line, () => resolve());
63
+ process.stdout.write(clean, () => resolve());
24
64
  });
25
65
  }
26
66
  /**
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;
@@ -23,7 +17,30 @@ export declare function getOutputMode(args: {
23
17
  json?: boolean | undefined;
24
18
  }, isTTY?: boolean): OutputMode;
25
19
  /**
26
- * Write a line to stdout, resolving only once it has been flushed.
20
+ * Remove ANSI escape sequences, in raw or JSON-escaped form.
21
+ *
22
+ * A bundler colours its own diagnostics unconditionally, so `aai build`'s
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.
33
+ */
34
+ export declare function stripAnsi(text: string): string;
35
+ /**
36
+ * Write a machine-readable line to stdout, resolving only once it has been
37
+ * flushed.
38
+ *
39
+ * ANSI escapes are stripped on the way out. This function carries JSON mode's
40
+ * one result line and nothing else — human output goes through `log.*` in
41
+ * `_ui.ts`, which is where colour belongs — so stripping here is what keeps
42
+ * the envelope clean no matter which module built the string inside it, and
43
+ * leaves a TTY's coloured diagnostic intact.
27
44
  *
28
45
  * Resolves (never rejects) even when the write fails: the common failure is
29
46
  * EPIPE — the consumer closed the pipe (`aai … --json | head -1`) — and there
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import { plural } from "@alexkroman1/aai/utils";
2
3
  import { requiredProviderEnvVars } from "@alexkroman1/aai-runtime";
3
- //#region _preflight.ts
4
+ //#region src/_preflight.ts
4
5
  /**
5
6
  * The deploy-time credential preflight — the classic dev/prod failure, caught
6
7
  * before the upload instead of at the deployed agent's first session.
@@ -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
  }>;
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import { i as errorCode } from "./_utils-B8QmtFhK.mjs";
2
+ import { a as errorCode } from "./_utils-D5JGcjiW.mjs";
3
3
  import path from "node:path";
4
4
  import { parseEnv } from "node:util";
5
5
  import fs from "node:fs/promises";
6
- //#region _server-common.ts
6
+ //#region src/_server-common.ts
7
7
  /**
8
8
  * Build the `ctx.env` record that agent tools will see at runtime.
9
9
  *
@@ -35,7 +35,7 @@ async function resolveServerEnv(cwd, baseEnv) {
35
35
  const env = {};
36
36
  for (const [key, fileVal] of Object.entries(fileEntries)) {
37
37
  const val = source[key] ?? fileVal;
38
- if (val !== void 0) env[key] = val;
38
+ if (val !== void 0 && val !== "") env[key] = val;
39
39
  }
40
40
  return env;
41
41
  }
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { n as getServerInfo } from "./_agent-BTwZJu4P.mjs";
3
- import { l as HINT_NOT_DEPLOYED, s as studioProjectApiUrl, u as apiRequest } from "./_studio-B1waXMuR.mjs";
4
- //#region _slug-api.ts
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
+ //#region src/_slug-api.ts
5
5
  /**
6
6
  * Authenticated request against a deployed agent's slug-scoped resource
7
7
  * (`${serverUrl}/${slug}${resourcePath}`) — the one shape every per-agent
@@ -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,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import { t as CliError } from "./_output-CKkmWs7i.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";
6
6
  import { isLocalOnlyFile, snapshotWorkspaceFiles } from "@alexkroman1/aai/workspace-files";
7
7
  import { FetchError, ofetch } from "ofetch";
8
8
  import { slugifyName } from "@alexkroman1/aai/slugify";
9
- //#region _api-client.ts
9
+ //#region src/_api-client.ts
10
10
  /**
11
11
  * Shared HTTP helper for platform API calls (deploy, delete, secrets).
12
12
  *
@@ -143,7 +143,7 @@ function toApiError(err, url, opts) {
143
143
  return new Error(`${opts.action} failed: could not reach ${url}\n ${hint}`, { cause });
144
144
  }
145
145
  //#endregion
146
- //#region _studio.ts
146
+ //#region src/_studio.ts
147
147
  /**
148
148
  * Internals of the studio-workspace commands (`aai list/pull/push/publish`):
149
149
  * the local source-file walk and the thin clients for the platform's
@@ -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-B8QmtFhK.mjs";
3
- import { t as getMonorepoRoot } from "./_agent-BTwZJu4P.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) {
@@ -1,15 +1,16 @@
1
1
  #!/usr/bin/env node
2
- import { t as CliError } from "./_output-CKkmWs7i.mjs";
3
- import { n as log } from "./_ui-u7T4YooX.mjs";
2
+ import { t as CliError } from "./_output-DBP9Op_d.mjs";
3
+ import { n as log } from "./_ui-DCt4qZrk.mjs";
4
4
  import { typecheckProject } from "./typecheck.mjs";
5
- //#region _typecheck-gate.ts
5
+ //#region src/_typecheck-gate.ts
6
6
  /**
7
7
  * The build/deploy typecheck gate: run the project's own `tsc --noEmit`
8
8
  * (see `typecheck.ts`) and turn a failure into a structured CliError. The
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>;
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { styleText } from "node:util";
3
3
  import * as p from "@clack/prompts";
4
- //#region _ui.ts
4
+ //#region src/_ui.ts
5
5
  const noop = () => {};
6
6
  let silenced = false;
7
7
  /** Log instance that delegates to clack (human mode) or no-ops (JSON mode). */
@@ -3,7 +3,7 @@ import { readFileSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { errorDetail, errorMessage, errorMessage as errorMessage$1, omitUndefined } from "@alexkroman1/aai/utils";
5
5
  import fs from "node:fs/promises";
6
- //#region _utils.ts
6
+ //#region src/_utils.ts
7
7
  /**
8
8
  * The file that marks a directory as an agent project — the single source
9
9
  * for the entry filename every command checks or scaffolds.
@@ -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;
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- //#region _vite-env.ts
2
+ //#region src/_vite-env.ts
3
3
  /**
4
4
  * Packages resolved from the project root rather than from whichever
5
5
  * `node_modules` happens to sit above the importing file.