@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.
- package/dist/{_agent-Clbq3a4h.mjs → _agent-BzUeqOdj.mjs} +32 -9
- package/dist/_agent.d.ts +23 -0
- package/dist/_artifacts-BJOYGQPp.mjs +21 -0
- package/dist/_artifacts.d.ts +16 -0
- package/dist/_build-target.d.ts +172 -0
- package/dist/{_bundler-DEkjO1d8.mjs → _bundler-DM0d0M7m.mjs} +8 -7
- package/dist/_cli-common.d.ts +45 -6
- package/dist/{_config-ZY2euhdh.mjs → _config-DYzC6WMD.mjs} +2 -2
- package/dist/_dev-env.d.ts +1 -1
- package/dist/{_dev-server-C13xuGB1.mjs → _dev-server-BzWB6-4y.mjs} +25 -24
- package/dist/_dev-vite-config.d.ts +3 -3
- package/dist/_e2e-test-utils.d.ts +1 -1
- package/dist/{_init-Ci4QGjp8.mjs → _init-Bsi3DZNJ.mjs} +9 -23
- package/dist/{_output-DbJt9gsl.mjs → _output-DBP9Op_d.mjs} +22 -13
- package/dist/_output.d.ts +10 -9
- package/dist/{_preflight-B2laWPdU.mjs → _preflight-CcAR577V.mjs} +2 -2
- package/dist/_resource-commands.d.ts +9 -9
- package/dist/_server-common-De0haHr9.mjs +70 -0
- package/dist/_server-common.d.ts +20 -1
- package/dist/{_slug-api-DlKpj91X.mjs → _slug-api-CGvc2YSQ.mjs} +18 -9
- package/dist/_slug-api.d.ts +19 -0
- package/dist/{_studio-DTi3EdXQ.mjs → _studio-DnR_BqFp.mjs} +7 -3
- package/dist/_studio-commands.d.ts +13 -13
- package/dist/{_templates-V4l5sZJy.mjs → _templates-CIlJ3Vay.mjs} +4 -4
- package/dist/_templates.d.ts +1 -1
- package/dist/{_typecheck-gate-sV4N5F93.mjs → _typecheck-gate-U3jcil8L.mjs} +3 -2
- package/dist/_typecheck-gate.d.ts +3 -1
- package/dist/{_utils-Dhd058D9.mjs → _utils-D5JGcjiW.mjs} +43 -1
- package/dist/_utils.d.ts +35 -0
- package/dist/_vercel-output.d.ts +63 -0
- package/dist/{_workflow-determinism-C9l8uC2R.mjs → _workflow-determinism-CvpqMpdh.mjs} +18 -26
- package/dist/_workflow-determinism.d.ts +3 -3
- package/dist/build-BhEaxBPu.mjs +481 -0
- package/dist/build.d.ts +23 -10
- package/dist/cli.d.ts +1 -2
- package/dist/cli.mjs +155 -138
- package/dist/{client-bundler-CT-_26c5.mjs → client-bundler-6mTLs6ny.mjs} +5 -5
- package/dist/client-bundler.d.ts +1 -1
- package/dist/client-bundler.mjs +1 -1
- package/dist/{delete-RqlQZ6Y_.mjs → delete-BdFnbnVR.mjs} +4 -4
- package/dist/{deploy-B7f3CK7v.mjs → deploy-CGqPU5U-.mjs} +11 -11
- package/dist/{dev-DamXieW_.mjs → dev-Bx9gYBHM.mjs} +3 -3
- package/dist/{eval-h3DTVfRw.mjs → eval-B3I7FqN9.mjs} +3 -3
- package/dist/{init-D-vFrV8h.mjs → init-CFyusRbq.mjs} +48 -20
- package/dist/init.d.ts +14 -0
- package/dist/{login-CDiemNmC.mjs → login-DaLrPOFg.mjs} +4 -6
- package/dist/{logs-DGwPNCzB.mjs → logs-7BRwq0cG.mjs} +21 -14
- package/dist/project-config.mjs +1 -1
- package/dist/scaffold/CLAUDE.md +155 -76
- package/dist/scaffold/package.json +6 -6
- package/dist/{secret-V1w7y9-j.mjs → secret-Cjp912yF.mjs} +5 -5
- package/dist/secret.d.ts +8 -6
- package/dist/start.d.ts +112 -0
- package/dist/start.mjs +156 -0
- package/dist/{studio-BpMCfUNx.mjs → studio-C_zuRC_z.mjs} +13 -15
- package/dist/templates/briefing-desk/agent.eval.test.ts +156 -0
- package/dist/templates/call-audit/agent.test.ts +11 -6
- package/dist/templates/call-audit/agent.ts +2 -2
- package/dist/templates/call-audit/client.tsx +27 -42
- package/dist/templates/call-audit/workflows/audit.ts +7 -5
- package/dist/templates/call-audit/workflows/ingest.ts +7 -7
- package/dist/templates/call-audit/workflows/media.ts +12 -37
- package/dist/templates/call-audit/workflows/summarize.ts +8 -8
- package/dist/templates/call-audit/workflows/sync-api.ts +3 -3
- package/dist/templates/code-interpreter/agent.test.ts +103 -0
- package/dist/templates/dispatch-center/agent.test.ts +7 -7
- package/dist/templates/dispatch-center/client.tsx +53 -61
- package/dist/templates/dispatch-center/shared.ts +0 -8
- package/dist/templates/dispatch-center/tools/ops_dashboard.ts +4 -1
- package/dist/templates/embedded-assets/agent.eval.test.ts +4 -2
- package/dist/templates/health-assistant/agent.eval.test.ts +4 -2
- package/dist/templates/infocom-adventure/client.tsx +40 -36
- package/dist/templates/link-digest/agent.test.ts +3 -3
- package/dist/templates/link-digest/agent.ts +2 -2
- package/dist/templates/link-digest/client.tsx +81 -19
- package/dist/templates/link-digest/workflows/digest.ts +13 -16
- package/dist/templates/math-buddy/agent.test.ts +126 -0
- package/dist/templates/night-owl/agent.eval.test.ts +4 -2
- package/dist/templates/night-owl/client.tsx +8 -2
- package/dist/templates/personal-finance/agent.test.ts +127 -0
- package/dist/templates/pipeline-simple/agent.test.ts +2 -2
- package/dist/templates/pizza-ordering/client.tsx +5 -4
- package/dist/templates/pizza-ordering/shared.ts +3 -8
- package/dist/templates/pizza-ordering/tools/add_pizza.ts +3 -2
- package/dist/templates/pizza-ordering/tools/place_order.ts +3 -2
- package/dist/templates/pizza-ordering/tools/remove_pizza.ts +3 -2
- package/dist/templates/pizza-ordering/tools/update_pizza.ts +3 -2
- package/dist/templates/pizza-ordering/tools/view_order.ts +3 -2
- package/dist/templates/plan-and-execute/agent.test.ts +10 -5
- package/dist/templates/plan-and-execute/client.tsx +2 -2
- package/dist/templates/podcast-digest/agent.test.ts +20 -11
- package/dist/templates/podcast-digest/client.tsx +16 -22
- package/dist/templates/podcast-digest/workflows/digest.ts +21 -19
- package/dist/templates/podcast-digest/workflows/feeds.ts +61 -33
- package/dist/templates/podcast-digest/workflows/slack.ts +7 -7
- package/dist/templates/recap-workflow/agent.eval.test.ts +21 -13
- package/dist/templates/recap-workflow/agent.test.ts +27 -23
- package/dist/templates/recap-workflow/workflows/recap.ts +24 -28
- package/dist/templates/redline/agent.eval.test.ts +5 -8
- package/dist/templates/redline/agent.test.ts +1 -1
- package/dist/templates/redline/client.tsx +36 -34
- package/dist/templates/redline/workflows/redline.ts +17 -17
- package/dist/templates/research-workflow/agent.eval.test.ts +5 -5
- package/dist/templates/research-workflow/agent.test.ts +10 -7
- package/dist/templates/research-workflow/workflows/research.ts +28 -30
- package/dist/templates/retail/agent.test.ts +17 -15
- package/dist/templates/retail/cancel.ts +7 -6
- package/dist/templates/retail/client.tsx +39 -58
- package/dist/templates/retail/payment.ts +4 -3
- package/dist/templates/retail/pending.test.ts +19 -19
- package/dist/templates/retail/store.test.ts +5 -5
- package/dist/templates/retail/swap.ts +8 -11
- package/dist/templates/solo-rpg/agent.eval.test.ts +2 -16
- package/dist/templates/solo-rpg/agent.test.ts +12 -12
- package/dist/templates/solo-rpg/client.tsx +3 -3
- package/dist/templates/spoken-summary/agent.test.ts +9 -9
- package/dist/templates/spoken-summary/agent.ts +3 -3
- package/dist/templates/spoken-summary/client.tsx +23 -30
- package/dist/templates/spoken-summary/workflows/summarize.ts +11 -11
- package/dist/templates/spoken-summary/workflows/transcribe.ts +14 -14
- package/dist/templates/support-line/agent.ts +8 -0
- package/dist/templates/support-line/client.tsx +4 -3
- package/dist/templates/support-line/prompts.ts +0 -2
- package/dist/templates/transcription-workflow/agent.test.ts +20 -12
- package/dist/templates/transcription-workflow/agent.ts +2 -2
- package/dist/templates/transcription-workflow/client.tsx +15 -14
- package/dist/templates/transcription-workflow/recover.ts +1 -1
- package/dist/templates/transcription-workflow/run-panel.tsx +34 -14
- package/dist/templates/transcription-workflow/total-latency.tsx +46 -20
- package/dist/templates/transcription-workflow/workflows/batch.ts +15 -15
- package/dist/templates/transcription-workflow/workflows/downsample.ts +1 -1
- package/dist/templates/transcription-workflow/workflows/normalize.ts +15 -19
- package/dist/templates/transcription-workflow/workflows/stream.ts +12 -12
- package/dist/templates/transcription-workflow/workflows/sync-api.ts +3 -3
- package/dist/templates/transcription-workflow/workflows/transcribe.ts +23 -34
- package/dist/templates/travel-concierge/agent.eval.test.ts +2 -2
- package/dist/templates/travel-concierge/agent.test.ts +15 -7
- package/dist/templates/travel-concierge/client.tsx +2 -2
- package/dist/templates/travel-concierge/routing.ts +64 -55
- package/dist/templates/travel-concierge/shared.ts +10 -14
- package/dist/templates/travel-concierge/tools/cancel_action.ts +3 -1
- package/dist/templates/travel-concierge/tools/complete_or_escalate.ts +3 -1
- package/dist/templates/travel-concierge/tools/confirm_action.ts +3 -1
- package/dist/templates/travel-concierge/tools/lookup_booking.ts +4 -3
- package/dist/templates/travel-concierge/tools/search_car_rentals.ts +3 -2
- package/dist/templates/travel-concierge/tools/search_excursions.ts +3 -2
- package/dist/templates/travel-concierge/tools/search_flights.ts +3 -2
- package/dist/templates/travel-concierge/tools/search_hotels.ts +3 -2
- package/dist/templates/web-researcher/agent.test.ts +130 -0
- package/dist/{test-guenJUxi.mjs → test-D032V2cA.mjs} +4 -17
- package/dist/typecheck.mjs +1 -1
- package/dist/worker-bundler.d.ts +1 -1
- package/dist/worker-bundler.mjs +8 -8
- package/dist/{workflow-BZyKLkhV.mjs → workflow-CVioyO3V.mjs} +6 -5
- package/package.json +9 -4
- package/dist/_server-common-BV4_CrP3.mjs +0 -43
- package/dist/build-CWyFGe-W.mjs +0 -69
- package/dist/scaffold/server.mjs +0 -204
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { r as isDevMode, t as getMonorepoRoot } from "./_agent-
|
|
4
|
-
import { REPO_URL, downloadAndMergeTemplate } from "./_templates-
|
|
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
|
|
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]) =>
|
|
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
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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
|
-
*
|
|
31
|
-
*
|
|
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
|
|
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
|
-
*
|
|
31
|
-
*
|
|
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
|
-
|
|
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 };
|
package/dist/_server-common.d.ts
CHANGED
|
@@ -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
|
|
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-
|
|
3
|
-
import { l as HINT_NOT_DEPLOYED, s as studioProjectApiUrl, u as apiRequest } from "./_studio-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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 {
|
|
63
|
+
export { slugRequestOn as n, secretRequest as t };
|
package/dist/_slug-api.d.ts
CHANGED
|
@@ -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-
|
|
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 {
|
|
3
|
-
import { t as getMonorepoRoot } from "./_agent-
|
|
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
|
|
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,
|
|
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
|
package/dist/_templates.d.ts
CHANGED
|
@@ -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,
|
|
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-
|
|
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
|
|
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 {
|
|
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;
|