@alexkroman1/aai-cli 5.11.0 → 5.12.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 (40) hide show
  1. package/dist/{_agent-DS2PUJcl.mjs → _agent-JGaAJ5j6.mjs} +3 -2
  2. package/dist/_agent.d.ts +1 -0
  3. package/dist/{_bundler-DC17suWN.mjs → _bundler-BvfPzf8J.mjs} +28 -9
  4. package/dist/_bundler.d.ts +16 -2
  5. package/dist/{_config-DMyolIk9.mjs → _config-riTmrwxX.mjs} +2 -2
  6. package/dist/_deploy.d.ts +0 -10
  7. package/dist/_dev-restart.d.ts +59 -0
  8. package/dist/{_dev-server-B3ivqyEd.mjs → _dev-server-BgXXSijx.mjs} +178 -89
  9. package/dist/_dev-server.d.ts +17 -0
  10. package/dist/{_init-5hHkHe-V.mjs → _init-Ca5Eg5pn.mjs} +2 -2
  11. package/dist/_preflight-BtfaYtbE.mjs +46 -0
  12. package/dist/_preflight.d.ts +57 -0
  13. package/dist/_slug-api-DXm7s9X5.mjs +50 -0
  14. package/dist/_slug-api.d.ts +16 -0
  15. package/dist/_studio-CXoY_Hsg.mjs +94 -0
  16. package/dist/_studio.d.ts +19 -16
  17. package/dist/{_templates-DmdG-voA.mjs → _templates-CKYDgGyY.mjs} +1 -1
  18. package/dist/{_typecheck-gate-DB-PY0A3.mjs → _typecheck-gate-4v9UBZHh.mjs} +1 -1
  19. package/dist/{_ui-DfwfDbT-.mjs → _ui-u7T4YooX.mjs} +5 -1
  20. package/dist/_ui.d.ts +2 -0
  21. package/dist/{build-CACFbdQ4.mjs → build-BTOyN3H5.mjs} +4 -4
  22. package/dist/cli.d.ts +2 -1
  23. package/dist/cli.mjs +69 -33
  24. package/dist/{delete-DEZ7u3u4.mjs → delete-D3gZdSs-.mjs} +3 -3
  25. package/dist/{deploy-Ch0d_jje.mjs → deploy-Df6e7YFn.mjs} +21 -21
  26. package/dist/deploy.d.ts +0 -2
  27. package/dist/{dev-CdeRcYiQ.mjs → dev-DqIUtFfS.mjs} +2 -2
  28. package/dist/{eject-DSrAp8PL.mjs → eject-DSldO60O.mjs} +2 -2
  29. package/dist/{init-BBAoRI_f.mjs → init-B-04lRSV.mjs} +4 -4
  30. package/dist/{login-BeFUiU6M.mjs → login-CtimAsZk.mjs} +4 -15
  31. package/dist/login.d.ts +0 -9
  32. package/dist/project-config.d.ts +23 -0
  33. package/dist/project-config.mjs +3 -0
  34. package/dist/scaffold/package.json +3 -3
  35. package/dist/{secret-4_dYyrpA.mjs → secret-LnFdg8SB.mjs} +7 -7
  36. package/dist/{storage-BTfErOOW.mjs → storage-Cjj3Pv6i.mjs} +2 -2
  37. package/dist/{studio-DPDFKZ9i.mjs → studio-Rl5Po2M6.mjs} +10 -177
  38. package/dist/{test-C-V98oC-.mjs → test-BtOzTcXE.mjs} +1 -1
  39. package/package.json +8 -3
  40. package/dist/_slug-api-fRNNR8tz.mjs +0 -27
@@ -1,25 +1,25 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as ok } from "./_output-CC300DzW.mjs";
3
- import { n as log, t as fmtUrl } from "./_ui-DfwfDbT-.mjs";
3
+ import { n as log, r as notify, t as fmtUrl } from "./_ui-u7T4YooX.mjs";
4
4
  import { a as errorMessage } from "./_utils-8KKw-bzi.mjs";
5
- import { t as buildAgentBundle } from "./_bundler-DC17suWN.mjs";
6
- import { s as updateProjectConfig } from "./_config-DMyolIk9.mjs";
5
+ import { s as updateProjectConfig } from "./_config-riTmrwxX.mjs";
6
+ import { i as evalWorkerConfig, t as buildAgentBundle } from "./_bundler-BvfPzf8J.mjs";
7
7
  import { t as resolveServerEnv } from "./_server-common-DX8Bfrf5.mjs";
