agentful 0.4.13 → 0.4.14

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 (3) hide show
  1. package/README.md +12 -1
  2. package/dist/index.cjs +1206 -437
  3. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -1904,10 +1904,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
1904
1904
  args = args.slice();
1905
1905
  let launchWithNode = false;
1906
1906
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
1907
- function findFile(baseDir, baseName) {
1908
- const localBin = path.resolve(baseDir, baseName);
1907
+ function findFile(baseDir, baseName2) {
1908
+ const localBin = path.resolve(baseDir, baseName2);
1909
1909
  if (fs.existsSync(localBin)) return localBin;
1910
- if (sourceExt.includes(path.extname(baseName))) return void 0;
1910
+ if (sourceExt.includes(path.extname(baseName2))) return void 0;
1911
1911
  const foundExt = sourceExt.find(
1912
1912
  (ext) => fs.existsSync(`${localBin}${ext}`)
1913
1913
  );
@@ -3044,7 +3044,7 @@ var VERSION, brand, useColor, wrap, paint, tuiDesign, sym, ui;
3044
3044
  var init_branding = __esm({
3045
3045
  "src/branding.ts"() {
3046
3046
  "use strict";
3047
- VERSION = "0.4.13" ? "0.4.13" : null.version;
3047
+ VERSION = "0.4.14" ? "0.4.14" : null.version;
3048
3048
  brand = {
3049
3049
  name: "agentful",
3050
3050
  // the command users type
@@ -5539,102 +5539,6 @@ function zipSync(data, opts) {
5539
5539
 
5540
5540
  // src/lib/zip.ts
5541
5541
  var import_ignore = __toESM(require_ignore());
5542
- var MAX_ZIP_BYTES = 6 * 1024 * 1024;
5543
- var ALWAYS_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
5544
- ".git",
5545
- "node_modules",
5546
- ".agentful",
5547
- ".opencode",
5548
- ".cache",
5549
- "__macosx",
5550
- "dist",
5551
- "build",
5552
- "out",
5553
- ".next",
5554
- ".output",
5555
- ".svelte-kit",
5556
- ".astro",
5557
- ".vercel",
5558
- ".netlify"
5559
- ]);
5560
- var ALWAYS_EXCLUDE_BASENAMES = /* @__PURE__ */ new Set([".ds_store", "thumbs.db"]);
5561
- var isEnvFile = (name) => name === ".env" || name.startsWith(".env.");
5562
- function collectFiles(rootDir, mode) {
5563
- const ig = (0, import_ignore.default)();
5564
- if (mode === "source") {
5565
- const gitignorePath = (0, import_node_path6.join)(rootDir, ".gitignore");
5566
- if ((0, import_node_fs4.existsSync)(gitignorePath)) {
5567
- ig.add((0, import_node_fs4.readFileSync)(gitignorePath, "utf8"));
5568
- }
5569
- }
5570
- const results = [];
5571
- const walk = (dir) => {
5572
- for (const entry of (0, import_node_fs4.readdirSync)(dir, { withFileTypes: true })) {
5573
- const abs = (0, import_node_path6.join)(dir, entry.name);
5574
- const rel = (0, import_node_path6.relative)(rootDir, abs).split(import_node_path6.sep).join("/");
5575
- const lower = entry.name.toLowerCase();
5576
- if (entry.isSymbolicLink()) continue;
5577
- if (entry.isDirectory()) {
5578
- if (mode === "source" && (ALWAYS_EXCLUDE_DIRS.has(lower) || ig.ignores(rel + "/"))) continue;
5579
- walk(abs);
5580
- continue;
5581
- }
5582
- if (!entry.isFile()) continue;
5583
- if (ALWAYS_EXCLUDE_BASENAMES.has(lower)) continue;
5584
- if (mode === "source") {
5585
- if (isEnvFile(entry.name)) continue;
5586
- if (ig.ignores(rel)) continue;
5587
- }
5588
- results.push({ rel, abs });
5589
- }
5590
- };
5591
- walk(rootDir);
5592
- return results;
5593
- }
5594
- function zipToBase64(files) {
5595
- if (files.length === 0) {
5596
- throw new ApiError(0, "empty_project", "Nothing to push \u2014 no files found after applying ignore rules.");
5597
- }
5598
- const entries = {};
5599
- for (const f of files) {
5600
- entries[f.rel] = (0, import_node_fs4.readFileSync)(f.abs);
5601
- }
5602
- const zipped = zipSync(entries, { level: 6 });
5603
- if (zipped.byteLength > MAX_ZIP_BYTES) {
5604
- throw new ApiError(
5605
- 0,
5606
- "zip_too_large",
5607
- `Project zip is ${(zipped.byteLength / 1024 / 1024).toFixed(1)} MB \u2014 the push cap is ${MAX_ZIP_BYTES / 1024 / 1024} MB. Check for large media/binary files; assets belong in the platform asset flow.`
5608
- );
5609
- }
5610
- return {
5611
- base64: Buffer.from(zipped).toString("base64"),
5612
- bytes: zipped.byteLength,
5613
- count: files.length
5614
- };
5615
- }
5616
- function detectDistDir(rootDir, override) {
5617
- if (override) {
5618
- const dir = (0, import_node_path6.join)(rootDir, override);
5619
- if (!(0, import_node_fs4.existsSync)(dir) || !(0, import_node_fs4.statSync)(dir).isDirectory()) {
5620
- throw new ApiError(0, "dist_not_found", `--dir ${override} does not exist or is not a directory.`);
5621
- }
5622
- return dir;
5623
- }
5624
- for (const candidate of ["dist", "build", "out", (0, import_node_path6.join)(".output", "public")]) {
5625
- const dir = (0, import_node_path6.join)(rootDir, candidate);
5626
- if ((0, import_node_fs4.existsSync)((0, import_node_path6.join)(dir, "index.html"))) return dir;
5627
- }
5628
- throw new ApiError(
5629
- 0,
5630
- "dist_not_found",
5631
- "No built output found (looked for dist/, build/, out/, .output/public with an index.html). Run your build first or pass --dir <path>."
5632
- );
5633
- }
5634
-
5635
- // src/lib/preflight.ts
5636
- var import_node_fs6 = require("fs");
5637
- var import_node_path8 = require("path");
5638
5542
 
5639
5543
  // ../platform-contract/contract.json
5640
5544
  var contract_default = {
@@ -5874,9 +5778,350 @@ var contract_default = {
5874
5778
  }
5875
5779
  };
5876
5780
 
5877
- // ../platform-contract/buildEnv.ts
5781
+ // src/lib/uploadLimits.ts
5782
+ var MAX_ZIP_UPLOAD_BYTES = contract_default.limits.max_zip_upload_bytes;
5783
+ var MAX_UNPACKED_BYTES = 25 * 1024 * 1024;
5784
+ var MAX_UPLOAD_FILES = 2e3;
5785
+ var MAX_INLINE_REQUEST_BYTES = 6 * 1024 * 1024 - 64 * 1024;
5786
+ function uploadRequestBody(zipBase64, mode, declaration) {
5787
+ return { zip_base64: zipBase64, mode, clean: true, ...declaration ? { backend_declaration: declaration } : {} };
5788
+ }
5789
+ function maxZipBytesForUpload(mode = "source", declaration = null) {
5790
+ const around = Buffer.byteLength(JSON.stringify(uploadRequestBody("", mode, declaration)), "utf8");
5791
+ const base64Budget = Math.max(0, MAX_INLINE_REQUEST_BYTES - around);
5792
+ return Math.min(MAX_ZIP_UPLOAD_BYTES, Math.floor(base64Budget / 4) * 3);
5793
+ }
5794
+ var SERVER_SKIP_DIRS = /* @__PURE__ */ new Set([
5795
+ "node_modules",
5796
+ ".git",
5797
+ ".opencode",
5798
+ ".cache",
5799
+ "__macosx",
5800
+ ".svelte-kit",
5801
+ ".next",
5802
+ ".nuxt",
5803
+ ".output",
5804
+ ".astro",
5805
+ ".vercel",
5806
+ ".netlify",
5807
+ ".wrangler",
5808
+ ".turbo",
5809
+ "dist",
5810
+ "build",
5811
+ "out"
5812
+ ]);
5813
+ var SERVER_SKIP_BASENAMES = /* @__PURE__ */ new Set([".ds_store", "thumbs.db"]);
5814
+ var SERVER_SECRET_MARKERS = [
5815
+ ".server/secrets/",
5816
+ "/secrets/",
5817
+ "secrets/",
5818
+ ".git/",
5819
+ "node_modules/",
5820
+ "opencode-logs/",
5821
+ ".opencode/"
5822
+ ];
5823
+ var isMacosMetadata = (rel) => {
5824
+ const lowered = rel.toLowerCase();
5825
+ if (lowered === "__macosx" || lowered.startsWith("__macosx/")) return true;
5826
+ return rel.slice(rel.lastIndexOf("/") + 1).startsWith("._");
5827
+ };
5828
+ function secretMatch(rel) {
5829
+ const base = rel.slice(rel.lastIndexOf("/") + 1);
5830
+ if (base === ".env" || base.startsWith(".env.") || base.endsWith(".env")) return { reason: "env_file", matched: base };
5831
+ const lowered = rel.toLowerCase();
5832
+ if (lowered.startsWith("knowledge/")) return { reason: "secret_path", matched: "knowledge/" };
5833
+ const marker = SERVER_SECRET_MARKERS.find((m) => lowered.includes(m));
5834
+ return marker ? { reason: "secret_path", matched: marker } : null;
5835
+ }
5836
+ function serverView(rels) {
5837
+ const topLevels = /* @__PURE__ */ new Set();
5838
+ let nested = false;
5839
+ for (const rel of rels) {
5840
+ if (isMacosMetadata(rel)) continue;
5841
+ const slash = rel.indexOf("/");
5842
+ topLevels.add(slash === -1 ? rel : rel.slice(0, slash));
5843
+ if (slash !== -1) nested = true;
5844
+ }
5845
+ const strippedPrefix = topLevels.size === 1 && nested ? `${[...topLevels][0]}/` : "";
5846
+ const stored = /* @__PURE__ */ new Map();
5847
+ const dropped = [];
5848
+ for (const rel of rels) {
5849
+ if (isMacosMetadata(rel)) {
5850
+ dropped.push({ rel, reason: "macos_metadata", matched: rel.slice(rel.lastIndexOf("/") + 1) });
5851
+ continue;
5852
+ }
5853
+ const inner = strippedPrefix && rel.startsWith(strippedPrefix) ? rel.slice(strippedPrefix.length) : rel;
5854
+ if (!inner) continue;
5855
+ const parts = inner.split("/");
5856
+ const skippedDir = parts.slice(0, -1).find((part) => SERVER_SKIP_DIRS.has(part.toLowerCase()));
5857
+ if (skippedDir) {
5858
+ dropped.push({ rel, reason: "skipped_directory", matched: `${skippedDir}/` });
5859
+ continue;
5860
+ }
5861
+ const base = parts[parts.length - 1];
5862
+ if (SERVER_SKIP_BASENAMES.has(base.toLowerCase())) {
5863
+ dropped.push({ rel, reason: "junk_file", matched: base });
5864
+ continue;
5865
+ }
5866
+ const secret = secretMatch(inner);
5867
+ if (secret) {
5868
+ dropped.push({ rel, ...secret });
5869
+ continue;
5870
+ }
5871
+ stored.set(rel, inner);
5872
+ }
5873
+ return { strippedPrefix, stored, dropped };
5874
+ }
5875
+
5876
+ // src/lib/zip.ts
5877
+ var ZIP_LEVEL = 6;
5878
+ var ALWAYS_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
5879
+ ".git",
5880
+ "node_modules",
5881
+ ".agentful",
5882
+ ".opencode",
5883
+ ".cache",
5884
+ "__macosx",
5885
+ "dist",
5886
+ "build",
5887
+ "out",
5888
+ ".next",
5889
+ ".output",
5890
+ ".svelte-kit",
5891
+ ".astro",
5892
+ ".vercel",
5893
+ ".netlify"
5894
+ ]);
5895
+ var ALWAYS_EXCLUDE_BASENAMES = /* @__PURE__ */ new Set([".ds_store", "thumbs.db"]);
5896
+ var isEnvFile = (name) => name === ".env" || name.startsWith(".env.");
5897
+ function scanTree(rootDir, mode) {
5898
+ const ig = (0, import_ignore.default)();
5899
+ let gitignoreApplied = false;
5900
+ if (mode === "source") {
5901
+ const gitignorePath = (0, import_node_path6.join)(rootDir, ".gitignore");
5902
+ if ((0, import_node_fs4.existsSync)(gitignorePath)) {
5903
+ ig.add((0, import_node_fs4.readFileSync)(gitignorePath, "utf8"));
5904
+ gitignoreApplied = true;
5905
+ }
5906
+ }
5907
+ const files = [];
5908
+ const excluded = [];
5909
+ const walk = (dir) => {
5910
+ for (const entry of (0, import_node_fs4.readdirSync)(dir, { withFileTypes: true })) {
5911
+ const abs = (0, import_node_path6.join)(dir, entry.name);
5912
+ const rel = (0, import_node_path6.relative)(rootDir, abs).split(import_node_path6.sep).join("/");
5913
+ const lower = entry.name.toLowerCase();
5914
+ if (entry.isSymbolicLink()) {
5915
+ excluded.push({ rel, kind: "symlink", reason: "symlink" });
5916
+ continue;
5917
+ }
5918
+ if (entry.isDirectory()) {
5919
+ if (mode === "source" && ALWAYS_EXCLUDE_DIRS.has(lower)) {
5920
+ excluded.push({ rel, kind: "dir", reason: "always_excluded_dir" });
5921
+ continue;
5922
+ }
5923
+ if (mode === "source" && ig.ignores(rel + "/")) {
5924
+ excluded.push({ rel, kind: "dir", reason: "gitignore" });
5925
+ continue;
5926
+ }
5927
+ walk(abs);
5928
+ continue;
5929
+ }
5930
+ if (!entry.isFile()) {
5931
+ excluded.push({ rel, kind: "other", reason: "not_a_regular_file" });
5932
+ continue;
5933
+ }
5934
+ if (ALWAYS_EXCLUDE_BASENAMES.has(lower)) {
5935
+ excluded.push({ rel, kind: "file", reason: "os_junk" });
5936
+ continue;
5937
+ }
5938
+ if (mode === "source") {
5939
+ if (isEnvFile(entry.name)) {
5940
+ excluded.push({ rel, kind: "file", reason: "env_file" });
5941
+ continue;
5942
+ }
5943
+ if (ig.ignores(rel)) {
5944
+ excluded.push({ rel, kind: "file", reason: "gitignore" });
5945
+ continue;
5946
+ }
5947
+ }
5948
+ files.push({ rel, abs });
5949
+ }
5950
+ };
5951
+ walk(rootDir);
5952
+ return { files, excluded, gitignoreApplied };
5953
+ }
5954
+ function collectFiles(rootDir, mode) {
5955
+ return scanTree(rootDir, mode).files;
5956
+ }
5957
+ function zipToBase64(files, maxBytes = maxZipBytesForUpload()) {
5958
+ if (files.length === 0) {
5959
+ throw new ApiError(0, "empty_project", "Nothing to push \u2014 no files found after applying ignore rules.");
5960
+ }
5961
+ const entries = {};
5962
+ for (const f of files) {
5963
+ entries[f.rel] = (0, import_node_fs4.readFileSync)(f.abs);
5964
+ }
5965
+ const zipped = zipSync(entries, { level: ZIP_LEVEL });
5966
+ if (zipped.byteLength > maxBytes) {
5967
+ throw new ApiError(
5968
+ 0,
5969
+ "zip_too_large",
5970
+ `The upload is ${zipped.byteLength.toLocaleString("en-US")} bytes compressed \u2014 a push carries at most ${maxBytes.toLocaleString("en-US")}. \`agentful push --dry-run\` shows what takes the space.`
5971
+ );
5972
+ }
5973
+ return {
5974
+ base64: Buffer.from(zipped).toString("base64"),
5975
+ bytes: zipped.byteLength,
5976
+ count: files.length
5977
+ };
5978
+ }
5979
+ function detectDistDir(rootDir, override) {
5980
+ if (override) {
5981
+ const dir = (0, import_node_path6.join)(rootDir, override);
5982
+ if (!(0, import_node_fs4.existsSync)(dir) || !(0, import_node_fs4.statSync)(dir).isDirectory()) {
5983
+ throw new ApiError(0, "dist_not_found", `--dir ${override} does not exist or is not a directory.`);
5984
+ }
5985
+ return dir;
5986
+ }
5987
+ for (const candidate of ["dist", "build", "out", (0, import_node_path6.join)(".output", "public")]) {
5988
+ const dir = (0, import_node_path6.join)(rootDir, candidate);
5989
+ if ((0, import_node_fs4.existsSync)((0, import_node_path6.join)(dir, "index.html"))) return dir;
5990
+ }
5991
+ throw new ApiError(
5992
+ 0,
5993
+ "dist_not_found",
5994
+ "No built output found (looked for dist/, build/, out/, .output/public with an index.html). Run your build first or pass --dir <path>."
5995
+ );
5996
+ }
5997
+
5998
+ // src/lib/pushAnalysis.ts
5999
+ var import_node_crypto = require("crypto");
6000
+ var import_node_fs8 = require("fs");
6001
+ var import_node_path10 = require("path");
6002
+ init_branding();
6003
+
6004
+ // src/lib/backendDecl.ts
5878
6005
  var import_node_fs5 = require("fs");
5879
6006
  var import_node_path7 = require("path");
6007
+ function readBackendDeclaration(dir = process.cwd()) {
6008
+ const path = (0, import_node_path7.join)(dir, ".agentful", "backend.json");
6009
+ if (!(0, import_node_fs5.existsSync)(path)) return null;
6010
+ try {
6011
+ const parsed = JSON.parse((0, import_node_fs5.readFileSync)(path, "utf8"));
6012
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
6013
+ const collections = Array.isArray(parsed.collections) ? parsed.collections.filter((c) => c && typeof c === "object" && !Array.isArray(c)) : [];
6014
+ const secrets = Array.isArray(parsed.secrets) ? parsed.secrets.filter((s) => s && typeof s === "object" && !Array.isArray(s)) : [];
6015
+ const auth = parsed.auth && typeof parsed.auth === "object" && !Array.isArray(parsed.auth) ? parsed.auth : void 0;
6016
+ const decl = {
6017
+ ...parsed,
6018
+ schema_version: Number(parsed.schema_version) || 1,
6019
+ managed_db: Boolean(parsed.managed_db),
6020
+ actions: Array.isArray(parsed.actions) ? parsed.actions.filter((a) => typeof a === "string") : [],
6021
+ contract_doc: typeof parsed.contract_doc === "string" ? parsed.contract_doc : void 0,
6022
+ collections,
6023
+ secrets,
6024
+ auth
6025
+ };
6026
+ const loginMethods = authMethodsOf(decl);
6027
+ if (!decl.managed_db && decl.actions.length === 0 && collections.length === 0 && secrets.length === 0 && loginMethods.length === 0) {
6028
+ return null;
6029
+ }
6030
+ return decl;
6031
+ } catch {
6032
+ return null;
6033
+ }
6034
+ }
6035
+ function authMethodsOf(decl) {
6036
+ const raw = decl?.auth?.end_user_login;
6037
+ return Array.isArray(raw) ? raw.filter((m) => typeof m === "string") : [];
6038
+ }
6039
+ function describeBackendDeclaration(decl) {
6040
+ const parts = [];
6041
+ const collections = decl.collections ?? [];
6042
+ if (decl.managed_db || collections.length > 0) {
6043
+ parts.push(collections.length > 0 ? `managed DB with ${collections.length} collection(s)` : "managed DB");
6044
+ }
6045
+ if (decl.actions && decl.actions.length > 0) parts.push(`${decl.actions.length} server action(s)`);
6046
+ if (decl.secrets && decl.secrets.length > 0) parts.push(`${decl.secrets.length} secret(s)`);
6047
+ const login = authMethodsOf(decl);
6048
+ if (login.length > 0) parts.push(`login: ${login.join(" + ")}`);
6049
+ const base = parts.join(" + ") || "managed backend";
6050
+ return decl.contract_doc ? `${base} (contract: ${decl.contract_doc})` : base;
6051
+ }
6052
+ function backendTabUrl(userId, projectId) {
6053
+ return `${APP_URL}/workspace/${userId}/${projectId}?view=backend`;
6054
+ }
6055
+ var ACTIONS_DIR = (0, import_node_path7.join)(".server", "actions");
6056
+ var ACTION_NAME = /^[a-z][a-z0-9_-]{0,62}$/;
6057
+ function checkBackendDeclaration(rootDir = process.cwd()) {
6058
+ const issues = [];
6059
+ const warnings = [];
6060
+ const actionsDir = (0, import_node_path7.join)(rootDir, ACTIONS_DIR);
6061
+ const presentFiles = /* @__PURE__ */ new Set();
6062
+ if ((0, import_node_fs5.existsSync)(actionsDir)) {
6063
+ for (const entry of (0, import_node_fs5.readdirSync)(actionsDir)) {
6064
+ const abs = (0, import_node_path7.join)(actionsDir, entry);
6065
+ let isDir = false;
6066
+ try {
6067
+ isDir = (0, import_node_fs5.statSync)(abs).isDirectory();
6068
+ } catch {
6069
+ continue;
6070
+ }
6071
+ if (isDir) {
6072
+ issues.push(
6073
+ `.server/actions/${entry}/ is a directory \u2014 action files must live directly under .server/actions/ (the platform rejects subdirectories).`
6074
+ );
6075
+ continue;
6076
+ }
6077
+ presentFiles.add(entry);
6078
+ const name = entry.endsWith(".js") ? entry.slice(0, -3) : null;
6079
+ if (name === null || !ACTION_NAME.test(name)) {
6080
+ issues.push(
6081
+ `.server/actions/${entry}: invalid action filename \u2014 must match [a-z][a-z0-9_-]{0,62}.js (names starting with _ are reserved). The platform rejects the whole push otherwise.`
6082
+ );
6083
+ }
6084
+ }
6085
+ }
6086
+ const decl = readBackendDeclaration(rootDir);
6087
+ const declared = decl?.actions ?? [];
6088
+ for (const name of declared) {
6089
+ if (!ACTION_NAME.test(name)) {
6090
+ issues.push(
6091
+ `.agentful/backend.json declares action "${name}" \u2014 names must match [a-z][a-z0-9_-]{0,62} (the filename without .js).`
6092
+ );
6093
+ continue;
6094
+ }
6095
+ if (!presentFiles.has(`${name}.js`)) {
6096
+ issues.push(
6097
+ `.agentful/backend.json declares action "${name}" but .server/actions/${name}.js does not exist \u2014 create the file or remove the declaration entry.`
6098
+ );
6099
+ }
6100
+ }
6101
+ const declaredSet = new Set(declared);
6102
+ for (const file of presentFiles) {
6103
+ const name = file.endsWith(".js") ? file.slice(0, -3) : file;
6104
+ if (ACTION_NAME.test(name) && !declaredSet.has(name)) {
6105
+ warnings.push(
6106
+ `.server/actions/${file} is not declared in .agentful/backend.json \u2014 it will deploy, but \`agentful backend\` and the Backend tab won't know about it. Add "${name}" to the actions list.`
6107
+ );
6108
+ }
6109
+ }
6110
+ if (decl && decl.schema_version < 2 && ((decl.collections?.length ?? 0) > 0 || (decl.secrets?.length ?? 0) > 0 || authMethodsOf(decl).length > 0)) {
6111
+ issues.push(
6112
+ '.agentful/backend.json declares collections/secrets/auth but has "schema_version": 1 \u2014 set it to 2 (schema_version 1 declares actions only).'
6113
+ );
6114
+ }
6115
+ return { issues, warnings };
6116
+ }
6117
+
6118
+ // src/lib/preflight.ts
6119
+ var import_node_fs7 = require("fs");
6120
+ var import_node_path9 = require("path");
6121
+
6122
+ // ../platform-contract/buildEnv.ts
6123
+ var import_node_fs6 = require("fs");
6124
+ var import_node_path8 = require("path");
5880
6125
  var BUILD_ENV = contract_default.publish.build_env;
5881
6126
  var PUBLIC_PREFIXES = BUILD_ENV.public_prefixes;
5882
6127
  var BUILTINS = new Set(BUILD_ENV.bundler_builtins);
@@ -5907,10 +6152,10 @@ var MENTIONS_ENV = /\benv\s*[.[]/;
5907
6152
  var stripNonCode = (text) => text.replace(QUOTED, (quoted) => MENTIONS_ENV.test(quoted) ? "''" : quoted).replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:'"`\\])\/\/[^\n]*/g, "$1");
5908
6153
  var isReportable = (name) => !BUILTINS.has(name) && splitName(name) !== null;
5909
6154
  function collectFiles2(dir, out, budget) {
5910
- if (budget.left <= 0 || !(0, import_node_fs5.existsSync)(dir)) return;
5911
- for (const entry of (0, import_node_fs5.readdirSync)(dir, { withFileTypes: true })) {
6155
+ if (budget.left <= 0 || !(0, import_node_fs6.existsSync)(dir)) return;
6156
+ for (const entry of (0, import_node_fs6.readdirSync)(dir, { withFileTypes: true })) {
5912
6157
  if (budget.left <= 0) return;
5913
- const abs = (0, import_node_path7.join)(dir, entry.name);
6158
+ const abs = (0, import_node_path8.join)(dir, entry.name);
5914
6159
  if (entry.isDirectory()) {
5915
6160
  if (!SKIP_DIRS.has(entry.name)) collectFiles2(abs, out, budget);
5916
6161
  } else if (entry.isFile() && SCAN_FILE.test(entry.name) && !SKIP_FILE.test(entry.name)) {
@@ -5926,7 +6171,7 @@ function scanEnvReads(rootDir) {
5926
6171
  for (const abs of files) {
5927
6172
  let text;
5928
6173
  try {
5929
- text = (0, import_node_fs5.readFileSync)(abs, "utf8");
6174
+ text = (0, import_node_fs6.readFileSync)(abs, "utf8");
5930
6175
  } catch {
5931
6176
  continue;
5932
6177
  }
@@ -5943,19 +6188,19 @@ function scanEnvReads(rootDir) {
5943
6188
  if (field) note(field[1], Boolean(field[2]));
5944
6189
  }
5945
6190
  }
5946
- const file = (0, import_node_path7.relative)(rootDir, abs).split(import_node_path7.sep).join("/");
6191
+ const file = (0, import_node_path8.relative)(rootDir, abs).split(import_node_path8.sep).join("/");
5947
6192
  for (const [name, hasFallback] of inFile) reads.push({ name, file, hasFallback });
5948
6193
  }
5949
6194
  return reads;
5950
6195
  }
5951
6196
  function envFileNames(rootDir) {
5952
6197
  const names = /* @__PURE__ */ new Set();
5953
- if (!(0, import_node_fs5.existsSync)(rootDir)) return names;
5954
- for (const entry of (0, import_node_fs5.readdirSync)(rootDir, { withFileTypes: true })) {
6198
+ if (!(0, import_node_fs6.existsSync)(rootDir)) return names;
6199
+ for (const entry of (0, import_node_fs6.readdirSync)(rootDir, { withFileTypes: true })) {
5955
6200
  if (!entry.isFile() || !(entry.name === ".env" || entry.name.startsWith(".env."))) continue;
5956
6201
  let text;
5957
6202
  try {
5958
- text = (0, import_node_fs5.readFileSync)((0, import_node_path7.join)(rootDir, entry.name), "utf8");
6203
+ text = (0, import_node_fs6.readFileSync)((0, import_node_path8.join)(rootDir, entry.name), "utf8");
5959
6204
  } catch {
5960
6205
  continue;
5961
6206
  }
@@ -6031,7 +6276,9 @@ function feature(id) {
6031
6276
  var FRAMEWORK_TABLE = contract_default.frameworks;
6032
6277
  var SUPPORTED_FRAMEWORKS = FRAMEWORK_TABLE.map((f) => f.display_name).map((name, i, all) => i === all.length - 1 ? `or ${name}` : name).join(", ");
6033
6278
  var PLATFORM_CONTRACT = contract_default.contract_statement;
6034
- var MAX_ZIP_UPLOAD_BYTES = contract_default.limits.max_zip_upload_bytes;
6279
+ function frameworkDisplayName(id) {
6280
+ return FRAMEWORK_TABLE.find((f) => f.id === id)?.display_name ?? id;
6281
+ }
6035
6282
  var NEXT_CONFIG_BASENAMES = ["next.config.js", "next.config.ts", "next.config.mjs", "next.config.cjs"];
6036
6283
  var ROUTES_FEATURE = feature("app_route_handlers");
6037
6284
  var ROUTE_HANDLER_FILE = new RegExp(
@@ -6052,11 +6299,11 @@ var USE_SERVER = /^\s*(['"])use server\1\s*;?\s*$/m;
6052
6299
  var SKIP_DIRS2 = /* @__PURE__ */ new Set(["node_modules", ".next", ".git", "dist", "build", "out"]);
6053
6300
  var SCAN_FILE_BUDGET2 = 5e3;
6054
6301
  function detectFramework(rootDir) {
6055
- const pkgPath = (0, import_node_path8.join)(rootDir, "package.json");
6056
- if (!(0, import_node_fs6.existsSync)(pkgPath)) return "vanilla";
6302
+ const pkgPath = (0, import_node_path9.join)(rootDir, "package.json");
6303
+ if (!(0, import_node_fs7.existsSync)(pkgPath)) return "vanilla";
6057
6304
  let pkg;
6058
6305
  try {
6059
- pkg = JSON.parse((0, import_node_fs6.readFileSync)(pkgPath, "utf8"));
6306
+ pkg = JSON.parse((0, import_node_fs7.readFileSync)(pkgPath, "utf8"));
6060
6307
  } catch {
6061
6308
  return "unknown";
6062
6309
  }
@@ -6066,30 +6313,30 @@ function detectFramework(rootDir) {
6066
6313
  }
6067
6314
  function readDeps(rootDir) {
6068
6315
  try {
6069
- const pkg = JSON.parse((0, import_node_fs6.readFileSync)((0, import_node_path8.join)(rootDir, "package.json"), "utf8"));
6316
+ const pkg = JSON.parse((0, import_node_fs7.readFileSync)((0, import_node_path9.join)(rootDir, "package.json"), "utf8"));
6070
6317
  return { ...pkg.dependencies, ...pkg.devDependencies };
6071
6318
  } catch {
6072
6319
  return {};
6073
6320
  }
6074
6321
  }
6075
6322
  function findFiles(rootDir, dir, pattern, out, cap = 20) {
6076
- if (out.length >= cap || !(0, import_node_fs6.existsSync)(dir)) return;
6077
- for (const entry of (0, import_node_fs6.readdirSync)(dir, { withFileTypes: true })) {
6323
+ if (out.length >= cap || !(0, import_node_fs7.existsSync)(dir)) return;
6324
+ for (const entry of (0, import_node_fs7.readdirSync)(dir, { withFileTypes: true })) {
6078
6325
  if (out.length >= cap) return;
6079
- const abs = (0, import_node_path8.join)(dir, entry.name);
6326
+ const abs = (0, import_node_path9.join)(dir, entry.name);
6080
6327
  if (entry.isDirectory()) {
6081
6328
  if (!SKIP_DIRS2.has(entry.name)) findFiles(rootDir, abs, pattern, out, cap);
6082
6329
  } else if (entry.isFile() && pattern.test(entry.name)) {
6083
- out.push((0, import_node_path8.relative)(rootDir, abs).split(import_node_path8.sep).join("/"));
6330
+ out.push((0, import_node_path9.relative)(rootDir, abs).split(import_node_path9.sep).join("/"));
6084
6331
  }
6085
6332
  }
6086
6333
  }
6087
6334
  var sample = (list) => list.slice(0, 3).join(", ") + (list.length > 3 ? ", \u2026" : "");
6088
6335
  function collectSourceFiles(dir, out, budget) {
6089
- if (budget.left <= 0 || !(0, import_node_fs6.existsSync)(dir)) return;
6090
- for (const entry of (0, import_node_fs6.readdirSync)(dir, { withFileTypes: true })) {
6336
+ if (budget.left <= 0 || !(0, import_node_fs7.existsSync)(dir)) return;
6337
+ for (const entry of (0, import_node_fs7.readdirSync)(dir, { withFileTypes: true })) {
6091
6338
  if (budget.left <= 0) return;
6092
- const abs = (0, import_node_path8.join)(dir, entry.name);
6339
+ const abs = (0, import_node_path9.join)(dir, entry.name);
6093
6340
  if (entry.isDirectory()) {
6094
6341
  if (!SKIP_DIRS2.has(entry.name)) collectSourceFiles(abs, out, budget);
6095
6342
  } else if (entry.isFile() && SOURCE_FILE.test(entry.name)) {
@@ -6101,10 +6348,10 @@ function collectSourceFiles(dir, out, budget) {
6101
6348
  function checkNextjs(rootDir) {
6102
6349
  const issues = [];
6103
6350
  const warnings = [];
6104
- const configName = NEXT_CONFIG_BASENAMES.find((name) => (0, import_node_fs6.existsSync)((0, import_node_path8.join)(rootDir, name)));
6351
+ const configName = NEXT_CONFIG_BASENAMES.find((name) => (0, import_node_fs7.existsSync)((0, import_node_path9.join)(rootDir, name)));
6105
6352
  let hasStaticExport = true;
6106
6353
  if (configName) {
6107
- const config = (0, import_node_fs6.readFileSync)((0, import_node_path8.join)(rootDir, configName), "utf8");
6354
+ const config = (0, import_node_fs7.readFileSync)((0, import_node_path9.join)(rootDir, configName), "utf8");
6108
6355
  hasStaticExport = STATIC_EXPORT.test(config);
6109
6356
  if (!hasStaticExport) {
6110
6357
  issues.push(
@@ -6118,15 +6365,15 @@ function checkNextjs(rootDir) {
6118
6365
  const hard = [];
6119
6366
  const pagesApi = [];
6120
6367
  for (const apiDir of PAGES_API_DIRS) {
6121
- findFiles(rootDir, (0, import_node_path8.join)(rootDir, apiDir), /\.(js|ts|jsx|tsx|mjs)$/, pagesApi);
6368
+ findFiles(rootDir, (0, import_node_path9.join)(rootDir, apiDir), /\.(js|ts|jsx|tsx|mjs)$/, pagesApi);
6122
6369
  }
6123
6370
  if (pagesApi.length > 0) hard.push(`pages/api/* routes (${pagesApi.length} file(s))`);
6124
6371
  const middlewareFiles = [];
6125
6372
  for (const base of ["", "src"]) {
6126
- const dir = base ? (0, import_node_path8.join)(rootDir, base) : rootDir;
6127
- if (!(0, import_node_fs6.existsSync)(dir)) continue;
6128
- for (const entry of (0, import_node_fs6.readdirSync)(dir)) {
6129
- if (MIDDLEWARE_FILE.test(entry)) middlewareFiles.push((0, import_node_path8.join)(base, entry).split(import_node_path8.sep).join("/"));
6373
+ const dir = base ? (0, import_node_path9.join)(rootDir, base) : rootDir;
6374
+ if (!(0, import_node_fs7.existsSync)(dir)) continue;
6375
+ for (const entry of (0, import_node_fs7.readdirSync)(dir)) {
6376
+ if (MIDDLEWARE_FILE.test(entry)) middlewareFiles.push((0, import_node_path9.join)(base, entry).split(import_node_path9.sep).join("/"));
6130
6377
  }
6131
6378
  }
6132
6379
  hard.push(...middlewareFiles);
@@ -6138,11 +6385,11 @@ function checkNextjs(rootDir) {
6138
6385
  if (gssp.length >= 20 && useServer.length >= 20) break;
6139
6386
  let text;
6140
6387
  try {
6141
- text = (0, import_node_fs6.readFileSync)(abs, "utf8");
6388
+ text = (0, import_node_fs7.readFileSync)(abs, "utf8");
6142
6389
  } catch {
6143
6390
  continue;
6144
6391
  }
6145
- const rel = (0, import_node_path8.relative)(rootDir, abs).split(import_node_path8.sep).join("/");
6392
+ const rel = (0, import_node_path9.relative)(rootDir, abs).split(import_node_path9.sep).join("/");
6146
6393
  if (gssp.length < 20 && GET_SERVER_SIDE_PROPS.test(text)) gssp.push(rel);
6147
6394
  if (useServer.length < 20 && USE_SERVER.test(text)) useServer.push(rel);
6148
6395
  }
@@ -6150,7 +6397,7 @@ function checkNextjs(rootDir) {
6150
6397
  if (useServer.length > 0) hard.push(`'use server' actions (${sample(useServer)})`);
6151
6398
  const routeFiles = [];
6152
6399
  for (const appDir of APP_DIRS) {
6153
- findFiles(rootDir, (0, import_node_path8.join)(rootDir, appDir), ROUTE_HANDLER_FILE, routeFiles);
6400
+ findFiles(rootDir, (0, import_node_path9.join)(rootDir, appDir), ROUTE_HANDLER_FILE, routeFiles);
6154
6401
  }
6155
6402
  if (routeFiles.length > 0) {
6156
6403
  const label = `${routeFiles.length} route handler(s) (${sample(routeFiles)})`;
@@ -6161,170 +6408,675 @@ function checkNextjs(rootDir) {
6161
6408
  } else {
6162
6409
  hard.push(label);
6163
6410
  }
6164
- }
6165
- const authDeps = Object.keys(readDeps(rootDir)).filter(isAuthDep);
6166
- if (authDeps.length > 0) {
6167
- const isAuthPath = (p) => p.toLowerCase().split("/").some((seg) => seg === "auth" || seg.includes("nextauth"));
6168
- const authRoutes = [...routeFiles, ...pagesApi].filter(isAuthPath);
6169
- if (authRoutes.length > 0) {
6170
- hard.push(`${authDeps.join(", ")} with its server-side auth route (${sample(authRoutes)})`);
6171
- } else {
6172
- warnings.push(
6173
- `${authDeps.join(", ")} is in dependencies but no auth route was found \u2014 server-side auth cannot run on the static platform. Remove the dependency, or keep auth in an external backend called from the client.`
6174
- );
6411
+ }
6412
+ const authDeps = Object.keys(readDeps(rootDir)).filter(isAuthDep);
6413
+ if (authDeps.length > 0) {
6414
+ const isAuthPath = (p) => p.toLowerCase().split("/").some((seg) => seg === "auth" || seg.includes("nextauth"));
6415
+ const authRoutes = [...routeFiles, ...pagesApi].filter(isAuthPath);
6416
+ if (authRoutes.length > 0) {
6417
+ hard.push(`${authDeps.join(", ")} with its server-side auth route (${sample(authRoutes)})`);
6418
+ } else {
6419
+ warnings.push(
6420
+ `${authDeps.join(", ")} is in dependencies but no auth route was found \u2014 server-side auth cannot run on the static platform. Remove the dependency, or keep auth in an external backend called from the client.`
6421
+ );
6422
+ }
6423
+ }
6424
+ if (hard.length > 0) {
6425
+ issues.push(
6426
+ `${PLATFORM_CONTRACT}
6427
+ Your project needs a server runtime (found: ${hard.join("; ")}).
6428
+ Options: (a) convert to a static export and move server logic to Managed Server Actions,
6429
+ (b) keep the backend elsewhere and call it from the client, (c) don't push this project.`
6430
+ );
6431
+ }
6432
+ return { issues, warnings };
6433
+ }
6434
+ function checkBuildEnv(rootDir) {
6435
+ const verdict = judgeEnvReads(scanEnvReads(rootDir), {
6436
+ defined: ALWAYS_DEFINED_NAMES,
6437
+ backendUnknown: true,
6438
+ localOnly: envFileNames(rootDir)
6439
+ });
6440
+ return [
6441
+ ...verdict.errors.map((e) => `The cloud build will reject this project: ${e}`),
6442
+ ...verdict.warnings
6443
+ ];
6444
+ }
6445
+ function runPreflight(rootDir) {
6446
+ const framework = detectFramework(rootDir);
6447
+ const issues = [];
6448
+ const warnings = [];
6449
+ if (framework === "unknown") {
6450
+ issues.push(
6451
+ `No supported framework found in package.json. The cloud build supports: ${SUPPORTED_FRAMEWORKS}.`
6452
+ );
6453
+ }
6454
+ if (framework === "nextjs") {
6455
+ const next = checkNextjs(rootDir);
6456
+ issues.push(...next.issues);
6457
+ warnings.push(...next.warnings);
6458
+ }
6459
+ if (framework !== "vanilla") warnings.push(...checkBuildEnv(rootDir));
6460
+ return { framework, issues, warnings };
6461
+ }
6462
+
6463
+ // src/lib/pushAnalysis.ts
6464
+ var ANALYSIS_SCHEMA = "agentful.push-analysis";
6465
+ var ANALYSIS_SCHEMA_VERSION = 1;
6466
+ var DEFAULT_BUDGETS = {
6467
+ exactFileBytes: 128 * 1024 * 1024,
6468
+ exactTotalBytes: 256 * 1024 * 1024,
6469
+ hashTotalBytes: 2 * 1024 * 1024 * 1024,
6470
+ fileListCap: 5e3
6471
+ };
6472
+ var ZIP_ENTRY_HEADER_BYTES = 76;
6473
+ var ZIP_END_BYTES = 22;
6474
+ var DEFLATE_MAX_RATIO = 1032;
6475
+ var deflateUpperBound = (size) => size + 5 * (Math.floor(size / 16384) + 1);
6476
+ var LARGEST_FILES = 15;
6477
+ var TOP_LEVEL_GROUPS = 20;
6478
+ var DUPLICATE_GROUPS = 50;
6479
+ var HTML_CANDIDATES = 50;
6480
+ var EXCLUDED_ENTRIES = 200;
6481
+ var FINDING_PATHS = 20;
6482
+ var TITLE_SCAN_BYTES = 64 * 1024;
6483
+ var EMBEDDED_REPORT_MIN_CHARS = 64 * 1024;
6484
+ var EMBEDDED_SCAN_MIN_BYTES = EMBEDDED_REPORT_MIN_CHARS;
6485
+ var CLASS_EXTENSIONS = {
6486
+ html: ["html", "htm", "xhtml"],
6487
+ css: ["css", "scss", "sass", "less"],
6488
+ script: ["js", "mjs", "cjs", "jsx", "ts", "tsx", "vue", "svelte", "astro"],
6489
+ data: ["json", "geojson", "topojson", "xml", "csv", "tsv", "yaml", "yml", "toml", "txt", "md", "map", "webmanifest"],
6490
+ image: ["png", "jpg", "jpeg", "gif", "webp", "avif", "svg", "ico", "bmp", "tif", "tiff", "heic"],
6491
+ video: ["mp4", "m4v", "webm", "ogv", "mov", "avi", "mkv"],
6492
+ audio: ["mp3", "wav", "ogg", "oga", "m4a", "flac", "aac", "opus"],
6493
+ font: ["woff", "woff2", "ttf", "otf", "eot"],
6494
+ document: ["pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "odt", "ods", "odp"],
6495
+ archive: ["zip", "gz", "tgz", "tar", "bz2", "xz", "7z", "rar", "br", "zst"],
6496
+ other: []
6497
+ };
6498
+ var CLASS_BY_EXTENSION = new Map(
6499
+ Object.entries(CLASS_EXTENSIONS).flatMap(([cls, exts]) => exts.map((ext) => [ext, cls]))
6500
+ );
6501
+ var EMBEDDED_SCAN_CLASSES = /* @__PURE__ */ new Set(["html", "css", "script", "data"]);
6502
+ var baseName = (rel) => rel.slice(rel.lastIndexOf("/") + 1);
6503
+ function classOf(rel) {
6504
+ const base = baseName(rel);
6505
+ const dot = base.lastIndexOf(".");
6506
+ if (dot <= 0) return "other";
6507
+ return CLASS_BY_EXTENSION.get(base.slice(dot + 1).toLowerCase()) ?? "other";
6508
+ }
6509
+ var byPath = (a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0;
6510
+ var sha256Of = (data) => (0, import_node_crypto.createHash)("sha256").update(data).digest("hex");
6511
+ function sha256OfFile(abs) {
6512
+ const hash = (0, import_node_crypto.createHash)("sha256");
6513
+ const fd2 = (0, import_node_fs8.openSync)(abs, "r");
6514
+ try {
6515
+ const chunk = Buffer.allocUnsafe(1024 * 1024);
6516
+ for (; ; ) {
6517
+ const read = (0, import_node_fs8.readSync)(fd2, chunk, 0, chunk.length, null);
6518
+ if (read === 0) break;
6519
+ hash.update(chunk.subarray(0, read));
6520
+ }
6521
+ } finally {
6522
+ (0, import_node_fs8.closeSync)(fd2);
6523
+ }
6524
+ return hash.digest("hex");
6525
+ }
6526
+ var TITLE = /<title[^>]*>([\s\S]*?)<\/title>/i;
6527
+ function htmlTitle(content) {
6528
+ const match = TITLE.exec(content.subarray(0, TITLE_SCAN_BYTES).toString("utf8"));
6529
+ const title = match?.[1].replace(/\s+/g, " ").trim();
6530
+ return title ? title.slice(0, 120) : null;
6531
+ }
6532
+ var DATA_URL = /data:([a-z0-9][a-z0-9.+-]*\/[a-z0-9][a-z0-9.+-]*)(?:;[a-z0-9-]+=[^;,\s"')]*)*;base64,([a-z0-9+/]+={0,2})/gi;
6533
+ function scanEmbeddedMedia(content) {
6534
+ const text = content.toString("latin1");
6535
+ if (!text.includes(";base64,")) return null;
6536
+ let references = 0;
6537
+ let base64Chars = 0;
6538
+ const mimeTypes = {};
6539
+ DATA_URL.lastIndex = 0;
6540
+ for (let match = DATA_URL.exec(text); match; match = DATA_URL.exec(text)) {
6541
+ references += 1;
6542
+ base64Chars += match[2].length;
6543
+ const mime = match[1].toLowerCase();
6544
+ mimeTypes[mime] = (mimeTypes[mime] ?? 0) + 1;
6545
+ }
6546
+ if (references === 0) return null;
6547
+ return {
6548
+ references,
6549
+ base64_chars: base64Chars,
6550
+ decoded_bytes: Math.floor(base64Chars * 3 / 4),
6551
+ mime_types: Object.fromEntries(Object.entries(mimeTypes).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0))
6552
+ };
6553
+ }
6554
+ function formatBytes(bytes2, rounding = "nearest") {
6555
+ const units = ["B", "KiB", "MiB", "GiB"];
6556
+ let value = bytes2;
6557
+ let unit = 0;
6558
+ while (value >= 1024 && unit < units.length - 1) {
6559
+ value /= 1024;
6560
+ unit += 1;
6561
+ }
6562
+ if (unit === 0 || Number.isInteger(value)) return `${value} ${units[unit]}`;
6563
+ const tenths = value * 10;
6564
+ const rounded = (rounding === "down" ? Math.floor(tenths) : Math.round(tenths)) / 10;
6565
+ return `${rounded.toFixed(1)} ${units[unit]}`;
6566
+ }
6567
+ function sizeOverLimit(bytes2, limit) {
6568
+ const size = formatBytes(bytes2);
6569
+ const allowed = formatBytes(limit, "down");
6570
+ const [value, unit] = size.split(" ");
6571
+ const [limitValue, limitUnit] = allowed.split(" ");
6572
+ if (unit !== limitUnit || Number(value) > Number(limitValue)) return [size, allowed];
6573
+ return [`${bytes2.toLocaleString("en-US")} bytes`, `${limit.toLocaleString("en-US")} bytes`];
6574
+ }
6575
+ var plural = (count2, singular, pluralForm = `${singular}s`) => `${count2.toLocaleString("en-US")} ${count2 === 1 ? singular : pluralForm}`;
6576
+ var samplePaths = (paths) => paths.slice(0, 3).join(", ") + (paths.length > 3 ? `, and ${paths.length - 3} more` : "");
6577
+ function groupSizes(files, keyOf) {
6578
+ const groups = /* @__PURE__ */ new Map();
6579
+ for (const file of files) {
6580
+ const name = keyOf(file);
6581
+ const group = groups.get(name) ?? { name, files: 0, bytes: 0, zip_bytes: 0 };
6582
+ group.files += 1;
6583
+ group.bytes += file.bytes;
6584
+ group.zip_bytes += file.zip_bytes;
6585
+ groups.set(name, group);
6586
+ }
6587
+ return [...groups.values()].sort((a, b) => b.bytes - a.bytes || (a.name < b.name ? -1 : 1));
6588
+ }
6589
+ var publicFile = ({ path, bytes: bytes2, zip_bytes, sha256, class: cls, zip_bytes_estimated }) => ({
6590
+ path,
6591
+ bytes: bytes2,
6592
+ zip_bytes,
6593
+ sha256,
6594
+ class: cls,
6595
+ ...zip_bytes_estimated ? { zip_bytes_estimated } : {}
6596
+ });
6597
+ function limits(maxZipBytes) {
6598
+ return { max_zip_bytes: maxZipBytes, max_unpacked_bytes: MAX_UNPACKED_BYTES, max_files: MAX_UPLOAD_FILES };
6599
+ }
6600
+ function projectLink(projectDir) {
6601
+ const config = readProjectConfig(projectDir);
6602
+ return { linked: config !== null, project_id: config?.projectId ?? null };
6603
+ }
6604
+ function failedAnalysis(mode, projectDir, blocker) {
6605
+ return {
6606
+ schema: ANALYSIS_SCHEMA,
6607
+ schema_version: ANALYSIS_SCHEMA_VERSION,
6608
+ cli_version: VERSION,
6609
+ mode,
6610
+ root: null,
6611
+ ok: false,
6612
+ framework: null,
6613
+ project: projectLink(projectDir),
6614
+ limits: limits(maxZipBytesForUpload(mode)),
6615
+ totals: {
6616
+ files: 0,
6617
+ bytes: 0,
6618
+ zip_bytes: 0,
6619
+ zip_bytes_exact: true,
6620
+ zip_bytes_without_duplicates: 0,
6621
+ stored_files: 0,
6622
+ stored_bytes: 0
6623
+ },
6624
+ entry: { required: mode === "dist", expected: "index.html", found: false, path: null, candidates: [] },
6625
+ flattened_prefix: null,
6626
+ largest_files: [],
6627
+ by_class: [],
6628
+ by_top_level: [],
6629
+ duplicates: [],
6630
+ embedded_media: [],
6631
+ excluded: { gitignore_applied: false, entries: [], omitted: 0 },
6632
+ dropped_by_platform: [],
6633
+ blockers: [{ overridable: false, ...blocker }],
6634
+ warnings: [],
6635
+ suggestions: [],
6636
+ files: [],
6637
+ files_omitted: 0,
6638
+ fingerprint: `sha256:${sha256Of(Buffer.from(JSON.stringify({ v: ANALYSIS_SCHEMA_VERSION, mode, files: [] })))}`
6639
+ };
6640
+ }
6641
+ function analyzePush(options) {
6642
+ const { rootDir, mode } = options;
6643
+ const projectDir = options.projectDir ?? rootDir;
6644
+ const budgets = { ...DEFAULT_BUDGETS, ...options.budgets };
6645
+ const scan = scanTree(rootDir, mode);
6646
+ const collected = [...scan.files].sort((a, b) => a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0);
6647
+ const measured = [];
6648
+ const unreadable = [];
6649
+ const embeddedByHash = /* @__PURE__ */ new Map();
6650
+ const embedded = [];
6651
+ let exactUsed = 0;
6652
+ let hashUsed = 0;
6653
+ for (const file of collected) {
6654
+ const cls = classOf(file.rel);
6655
+ const headerBytes = ZIP_ENTRY_HEADER_BYTES + 2 * Buffer.byteLength(file.rel, "utf8");
6656
+ try {
6657
+ const size = (0, import_node_fs8.statSync)(file.abs).size;
6658
+ if (size <= budgets.exactFileBytes && exactUsed + size <= budgets.exactTotalBytes) {
6659
+ const content = (0, import_node_fs8.readFileSync)(file.abs);
6660
+ exactUsed += content.length;
6661
+ const sha2562 = sha256Of(content);
6662
+ const zipBytes2 = headerBytes + deflateSync(content, { level: ZIP_LEVEL }).length;
6663
+ if (EMBEDDED_SCAN_CLASSES.has(cls) && content.length >= EMBEDDED_SCAN_MIN_BYTES) {
6664
+ if (!embeddedByHash.has(sha2562)) embeddedByHash.set(sha2562, scanEmbeddedMedia(content));
6665
+ const found = embeddedByHash.get(sha2562);
6666
+ if (found && found.base64_chars >= EMBEDDED_REPORT_MIN_CHARS) embedded.push({ path: file.rel, ...found });
6667
+ }
6668
+ measured.push({
6669
+ path: file.rel,
6670
+ bytes: content.length,
6671
+ zip_bytes: zipBytes2,
6672
+ zipLower: zipBytes2,
6673
+ sha256: sha2562,
6674
+ class: cls,
6675
+ title: cls === "html" ? htmlTitle(content) : null
6676
+ });
6677
+ continue;
6678
+ }
6679
+ let sha256 = null;
6680
+ if (hashUsed + size <= budgets.hashTotalBytes) {
6681
+ sha256 = sha256OfFile(file.abs);
6682
+ hashUsed += size;
6683
+ }
6684
+ measured.push({
6685
+ path: file.rel,
6686
+ bytes: size,
6687
+ sha256,
6688
+ class: cls,
6689
+ title: null,
6690
+ zip_bytes: headerBytes + deflateUpperBound(size),
6691
+ zipLower: headerBytes + Math.ceil(size / DEFLATE_MAX_RATIO),
6692
+ zip_bytes_estimated: true
6693
+ });
6694
+ } catch (err2) {
6695
+ unreadable.push({ path: file.rel, error: err2.code ?? String(err2) });
6696
+ }
6697
+ }
6698
+ const zipExact = measured.every((file) => !file.zip_bytes_estimated);
6699
+ const sum = (pick) => measured.reduce((total, file) => total + pick(file), 0);
6700
+ const totalBytes = sum((file) => file.bytes);
6701
+ const zipBytes = measured.length > 0 ? sum((file) => file.zip_bytes) + ZIP_END_BYTES : 0;
6702
+ const zipLower = measured.length > 0 ? sum((file) => file.zipLower) + ZIP_END_BYTES : 0;
6703
+ const view = serverView(measured.map((file) => file.path));
6704
+ const byPathMap = new Map(measured.map((file) => [file.path, file]));
6705
+ const stored = measured.filter((file) => view.stored.has(file.path));
6706
+ const storedBytes = stored.reduce((total, file) => total + file.bytes, 0);
6707
+ const storedNames = new Map([...view.stored].map(([local, name]) => [name, local]));
6708
+ const dropped = view.dropped.map((drop) => ({ path: drop.rel, bytes: byPathMap.get(drop.rel)?.bytes ?? 0, reason: drop.reason, matched: drop.matched })).sort(byPath);
6709
+ const byHash = /* @__PURE__ */ new Map();
6710
+ for (const file of measured) {
6711
+ if (!file.sha256 || file.bytes === 0) continue;
6712
+ const group = byHash.get(file.sha256);
6713
+ if (group) group.push(file);
6714
+ else byHash.set(file.sha256, [file]);
6715
+ }
6716
+ const firstOfContent = /* @__PURE__ */ new Map();
6717
+ const duplicates = [];
6718
+ for (const [sha256, group] of byHash) {
6719
+ firstOfContent.set(sha256, group[0].path);
6720
+ if (group.length < 2) continue;
6721
+ duplicates.push({
6722
+ sha256,
6723
+ bytes: group[0].bytes,
6724
+ paths: group.map((file) => file.path),
6725
+ redundant_zip_bytes: group.slice(1).reduce((total, file) => total + file.zip_bytes, 0)
6726
+ });
6727
+ }
6728
+ duplicates.sort((a, b) => b.redundant_zip_bytes - a.redundant_zip_bytes || (a.paths[0] < b.paths[0] ? -1 : 1));
6729
+ const redundantZipBytes = duplicates.reduce((total, group) => total + group.redundant_zip_bytes, 0);
6730
+ const preflight = mode === "source" ? runPreflight(rootDir) : null;
6731
+ const framework = preflight?.framework ?? null;
6732
+ const hasManifest = storedNames.has("package.json");
6733
+ const entryRequired = mode === "dist" || !hasManifest;
6734
+ const entryPath = storedNames.get("index.html") ?? null;
6735
+ const entryMissing = entryRequired && entryPath === null && measured.length > 0;
6736
+ const candidates = !entryMissing ? [] : stored.filter((file) => file.class === "html").sort((a, b) => a.path.split("/").length - b.path.split("/").length || byPath(a, b)).slice(0, HTML_CANDIDATES).map((file) => {
6737
+ const first = file.sha256 ? firstOfContent.get(file.sha256) : void 0;
6738
+ return {
6739
+ path: file.path,
6740
+ bytes: file.bytes,
6741
+ title: file.title,
6742
+ sha256: file.sha256,
6743
+ same_content_as: first && first !== file.path ? first : null
6744
+ };
6745
+ });
6746
+ const maxZipBytes = maxZipBytesForUpload(mode, mode === "source" ? readBackendDeclaration(rootDir) : null);
6747
+ const zipLimit = formatBytes(maxZipBytes, "down");
6748
+ const blockers = [];
6749
+ const warnings = [];
6750
+ const suggestions = [];
6751
+ const dirLabel = mode === "dist" ? (0, import_node_path10.relative)(projectDir, rootDir).split(import_node_path10.sep).join("/") || "." : ".";
6752
+ if (measured.length === 0 && unreadable.length === 0) {
6753
+ blockers.push({
6754
+ code: "empty_upload",
6755
+ overridable: false,
6756
+ message: "Nothing to push \u2014 no files found after applying ignore rules."
6757
+ });
6758
+ }
6759
+ if (unreadable.length > 0) {
6760
+ blockers.push({
6761
+ code: "unreadable_file",
6762
+ overridable: false,
6763
+ message: `${plural(unreadable.length, "file")} cannot be read (${unreadable[0].error}): ${samplePaths(unreadable.map((f) => f.path))}.`,
6764
+ paths: unreadable.map((f) => f.path).slice(0, FINDING_PATHS)
6765
+ });
6766
+ }
6767
+ if (measured.length > MAX_UPLOAD_FILES) {
6768
+ blockers.push({
6769
+ code: "too_many_files",
6770
+ overridable: false,
6771
+ message: `${plural(measured.length, "file")} \u2014 the platform accepts at most ${MAX_UPLOAD_FILES.toLocaleString("en-US")} files per push.`
6772
+ });
6773
+ }
6774
+ if (storedBytes > MAX_UNPACKED_BYTES) {
6775
+ const [size, allowed] = sizeOverLimit(storedBytes, MAX_UNPACKED_BYTES);
6776
+ blockers.push({
6777
+ code: "unpacked_too_large",
6778
+ overridable: false,
6779
+ message: `The files add up to ${size} \u2014 the platform accepts at most ${allowed} of files per push.`
6780
+ });
6781
+ }
6782
+ if (zipLower > maxZipBytes) {
6783
+ const [size, allowed] = sizeOverLimit(zipExact ? zipBytes : zipLower, maxZipBytes);
6784
+ blockers.push({
6785
+ code: "zip_too_large",
6786
+ overridable: false,
6787
+ message: `The upload is ${zipExact ? "" : "at least "}${size} compressed \u2014 a push carries at most ${allowed} compressed.`
6788
+ });
6789
+ } else if (!zipExact && zipBytes > maxZipBytes) {
6790
+ warnings.push({
6791
+ code: "zip_size_unknown",
6792
+ message: `The compressed size could not be determined: part of the tree is too large to compress during the analysis. It is between ${formatBytes(zipLower)} and ${formatBytes(zipBytes)}; a push carries at most ${zipLimit} compressed.`
6793
+ });
6794
+ }
6795
+ if (entryMissing) {
6796
+ blockers.push({
6797
+ code: "entry_missing",
6798
+ overridable: false,
6799
+ message: mode === "dist" ? `No index.html at the top level of ${dirLabel === "." ? "the uploaded folder" : `${dirLabel}/`}. A prebuilt upload is served as it is, and index.html is its start page.` : "No index.html at the top level. Without a package.json this is a static site, and the platform serves index.html as its start page."
6800
+ });
6801
+ }
6802
+ const backend = mode === "source" ? checkBackendDeclaration(rootDir) : null;
6803
+ for (const message of preflight?.issues ?? []) blockers.push({ code: "preflight", overridable: true, message });
6804
+ for (const message of backend?.issues ?? []) blockers.push({ code: "backend_declaration", overridable: true, message });
6805
+ for (const message of preflight?.warnings ?? []) warnings.push({ code: "preflight", message });
6806
+ for (const message of backend?.warnings ?? []) warnings.push({ code: "backend_declaration", message });
6807
+ if (view.strippedPrefix) {
6808
+ warnings.push({
6809
+ code: "wrapper_directory_flattened",
6810
+ message: `Everything sits in one folder, ${view.strippedPrefix} \u2014 the platform stores its content at the top level (${view.strippedPrefix}index.html becomes index.html).`
6811
+ });
6812
+ }
6813
+ if (dropped.length > 0) {
6814
+ const matched = [...new Set(dropped.map((drop) => drop.matched))];
6815
+ warnings.push({
6816
+ code: "dropped_by_platform",
6817
+ message: `${plural(dropped.length, "file")} (${formatBytes(dropped.reduce((total, drop) => total + drop.bytes, 0))}) will be uploaded but not stored \u2014 the platform drops these paths by name (${samplePaths(matched)}): ${samplePaths(dropped.map((drop) => drop.path))}. If the site needs them, they have to live under another name.`,
6818
+ paths: dropped.map((drop) => drop.path).slice(0, FINDING_PATHS)
6819
+ });
6820
+ }
6821
+ if (mode === "source") {
6822
+ const lostDirs = scan.excluded.filter((entry) => entry.reason === "always_excluded_dir" && ["dist", "build", "out"].includes(baseName(entry.rel).toLowerCase())).filter((entry) => framework === "vanilla" || /^(public|static)\//.test(entry.rel)).map((entry) => `${entry.rel}/`).sort();
6823
+ if (lostDirs.length > 0) {
6824
+ warnings.push({
6825
+ code: "excluded_static_directory",
6826
+ message: `${samplePaths(lostDirs)} ${lostDirs.length === 1 ? "is" : "are"} never uploaded: folders named dist, build or out are treated as build output. ${framework === "vanilla" ? "This site has no build step" : "Files under public/ and static/ are served as they are"}, so nothing recreates them \u2014 if the site needs these files, move them to a folder with another name.`,
6827
+ paths: lostDirs.slice(0, FINDING_PATHS)
6828
+ });
6829
+ }
6830
+ }
6831
+ const filesOmitted = Math.max(0, measured.length - budgets.fileListCap);
6832
+ if (filesOmitted > 0) {
6833
+ warnings.push({
6834
+ code: "file_list_truncated",
6835
+ message: `The file list of this report holds the first ${budgets.fileListCap.toLocaleString("en-US")} of ${plural(measured.length, "file")}; the totals cover all of them.`
6836
+ });
6837
+ }
6838
+ const tooLarge = blockers.some((b) => b.code === "zip_too_large" || b.code === "unpacked_too_large");
6839
+ if (entryMissing) {
6840
+ if (candidates.length > 0) {
6841
+ suggestions.push({
6842
+ code: "name_start_page",
6843
+ message: "Rename or copy the page that should open first to index.html at the top level, check that its relative links still resolve, then push again.",
6844
+ paths: candidates.map((candidate) => candidate.path).slice(0, FINDING_PATHS)
6845
+ });
6846
+ }
6847
+ const siteDirs = /* @__PURE__ */ new Set();
6848
+ for (const file of stored) {
6849
+ if (baseName(file.path) === "index.html" && file.path.includes("/")) siteDirs.add(file.path.slice(0, file.path.lastIndexOf("/")));
6850
+ }
6851
+ if (mode === "source") {
6852
+ for (const dir of ["dist", "build", "out", ".output/public"]) {
6853
+ if ((0, import_node_fs8.existsSync)((0, import_node_path10.join)(rootDir, dir, "index.html"))) siteDirs.add(dir);
6854
+ }
6855
+ }
6856
+ for (const dir of [...siteDirs].sort().slice(0, 5)) {
6857
+ const target = mode === "dist" ? `${dirLabel === "." ? "" : `${dirLabel}/`}${dir}` : dir;
6858
+ suggestions.push({
6859
+ code: "push_site_directory",
6860
+ message: `${target}/ has an index.html of its own. If that folder is the finished site, upload it on its own \u2014 only the files inside it are served.`,
6861
+ command: `agentful push --prebuilt --dir ${target}`
6862
+ });
6175
6863
  }
6176
6864
  }
6177
- if (hard.length > 0) {
6178
- issues.push(
6179
- `${PLATFORM_CONTRACT}
6180
- Your project needs a server runtime (found: ${hard.join("; ")}).
6181
- Options: (a) convert to a static export and move server logic to Managed Server Actions,
6182
- (b) keep the backend elsewhere and call it from the client, (c) don't push this project.`
6183
- );
6865
+ if (tooLarge) {
6866
+ const oversized = measured.filter((file) => file.zipLower > maxZipBytes).sort((a, b) => b.bytes - a.bytes || byPath(a, b));
6867
+ if (oversized.length > 0) {
6868
+ suggestions.push({
6869
+ code: "oversized_files",
6870
+ message: `${samplePaths(oversized.map((file) => `${file.path} (${formatBytes(file.zipLower)} compressed)`))} ${oversized.length === 1 ? "is" : "are each"} larger than a whole push can carry. The upload cannot fit while ${oversized.length === 1 ? "it is" : "they are"} part of it.`,
6871
+ paths: oversized.map((file) => file.path).slice(0, FINDING_PATHS)
6872
+ });
6873
+ }
6874
+ if (duplicates.length > 0 && redundantZipBytes >= zipBytes / 20) {
6875
+ const copies = duplicates.reduce((total, group) => total + group.paths.length - 1, 0);
6876
+ const remaining = zipBytes - redundantZipBytes;
6877
+ suggestions.push({
6878
+ code: "remove_identical_copies",
6879
+ message: `${plural(copies, "file is an identical copy", "files are identical copies")} of another file and ${copies === 1 ? "adds" : "add"} ${formatBytes(redundantZipBytes)} to the compressed upload. If nothing links to the extra names, one copy each brings it to ${formatBytes(remaining)}${remaining > maxZipBytes ? ` \u2014 still above the ${zipLimit} limit` : ""}.`,
6880
+ paths: duplicates.flatMap((group) => group.paths.slice(1)).slice(0, FINDING_PATHS)
6881
+ });
6882
+ }
6883
+ suggestions.push(mode === "source" ? {
6884
+ code: "exclude_with_gitignore",
6885
+ message: "Files and folders listed in .gitignore are not uploaded. Leave out what the site does not serve \u2014 originals, working files, exports."
6886
+ } : {
6887
+ code: "narrow_prebuilt_directory",
6888
+ message: "A prebuilt upload takes every file in the folder. Put the finished site into a folder of its own and pass it with --dir."
6889
+ });
6890
+ suggestions.push({
6891
+ code: "upload_limit",
6892
+ message: `A push carries at most ${zipLimit} compressed and ${formatBytes(MAX_UNPACKED_BYTES)} of files. A site that stays above that cannot be pushed yet.`
6893
+ });
6184
6894
  }
6185
- return { issues, warnings };
6186
- }
6187
- function checkBuildEnv(rootDir) {
6188
- const verdict = judgeEnvReads(scanEnvReads(rootDir), {
6189
- defined: ALWAYS_DEFINED_NAMES,
6190
- backendUnknown: true,
6191
- localOnly: envFileNames(rootDir)
6895
+ const excludedEntries = [...scan.excluded].sort((a, b) => a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0).slice(0, EXCLUDED_ENTRIES).map((entry) => {
6896
+ let bytes2;
6897
+ if (entry.kind === "file") {
6898
+ try {
6899
+ bytes2 = (0, import_node_fs8.lstatSync)((0, import_node_path10.join)(rootDir, entry.rel)).size;
6900
+ } catch {
6901
+ }
6902
+ }
6903
+ return { path: entry.rel, kind: entry.kind, reason: entry.reason, ...bytes2 === void 0 ? {} : { bytes: bytes2 } };
6192
6904
  });
6193
- return [
6194
- ...verdict.errors.map((e) => `The cloud build will reject this project: ${e}`),
6195
- ...verdict.warnings
6196
- ];
6905
+ const fingerprint = sha256Of(Buffer.from(JSON.stringify({
6906
+ v: ANALYSIS_SCHEMA_VERSION,
6907
+ mode,
6908
+ files: measured.map((file) => [file.path, file.bytes, file.sha256])
6909
+ })));
6910
+ return {
6911
+ schema: ANALYSIS_SCHEMA,
6912
+ schema_version: ANALYSIS_SCHEMA_VERSION,
6913
+ cli_version: VERSION,
6914
+ mode,
6915
+ root: rootDir,
6916
+ ok: blockers.length === 0,
6917
+ framework,
6918
+ project: projectLink(projectDir),
6919
+ limits: limits(maxZipBytes),
6920
+ totals: {
6921
+ files: measured.length,
6922
+ bytes: totalBytes,
6923
+ zip_bytes: zipBytes,
6924
+ zip_bytes_exact: zipExact,
6925
+ zip_bytes_without_duplicates: zipBytes - redundantZipBytes,
6926
+ stored_files: stored.length,
6927
+ stored_bytes: storedBytes
6928
+ },
6929
+ entry: { required: entryRequired, expected: "index.html", found: entryPath !== null, path: entryPath, candidates },
6930
+ flattened_prefix: view.strippedPrefix || null,
6931
+ largest_files: [...measured].sort((a, b) => b.bytes - a.bytes || byPath(a, b)).slice(0, LARGEST_FILES).map(publicFile),
6932
+ by_class: groupSizes(measured, (file) => file.class),
6933
+ by_top_level: groupSizes(measured, (file) => file.path.includes("/") ? file.path.slice(0, file.path.indexOf("/")) : ".").slice(0, TOP_LEVEL_GROUPS),
6934
+ duplicates: duplicates.slice(0, DUPLICATE_GROUPS),
6935
+ embedded_media: embedded.sort((a, b) => b.base64_chars - a.base64_chars || byPath(a, b)),
6936
+ excluded: {
6937
+ gitignore_applied: scan.gitignoreApplied,
6938
+ entries: excludedEntries,
6939
+ omitted: Math.max(0, scan.excluded.length - EXCLUDED_ENTRIES)
6940
+ },
6941
+ dropped_by_platform: dropped,
6942
+ blockers,
6943
+ warnings,
6944
+ suggestions,
6945
+ files: measured.slice(0, budgets.fileListCap).map(publicFile),
6946
+ files_omitted: filesOmitted,
6947
+ fingerprint: `sha256:${fingerprint}`
6948
+ };
6197
6949
  }
6198
- function runPreflight(rootDir) {
6199
- const framework = detectFramework(rootDir);
6200
- const issues = [];
6201
- const warnings = [];
6202
- if (framework === "unknown") {
6203
- issues.push(
6204
- `No supported framework found in package.json. The cloud build supports: ${SUPPORTED_FRAMEWORKS}.`
6205
- );
6206
- }
6207
- if (framework === "nextjs") {
6208
- const next = checkNextjs(rootDir);
6209
- issues.push(...next.issues);
6210
- warnings.push(...next.warnings);
6211
- }
6212
- if (framework !== "vanilla") warnings.push(...checkBuildEnv(rootDir));
6213
- return { framework, issues, warnings };
6950
+ function wouldUpload(analysis, force = false) {
6951
+ return analysis.blockers.every((blocker) => blocker.overridable && force);
6214
6952
  }
6215
6953
 
6216
- // src/lib/backendDecl.ts
6217
- var import_node_fs7 = require("fs");
6218
- var import_node_path9 = require("path");
6219
- function readBackendDeclaration(dir = process.cwd()) {
6220
- const path = (0, import_node_path9.join)(dir, ".agentful", "backend.json");
6221
- if (!(0, import_node_fs7.existsSync)(path)) return null;
6222
- try {
6223
- const parsed = JSON.parse((0, import_node_fs7.readFileSync)(path, "utf8"));
6224
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
6225
- const collections = Array.isArray(parsed.collections) ? parsed.collections.filter((c) => c && typeof c === "object" && !Array.isArray(c)) : [];
6226
- const secrets = Array.isArray(parsed.secrets) ? parsed.secrets.filter((s) => s && typeof s === "object" && !Array.isArray(s)) : [];
6227
- const auth = parsed.auth && typeof parsed.auth === "object" && !Array.isArray(parsed.auth) ? parsed.auth : void 0;
6228
- const decl = {
6229
- ...parsed,
6230
- schema_version: Number(parsed.schema_version) || 1,
6231
- managed_db: Boolean(parsed.managed_db),
6232
- actions: Array.isArray(parsed.actions) ? parsed.actions.filter((a) => typeof a === "string") : [],
6233
- contract_doc: typeof parsed.contract_doc === "string" ? parsed.contract_doc : void 0,
6234
- collections,
6235
- secrets,
6236
- auth
6237
- };
6238
- const loginMethods = authMethodsOf(decl);
6239
- if (!decl.managed_db && decl.actions.length === 0 && collections.length === 0 && secrets.length === 0 && loginMethods.length === 0) {
6240
- return null;
6954
+ // src/lib/pushReport.ts
6955
+ init_branding();
6956
+ var EXCLUDE_REASON = {
6957
+ always_excluded_dir: "never uploaded",
6958
+ gitignore: ".gitignore",
6959
+ env_file: "env file, never uploaded",
6960
+ os_junk: "system file",
6961
+ symlink: "symbolic link",
6962
+ not_a_regular_file: "not a regular file"
6963
+ };
6964
+ var SHOWN_FILES = 8;
6965
+ var SHOWN_GROUPS = 6;
6966
+ var SHOWN_EXCLUDED = 12;
6967
+ var count = (n, singular, pluralForm = `${singular}s`) => `${n.toLocaleString("en-US")} ${n === 1 ? singular : pluralForm}`;
6968
+ var more = (total, shown, what) => total > shown ? [paint.dim(` \u2026 and ${count(total - shown, `more ${what}`, `more ${what}s`)}`)] : [];
6969
+ var bullet = (message) => message.split("\n").map((line, index) => index === 0 ? ` \u2022 ${line}` : ` ${line.trimStart()}`);
6970
+ var sizeColumn = (bytes2) => formatBytes(bytes2).padStart(9);
6971
+ var fileLine = (file) => ` ${sizeColumn(file.bytes)} ${file.path}` + paint.dim(` (${file.zip_bytes_estimated ? "up to " : ""}${formatBytes(file.zip_bytes)} compressed)`);
6972
+ function headline(analysis) {
6973
+ if (analysis.mode === "dist") return "prebuilt upload, served as it is (no cloud build)";
6974
+ if (analysis.framework === "vanilla") return "source push, static site without a build step";
6975
+ if (analysis.framework === "unknown" || analysis.framework === null) return "source push";
6976
+ return `source push, built in the cloud as ${frameworkDisplayName(analysis.framework)}`;
6977
+ }
6978
+ function renderAnalysis(analysis, options = {}) {
6979
+ const { totals, limits: limits2 } = analysis;
6980
+ const lines = [];
6981
+ const section = (title) => {
6982
+ lines.push("", paint.bold(title));
6983
+ };
6984
+ lines.push(paint.bold("Upload analysis") + paint.dim(`: ${headline(analysis)}`));
6985
+ if (analysis.root) lines.push(paint.dim(analysis.root));
6986
+ if (totals.files > 0) {
6987
+ lines.push(
6988
+ `${count(totals.files, "file")} \xB7 ${formatBytes(totals.bytes)} \xB7 ${totals.zip_bytes_exact ? "" : "up to "}${formatBytes(totals.zip_bytes)} compressed`
6989
+ );
6990
+ }
6991
+ lines.push(paint.dim(
6992
+ `Limits per push: ${formatBytes(limits2.max_zip_bytes, "down")} compressed \xB7 ${formatBytes(limits2.max_unpacked_bytes)} of files \xB7 ${limits2.max_files.toLocaleString("en-US")} files`
6993
+ ));
6994
+ if (analysis.blockers.length > 0) {
6995
+ lines.push("", `${sym.fail} ${paint.bold("This push cannot go through as it is:")}`);
6996
+ for (const blocker of analysis.blockers) {
6997
+ lines.push(...bullet(blocker.message + (blocker.overridable ? paint.dim(" [--force uploads anyway]") : "")));
6241
6998
  }
6242
- return decl;
6243
- } catch {
6244
- return null;
6245
6999
  }
6246
- }
6247
- function authMethodsOf(decl) {
6248
- const raw = decl?.auth?.end_user_login;
6249
- return Array.isArray(raw) ? raw.filter((m) => typeof m === "string") : [];
6250
- }
6251
- function describeBackendDeclaration(decl) {
6252
- const parts = [];
6253
- const collections = decl.collections ?? [];
6254
- if (decl.managed_db || collections.length > 0) {
6255
- parts.push(collections.length > 0 ? `managed DB with ${collections.length} collection(s)` : "managed DB");
7000
+ if (analysis.warnings.length > 0) {
7001
+ section("Warnings");
7002
+ for (const warning of analysis.warnings) lines.push(...bullet(warning.message));
6256
7003
  }
6257
- if (decl.actions && decl.actions.length > 0) parts.push(`${decl.actions.length} server action(s)`);
6258
- if (decl.secrets && decl.secrets.length > 0) parts.push(`${decl.secrets.length} secret(s)`);
6259
- const login = authMethodsOf(decl);
6260
- if (login.length > 0) parts.push(`login: ${login.join(" + ")}`);
6261
- const base = parts.join(" + ") || "managed backend";
6262
- return decl.contract_doc ? `${base} (contract: ${decl.contract_doc})` : base;
6263
- }
6264
- function backendTabUrl(userId, projectId) {
6265
- return `${APP_URL}/workspace/${userId}/${projectId}?view=backend`;
6266
- }
6267
- var ACTIONS_DIR = (0, import_node_path9.join)(".server", "actions");
6268
- var ACTION_NAME = /^[a-z][a-z0-9_-]{0,62}$/;
6269
- function checkBackendDeclaration(rootDir = process.cwd()) {
6270
- const issues = [];
6271
- const warnings = [];
6272
- const actionsDir = (0, import_node_path9.join)(rootDir, ACTIONS_DIR);
6273
- const presentFiles = /* @__PURE__ */ new Set();
6274
- if ((0, import_node_fs7.existsSync)(actionsDir)) {
6275
- for (const entry of (0, import_node_fs7.readdirSync)(actionsDir)) {
6276
- const abs = (0, import_node_path9.join)(actionsDir, entry);
6277
- let isDir = false;
6278
- try {
6279
- isDir = (0, import_node_fs7.statSync)(abs).isDirectory();
6280
- } catch {
6281
- continue;
7004
+ if (analysis.entry.required && totals.files > 0) {
7005
+ section("Start page");
7006
+ if (analysis.entry.found) {
7007
+ lines.push(` ${analysis.entry.path}`);
7008
+ } else {
7009
+ lines.push(" missing \u2014 the platform serves index.html from the top level");
7010
+ if (analysis.entry.candidates.length > 0) {
7011
+ lines.push(" HTML files found:");
7012
+ for (const candidate of analysis.entry.candidates.slice(0, SHOWN_FILES)) {
7013
+ lines.push(
7014
+ ` ${sizeColumn(candidate.bytes)} ${candidate.path}` + (candidate.title ? paint.dim(` "${candidate.title}"`) : "") + (candidate.same_content_as ? paint.dim(` (same content as ${candidate.same_content_as})`) : "")
7015
+ );
7016
+ }
7017
+ lines.push(...more(analysis.entry.candidates.length, SHOWN_FILES, "HTML file"));
7018
+ } else {
7019
+ lines.push(" no HTML file in the upload");
6282
7020
  }
6283
- if (isDir) {
6284
- issues.push(
6285
- `.server/actions/${entry}/ is a directory \u2014 action files must live directly under .server/actions/ (the platform rejects subdirectories).`
7021
+ }
7022
+ }
7023
+ const sizeMatters = analysis.blockers.some((b) => ["zip_too_large", "unpacked_too_large", "too_many_files"].includes(b.code)) || analysis.warnings.some((w) => w.code === "zip_size_unknown");
7024
+ if (totals.files > 0 && (sizeMatters || options.dryRun)) {
7025
+ section("Largest files");
7026
+ for (const file of analysis.largest_files.slice(0, SHOWN_FILES)) lines.push(fileLine(file));
7027
+ lines.push(...more(totals.files, Math.min(SHOWN_FILES, analysis.largest_files.length), "file"));
7028
+ section("By type");
7029
+ for (const group of analysis.by_class.slice(0, SHOWN_GROUPS)) {
7030
+ lines.push(
7031
+ ` ${sizeColumn(group.bytes)} ${group.name.padEnd(9)} ${count(group.files, "file")}` + paint.dim(` (${formatBytes(group.zip_bytes)} compressed)`)
7032
+ );
7033
+ }
7034
+ if (analysis.duplicates.length > 0) {
7035
+ section("Identical content");
7036
+ for (const group of analysis.duplicates.slice(0, SHOWN_GROUPS)) {
7037
+ lines.push(
7038
+ ` ${group.paths.length} \xD7 ${formatBytes(group.bytes)} ${group.paths.join(" = ")}` + paint.dim(` (the extra ${group.paths.length === 2 ? "copy is" : "copies are"} ${formatBytes(group.redundant_zip_bytes)} of the compressed upload)`)
6286
7039
  );
6287
- continue;
6288
7040
  }
6289
- presentFiles.add(entry);
6290
- const name = entry.endsWith(".js") ? entry.slice(0, -3) : null;
6291
- if (name === null || !ACTION_NAME.test(name)) {
6292
- issues.push(
6293
- `.server/actions/${entry}: invalid action filename \u2014 must match [a-z][a-z0-9_-]{0,62}.js (names starting with _ are reserved). The platform rejects the whole push otherwise.`
7041
+ lines.push(...more(analysis.duplicates.length, SHOWN_GROUPS, "group"));
7042
+ }
7043
+ if (analysis.embedded_media.length > 0) {
7044
+ section("Media embedded in text files");
7045
+ const sizeOf = new Map(analysis.files.map((file) => [file.path, file.bytes]));
7046
+ for (const item of analysis.embedded_media.slice(0, SHOWN_GROUPS)) {
7047
+ const fileBytes = sizeOf.get(item.path);
7048
+ lines.push(
7049
+ ` ${item.path}: ${count(item.references, "embedded file")} (data: URLs), ${formatBytes(item.base64_chars)}` + (fileBytes ? ` of its ${formatBytes(fileBytes)}` : "")
6294
7050
  );
6295
7051
  }
7052
+ lines.push(...more(analysis.embedded_media.length, SHOWN_GROUPS, "file"));
6296
7053
  }
6297
7054
  }
6298
- const decl = readBackendDeclaration(rootDir);
6299
- const declared = decl?.actions ?? [];
6300
- for (const name of declared) {
6301
- if (!ACTION_NAME.test(name)) {
6302
- issues.push(
6303
- `.agentful/backend.json declares action "${name}" \u2014 names must match [a-z][a-z0-9_-]{0,62} (the filename without .js).`
6304
- );
6305
- continue;
6306
- }
6307
- if (!presentFiles.has(`${name}.js`)) {
6308
- issues.push(
6309
- `.agentful/backend.json declares action "${name}" but .server/actions/${name}.js does not exist \u2014 create the file or remove the declaration entry.`
7055
+ if (options.dryRun && analysis.excluded.entries.length > 0) {
7056
+ section("Not uploaded");
7057
+ for (const entry of analysis.excluded.entries.slice(0, SHOWN_EXCLUDED)) {
7058
+ lines.push(
7059
+ ` ${entry.path}${entry.kind === "dir" ? "/" : ""}` + paint.dim(` (${EXCLUDE_REASON[entry.reason] ?? entry.reason}${entry.bytes === void 0 ? "" : `, ${formatBytes(entry.bytes)}`})`)
6310
7060
  );
6311
7061
  }
7062
+ lines.push(...more(analysis.excluded.entries.length + analysis.excluded.omitted, SHOWN_EXCLUDED, "entry"));
6312
7063
  }
6313
- const declaredSet = new Set(declared);
6314
- for (const file of presentFiles) {
6315
- const name = file.endsWith(".js") ? file.slice(0, -3) : file;
6316
- if (ACTION_NAME.test(name) && !declaredSet.has(name)) {
6317
- warnings.push(
6318
- `.server/actions/${file} is not declared in .agentful/backend.json \u2014 it will deploy, but \`agentful backend\` and the Backend tab won't know about it. Add "${name}" to the actions list.`
6319
- );
7064
+ if (analysis.suggestions.length > 0) {
7065
+ section("What you can do");
7066
+ for (const suggestion of analysis.suggestions) {
7067
+ lines.push(...bullet(suggestion.message));
7068
+ if (suggestion.command) lines.push(` ${paint.accent(suggestion.command)}`);
6320
7069
  }
6321
7070
  }
6322
- if (decl && decl.schema_version < 2 && ((decl.collections?.length ?? 0) > 0 || (decl.secrets?.length ?? 0) > 0 || authMethodsOf(decl).length > 0)) {
6323
- issues.push(
6324
- '.agentful/backend.json declares collections/secrets/auth but has "schema_version": 1 \u2014 set it to 2 (schema_version 1 declares actions only).'
6325
- );
7071
+ if (options.dryRun) {
7072
+ lines.push("");
7073
+ if (wouldUpload(analysis, options.force)) {
7074
+ lines.push(`${sym.ok} Nothing blocks this push.` + paint.dim(" Dry run: nothing was uploaded, no project was created."));
7075
+ } else {
7076
+ lines.push(`${sym.fail} ${paint.error("agentful push would stop here.")}` + paint.dim(" Dry run: nothing was uploaded, no project was created."));
7077
+ }
6326
7078
  }
6327
- return { issues, warnings };
7079
+ return lines;
6328
7080
  }
6329
7081
 
6330
7082
  // src/lib/backendSync.ts
@@ -6493,8 +7245,8 @@ function describeBackendState(state) {
6493
7245
  }
6494
7246
 
6495
7247
  // src/lib/backendConsent.ts
6496
- var import_node_fs8 = require("fs");
6497
- var import_node_path10 = require("path");
7248
+ var import_node_fs9 = require("fs");
7249
+ var import_node_path11 = require("path");
6498
7250
  var import_promises2 = require("readline/promises");
6499
7251
  function declaresManagedDatabase(decl) {
6500
7252
  if (!decl) return false;
@@ -6520,10 +7272,10 @@ function consentQuestion(decl) {
6520
7272
  'It is the same switch as the Backend tab; this push then applies the declaration. A "no" is remembered for this project (`agentful push --yes` or `agentful backend` enables it later).'
6521
7273
  ].join("\n");
6522
7274
  }
6523
- var consentFile = () => (0, import_node_path10.join)(configDir(), "backend-consent.json");
7275
+ var consentFile = () => (0, import_node_path11.join)(configDir(), "backend-consent.json");
6524
7276
  function readConsentFile() {
6525
7277
  try {
6526
- const raw = JSON.parse((0, import_node_fs8.readFileSync)(consentFile(), "utf8"));
7278
+ const raw = JSON.parse((0, import_node_fs9.readFileSync)(consentFile(), "utf8"));
6527
7279
  return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
6528
7280
  } catch {
6529
7281
  return {};
@@ -6535,15 +7287,15 @@ function hasRememberedDecline(projectId) {
6535
7287
  function rememberDecline(projectId) {
6536
7288
  const all = readConsentFile();
6537
7289
  all[projectId] = { declined_at: (/* @__PURE__ */ new Date()).toISOString() };
6538
- (0, import_node_fs8.mkdirSync)(configDir(), { recursive: true });
6539
- (0, import_node_fs8.writeFileSync)(consentFile(), JSON.stringify(all, null, 2) + "\n", "utf8");
7290
+ (0, import_node_fs9.mkdirSync)(configDir(), { recursive: true });
7291
+ (0, import_node_fs9.writeFileSync)(consentFile(), JSON.stringify(all, null, 2) + "\n", "utf8");
6540
7292
  }
6541
7293
  function forgetDecline(projectId) {
6542
- if (!(0, import_node_fs8.existsSync)(consentFile())) return;
7294
+ if (!(0, import_node_fs9.existsSync)(consentFile())) return;
6543
7295
  const all = readConsentFile();
6544
7296
  if (!(projectId in all)) return;
6545
7297
  delete all[projectId];
6546
- (0, import_node_fs8.writeFileSync)(consentFile(), JSON.stringify(all, null, 2) + "\n", "utf8");
7298
+ (0, import_node_fs9.writeFileSync)(consentFile(), JSON.stringify(all, null, 2) + "\n", "utf8");
6547
7299
  }
6548
7300
  async function enableManagedDatabase(auth, userId, projectId) {
6549
7301
  try {
@@ -6666,25 +7418,54 @@ function brandPreviewUrl(previewUrl, projectId) {
6666
7418
  }
6667
7419
  }
6668
7420
  async function pushCommand(opts) {
6669
- console.log(banner());
7421
+ if (opts.json && !opts.dryRun) {
7422
+ throw new ApiError(
7423
+ 0,
7424
+ "invalid_options",
7425
+ "--json belongs to --dry-run: `agentful push --dry-run --json` prints the upload analysis and uploads nothing."
7426
+ );
7427
+ }
7428
+ if (!opts.json) console.log(banner());
6670
7429
  const mode = opts.prebuilt ? "dist" : "source";
6671
- const rootDir = opts.prebuilt ? detectDistDir(process.cwd(), opts.dir) : process.cwd();
6672
- if (mode === "source") {
6673
- const pre = runPreflight(process.cwd());
6674
- const backendCheck = checkBackendDeclaration(process.cwd());
6675
- const issues = [...pre.issues, ...backendCheck.issues];
6676
- for (const warning of [...pre.warnings, ...backendCheck.warnings]) ui.warn(warning);
6677
- if (issues.length > 0) {
6678
- if (opts.force) {
6679
- ui.warn(`Preflight found ${issues.length} blocking issue(s) \u2014 continuing because of --force:`);
6680
- for (const issue of issues) ui.info(issue);
6681
- } else {
6682
- throw new ApiError(
6683
- 0,
6684
- "preflight_failed",
6685
- "This project cannot run on the platform as pushed:\n" + issues.map((issue) => `\u2022 ${issue}`).join("\n") + "\nPush with --force to attempt the cloud build anyway."
6686
- );
6687
- }
7430
+ const cwd = process.cwd();
7431
+ let rootDir = cwd;
7432
+ let analysis;
7433
+ try {
7434
+ if (opts.prebuilt) rootDir = detectDistDir(cwd, opts.dir);
7435
+ analysis = analyzePush({ rootDir, mode, projectDir: cwd });
7436
+ } catch (err2) {
7437
+ if (!opts.dryRun || !(err2 instanceof ApiError)) throw err2;
7438
+ analysis = failedAnalysis(mode, cwd, { code: err2.code, message: err2.message });
7439
+ }
7440
+ if (opts.dryRun) {
7441
+ if (opts.json) process.stdout.write(`${JSON.stringify(analysis, null, 2)}
7442
+ `);
7443
+ else for (const line of renderAnalysis(analysis, { force: opts.force, dryRun: true })) console.log(line);
7444
+ if (!wouldUpload(analysis, opts.force)) process.exitCode = 1;
7445
+ return;
7446
+ }
7447
+ const overridable = analysis.blockers.filter((blocker) => blocker.overridable);
7448
+ if (analysis.blockers.length > overridable.length) {
7449
+ for (const line of renderAnalysis(analysis, { force: opts.force })) console.log(line);
7450
+ console.log("");
7451
+ throw new ApiError(
7452
+ 0,
7453
+ "push_blocked",
7454
+ "Push stopped before sign-in: nothing was uploaded and no project was created. `agentful push --dry-run` shows this analysis again."
7455
+ );
7456
+ }
7457
+ for (const warning of analysis.warnings) ui.warn(warning.message);
7458
+ if (overridable.length > 0) {
7459
+ const issues = overridable.map((blocker) => blocker.message);
7460
+ if (opts.force) {
7461
+ ui.warn(`Preflight found ${issues.length} blocking issue(s) \u2014 continuing because of --force:`);
7462
+ for (const issue of issues) ui.info(issue);
7463
+ } else {
7464
+ throw new ApiError(
7465
+ 0,
7466
+ "preflight_failed",
7467
+ "This project cannot run on the platform as pushed:\n" + issues.map((issue) => `\u2022 ${issue}`).join("\n") + "\nPush with --force to attempt the cloud build anyway."
7468
+ );
6688
7469
  }
6689
7470
  }
6690
7471
  const auth = await ensureAuth();
@@ -6703,17 +7484,10 @@ async function pushCommand(opts) {
6703
7484
  );
6704
7485
  }
6705
7486
  ui.step(mode === "source" ? "Packing source tree\u2026" : `Packing built output (${rootDir})\u2026`);
6706
- const files = collectFiles(rootDir, mode);
6707
- const zip = zipToBase64(files);
6708
- ui.info(`${zip.count} files, ${(zip.bytes / 1024).toFixed(0)} KB compressed`);
6709
- if (zip.bytes > MAX_ZIP_UPLOAD_BYTES) {
6710
- throw new ApiError(
6711
- 0,
6712
- "zip_too_large",
6713
- `Packed ${(zip.bytes / 1024 / 1024).toFixed(1)} MB compressed \u2014 the platform caps uploads at ${Math.floor(MAX_ZIP_UPLOAD_BYTES / (1024 * 1024))} MB. Remove large media or generated files from the source tree (node_modules, dist and .git are excluded automatically); large assets belong in the platform asset flow.`
6714
- );
6715
- }
6716
7487
  const declaration = mode === "source" ? readBackendDeclaration() : null;
7488
+ const files = collectFiles(rootDir, mode);
7489
+ const zip = zipToBase64(files, maxZipBytesForUpload(mode, declaration));
7490
+ ui.info(`${zip.count} files, ${(zip.bytes / 1024).toFixed(0)} KiB compressed`);
6717
7491
  if (declaration) {
6718
7492
  const consent = await ensureDatabaseConsent({
6719
7493
  auth,
@@ -6734,12 +7508,7 @@ async function pushCommand(opts) {
6734
7508
  `${API_URL}/api/workspaces/${userId}/${projectId}/source-zip`,
6735
7509
  {
6736
7510
  token: auth.pb_token,
6737
- body: {
6738
- zip_base64: zip.base64,
6739
- mode,
6740
- clean: true,
6741
- ...declaration ? { backend_declaration: declaration } : {}
6742
- },
7511
+ body: uploadRequestBody(zip.base64, mode, declaration),
6743
7512
  timeoutMs: 12e4,
6744
7513
  retry: platformRetry
6745
7514
  }
@@ -6919,15 +7688,15 @@ async function openCommand() {
6919
7688
  // src/commands/tui.ts
6920
7689
  var import_node_child_process6 = require("child_process");
6921
7690
  var import_promises4 = require("readline/promises");
6922
- var import_node_path21 = require("path");
7691
+ var import_node_path22 = require("path");
6923
7692
 
6924
7693
  // src/lib/binWrapper.ts
6925
- var import_node_fs9 = require("fs");
6926
- var import_node_path11 = require("path");
7694
+ var import_node_fs10 = require("fs");
7695
+ var import_node_path12 = require("path");
6927
7696
  var import_node_os3 = require("os");
6928
7697
  function ensureCliOnPath() {
6929
- const binDir = (0, import_node_path11.join)((0, import_node_os3.homedir)(), ".local", "share", "agentful", "bin");
6930
- (0, import_node_fs9.mkdirSync)(binDir, { recursive: true });
7698
+ const binDir = (0, import_node_path12.join)((0, import_node_os3.homedir)(), ".local", "share", "agentful", "bin");
7699
+ (0, import_node_fs10.mkdirSync)(binDir, { recursive: true });
6931
7700
  const entry = process.argv[1];
6932
7701
  if (!entry) return binDir;
6933
7702
  const restore = (name) => {
@@ -6936,8 +7705,8 @@ function ensureCliOnPath() {
6936
7705
  ` : `export ${name}=${JSON.stringify(original)}
6937
7706
  `;
6938
7707
  };
6939
- const wrapper = (0, import_node_path11.join)(binDir, "agentful");
6940
- (0, import_node_fs9.writeFileSync)(
7708
+ const wrapper = (0, import_node_path12.join)(binDir, "agentful");
7709
+ (0, import_node_fs10.writeFileSync)(
6941
7710
  wrapper,
6942
7711
  `#!/bin/sh
6943
7712
  # Generated by the Agentful CLI so the coding agent can run \`agentful \u2026\`.
@@ -6947,7 +7716,7 @@ function ensureCliOnPath() {
6947
7716
  "utf8"
6948
7717
  );
6949
7718
  try {
6950
- (0, import_node_fs9.chmodSync)(wrapper, 493);
7719
+ (0, import_node_fs10.chmodSync)(wrapper, 493);
6951
7720
  } catch {
6952
7721
  }
6953
7722
  return binDir;
@@ -6957,9 +7726,9 @@ function ensureCliOnPath() {
6957
7726
  init_branding();
6958
7727
 
6959
7728
  // src/lib/engine.ts
6960
- var import_node_fs10 = require("fs");
6961
- var import_node_crypto = require("crypto");
6962
- var import_node_path12 = require("path");
7729
+ var import_node_fs11 = require("fs");
7730
+ var import_node_crypto2 = require("crypto");
7731
+ var import_node_path13 = require("path");
6963
7732
  var import_node_os4 = require("os");
6964
7733
  var import_node_child_process3 = require("child_process");
6965
7734
  var import_node_stream = require("stream");
@@ -7004,8 +7773,8 @@ function platformKey() {
7004
7773
  );
7005
7774
  }
7006
7775
  function cacheDir(version) {
7007
- const base = process.env.XDG_CACHE_HOME || (0, import_node_path12.join)((0, import_node_os4.homedir)(), ".cache");
7008
- return (0, import_node_path12.join)(base, "agentful", "engine", version);
7776
+ const base = process.env.XDG_CACHE_HOME || (0, import_node_path13.join)((0, import_node_os4.homedir)(), ".cache");
7777
+ return (0, import_node_path13.join)(base, "agentful", "engine", version);
7009
7778
  }
7010
7779
  async function fetchManifest() {
7011
7780
  try {
@@ -7015,8 +7784,8 @@ async function fetchManifest() {
7015
7784
  }
7016
7785
  }
7017
7786
  async function sha256File(path) {
7018
- const hash = (0, import_node_crypto.createHash)("sha256");
7019
- await (0, import_promises3.pipeline)((0, import_node_fs10.createReadStream)(path), hash);
7787
+ const hash = (0, import_node_crypto2.createHash)("sha256");
7788
+ await (0, import_promises3.pipeline)((0, import_node_fs11.createReadStream)(path), hash);
7020
7789
  return hash.digest("hex");
7021
7790
  }
7022
7791
  async function ensureEngine() {
@@ -7025,12 +7794,12 @@ async function ensureEngine() {
7025
7794
  noticeIfOutdated(manifest);
7026
7795
  const version = manifest?.engine_version || FALLBACK_ENGINE_VERSION;
7027
7796
  const dir = cacheDir(version);
7028
- const binPath = (0, import_node_path12.join)(dir, ENGINE_BINARY);
7029
- if ((0, import_node_fs10.existsSync)(binPath)) return binPath;
7797
+ const binPath = (0, import_node_path13.join)(dir, ENGINE_BINARY);
7798
+ if ((0, import_node_fs11.existsSync)(binPath)) return binPath;
7030
7799
  const artifact = manifest?.artifacts?.[key];
7031
7800
  const url = artifact?.url || `${APP_URL}/cli/engine/${version}/${key}${key.startsWith("darwin") ? ".zip" : ".tar.gz"}`;
7032
- (0, import_node_fs10.mkdirSync)(dir, { recursive: true });
7033
- const archivePath = (0, import_node_path12.join)(dir, key.startsWith("darwin") ? "engine.zip" : "engine.tar.gz");
7801
+ (0, import_node_fs11.mkdirSync)(dir, { recursive: true });
7802
+ const archivePath = (0, import_node_path13.join)(dir, key.startsWith("darwin") ? "engine.zip" : "engine.tar.gz");
7034
7803
  ui.step(`Preparing the ${brand.displayName} engine (first run only)\u2026`);
7035
7804
  const resp = await fetch(url, {
7036
7805
  redirect: "follow",
@@ -7044,11 +7813,11 @@ async function ensureEngine() {
7044
7813
  `Could not download the engine (HTTP ${resp.status}). Check your connection and try again.`
7045
7814
  );
7046
7815
  }
7047
- await (0, import_promises3.pipeline)(import_node_stream.Readable.fromWeb(resp.body), (0, import_node_fs10.createWriteStream)(archivePath));
7816
+ await (0, import_promises3.pipeline)(import_node_stream.Readable.fromWeb(resp.body), (0, import_node_fs11.createWriteStream)(archivePath));
7048
7817
  if (artifact?.sha256) {
7049
7818
  const actual = await sha256File(archivePath);
7050
7819
  if (actual !== artifact.sha256) {
7051
- (0, import_node_fs10.rmSync)(archivePath, { force: true });
7820
+ (0, import_node_fs11.rmSync)(archivePath, { force: true });
7052
7821
  throw new ApiError(
7053
7822
  0,
7054
7823
  "engine_checksum_mismatch",
@@ -7057,7 +7826,7 @@ async function ensureEngine() {
7057
7826
  }
7058
7827
  }
7059
7828
  const extract = archivePath.endsWith(".zip") ? (0, import_node_child_process3.spawnSync)("unzip", ["-oq", archivePath], { cwd: dir, stdio: "ignore" }) : (0, import_node_child_process3.spawnSync)("tar", ["-xzf", archivePath], { cwd: dir, stdio: "ignore" });
7060
- (0, import_node_fs10.rmSync)(archivePath, { force: true });
7829
+ (0, import_node_fs11.rmSync)(archivePath, { force: true });
7061
7830
  if (extract.status !== 0) {
7062
7831
  throw new ApiError(
7063
7832
  0,
@@ -7065,24 +7834,24 @@ async function ensureEngine() {
7065
7834
  `Could not unpack the engine (needs ${archivePath.endsWith(".zip") ? "unzip" : "tar"} on PATH).`
7066
7835
  );
7067
7836
  }
7068
- const extracted = (0, import_node_path12.join)(dir, "opencode");
7069
- if ((0, import_node_fs10.existsSync)(extracted) && !(0, import_node_fs10.existsSync)(binPath)) (0, import_node_fs10.renameSync)(extracted, binPath);
7070
- if (!(0, import_node_fs10.existsSync)(binPath)) {
7837
+ const extracted = (0, import_node_path13.join)(dir, "opencode");
7838
+ if ((0, import_node_fs11.existsSync)(extracted) && !(0, import_node_fs11.existsSync)(binPath)) (0, import_node_fs11.renameSync)(extracted, binPath);
7839
+ if (!(0, import_node_fs11.existsSync)(binPath)) {
7071
7840
  throw new ApiError(0, "engine_extract_failed", "Engine archive did not contain the expected binary.");
7072
7841
  }
7073
- (0, import_node_fs10.chmodSync)(binPath, 493);
7842
+ (0, import_node_fs11.chmodSync)(binPath, 493);
7074
7843
  ui.ok(`Engine ready.`);
7075
7844
  console.log(paint.dim(" powered by opencode (MIT) \xB7 run `agentful licenses` for details"));
7076
7845
  return binPath;
7077
7846
  }
7078
7847
 
7079
7848
  // src/lib/gatewayToken.ts
7080
- var import_node_fs11 = require("fs");
7081
- var import_node_path13 = require("path");
7082
- var cachePath = () => (0, import_node_path13.join)(configDir(), "gateway.json");
7849
+ var import_node_fs12 = require("fs");
7850
+ var import_node_path14 = require("path");
7851
+ var cachePath = () => (0, import_node_path14.join)(configDir(), "gateway.json");
7083
7852
  async function ensureGatewaySession(auth, projectId) {
7084
7853
  try {
7085
- const cached = JSON.parse((0, import_node_fs11.readFileSync)(cachePath(), "utf8"));
7854
+ const cached = JSON.parse((0, import_node_fs12.readFileSync)(cachePath(), "utf8"));
7086
7855
  if (cached?.token && cached.expires_at - Date.now() / 1e3 > 3600) {
7087
7856
  return cached;
7088
7857
  }
@@ -7093,18 +7862,18 @@ async function ensureGatewaySession(auth, projectId) {
7093
7862
  body: projectId ? { project_id: projectId } : {},
7094
7863
  timeoutMs: 3e4
7095
7864
  });
7096
- (0, import_node_fs11.mkdirSync)(configDir(), { recursive: true });
7097
- (0, import_node_fs11.writeFileSync)(cachePath(), JSON.stringify(res, null, 2) + "\n", "utf8");
7865
+ (0, import_node_fs12.mkdirSync)(configDir(), { recursive: true });
7866
+ (0, import_node_fs12.writeFileSync)(cachePath(), JSON.stringify(res, null, 2) + "\n", "utf8");
7098
7867
  try {
7099
- (0, import_node_fs11.chmodSync)(cachePath(), 384);
7868
+ (0, import_node_fs12.chmodSync)(cachePath(), 384);
7100
7869
  } catch {
7101
7870
  }
7102
7871
  return res;
7103
7872
  }
7104
7873
 
7105
7874
  // src/lib/engineConfig.ts
7106
- var import_node_fs13 = require("fs");
7107
- var import_node_path15 = require("path");
7875
+ var import_node_fs14 = require("fs");
7876
+ var import_node_path16 = require("path");
7108
7877
  var import_node_os5 = require("os");
7109
7878
 
7110
7879
  // src/branding/agentful-theme.json
@@ -7247,22 +8016,22 @@ ${cmd.template}
7247
8016
  }
7248
8017
 
7249
8018
  // src/lib/tools.ts
7250
- var import_node_fs12 = require("fs");
7251
- var import_node_path14 = require("path");
8019
+ var import_node_fs13 = require("fs");
8020
+ var import_node_path15 = require("path");
7252
8021
  function assetsDir() {
7253
- const here = (0, import_node_path14.dirname)(process.argv[1] || "");
7254
- for (const candidate of [(0, import_node_path14.join)(here, "..", "assets"), (0, import_node_path14.join)(here, "assets")]) {
7255
- if ((0, import_node_fs12.existsSync)((0, import_node_path14.join)(candidate, "tools", "imagegen.ts"))) return candidate;
8022
+ const here = (0, import_node_path15.dirname)(process.argv[1] || "");
8023
+ for (const candidate of [(0, import_node_path15.join)(here, "..", "assets"), (0, import_node_path15.join)(here, "assets")]) {
8024
+ if ((0, import_node_fs13.existsSync)((0, import_node_path15.join)(candidate, "tools", "imagegen.ts"))) return candidate;
7256
8025
  }
7257
8026
  return null;
7258
8027
  }
7259
8028
  function installEngineTools(configDir2) {
7260
8029
  const assets = assetsDir();
7261
8030
  if (!assets) return false;
7262
- const target = (0, import_node_path14.join)(configDir2, "tools");
7263
- (0, import_node_fs12.mkdirSync)(target, { recursive: true });
8031
+ const target = (0, import_node_path15.join)(configDir2, "tools");
8032
+ (0, import_node_fs13.mkdirSync)(target, { recursive: true });
7264
8033
  try {
7265
- (0, import_node_fs12.cpSync)((0, import_node_path14.join)(assets, "tools"), target, { recursive: true });
8034
+ (0, import_node_fs13.cpSync)((0, import_node_path15.join)(assets, "tools"), target, { recursive: true });
7266
8035
  return true;
7267
8036
  } catch {
7268
8037
  return false;
@@ -7272,7 +8041,7 @@ function installEngineTools(configDir2) {
7272
8041
  // src/lib/cloudTarget.ts
7273
8042
  var CLOUD_INSTRUCTIONS_FILENAME = "AGENTFUL_CLOUD.md";
7274
8043
  var FRAMEWORKS = contract_default.frameworks;
7275
- function frameworkDisplayName(framework) {
8044
+ function frameworkDisplayName2(framework) {
7276
8045
  if (framework === "unknown") return "no supported framework";
7277
8046
  return FRAMEWORKS.find((f) => f.id === framework)?.display_name ?? framework;
7278
8047
  }
@@ -7285,7 +8054,7 @@ function frameworkTable() {
7285
8054
  return rows.join("\n");
7286
8055
  }
7287
8056
  function sessionLine(framework) {
7288
- const name = frameworkDisplayName(framework);
8057
+ const name = frameworkDisplayName2(framework);
7289
8058
  switch (framework) {
7290
8059
  case "nextjs":
7291
8060
  return `Detected framework in this directory: **${name}** \u2014 next.config must keep \`output: 'export'\`; no route handlers/middleware/server actions.`;
@@ -7343,7 +8112,7 @@ function buildEnvList() {
7343
8112
  var bytes = (n) => n >= 1024 * 1024 ? `${n / (1024 * 1024)} MB` : `${n / 1024} KB`;
7344
8113
  function serverSideSection() {
7345
8114
  const tracks = contract_default.server_side;
7346
- const track = (key, heading, limits) => {
8115
+ const track = (key, heading, limits2) => {
7347
8116
  const t = tracks[key];
7348
8117
  const out = [
7349
8118
  `### ${heading} ${t.label}`,
@@ -7357,7 +8126,7 @@ function serverSideSection() {
7357
8126
  "Does NOT offer \u2014 name the limit and offer the way out, never promise it:",
7358
8127
  ...t.not_offered.map((n) => `- **${n.what}.** ${n.why}. Way out: ${n.workaround}.`)
7359
8128
  ];
7360
- if (limits) out.push("", `Limits: ${limits}`);
8129
+ if (limits2) out.push("", `Limits: ${limits2}`);
7361
8130
  if (t.limits_note) out.push("", t.limits_note);
7362
8131
  return out.join("\n");
7363
8132
  };
@@ -7566,7 +8335,7 @@ var CLOUD_AGENT_PROMPT = `You are the Agentful Cloud build agent: everything you
7566
8335
  var LOCAL_AGENT_PROMPT = `You are in local-only mode: free local development with no cloud deployment target \u2014 any stack is fine. Be clear that this directory will NOT be deployable with \`agentful push\` unless it satisfies the platform contract in AGENTFUL_CLOUD.md (${SERVER_SIDE_IN_ONE_SENTENCE}). If the user asks to deploy or push, check the contract first and say honestly whether the project can ship on the platform; never claim cloud deployability without evidence.`;
7567
8336
  function readAuthFile(path) {
7568
8337
  try {
7569
- return (0, import_node_fs13.readFileSync)(path, "utf8");
8338
+ return (0, import_node_fs14.readFileSync)(path, "utf8");
7570
8339
  } catch {
7571
8340
  return null;
7572
8341
  }
@@ -7636,11 +8405,11 @@ function buildEngineConfig(opts) {
7636
8405
  };
7637
8406
  }
7638
8407
  function engineXdg() {
7639
- const root = (0, import_node_path15.join)((0, import_node_os5.homedir)(), ".local", "share", "agentful");
8408
+ const root = (0, import_node_path16.join)((0, import_node_os5.homedir)(), ".local", "share", "agentful");
7640
8409
  return {
7641
- configHome: (0, import_node_path15.join)(root, "engine-config"),
7642
- dataHome: (0, import_node_path15.join)(root, "engine-data"),
7643
- stateHome: (0, import_node_path15.join)(root, "engine-state")
8410
+ configHome: (0, import_node_path16.join)(root, "engine-config"),
8411
+ dataHome: (0, import_node_path16.join)(root, "engine-data"),
8412
+ stateHome: (0, import_node_path16.join)(root, "engine-state")
7644
8413
  };
7645
8414
  }
7646
8415
  async function resolveTheme() {
@@ -7659,24 +8428,24 @@ async function resolveTheme() {
7659
8428
  }
7660
8429
  async function writeEngineSession(session, catalog, localProviders = {}, framework = "unknown", skillsInstruction = null, backendState, model) {
7661
8430
  const xdg = engineXdg();
7662
- const configDir2 = (0, import_node_path15.join)(xdg.configHome, "opencode");
7663
- const dataDir = (0, import_node_path15.join)(xdg.dataHome, "opencode");
7664
- const themesDir = (0, import_node_path15.join)(configDir2, "themes");
7665
- const pluginsDir = (0, import_node_path15.join)(configDir2, "plugins");
7666
- const commandsDir = (0, import_node_path15.join)(configDir2, "commands");
7667
- for (const dir of [themesDir, pluginsDir, commandsDir, dataDir, (0, import_node_path15.join)(xdg.stateHome, "opencode")]) {
7668
- (0, import_node_fs13.mkdirSync)(dir, { recursive: true });
8431
+ const configDir2 = (0, import_node_path16.join)(xdg.configHome, "opencode");
8432
+ const dataDir = (0, import_node_path16.join)(xdg.dataHome, "opencode");
8433
+ const themesDir = (0, import_node_path16.join)(configDir2, "themes");
8434
+ const pluginsDir = (0, import_node_path16.join)(configDir2, "plugins");
8435
+ const commandsDir = (0, import_node_path16.join)(configDir2, "commands");
8436
+ for (const dir of [themesDir, pluginsDir, commandsDir, dataDir, (0, import_node_path16.join)(xdg.stateHome, "opencode")]) {
8437
+ (0, import_node_fs14.mkdirSync)(dir, { recursive: true });
7669
8438
  }
7670
8439
  const imagegenAvailable = installEngineTools(configDir2) && session.img_on !== false;
7671
- const cloudInstructionsPath = (0, import_node_path15.join)(configDir2, CLOUD_INSTRUCTIONS_FILENAME);
7672
- (0, import_node_fs13.writeFileSync)(cloudInstructionsPath, renderCloudInstructions(framework, backendState));
7673
- const skillsPath = (0, import_node_path15.join)(configDir2, "AGENTFUL_SKILLS.md");
8440
+ const cloudInstructionsPath = (0, import_node_path16.join)(configDir2, CLOUD_INSTRUCTIONS_FILENAME);
8441
+ (0, import_node_fs14.writeFileSync)(cloudInstructionsPath, renderCloudInstructions(framework, backendState));
8442
+ const skillsPath = (0, import_node_path16.join)(configDir2, "AGENTFUL_SKILLS.md");
7674
8443
  const instructionPaths = [cloudInstructionsPath];
7675
8444
  if (skillsInstruction) {
7676
- (0, import_node_fs13.writeFileSync)(skillsPath, skillsInstruction);
8445
+ (0, import_node_fs14.writeFileSync)(skillsPath, skillsInstruction);
7677
8446
  instructionPaths.push(skillsPath);
7678
8447
  } else {
7679
- (0, import_node_fs13.rmSync)(skillsPath, { force: true });
8448
+ (0, import_node_fs14.rmSync)(skillsPath, { force: true });
7680
8449
  }
7681
8450
  const config = buildEngineConfig({
7682
8451
  session,
@@ -7686,13 +8455,13 @@ async function writeEngineSession(session, catalog, localProviders = {}, framewo
7686
8455
  instructionPaths,
7687
8456
  model
7688
8457
  });
7689
- (0, import_node_fs13.writeFileSync)((0, import_node_path15.join)(configDir2, "config.json"), JSON.stringify(config, null, 2));
7690
- const authPath2 = (0, import_node_path15.join)(dataDir, "auth.json");
7691
- (0, import_node_fs13.writeFileSync)(authPath2, mergedAuthJson(readAuthFile(authPath2), session.token));
8458
+ (0, import_node_fs14.writeFileSync)((0, import_node_path16.join)(configDir2, "config.json"), JSON.stringify(config, null, 2));
8459
+ const authPath2 = (0, import_node_path16.join)(dataDir, "auth.json");
8460
+ (0, import_node_fs14.writeFileSync)(authPath2, mergedAuthJson(readAuthFile(authPath2), session.token));
7692
8461
  const { brandingPluginSource: brandingPluginSource2 } = await Promise.resolve().then(() => (init_plugin(), plugin_exports));
7693
- const pluginPath = (0, import_node_path15.join)(pluginsDir, "agentful-branding.tsx");
7694
- (0, import_node_fs13.writeFileSync)(pluginPath, brandingPluginSource2());
7695
- (0, import_node_fs13.writeFileSync)((0, import_node_path15.join)(configDir2, "tui.json"), JSON.stringify({
8462
+ const pluginPath = (0, import_node_path16.join)(pluginsDir, "agentful-branding.tsx");
8463
+ (0, import_node_fs14.writeFileSync)(pluginPath, brandingPluginSource2());
8464
+ (0, import_node_fs14.writeFileSync)((0, import_node_path16.join)(configDir2, "tui.json"), JSON.stringify({
7696
8465
  theme: "agentful",
7697
8466
  plugin: [`file://${pluginPath}`],
7698
8467
  // The engine's own sidebar sections are plugins with fixed ids, and
@@ -7704,13 +8473,13 @@ async function writeEngineSession(session, catalog, localProviders = {}, framewo
7704
8473
  // state over this map.
7705
8474
  plugin_enabled: DISABLED_ENGINE_PLUGINS
7706
8475
  }, null, 2));
7707
- (0, import_node_fs13.writeFileSync)((0, import_node_path15.join)(themesDir, "agentful.json"), JSON.stringify(await resolveTheme(), null, 2));
8476
+ (0, import_node_fs14.writeFileSync)((0, import_node_path16.join)(themesDir, "agentful.json"), JSON.stringify(await resolveTheme(), null, 2));
7708
8477
  for (const name of Object.keys(SLASH_COMMANDS)) {
7709
- (0, import_node_fs13.writeFileSync)((0, import_node_path15.join)(commandsDir, `${name}.md`), commandMarkdown(name));
8478
+ (0, import_node_fs14.writeFileSync)((0, import_node_path16.join)(commandsDir, `${name}.md`), commandMarkdown(name));
7710
8479
  }
7711
8480
  for (const legacy of ["xdg-config", "xdg-data", "xdg-state"]) {
7712
8481
  try {
7713
- (0, import_node_fs13.rmSync)((0, import_node_path15.join)((0, import_node_os5.homedir)(), ".local", "share", "agentful", legacy), { recursive: true, force: true });
8482
+ (0, import_node_fs14.rmSync)((0, import_node_path16.join)((0, import_node_os5.homedir)(), ".local", "share", "agentful", legacy), { recursive: true, force: true });
7714
8483
  } catch {
7715
8484
  }
7716
8485
  }
@@ -7718,8 +8487,8 @@ async function writeEngineSession(session, catalog, localProviders = {}, framewo
7718
8487
  }
7719
8488
 
7720
8489
  // src/lib/skills.ts
7721
- var import_node_fs14 = require("fs");
7722
- var import_node_path16 = require("path");
8490
+ var import_node_fs15 = require("fs");
8491
+ var import_node_path17 = require("path");
7723
8492
 
7724
8493
  // src/generated/skills.json
7725
8494
  var skills_default = {
@@ -7788,7 +8557,7 @@ var SCAFFOLD_BY_FRAMEWORK = {
7788
8557
  };
7789
8558
  function hasVueDependency(rootDir) {
7790
8559
  try {
7791
- const pkg = JSON.parse((0, import_node_fs14.readFileSync)((0, import_node_path16.join)(rootDir, "package.json"), "utf8"));
8560
+ const pkg = JSON.parse((0, import_node_fs15.readFileSync)((0, import_node_path17.join)(rootDir, "package.json"), "utf8"));
7792
8561
  return Boolean({ ...pkg.dependencies, ...pkg.devDependencies }["vue"]);
7793
8562
  } catch {
7794
8563
  return false;
@@ -7818,7 +8587,7 @@ function renderSkillsInstruction(names) {
7818
8587
  return "# Platform deployment skills (mandatory)\n\nThese are the same skills the cloud build agent loads for this stack.\nFollow them when scaffolding or editing \u2014 they are not optional hints.\n\n" + sections.join("\n\n---\n\n") + "\n";
7819
8588
  }
7820
8589
  function skillsInstructionFor(framework, rootDir) {
7821
- if (!(0, import_node_fs14.existsSync)(rootDir)) return null;
8590
+ if (!(0, import_node_fs15.existsSync)(rootDir)) return null;
7822
8591
  return renderSkillsInstruction(selectSkills(framework, rootDir));
7823
8592
  }
7824
8593
 
@@ -7977,15 +8746,15 @@ function decideLocalProviders(policy) {
7977
8746
  }
7978
8747
 
7979
8748
  // src/lib/localProviders.ts
7980
- var import_node_fs15 = require("fs");
7981
- var import_node_path17 = require("path");
8749
+ var import_node_fs16 = require("fs");
8750
+ var import_node_path18 = require("path");
7982
8751
  var LOCAL_PROVIDERS_FILE = "local-providers.json";
7983
8752
  function localProvidersPath() {
7984
- return (0, import_node_path17.join)(configDir(), LOCAL_PROVIDERS_FILE);
8753
+ return (0, import_node_path18.join)(configDir(), LOCAL_PROVIDERS_FILE);
7985
8754
  }
7986
8755
  function readLocalProviders() {
7987
8756
  try {
7988
- const raw = JSON.parse((0, import_node_fs15.readFileSync)(localProvidersPath(), "utf8"));
8757
+ const raw = JSON.parse((0, import_node_fs16.readFileSync)(localProvidersPath(), "utf8"));
7989
8758
  if (raw && typeof raw === "object" && !Array.isArray(raw)) {
7990
8759
  const map = raw.providers && typeof raw.providers === "object" ? raw.providers : raw;
7991
8760
  const usable = {};
@@ -8020,8 +8789,8 @@ function resolveLocalProviders(policy) {
8020
8789
  }
8021
8790
  function writeLocalProvidersExample() {
8022
8791
  const path = localProvidersPath();
8023
- if ((0, import_node_fs15.existsSync)(path)) return path;
8024
- (0, import_node_fs15.mkdirSync)(configDir(), { recursive: true });
8792
+ if ((0, import_node_fs16.existsSync)(path)) return path;
8793
+ (0, import_node_fs16.mkdirSync)(configDir(), { recursive: true });
8025
8794
  const example = {
8026
8795
  _comment: 'Providers with your own API key, running locally. Requests go directly to the provider \u2014 no Agentful credits, no platform region guarantee. Remove the leading underscore from "_anthropic" and fill in your key to enable it.',
8027
8796
  _anthropic: {
@@ -8031,17 +8800,17 @@ function writeLocalProvidersExample() {
8031
8800
  models: { "claude-sonnet-4-5": { name: "Claude Sonnet 4.5 (my key)" } }
8032
8801
  }
8033
8802
  };
8034
- (0, import_node_fs15.writeFileSync)(path, JSON.stringify(example, null, 2) + "\n", "utf8");
8803
+ (0, import_node_fs16.writeFileSync)(path, JSON.stringify(example, null, 2) + "\n", "utf8");
8035
8804
  try {
8036
- (0, import_node_fs15.chmodSync)(path, 384);
8805
+ (0, import_node_fs16.chmodSync)(path, 384);
8037
8806
  } catch {
8038
8807
  }
8039
8808
  return path;
8040
8809
  }
8041
8810
 
8042
8811
  // src/lib/sessions.ts
8043
- var import_node_fs16 = require("fs");
8044
- var import_node_path18 = require("path");
8812
+ var import_node_fs17 = require("fs");
8813
+ var import_node_path19 = require("path");
8045
8814
  var import_node_child_process4 = require("child_process");
8046
8815
  function parseSessionModel(raw) {
8047
8816
  if (!raw) return null;
@@ -8058,8 +8827,8 @@ function parseSessionModel(raw) {
8058
8827
  }
8059
8828
  }
8060
8829
  function modelForSession(sessionID) {
8061
- const dbPath = (0, import_node_path18.join)(engineXdg().dataHome, "opencode", "opencode.db");
8062
- if (!(0, import_node_fs16.existsSync)(dbPath)) return null;
8830
+ const dbPath = (0, import_node_path19.join)(engineXdg().dataHome, "opencode", "opencode.db");
8831
+ if (!(0, import_node_fs17.existsSync)(dbPath)) return null;
8063
8832
  const escaped = sessionID.replace(/'/g, "''");
8064
8833
  const res = (0, import_node_child_process4.spawnSync)(
8065
8834
  "sqlite3",
@@ -8080,8 +8849,8 @@ function modelForSession(sessionID) {
8080
8849
  }
8081
8850
  }
8082
8851
  function lastSessionForDirectory(cwd = process.cwd()) {
8083
- const dbPath = (0, import_node_path18.join)(engineXdg().dataHome, "opencode", "opencode.db");
8084
- if (!(0, import_node_fs16.existsSync)(dbPath)) return null;
8852
+ const dbPath = (0, import_node_path19.join)(engineXdg().dataHome, "opencode", "opencode.db");
8853
+ if (!(0, import_node_fs17.existsSync)(dbPath)) return null;
8085
8854
  const escaped = cwd.replace(/'/g, "''");
8086
8855
  const res = (0, import_node_child_process4.spawnSync)(
8087
8856
  "sqlite3",
@@ -8107,9 +8876,9 @@ function lastSessionForDirectory(cwd = process.cwd()) {
8107
8876
  }
8108
8877
 
8109
8878
  // src/lib/modelPin.ts
8110
- var import_node_fs17 = require("fs");
8111
- var import_node_path19 = require("path");
8112
- var PROJECT_MODEL_PIN = (0, import_node_path19.join)(".agentful", "model.json");
8879
+ var import_node_fs18 = require("fs");
8880
+ var import_node_path20 = require("path");
8881
+ var PROJECT_MODEL_PIN = (0, import_node_path20.join)(".agentful", "model.json");
8113
8882
  function isCanonicalModel(value) {
8114
8883
  const slash = value.indexOf("/");
8115
8884
  return slash > 0 && slash < value.length - 1 && !/\s/.test(value);
@@ -8129,10 +8898,10 @@ function assertModelPin(pin, source) {
8129
8898
  return pin;
8130
8899
  }
8131
8900
  function readProjectModelPin(cwd = process.cwd()) {
8132
- const path = (0, import_node_path19.join)(cwd, PROJECT_MODEL_PIN);
8901
+ const path = (0, import_node_path20.join)(cwd, PROJECT_MODEL_PIN);
8133
8902
  let raw;
8134
8903
  try {
8135
- raw = (0, import_node_fs17.readFileSync)(path, "utf8");
8904
+ raw = (0, import_node_fs18.readFileSync)(path, "utf8");
8136
8905
  } catch (error) {
8137
8906
  const code = error.code;
8138
8907
  if (code === "ENOENT") return null;
@@ -8159,7 +8928,7 @@ function readProjectModelPin(cwd = process.cwd()) {
8159
8928
  function readEngineVariants(xdg) {
8160
8929
  try {
8161
8930
  const parsed = JSON.parse(
8162
- (0, import_node_fs17.readFileSync)((0, import_node_path19.join)(xdg.stateHome, "opencode", "model.json"), "utf8")
8931
+ (0, import_node_fs18.readFileSync)((0, import_node_path20.join)(xdg.stateHome, "opencode", "model.json"), "utf8")
8163
8932
  );
8164
8933
  if (!parsed.variant || typeof parsed.variant !== "object" || Array.isArray(parsed.variant)) return {};
8165
8934
  return Object.fromEntries(
@@ -8171,17 +8940,17 @@ function readEngineVariants(xdg) {
8171
8940
  }
8172
8941
  function writeEngineVariant(xdg, model, variant) {
8173
8942
  assertModelPin({ model, variant }, "Selected");
8174
- const path = (0, import_node_path19.join)(xdg.stateHome, "opencode", "model.json");
8943
+ const path = (0, import_node_path20.join)(xdg.stateHome, "opencode", "model.json");
8175
8944
  let state = {};
8176
8945
  try {
8177
- const parsed = JSON.parse((0, import_node_fs17.readFileSync)(path, "utf8"));
8946
+ const parsed = JSON.parse((0, import_node_fs18.readFileSync)(path, "utf8"));
8178
8947
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) state = parsed;
8179
8948
  } catch {
8180
8949
  }
8181
8950
  const existing = state.variant;
8182
8951
  const variants = existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {};
8183
8952
  state.variant = { ...variants, [model]: variant };
8184
- (0, import_node_fs17.writeFileSync)(path, JSON.stringify(state, null, 2) + "\n", "utf8");
8953
+ (0, import_node_fs18.writeFileSync)(path, JSON.stringify(state, null, 2) + "\n", "utf8");
8185
8954
  }
8186
8955
  function resolveModelPin(opts) {
8187
8956
  const cliPin = opts.cliModel ? assertModelPin({ model: opts.cliModel, variant: opts.cliVariant }, "CLI") : null;
@@ -8230,8 +8999,8 @@ function resolveModelPin(opts) {
8230
8999
  // src/lib/engineModels.ts
8231
9000
  var import_node_child_process5 = require("child_process");
8232
9001
  var import_node_util = require("util");
8233
- var import_node_fs18 = require("fs");
8234
- var import_node_path20 = require("path");
9002
+ var import_node_fs19 = require("fs");
9003
+ var import_node_path21 = require("path");
8235
9004
  var execFileAsync = (0, import_node_util.promisify)(import_node_child_process5.execFile);
8236
9005
  function parseEngineModelsOutput(output) {
8237
9006
  return Array.from(new Set(
@@ -8282,7 +9051,7 @@ async function assertEngineModelAvailable(binPath, xdg, model, listModels = list
8282
9051
  function readConnectedEngineProviders(xdg) {
8283
9052
  try {
8284
9053
  const parsed = JSON.parse(
8285
- (0, import_node_fs18.readFileSync)((0, import_node_path20.join)(xdg.dataHome, "opencode", "auth.json"), "utf8")
9054
+ (0, import_node_fs19.readFileSync)((0, import_node_path21.join)(xdg.dataHome, "opencode", "auth.json"), "utf8")
8286
9055
  );
8287
9056
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return [];
8288
9057
  return Object.keys(parsed).filter((id) => id !== "agentful").sort();
@@ -8313,7 +9082,7 @@ function describeModelRoute(model, catalog, localProviders, connectedProviders)
8313
9082
  }
8314
9083
 
8315
9084
  // src/lib/engineEvents.ts
8316
- var import_node_crypto2 = require("crypto");
9085
+ var import_node_crypto3 = require("crypto");
8317
9086
  var import_node_net = require("net");
8318
9087
  function payloadOf(event) {
8319
9088
  return event.payload && typeof event.payload === "object" ? event.payload : event;
@@ -8439,7 +9208,7 @@ var EngineLivenessPolicy = class {
8439
9208
  function engineServerCredentials(env = process.env) {
8440
9209
  return {
8441
9210
  username: env.OPENCODE_SERVER_USERNAME || void 0,
8442
- password: env.OPENCODE_SERVER_PASSWORD || (0, import_node_crypto2.randomUUID)()
9211
+ password: env.OPENCODE_SERVER_PASSWORD || (0, import_node_crypto3.randomUUID)()
8443
9212
  };
8444
9213
  }
8445
9214
  async function reserveLoopbackPort() {
@@ -8644,7 +9413,7 @@ async function tuiCommand(opts = {}) {
8644
9413
  ));
8645
9414
  } else {
8646
9415
  ui.info(paint.dim(
8647
- `Mode: ${CLOUD_AGENT} \u2014 building for Agentful Cloud (${frameworkDisplayName(framework)}). Tab switches to ${LOCAL_AGENT} for work that is not meant to deploy.`
9416
+ `Mode: ${CLOUD_AGENT} \u2014 building for Agentful Cloud (${frameworkDisplayName2(framework)}). Tab switches to ${LOCAL_AGENT} for work that is not meant to deploy.`
8648
9417
  ));
8649
9418
  }
8650
9419
  const localCount = Object.keys(local.providers).length;
@@ -8718,7 +9487,7 @@ async function tuiCommand(opts = {}) {
8718
9487
  ...process.env,
8719
9488
  // Slash commands run `agentful …` through the agent's bash tool, which
8720
9489
  // has no access to the user's shell aliases.
8721
- PATH: `${binDir}${import_node_path21.delimiter}${process.env.PATH || ""}`,
9490
+ PATH: `${binDir}${import_node_path22.delimiter}${process.env.PATH || ""}`,
8722
9491
  XDG_CONFIG_HOME: xdg.configHome,
8723
9492
  XDG_DATA_HOME: xdg.dataHome,
8724
9493
  XDG_STATE_HOME: xdg.stateHome,
@@ -8852,38 +9621,38 @@ async function shareCommand(opts) {
8852
9621
  }
8853
9622
 
8854
9623
  // src/commands/pull.ts
8855
- var import_node_fs20 = require("fs");
8856
- var import_node_path23 = require("path");
9624
+ var import_node_fs21 = require("fs");
9625
+ var import_node_path24 = require("path");
8857
9626
  init_branding();
8858
9627
  init_branding();
8859
9628
 
8860
9629
  // src/lib/pullFiles.ts
8861
- var import_node_fs19 = require("fs");
8862
- var import_node_path22 = require("path");
9630
+ var import_node_fs20 = require("fs");
9631
+ var import_node_path23 = require("path");
8863
9632
  var INLINE_BASE64_EXTENSIONS = /\.(?:png|jpe?g|gif|webp|avif|svg|ico|bmp|tiff?|woff2?|ttf|otf|eot|pdf|docx?|xlsx?|pptx?|zip|tar|gz|7z|rar|bz2|mp[34]|m4a|wav|ogg|avi|mov|mkv|flac|webm|exe|dll|so|dylib|bin|wasm)$/i;
8864
9633
  function inlineContent(value) {
8865
9634
  return typeof value === "object" && value !== null && "content" in value ? String(value.content) : String(value ?? "");
8866
9635
  }
8867
9636
  function writePulledWorkspaceEntry(root, rel, value) {
8868
- const target = (0, import_node_path22.join)(root, rel);
8869
- (0, import_node_fs19.mkdirSync)((0, import_node_path22.dirname)(target), { recursive: true });
9637
+ const target = (0, import_node_path23.join)(root, rel);
9638
+ (0, import_node_fs20.mkdirSync)((0, import_node_path23.dirname)(target), { recursive: true });
8870
9639
  const content = inlineContent(value);
8871
9640
  if (INLINE_BASE64_EXTENSIONS.test(rel)) {
8872
- (0, import_node_fs19.writeFileSync)(target, Buffer.from(content, "base64"));
9641
+ (0, import_node_fs20.writeFileSync)(target, Buffer.from(content, "base64"));
8873
9642
  return;
8874
9643
  }
8875
- (0, import_node_fs19.writeFileSync)(target, content, "utf8");
9644
+ (0, import_node_fs20.writeFileSync)(target, content, "utf8");
8876
9645
  }
8877
9646
  function writePulledBackendDeclaration(root, declaration) {
8878
- const target = (0, import_node_path22.join)(root, ".agentful", "backend.json");
8879
- (0, import_node_fs19.mkdirSync)((0, import_node_path22.dirname)(target), { recursive: true });
9647
+ const target = (0, import_node_path23.join)(root, ".agentful", "backend.json");
9648
+ (0, import_node_fs20.mkdirSync)((0, import_node_path23.dirname)(target), { recursive: true });
8880
9649
  const {
8881
9650
  source: _source,
8882
9651
  received_at: _receivedAt,
8883
9652
  last_sync: _lastSync,
8884
9653
  ...portableDeclaration
8885
9654
  } = declaration;
8886
- (0, import_node_fs19.writeFileSync)(target, `${JSON.stringify(portableDeclaration, null, 2)}
9655
+ (0, import_node_fs20.writeFileSync)(target, `${JSON.stringify(portableDeclaration, null, 2)}
8887
9656
  `, "utf8");
8888
9657
  }
8889
9658
 
@@ -8892,7 +9661,7 @@ async function pullCommand(opts) {
8892
9661
  console.log(banner());
8893
9662
  const auth = await ensureAuth();
8894
9663
  const project = requireProject();
8895
- const nonHidden = (0, import_node_fs20.readdirSync)(process.cwd()).filter((n) => n !== ".agentful" && !n.startsWith("."));
9664
+ const nonHidden = (0, import_node_fs21.readdirSync)(process.cwd()).filter((n) => n !== ".agentful" && !n.startsWith("."));
8896
9665
  if (nonHidden.length > 0 && !opts.force) {
8897
9666
  throw new ApiError(
8898
9667
  0,
@@ -8919,9 +9688,9 @@ async function pullCommand(opts) {
8919
9688
  ui.warn(`Skipped ${rel} (HTTP ${resp.status})`);
8920
9689
  continue;
8921
9690
  }
8922
- const target = (0, import_node_path23.join)(process.cwd(), rel);
8923
- (0, import_node_fs20.mkdirSync)((0, import_node_path23.dirname)(target), { recursive: true });
8924
- (0, import_node_fs20.writeFileSync)(target, Buffer.from(await resp.arrayBuffer()));
9691
+ const target = (0, import_node_path24.join)(process.cwd(), rel);
9692
+ (0, import_node_fs21.mkdirSync)((0, import_node_path24.dirname)(target), { recursive: true });
9693
+ (0, import_node_fs21.writeFileSync)(target, Buffer.from(await resp.arrayBuffer()));
8925
9694
  written++;
8926
9695
  }
8927
9696
  } else if (data.files) {
@@ -9126,7 +9895,7 @@ program2.command("login").description("Sign in to your Agentful account (device
9126
9895
  program2.command("logout").description("Remove the stored credentials").action(run(logoutCommand));
9127
9896
  program2.command("whoami").description("Show the signed-in account").action(run(whoamiCommand));
9128
9897
  program2.command("init").description("Create (or link) the Agentful cloud project for this directory").argument("[directory]", "with --template: new folder to scaffold into (created for you)").option("--link <projectId>", "link an existing project instead of creating one").option("--title <title>", "project title (skips the prompt)").option("--template <framework>", "scaffold from a platform starter (nextjs, react, vue, vanilla)").action(run((directory, opts) => initCommand(opts, directory)));
9129
- program2.command("push").description("Upload this project and get a live preview URL").option("--prebuilt", "upload a locally built output instead of building in the cloud").option("--dir <path>", "built-output directory for --prebuilt (default: auto-detect)").option("--force", "push even when the pre-upload compatibility check finds blocking issues").option("-y, --yes", "enable the declared managed database without asking (CI); a non-interactive push never enables it").action(run((opts) => pushCommand(opts)));
9898
+ program2.command("push").description("Upload this project and get a live preview URL").option("--prebuilt", "upload a locally built output instead of building in the cloud").option("--dir <path>", "built-output directory for --prebuilt (default: auto-detect)").option("--force", "push even when the pre-upload compatibility check finds blocking issues").option("-y, --yes", "enable the declared managed database without asking (CI); a non-interactive push never enables it").option("--dry-run", "analyse only: what would be uploaded, how large, and what would block it \u2014 no sign-in, no project, no upload").option("--json", "with --dry-run: print the analysis as JSON (for scripts and agents)").action(run((opts) => pushCommand(opts)));
9130
9899
  program2.command("build-status").description("Show the cloud's record of the last build (the real error on failures)").action(run(buildStatusCommand));
9131
9900
  program2.command("backend").description("Open this project's Backend tab (managed DB + server actions) in the browser").action(run(backendCommand));
9132
9901
  program2.command("open").description("Open the live preview in the browser").action(run(openCommand));