8
- import { i as resolveDeployTarget } from "./_agent-DS2PUJcl.mjs";
9
- import { assertTypechecks } from "./_typecheck-gate-DB-PY0A3.mjs";
8
+ import { i as resolveDeployTarget } from "./_agent-JGaAJ5j6.mjs";
9
+ import { assertTypechecks } from "./_typecheck-gate-4v9UBZHh.mjs";
10
10
  import { n as apiRequest } from "./_api-client-B-upMGkc.mjs";
11
+ import { i as projectNameFromDir } from "./_studio-CXoY_Hsg.mjs";
11
12
  import { gzipSync } from "node:zlib";
12
13
  //#region _deploy.ts
13
14
  async function runDeploy(opts) {
14
15
  const body = gzipSync(JSON.stringify({
15
16
  ...opts.slug ? { slug: opts.slug } : {},
16
- ...opts.allowMissingSecrets ? { credentialPolicy: "warn" } : {},
17
17
  ...opts.allowPreviewSlug ? { allowPreviewSlug: true } : {},
18
18
  env: opts.env,
19
19
  worker: opts.bundle.worker,
20
20
  clientFiles: opts.bundle.clientFiles
21
21
  }));
22
- const data = await apiRequest(`${opts.url}/deploy`, {
22
+ return { slug: (await apiRequest(`${opts.url}/deploy`, {
23
23
  method: "POST",
24
24
  body,
25
25
  headers: {
@@ -32,11 +32,7 @@ async function runDeploy(opts) {
32
32
  ...opts.slug ? {} : { retry: 0 },
33
33
  ...opts.retryDelay !== void 0 ? { retryDelay: opts.retryDelay } : {},
34
34
  ...opts.fetch ? { fetch: opts.fetch } : {}
35
- });
36
- return {
37
- slug: data.slug,
38
- ...data.warnings ? { warnings: data.warnings } : {}
39
- };
35
+ })).slug };
40
36
  }
41
37
  //#endregion
42
38
  //#region deploy.ts
@@ -44,19 +40,24 @@ async function executeDeploy(opts) {
44
40
  const { cwd } = opts;
45
41
  const { config: projectConfig, serverUrl, apiKey } = await resolveDeployTarget(cwd, opts.server);
46
42
  if (!opts.skipTypecheck) await assertTypechecks(cwd);
43
+ const preflightModule = import("./_preflight-BtfaYtbE.mjs");
47
44
  const bundle = await buildAgentBundle(cwd, { minify: true });
48
- const slug = projectConfig?.slug;
49
- const env = await resolveServerEnv(cwd);
45
+ const slug = projectConfig?.slug ?? projectNameFromDir(cwd) ?? void 0;
46
+ const uploadEnv = {
47
+ ASSEMBLYAI_API_KEY: apiKey,
48
+ ...await resolveServerEnv(cwd)
49
+ };
50
+ const { missingCredentialMessage, missingCredentials } = await preflightModule;
51
+ const config = await evalWorkerConfig(bundle.worker);
52
+ const missing = config ? missingCredentials(config, uploadEnv) : [];
53
+ const warnings = missing.length > 0 ? [missingCredentialMessage(missing)] : [];
54
+ for (const warning of warnings) notify("warn", warning);
50
55
  log.step(`Deploying${slug ? ` ${slug}` : ""}…`);
51
56
  const deployed = await runDeploy({
52
57
  url: serverUrl,
53
58
  bundle,
54
- env: {
55
- ASSEMBLYAI_API_KEY: apiKey,
56
- ...env
57
- },
59
+ env: uploadEnv,
58
60
  ...slug ? { slug } : {},
59
- ...opts.allowMissingSecrets ? { allowMissingSecrets: true } : {},
60
61
  ...opts.allowPreviewSlug ? { allowPreviewSlug: true } : {},
61
62
  apiKey
62
63
  });
@@ -73,12 +74,11 @@ async function executeDeploy(opts) {
73
74
  serverUrl
74
75
  })}`);
75
76
  }
76
- for (const warning of deployed.warnings ?? []) log.warn(warning);
77
77
  log.success(`Deployed ${fmtUrl(agentUrl)}`);
78
78
  return ok({
79
79
  slug: deployed.slug,
80
80
  url: agentUrl,
81
- ...deployed.warnings ? { warnings: deployed.warnings } : {}
81
+ ...warnings.length > 0 ? { warnings } : {}
82
82
  });
83
83
  }
84
84
  //#endregion
package/dist/deploy.d.ts CHANGED
@@ -7,8 +7,6 @@ type DeployData = {
7
7
  export declare function executeDeploy(opts: {
8
8
  cwd: string;
9
9
  server?: string | undefined;
10
- /** See DeployOpts.allowMissingSecrets (`--allow-missing-secrets`). */
11
- allowMissingSecrets?: boolean | undefined;
12
10
  /** See DeployOpts.allowPreviewSlug (`--allow-preview-slug`; studio-internal). */
13
11
  allowPreviewSlug?: boolean | undefined;
14
12
  /** `--skipTypecheck`: deploy without the tsc gate. */
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as ok } from "./_output-CC300DzW.mjs";
3
- import { i as parsePort, n as log, r as notify, t as fmtUrl } from "./_ui-DfwfDbT-.mjs";
3
+ import { a as parsePort, n as log, r as notify, t as fmtUrl } from "./_ui-u7T4YooX.mjs";
4
4
  import { i as errorDetail } from "./_utils-8KKw-bzi.mjs";
5
5
  import path from "node:path";
6
6
  import { styleText } from "node:util";
@@ -12,7 +12,7 @@ import { styleText } from "node:util";
12
12
  async function executeDev(opts) {
13
13
  const port = parsePort(opts.port);
14
14
  const agentName = path.basename(path.resolve(opts.cwd));
15
- const { startDevServer } = await import("./_dev-server-B3ivqyEd.mjs");
15
+ const { startDevServer } = await import("./_dev-server-BgXXSijx.mjs");
16
16
  let cleanup;
17
17
  let shuttingDown = false;
18
18
  const onSignal = () => {
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as ok, t as CliError } from "./_output-CC300DzW.mjs";
3
- import { n as log } from "./_ui-DfwfDbT-.mjs";
3
+ import { n as log } from "./_ui-u7T4YooX.mjs";
4
4
  import { a as errorMessage, c as readJson, d as writeJson, o as fileExists } from "./_utils-8KKw-bzi.mjs";
5
- import { scaffoldDir } from "./_templates-DmdG-voA.mjs";
5
+ import { scaffoldDir } from "./_templates-CKYDgGyY.mjs";
6
6
  import path from "node:path";
7
7
  import fs from "node:fs/promises";
8
8
  //#region eject.ts
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as ok } from "./_output-CC300DzW.mjs";
3
- import { n as log, o as unwrapCancel } from "./_ui-DfwfDbT-.mjs";
3
+ import { n as log, s as unwrapCancel } from "./_ui-u7T4YooX.mjs";
4
4
  import { a as errorMessage, c as readJson, l as resolveCwd, o as fileExists, t as AGENT_ENTRY } from "./_utils-8KKw-bzi.mjs";
5
- import { r as isDevMode, t as getMonorepoRoot } from "./_agent-DS2PUJcl.mjs";
5
+ import { r as isDevMode, t as getMonorepoRoot } from "./_agent-JGaAJ5j6.mjs";
6
6
  import path from "node:path";
7
7
  import { styleText } from "node:util";
8
8
  import * as p from "@clack/prompts";
@@ -75,7 +75,7 @@ function resolveTargetDir(dir) {
75
75
  }
76
76
  /** Publish after init and return deploy metadata if successful. */
77
77
  async function tryPublish(cwd, server) {
78
- const { executePublish } = await import("./studio-DPDFKZ9i.mjs");
78
+ const { executePublish } = await import("./studio-Rl5Po2M6.mjs");
79
79
  try {
80
80
  const result = await executePublish({
81
81
  cwd,
@@ -94,7 +94,7 @@ async function tryPublish(cwd, server) {
94
94
  }
95
95
  /** Scaffold the project, optionally showing a spinner. */
96
96
  async function scaffoldProject(dir, cwd, template, silent) {
97
- const { runInit } = await import("./_init-5hHkHe-V.mjs");
97
+ const { runInit } = await import("./_init-Ca5Eg5pn.mjs");
98
98
  const s = silent ? void 0 : p.spinner();
99
99
  s?.start(`Creating ${dir}`);
100
100
  await runInit({
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as ok, t as CliError } from "./_output-CC300DzW.mjs";
3
- import { n as log } from "./_ui-DfwfDbT-.mjs";
4
- import { o as updateGlobalConfig, r as readGlobalConfig, t as approveServer } from "./_config-DMyolIk9.mjs";
5
- import { a as resolveServerUrl } from "./_agent-DS2PUJcl.mjs";
3
+ import { n as log } from "./_ui-u7T4YooX.mjs";
4
+ import { o as updateGlobalConfig, r as readGlobalConfig, t as approveServer } from "./_config-riTmrwxX.mjs";
5
+ import { a as resolveServerUrl } from "./_agent-JGaAJ5j6.mjs";
6
+ import { linkConfirmationCode } from "@alexkroman1/aai/utils";
6
7
  import { spawn } from "node:child_process";
7
8
  import { setTimeout } from "node:timers/promises";
8
9
  import { randomBytes } from "node:crypto";
@@ -115,18 +116,6 @@ function openerFor(platform) {
115
116
  ]];
116
117
  return ["xdg-open", []];
117
118
  }
118
- /**
119
- * Human-matchable confirmation derived from the link code — printed in the
120
- * terminal AND shown on the browser approval gate (aai-studio-client's
121
- * cli-link.ts derives the same value; keep the two in lockstep). Not a
122
- * secret: both ends already hold the full code. It exists so someone who
123
- * lands on an approval page they didn't cause has a concrete mismatch to
124
- * notice ("what terminal?") instead of a bare Approve button.
125
- */
126
- function linkConfirmationCode(code) {
127
- const head = code.slice(0, 8).toUpperCase();
128
- return `${head.slice(0, 4)}-${head.slice(4)}`;
129
- }
130
119
  /** Best-effort: the link URL is always printed, so a failure is fine. */
131
120
  function defaultOpenBrowser(url) {
132
121
  const [cmd, args] = openerFor(process.platform);
package/dist/login.d.ts CHANGED
@@ -30,15 +30,6 @@ export type LoginDeps = {
30
30
  /** Test seam — never set outside tests. */
31
31
  timeoutMs?: number;
32
32
  };
33
- /**
34
- * Human-matchable confirmation derived from the link code — printed in the
35
- * terminal AND shown on the browser approval gate (aai-studio-client's
36
- * cli-link.ts derives the same value; keep the two in lockstep). Not a
37
- * secret: both ends already hold the full code. It exists so someone who
38
- * lands on an approval page they didn't cause has a concrete mismatch to
39
- * notice ("what terminal?") instead of a bare Approve button.
40
- */
41
- export declare function linkConfirmationCode(code: string): string;
42
33
  export declare function executeLogin(opts: {
43
34
  server?: string | undefined;
44
35
  }, deps?: LoginDeps): Promise<CommandResult<{
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The two on-disk formats a CLI invocation reads, as WRITERS other packages
3
+ * can call — the config home (`config.json`, which holds the API key) and a
4
+ * project's `.aai/project.json` pin.
5
+ *
6
+ * It exists for the studio guest. Publish materializes a workspace into a
7
+ * real project and then spawns this CLI against it
8
+ * (`aai-guest/studio-publish.ts`), which means writing both files first; it
9
+ * used to do that with `JSON.stringify`, so the shapes agreed by coincidence
10
+ * and nothing tied them to the schemas the CLI parses them back with. Two of
11
+ * this file's properties are not obvious from the JSON and were absent from
12
+ * the hand-written copies' successors-in-waiting: the config home is written
13
+ * 0600 through an atomic rename (so an older world-readable file is
14
+ * TIGHTENED rather than left), and the project pin is MERGED, never
15
+ * replaced — `.aai/project.json` also carries the studio link fields, and a
16
+ * writer that replaces the document drops them.
17
+ *
18
+ * Deliberately a thin re-export of `_config.ts` rather than a second
19
+ * implementation: the point is that there is one writer per format, not that
20
+ * there is a nicer one here.
21
+ */
22
+ export type { GlobalConfig, ProjectConfig } from "./_config.ts";
23
+ export { updateProjectConfig, writeGlobalConfig as writeConfigHome } from "./_config.ts";
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { c as writeGlobalConfig, s as updateProjectConfig } from "./_config-riTmrwxX.mjs";
3
+ export { updateProjectConfig, writeGlobalConfig as writeConfigHome };
@@ -12,15 +12,15 @@
12
12
  "publish:agent": "aai publish"
13
13
  },
14
14
  "dependencies": {
15
- "@alexkroman1/aai": "^5.11.0",
16
- "@alexkroman1/aai-ui": "^5.11.0",
15
+ "@alexkroman1/aai": "^5.12.0",
16
+ "@alexkroman1/aai-ui": "^5.12.0",
17
17
  "react": "^19.2.8",
18
18
  "react-dom": "^19.2.8",
19
19
  "tailwindcss": "^4.0.0",
20
20
  "zod": "^4.4.3"
21
21
  },
22
22
  "devDependencies": {
23
- "@alexkroman1/aai-cli": "^5.11.0",
23
+ "@alexkroman1/aai-cli": "^5.12.0",
24
24
  "@tailwindcss/vite": "^4.3.3",
25
25
  "@types/node": "^26.1.1",
26
26
  "@types/react": "^19.2.17",
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as ok, n as fail } from "./_output-CC300DzW.mjs";
3
- import { n as log, o as unwrapCancel } from "./_ui-DfwfDbT-.mjs";
4
- import { t as slugRequest } from "./_slug-api-fRNNR8tz.mjs";
3
+ import { n as log, s as unwrapCancel } from "./_ui-u7T4YooX.mjs";
4
+ import { t as secretRequest } from "./_slug-api-DXm7s9X5.mjs";
5
5
  import * as p from "@clack/prompts";
6
6
  import { text } from "node:stream/consumers";
7
7
  //#region secret.ts
@@ -29,24 +29,24 @@ async function executeSecretPut(cwd, name, value, server) {
29
29
  if (!result) return fail(...NO_INPUT);
30
30
  secretValue = result;
31
31
  }
32
- const { slug } = await slugRequest(cwd, "/secret", {
32
+ const { target } = await secretRequest(cwd, "", {
33
33
  method: "PUT",
34
34
  body: { [name]: secretValue },
35
35
  action: "secret"
36
36
  }, server);
37
- log.success(`Set ${name} for ${slug}`);
37
+ log.success(`Set ${name} for ${target}`);
38
38
  return ok({ name });
39
39
  }
40
40
  async function executeSecretDelete(cwd, name, server) {
41
- const { slug } = await slugRequest(cwd, `/secret/${encodeURIComponent(name)}`, {
41
+ const { target } = await secretRequest(cwd, `/${encodeURIComponent(name)}`, {
42
42
  method: "DELETE",
43
43
  action: "secret"
44
44
  }, server);
45
- log.success(`Deleted ${name} from ${slug}`);
45
+ log.success(`Deleted ${name} from ${target}`);
46
46
  return ok({ name });
47
47
  }
48
48
  async function executeSecretList(cwd, server) {
49
- const { data: { vars } } = await slugRequest(cwd, "/secret", { action: "secret" }, server);
49
+ const { data: { vars } } = await secretRequest(cwd, "", { action: "secret" }, server);
50
50
  if (vars.length === 0) log.info("No secrets set. Use `aai secret put <name>` to add one.");
51
51
  else {
52
52
  log.message(`${vars.length} secret${vars.length === 1 ? "" : "s"}:`);
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as ok, n as fail } from "./_output-CC300DzW.mjs";
3
- import { n as log } from "./_ui-DfwfDbT-.mjs";
4
- import { t as slugRequest } from "./_slug-api-fRNNR8tz.mjs";
3
+ import { n as log } from "./_ui-u7T4YooX.mjs";
4
+ import { n as slugRequest } from "./_slug-api-DXm7s9X5.mjs";
5
5
  import * as p from "@clack/prompts";
6
6
  //#region storage.ts
7
7
  async function storageRequest(cwd, init, server) {
@@ -1,181 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as ok, t as CliError } from "./_output-CC300DzW.mjs";
3
- import { n as log, t as fmtUrl } from "./_ui-DfwfDbT-.mjs";
4
- import { s as updateProjectConfig } from "./_config-DMyolIk9.mjs";
3
+ import { n as log, t as fmtUrl } from "./_ui-u7T4YooX.mjs";
4
+ import { s as updateProjectConfig } from "./_config-riTmrwxX.mjs";
5
5
  import { t as resolveServerEnv } from "./_server-common-DX8Bfrf5.mjs";
6
- import { i as resolveDeployTarget } from "./_agent-DS2PUJcl.mjs";
7
- import { layerScaffold } from "./_templates-DmdG-voA.mjs";
6
+ import { i as resolveDeployTarget } from "./_agent-JGaAJ5j6.mjs";
7
+ import { layerScaffold } from "./_templates-CKYDgGyY.mjs";
8
8
  import { n as apiRequest } from "./_api-client-B-upMGkc.mjs";
9
+ import { a as publishStudioProject, i as projectNameFromDir, n as fetchStudioProject, o as pushStudioSource, r as listStudioProjects, s as studioProjectUrl, t as collectSourceFiles } from "./_studio-CXoY_Hsg.mjs";
9
10
  import path from "node:path";
10
- import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
11
- import { PREVIEW_SLUG_SUFFIX, VALID_SLUG_RE } from "@alexkroman1/aai/utils";
12
- //#region _studio.ts
13
- /**
14
- * Internals of the studio-workspace commands (`aai list/pull/push/publish`):
15
- * the local source-file walk and the thin clients for the platform's
16
- * `/studio/projects` routes. The workspace is the single source of truth —
17
- * these helpers only move files between a local directory and the project's
18
- * workspace row; production deploys happen exclusively through the studio's
19
- * Publish route (which runs the deploy machinery in the project's sandbox).
20
- */
21
- /**
22
- * Mirrors of the studio workspace caps (`aai-studio-server/studio-limits.ts`)
23
- * — by value, like `aai-guest/limits.ts`, since the CLI cannot depend on the
24
- * private server package. The server re-validates every push; these exist so
25
- * an oversized file is a named warning locally instead of a rejected upload.
26
- */
27
- const MAX_STUDIO_FILE_BYTES = 256e3;
28
- /**
29
- * What never syncs to a workspace — the same set the guest's snapshot skips
30
- * (`aai-guest/studio-workspace-fs.ts`), plus what only exists locally:
31
- * secrets (`.env` rides the secret routes, never a workspace row) and
32
- * lockfiles (regenerated by install, noise in the studio editor).
33
- */
34
- const IGNORED_DIRS = /* @__PURE__ */ new Set([
35
- "node_modules",
36
- ".git",
37
- "dist",
38
- ".aai"
39
- ]);
40
- const IGNORED_FILES = [
41
- /^\.env(\..+)?$/,
42
- /^pnpm-lock\.yaml$/,
43
- /^package-lock\.json$/,
44
- /^yarn\.lock$/,
45
- /^\.DS_Store$/
46
- ];
47
- function isIgnoredFile(name) {
48
- return IGNORED_FILES.some((re) => re.test(name));
49
- }
50
- /** Project-relative paths of every syncable file under `dir`, sorted. */
51
- async function walkProject(dir, current = dir) {
52
- const out = [];
53
- const entries = await readdir(current, { withFileTypes: true });
54
- for (const entry of entries) {
55
- if (entry.isSymbolicLink()) continue;
56
- const abs = path.join(current, entry.name);
57
- if (entry.isDirectory()) {
58
- if (!IGNORED_DIRS.has(entry.name)) out.push(...await walkProject(dir, abs));
59
- } else if (entry.isFile() && !isIgnoredFile(entry.name)) out.push(path.relative(dir, abs));
60
- }
61
- return out.sort((a, b) => a.localeCompare(b));
62
- }
63
- /**
64
- * Decode `buf` as UTF-8, or null when it isn't valid UTF-8.
65
- *
66
- * `fatal` makes an invalid sequence throw instead of becoming U+FFFD, which
67
- * is the whole point: a workspace is a JSON path→string map and cannot carry
68
- * arbitrary bytes, so a lossy read turned a pushed PNG into replacement
69
- * characters while reporting success — and a later `aai pull` wrote the
70
- * mangled version back over the local original. `ignoreBOM` keeps a leading
71
- * U+FEFF in the string; without it the decoder strips the BOM and the check
72
- * meant to stop corruption would quietly perform some of its own.
73
- */
74
- const UTF8_STRICT = new TextDecoder("utf-8", {
75
- fatal: true,
76
- ignoreBOM: true
77
- });
78
- function decodeUtf8(buf) {
79
- try {
80
- return UTF8_STRICT.decode(buf);
81
- } catch {
82
- return null;
83
- }
84
- }
85
- /**
86
- * Walk a local project into the path→content record a workspace stores —
87
- * the CLI-side twin of the guest's `snapshotWorkspace`: same ignored
88
- * directories, same caps, oversized and non-text files skipped with a
89
- * warning rather than failing the whole push.
90
- */
91
- async function collectSourceFiles(dir) {
92
- const paths = await walkProject(dir);
93
- const files = {};
94
- const warnings = [];
95
- if (paths.length > 100) warnings.push(`Project has ${paths.length} files; only the first 100 sync to the studio.`);
96
- const read = await Promise.all(paths.slice(0, 100).map(async (rel) => {
97
- const abs = path.join(dir, rel);
98
- const st = await stat(abs);
99
- if (st.size > 256e3) return {
100
- ok: false,
101
- warning: `${rel} is ${st.size} bytes (max ${MAX_STUDIO_FILE_BYTES}) — not synced.`
102
- };
103
- const content = decodeUtf8(await readFile(abs));
104
- if (content === null) return {
105
- ok: false,
106
- warning: `${rel} is not valid UTF-8 (binary file?) — not synced.`
107
- };
108
- return {
109
- ok: true,
110
- rel,
111
- content
112
- };
113
- }));
114
- for (const entry of read) {
115
- if (!entry.ok) {
116
- warnings.push(entry.warning);
117
- continue;
118
- }
119
- files[entry.rel.split(path.sep).join("/")] = entry.content;
120
- }
121
- return {
122
- files,
123
- warnings
124
- };
125
- }
126
- /**
127
- * A studio project name derived from a directory name, or null if unusable.
128
- *
129
- * A `-preview` suffix is deliberately unusable. Publishing a project deploys
130
- * it under the project's own name, so a `*-preview` project would claim a
131
- * slug the studio's orphan-preview sweep reaps hourly — deleting the agent,
132
- * its app-database schema, and its secrets on a schedule the user never
133
- * asked for. Refusing the name is recoverable (rename the directory); losing
134
- * a published agent to the reaper is not.
135
- */
136
- function projectNameFromDir(dir) {
137
- const name = path.basename(dir).toLowerCase().replace(/[^a-z0-9-_]+/g, "-").replace(/-{2,}/g, "-").replace(/^[-_]+|[-_]+$/g, "").slice(0, 64);
138
- if (name.endsWith(PREVIEW_SLUG_SUFFIX)) return null;
139
- return VALID_SLUG_RE.test(name) ? name : null;
140
- }
141
- /** The shareable studio URL for a project — what every command prints. */
142
- function studioProjectUrl(serverUrl, project) {
143
- return `${serverUrl}/studio/chat/${project}`;
144
- }
145
- function listStudioProjects(serverUrl, apiKey) {
146
- return apiRequest(`${serverUrl}/studio/projects`, {
147
- apiKey,
148
- action: "list"
149
- }).then((res) => res.projects);
150
- }
151
- /** Fetch a project, or null when it doesn't exist (the push existence probe). */
152
- function fetchStudioProject(serverUrl, apiKey, project) {
153
- return apiRequest(`${serverUrl}/studio/projects/${encodeURIComponent(project)}`, {
154
- apiKey,
155
- action: "pull",
156
- allow404: true
157
- });
158
- }
159
- /** `PUT /studio/projects/:project/source` — the atomic whole-tree push. */
160
- function pushStudioSource(serverUrl, apiKey, project, body) {
161
- return apiRequest(`${serverUrl}/studio/projects/${encodeURIComponent(project)}/source`, {
162
- apiKey,
163
- action: "push",
164
- method: "PUT",
165
- body,
166
- hints: { 409: "The studio has newer changes. Run `aai pull` to fetch them, or `aai push --force` to overwrite." }
167
- });
168
- }
169
- /** `POST /studio/projects/:project/deploy` — Publish, in the project's sandbox. */
170
- function publishStudioProject(serverUrl, apiKey, project) {
171
- return apiRequest(`${serverUrl}/studio/projects/${encodeURIComponent(project)}/deploy`, {
172
- apiKey,
173
- action: "publish",
174
- method: "POST",
175
- retry: 0
176
- });
177
- }
178
- //#endregion
11
+ import { mkdir, readdir, writeFile } from "node:fs/promises";
179
12
  //#region studio.ts
180
13
  /**
181
14
  * The studio-workspace commands: `aai list`, `aai pull`, `aai push`,
@@ -310,11 +143,11 @@ async function executePush(opts) {
310
143
  * deploy time, which is why publish syncs them BEFORE deploying when the
311
144
  * slug is already known.
312
145
  */
313
- async function syncEnvSecrets(cwd, serverUrl, apiKey, slug) {
146
+ async function syncEnvSecrets(cwd, serverUrl, apiKey, project) {
314
147
  const env = await resolveServerEnv(cwd);
315
148
  const names = Object.keys(env);
316
149
  if (names.length === 0) return [];
317
- await apiRequest(`${serverUrl}/${slug}/secret`, {
150
+ await apiRequest(`${serverUrl}/studio/projects/${encodeURIComponent(project)}/secret`, {
318
151
  apiKey,
319
152
  action: "secret",
320
153
  method: "PUT",
@@ -325,13 +158,13 @@ async function syncEnvSecrets(cwd, serverUrl, apiKey, slug) {
325
158
  }
326
159
  async function executePublish(opts) {
327
160
  if (!opts.skipTypecheck) {
328
- const { assertTypechecks } = await import("./_typecheck-gate-DB-PY0A3.mjs");
161
+ const { assertTypechecks } = await import("./_typecheck-gate-4v9UBZHh.mjs");
329
162
  await assertTypechecks(opts.cwd);
330
163
  }
331
164
  const pushed = await pushProject(opts);
332
165
  const { project, serverUrl, apiKey } = pushed;
333
166
  const hadSlug = pushed.slug !== void 0;
334
- if (pushed.slug) await syncEnvSecrets(opts.cwd, serverUrl, apiKey, pushed.slug);
167
+ if (pushed.slug) await syncEnvSecrets(opts.cwd, serverUrl, apiKey, project);
335
168
  log.step(`Publishing ${project} (builds in the project's sandbox)…`);
336
169
  const result = await publishStudioProject(serverUrl, apiKey, project);
337
170
  if (typeof result?.slug !== "string" || typeof result?.output !== "string") throw new CliError("bad_publish_response", `Unexpected response from the publish route at ${serverUrl}.`, "Check that --server points at an aai platform server, then try again.");
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as ok, n as fail } from "./_output-CC300DzW.mjs";
3
- import { n as log } from "./_ui-DfwfDbT-.mjs";
3
+ import { n as log } from "./_ui-u7T4YooX.mjs";
4
4
  import { a as errorMessage, n as binFromPackageJson, r as errorCode } from "./_utils-8KKw-bzi.mjs";
5
5
  import { createRequire } from "node:module";
6
6
  import { existsSync } from "node:fs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alexkroman1/aai-cli",
3
- "version": "5.11.0",
3
+ "version": "5.12.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "aai": "bin.mjs"
@@ -20,6 +20,11 @@
20
20
  "@dev/source": "./typecheck.ts",
21
21
  "types": "./dist/typecheck.d.ts",
22
22
  "import": "./dist/typecheck.mjs"
23
+ },
24
+ "./project-config": {
25
+ "@dev/source": "./project-config.ts",
26
+ "types": "./dist/project-config.d.ts",
27
+ "import": "./dist/project-config.mjs"
23
28
  }
24
29
  },
25
30
  "files": [
@@ -38,8 +43,8 @@
38
43
  "p-timeout": "^7.0.1",
39
44
  "vite": "^8.1.5",
40
45
  "zod": "^4.4.3",
41
- "@alexkroman1/aai": "5.11.0",
42
- "@alexkroman1/aai-ui": "5.11.0"
46
+ "@alexkroman1/aai": "5.12.0",
47
+ "@alexkroman1/aai-ui": "5.12.0"
43
48
  },
44
49
  "devDependencies": {
45
50
  "playwright": "^1.61.1",
@@ -1,27 +0,0 @@
1
- #!/usr/bin/env node
2
- import { n as getServerInfo } from "./_agent-DS2PUJcl.mjs";
3
- import { n as apiRequest, t as HINT_NOT_DEPLOYED } from "./_api-client-B-upMGkc.mjs";
4
- //#region _slug-api.ts
5
- /**
6
- * Authenticated request against a deployed agent's slug-scoped resource
7
- * (`${serverUrl}/${slug}${resourcePath}`) — the one shape every per-agent
8
- * command (secret, storage) shares, including the standard "not deployed"
9
- * 404 hint.
10
- *
11
- * Its own module (rather than living beside `getServerInfo` in `_agent.ts`)
12
- * so tests can mock `_agent.ts`/`_api-client.ts` while this composition
13
- * stays real — an intra-module call would bypass those mocks.
14
- */
15
- async function slugRequest(cwd, resourcePath, init, server) {
16
- const { serverUrl, slug, apiKey } = await getServerInfo(cwd, server);
17
- return {
18
- data: await apiRequest(`${serverUrl}/${slug}${resourcePath}`, {
19
- ...init,
20
- apiKey,
21
- hints: { 404: HINT_NOT_DEPLOYED }
22
- }),
23
- slug
24
- };
25
- }
26
- //#endregion
27
- export { slugRequest as t };