deepspace 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli/cli.ts
4
- import { defineCommand as defineCommand13, runMain, runCommand } from "citty";
4
+ import { defineCommand as defineCommand14, runMain, runCommand } from "citty";
5
5
  import { sync as spawnSync8 } from "cross-spawn";
6
- import { readFileSync as readFileSync28, existsSync as existsSync25 } from "fs";
6
+ import { readFileSync as readFileSync28, existsSync as existsSync26 } from "fs";
7
7
  import { dirname as dirname10, join as join24 } from "path";
8
8
  import { fileURLToPath as fileURLToPath3 } from "url";
9
9
 
@@ -709,7 +709,7 @@ async function provisionProfile(jwt) {
709
709
  }
710
710
  }
711
711
  function sleep(ms) {
712
- return new Promise((resolve18) => setTimeout(resolve18, ms));
712
+ return new Promise((resolve19) => setTimeout(resolve19, ms));
713
713
  }
714
714
  function base64url(bytes) {
715
715
  return bytes.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
@@ -929,6 +929,7 @@ import { dirname as dirname2, join as join5, resolve as resolve3 } from "path";
929
929
  // src/cli/lib/git/process.ts
930
930
  import { sync as spawnSync } from "cross-spawn";
931
931
  var MAX_GIT_BUFFER = 512 * 1024 * 1024;
932
+ var GIT_TIMEOUT_MS = 3e5;
932
933
  var GitError = class extends Error {
933
934
  constructor(message, code) {
934
935
  super(message);
@@ -937,10 +938,16 @@ var GitError = class extends Error {
937
938
  }
938
939
  };
939
940
  function runGit(cwd, args, opts = {}) {
941
+ const timeout = opts.timeoutMs ?? GIT_TIMEOUT_MS;
940
942
  const result = spawnSync("git", args, {
941
943
  cwd,
942
944
  input: opts.input,
943
945
  maxBuffer: MAX_GIT_BUFFER,
946
+ timeout,
947
+ // SIGKILL, not the default SIGTERM: a child that ignores or traps TERM
948
+ // would otherwise keep running for its full duration and the "timeout"
949
+ // would bound nothing.
950
+ killSignal: "SIGKILL",
944
951
  stdio: ["pipe", "pipe", "pipe"],
945
952
  env: {
946
953
  ...process.env,
@@ -949,6 +956,12 @@ function runGit(cwd, args, opts = {}) {
949
956
  ...opts.env ?? {}
950
957
  }
951
958
  });
959
+ if (result.error?.code === "ETIMEDOUT") {
960
+ throw new GitError(
961
+ `git ${args[0]} did not finish within ${Math.round(timeout / 1e3)}s and was stopped. If this was a push, the history is probably too large to transfer \u2014 see \`deepspace push\` for the size limits; otherwise retry, and report it with \`deepspace feedback\` if it persists.`,
962
+ "git_timeout"
963
+ );
964
+ }
952
965
  if (result.error) {
953
966
  const code = result.error.code;
954
967
  if (code === "ENOENT") {
@@ -2534,10 +2547,10 @@ function formatSchemaLintFindings(findings) {
2534
2547
  import { createServer } from "net";
2535
2548
  var DEFAULT_PORT = 5173;
2536
2549
  function checkPortAvailable(port, host = "0.0.0.0") {
2537
- return new Promise((resolve18) => {
2550
+ return new Promise((resolve19) => {
2538
2551
  const srv = createServer();
2539
- srv.once("error", () => resolve18(false));
2540
- srv.once("listening", () => srv.close(() => resolve18(true)));
2552
+ srv.once("error", () => resolve19(false));
2553
+ srv.once("listening", () => srv.close(() => resolve19(true)));
2541
2554
  srv.listen(port, host);
2542
2555
  });
2543
2556
  }
@@ -3679,8 +3692,8 @@ var clear = defineDeepspaceCommand({
3679
3692
  failures.push({ email: a.email, error: err.message });
3680
3693
  }
3681
3694
  }
3682
- const deleted = targets.filter((target) => !failures.some((failure) => failure.email === target.email));
3683
- removeTestAccounts(deleted.map((target) => target.id), deleted.map((target) => target.email));
3695
+ const deleted = targets.filter((target2) => !failures.some((failure) => failure.email === target2.email));
3696
+ removeTestAccounts(deleted.map((target2) => target2.id), deleted.map((target2) => target2.email));
3684
3697
  if (failures.length > 0) {
3685
3698
  throw new Refusal(
3686
3699
  `Deleted ${ok2}/${targets.length} test account(s).
@@ -4657,6 +4670,37 @@ function repoApi(deployUrl, token, appId) {
4657
4670
  };
4658
4671
  }
4659
4672
 
4673
+ // src/shared/app-files.ts
4674
+ var MAX_APP_FILE_BYTES = 25 * 1024 * 1024;
4675
+ function formatBytes(bytes) {
4676
+ if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
4677
+ if (bytes >= 1024) return `${Math.round(bytes / 1024)} KiB`;
4678
+ return `${bytes} B`;
4679
+ }
4680
+ function encodeKeyPath(key) {
4681
+ return key.split("/").map(encodeURIComponent).join("/");
4682
+ }
4683
+ function oversizeMessage(bytes) {
4684
+ return `That file is ${formatBytes(bytes)}; the limit is ${formatBytes(MAX_APP_FILE_BYTES)} per file.`;
4685
+ }
4686
+ function describeFilesFailure(status3, body) {
4687
+ const trimmed = body.trim();
4688
+ if (trimmed.startsWith("{")) {
4689
+ try {
4690
+ const parsed = JSON.parse(trimmed);
4691
+ if (parsed.error) return parsed.error;
4692
+ } catch {
4693
+ }
4694
+ }
4695
+ if (status3 === 413) {
4696
+ return `The file is too large to upload. The limit is ${formatBytes(MAX_APP_FILE_BYTES)} per file; larger uploads are rejected by the network before the app receives them.`;
4697
+ }
4698
+ if (status3 === 401 || status3 === 403) return "Not authorized to write these files.";
4699
+ if (status3 === 415) return "That file type is not accepted.";
4700
+ if (status3 >= 500) return `The files service failed (HTTP ${status3}). Retry shortly.`;
4701
+ return `Upload failed (HTTP ${status3}).`;
4702
+ }
4703
+
4660
4704
  // src/cli/lib/vc-remote.ts
4661
4705
  import { existsSync as existsSync17 } from "fs";
4662
4706
  import { tmpdir } from "os";
@@ -4821,7 +4865,7 @@ function isThinPackRejection(result) {
4821
4865
  function isRecoverablePushFailure(status3) {
4822
4866
  return status3 === "non_fast_forward" || status3 === "ref_conflict";
4823
4867
  }
4824
- function classifyPushTransportFailure(error) {
4868
+ function classifyPushTransportFailure(error, cwd) {
4825
4869
  const message = error instanceof Error ? error.message : String(error);
4826
4870
  if (/(?:HTTP |error: )409\b/i.test(message)) {
4827
4871
  return {
@@ -4841,6 +4885,9 @@ function classifyPushTransportFailure(error) {
4841
4885
  error: "The cloud repo rate-limited this push. Wait a few seconds, then retry."
4842
4886
  };
4843
4887
  }
4888
+ if (/(?:HTTP |error: )413\b/i.test(message)) {
4889
+ return { code: "push_too_large", error: pushTooLargeMessage(cwd) };
4890
+ }
4844
4891
  return null;
4845
4892
  }
4846
4893
  function pushToSpace(cwd, token, refspec, opts = {}) {
@@ -4865,18 +4912,28 @@ function pushToSpace(cwd, token, refspec, opts = {}) {
4865
4912
  }
4866
4913
  var OVERSIZED_PUSH_RE = /too large|exceeds|\bLFS\b/i;
4867
4914
  var SERVER_OBJECT_CAP_BYTES = 20 * 1024 * 1024;
4868
- var OVERSIZED_PUSH_FIX = `Find the offending object with \`git rev-list --objects --all | git cat-file --batch-check='%(objecttype) %(objectsize) %(rest)' | sort -k2 -n | tail\`, then .gitignore it (or move it to Git LFS) and re-commit.`;
4869
- function formatBytes(bytes) {
4870
- if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
4871
- if (bytes >= 1024) return `${Math.round(bytes / 1024)} KiB`;
4872
- return `${bytes} B`;
4873
- }
4874
- function oversizedPushFix(cwd, cap = SERVER_OBJECT_CAP_BYTES) {
4875
- const objects = cwd ? findOversizedObjects(cwd, cap) : [];
4876
- if (objects.length === 0) return OVERSIZED_PUSH_FIX;
4915
+ var SERVER_PACK_CAP_BYTES = 32 * 1024 * 1024;
4916
+ var OVERSIZED_PUSH_FIX = `Find the offending object with \`git rev-list --objects --all | git cat-file --batch-check='%(objecttype) %(objectsize) %(rest)' | sort -k2 -n | tail\`.`;
4917
+ function removeFromHistoryAdvice(path) {
4918
+ const file = path ? shQuote(path) : "<file>";
4919
+ return `Media belongs in the app's files rather than Git history \u2014 \`deepspace app files put ${file}\` stores it in the app's own allocation and serves it over HTTP. Then get it out of the push: add it to .gitignore, and drop it from the commits that carry it \u2014 if those commits are still local, \`git reset --soft <last pushed commit>\` and re-commit without the file. \`git rm --cached\` plus a new commit is NOT sufficient on its own: the blob stays reachable from the earlier commit and the push is rejected the same way. If it already reached pushed history, rewrite it out (\`git filter-repo --path ${file} --invert-paths\`).`;
4920
+ }
4921
+ function fixForObjects(objects) {
4922
+ if (objects.length === 0) return `${OVERSIZED_PUSH_FIX} ${removeFromHistoryAdvice(null)}`;
4877
4923
  const one = objects.length === 1;
4878
4924
  const named = objects.map((object) => `${object.path} (${formatBytes(object.bytes)})`).join(", ");
4879
- return `The oversized ${one ? "file is" : "files are"} ${named}. Remove ${one ? "it" : "them"} from history (\`git rm --cached ${one ? shQuote(objects[0].path) : "<path>"}\`, add to .gitignore, re-commit) or track ${one ? "it" : "them"} with Git LFS.`;
4925
+ return `The oversized ${one ? "file is" : "files are"} ${named}. ${removeFromHistoryAdvice(one ? objects[0].path : null)}`;
4926
+ }
4927
+ function oversizedPushFix(cwd, cap = SERVER_OBJECT_CAP_BYTES) {
4928
+ return fixForObjects(cwd ? findOversizedObjects(cwd, cap) : []);
4929
+ }
4930
+ function pushTooLargeMessage(cwd) {
4931
+ const ceilings = `The cloud repo refused this push as too large (HTTP 413). The ceilings are ${formatBytes(SERVER_OBJECT_CAP_BYTES)} per file and ${formatBytes(SERVER_PACK_CAP_BYTES)} of compressed history per push. Large media never compresses, so every clone would pay for it forever.`;
4932
+ const objects = cwd ? findOversizedObjects(cwd, SERVER_OBJECT_CAP_BYTES) : [];
4933
+ if (objects.length === 0) {
4934
+ return `${ceilings} No single file is over the per-file cap, so this is the volume of history \u2014 push it in smaller batches (\`git push space <earlier-commit>:refs/heads/<branch>\` first, then the rest). ${OVERSIZED_PUSH_FIX}`;
4935
+ }
4936
+ return `${ceilings} ${fixForObjects(objects)}`;
4880
4937
  }
4881
4938
  function pushFailureMessage(label, push, cwd) {
4882
4939
  const reason = push.reason ?? push.summary ?? "";
@@ -4966,7 +5023,7 @@ function remotePublicRefs(cwd, remote) {
4966
5023
  function remoteBranchOid(cwd, remote, branch) {
4967
5024
  return remotePublicRefs(cwd, remote).find((ref) => ref.name === `refs/heads/${branch}`)?.oid ?? null;
4968
5025
  }
4969
- function localTransferRefs(cwd, prefix, target) {
5026
+ function localTransferRefs(cwd, prefix, target2) {
4970
5027
  const output = runGit(cwd, [
4971
5028
  "for-each-ref",
4972
5029
  "--format=%(refname) %(objectname)",
@@ -4974,7 +5031,7 @@ function localTransferRefs(cwd, prefix, target) {
4974
5031
  ]).stdout.toString("utf-8");
4975
5032
  return output.split("\n").map((line) => line.trim()).filter(Boolean).map((line) => {
4976
5033
  const [name, oid] = line.split(/\s+/, 2);
4977
- return { name: `${target}/${name.slice(`${prefix}/`.length)}`, oid };
5034
+ return { name: `${target2}/${name.slice(`${prefix}/`.length)}`, oid };
4978
5035
  }).filter((ref) => /^[0-9a-f]{40}$/.test(ref.oid));
4979
5036
  }
4980
5037
 
@@ -5087,7 +5144,7 @@ async function syncDeployRepository(options) {
5087
5144
  }
5088
5145
  } else if (pushResult.status === "rejected") {
5089
5146
  output.die(
5090
- `Cloud repo rejected ${branch}${pushResult.reason ? `: ${pushResult.reason}` : ""}. Resolve the rejection and publish the commit before deploying.`,
5147
+ `${pushFailureMessage("Cloud repo push", pushResult, appDir)} Publish the commit before deploying.`,
5091
5148
  "vc_push_rejected"
5092
5149
  );
5093
5150
  } else {
@@ -5131,7 +5188,7 @@ async function syncDeployRepository(options) {
5131
5188
  sourceRevision = claimed.revision;
5132
5189
  }
5133
5190
  } catch (error) {
5134
- const failure = deployRepositoryFailure(error);
5191
+ const failure = deployRepositoryFailure(error, appDir);
5135
5192
  output.die(failure.error, failure.code);
5136
5193
  }
5137
5194
  return { commitOid, recoverable, deployKey, source, sourceRevision, baseReleaseId };
@@ -5158,8 +5215,8 @@ function classifyRemoteState(appDir, token, branch, localTip, remote) {
5158
5215
  function errorMessage3(error) {
5159
5216
  return error instanceof Error ? error.message : String(error);
5160
5217
  }
5161
- function deployRepositoryFailure(error) {
5162
- const transportFailure = classifyPushTransportFailure(error);
5218
+ function deployRepositoryFailure(error, cwd) {
5219
+ const transportFailure = classifyPushTransportFailure(error, cwd);
5163
5220
  if (transportFailure) return transportFailure;
5164
5221
  const code = errorCode(error);
5165
5222
  const message = errorMessage3(error);
@@ -5169,15 +5226,18 @@ function deployRepositoryFailure(error) {
5169
5226
  error: `Version-control sync failed: ${message}`
5170
5227
  };
5171
5228
  }
5229
+ var PUSH_RETRY_BUDGET_MS = 12e4;
5172
5230
  async function pushWithTransientRetry(push) {
5173
5231
  const backoffMs = [500, 1500, 3500];
5232
+ const deadline = Date.now() + PUSH_RETRY_BUDGET_MS;
5174
5233
  for (let attempt = 0; ; attempt++) {
5175
5234
  try {
5176
5235
  return push();
5177
5236
  } catch (error) {
5178
5237
  const message = errorMessage3(error);
5179
- if (/(?:HTTP |error: )(?:429|503)\b/i.test(message) && attempt < backoffMs.length) {
5180
- await new Promise((resolve18) => setTimeout(resolve18, backoffMs[attempt]));
5238
+ const transient = /(?:HTTP |error: )(?:429|503)\b/i.test(message);
5239
+ if (transient && attempt < backoffMs.length && Date.now() + backoffMs[attempt] < deadline) {
5240
+ await new Promise((resolve19) => setTimeout(resolve19, backoffMs[attempt]));
5181
5241
  continue;
5182
5242
  }
5183
5243
  throw error;
@@ -5521,7 +5581,7 @@ async function postWithRetry(url, makeInit, {
5521
5581
  lastError = error;
5522
5582
  }
5523
5583
  if (attempt < attempts) {
5524
- await new Promise((resolve18) => setTimeout(resolve18, Math.min(8e3, 500 * 2 ** (attempt - 1))));
5584
+ await new Promise((resolve19) => setTimeout(resolve19, Math.min(8e3, 500 * 2 ** (attempt - 1))));
5525
5585
  }
5526
5586
  }
5527
5587
  if (lastResponse) return lastResponse;
@@ -5903,7 +5963,7 @@ async function waitForAssetsReady(url, timeoutMs) {
5903
5963
  if (!body.includes("Assets have not yet deployed") && !body.includes("No app configured")) {
5904
5964
  streak++;
5905
5965
  if (streak >= 3) return true;
5906
- await new Promise((resolve18) => setTimeout(resolve18, 1500));
5966
+ await new Promise((resolve19) => setTimeout(resolve19, 1500));
5907
5967
  continue;
5908
5968
  }
5909
5969
  streak = 0;
@@ -5911,7 +5971,7 @@ async function waitForAssetsReady(url, timeoutMs) {
5911
5971
  streak = 0;
5912
5972
  }
5913
5973
  const waitMs = Math.min(8e3, 1e3 * 2 ** (attempt - 1));
5914
- await new Promise((resolve18) => setTimeout(resolve18, waitMs));
5974
+ await new Promise((resolve19) => setTimeout(resolve19, waitMs));
5915
5975
  }
5916
5976
  return false;
5917
5977
  }
@@ -7621,11 +7681,11 @@ function fmtDate(iso) {
7621
7681
  }
7622
7682
  async function confirm4(message) {
7623
7683
  process.stdout.write(`${message} [y/N] `);
7624
- return new Promise((resolve18) => {
7684
+ return new Promise((resolve19) => {
7625
7685
  process.stdin.setEncoding("utf-8");
7626
7686
  process.stdin.once("data", (data) => {
7627
7687
  const answer = data.toString().trim().toLowerCase();
7628
- resolve18(answer === "y" || answer === "yes");
7688
+ resolve19(answer === "y" || answer === "yes");
7629
7689
  });
7630
7690
  });
7631
7691
  }
@@ -7913,8 +7973,289 @@ var domain_default = defineCommand5({
7913
7973
  subCommands: { search, buy, list: list2, status, attach, detach, renew }
7914
7974
  });
7915
7975
 
7916
- // src/cli/commands/collaborators.ts
7976
+ // src/cli/commands/app-files.ts
7917
7977
  import { defineCommand as defineCommand6 } from "citty";
7978
+ import { basename as basename4, resolve as resolve15 } from "path";
7979
+ import { existsSync as existsSync21, statSync as statSync2 } from "fs";
7980
+
7981
+ // src/cli/lib/app-files-api.ts
7982
+ import { createWriteStream, openAsBlob, statSync } from "fs";
7983
+ import { basename as basename3, extname } from "path";
7984
+ import { Readable } from "stream";
7985
+ import { pipeline } from "stream/promises";
7986
+ import { randomUUID as randomUUID2 } from "crypto";
7987
+ var CONTENT_TYPES = {
7988
+ ".avif": "image/avif",
7989
+ ".css": "text/css",
7990
+ ".csv": "text/csv",
7991
+ ".gif": "image/gif",
7992
+ ".ico": "image/x-icon",
7993
+ ".jpeg": "image/jpeg",
7994
+ ".jpg": "image/jpeg",
7995
+ ".json": "application/json",
7996
+ ".md": "text/markdown",
7997
+ ".mp3": "audio/mpeg",
7998
+ ".mp4": "video/mp4",
7999
+ ".otf": "font/otf",
8000
+ ".pdf": "application/pdf",
8001
+ ".png": "image/png",
8002
+ ".txt": "text/plain",
8003
+ ".wasm": "application/wasm",
8004
+ ".wav": "audio/wav",
8005
+ ".webm": "video/webm",
8006
+ ".webp": "image/webp",
8007
+ ".woff": "font/woff",
8008
+ ".woff2": "font/woff2",
8009
+ ".zip": "application/zip"
8010
+ };
8011
+ function contentTypeFor(path) {
8012
+ return CONTENT_TYPES[extname(path).toLowerCase()] ?? "application/octet-stream";
8013
+ }
8014
+ function filesPath(appId, rest = "") {
8015
+ return `/api/app-files/${appId}${rest}`;
8016
+ }
8017
+ async function request(baseUrl, token, path, init = {}) {
8018
+ let res;
8019
+ try {
8020
+ res = await fetch(`${baseUrl}${path}`, {
8021
+ ...init,
8022
+ headers: { Authorization: `Bearer ${token}`, ...init.headers ?? {} }
8023
+ });
8024
+ } catch (err) {
8025
+ throw new ApiError(
8026
+ `Could not reach the DeepSpace platform at ${baseUrl}: ${err instanceof Error ? err.message : String(err)}. Check your connection (and DEEPSPACE_PLATFORM_URL), then retry.`,
8027
+ 0,
8028
+ "network_error",
8029
+ path
8030
+ );
8031
+ }
8032
+ if (res.ok) return res;
8033
+ const text2 = await res.text();
8034
+ let code;
8035
+ try {
8036
+ code = JSON.parse(text2.trim()).code;
8037
+ } catch {
8038
+ }
8039
+ throw new ApiError(
8040
+ describeFilesFailure(res.status, text2),
8041
+ res.status,
8042
+ code ?? (res.status === 413 ? "file_too_large" : res.status >= 500 ? "server_error" : "http_error"),
8043
+ path
8044
+ );
8045
+ }
8046
+ async function readJson(res, path) {
8047
+ const text2 = await res.text();
8048
+ try {
8049
+ return JSON.parse(text2);
8050
+ } catch {
8051
+ throw new ApiError(
8052
+ `The DeepSpace platform returned a malformed (non-JSON) response.`,
8053
+ res.status,
8054
+ "invalid_response",
8055
+ path
8056
+ );
8057
+ }
8058
+ }
8059
+ async function multipartBody(localPath, fileName, contentType) {
8060
+ const boundary = `----deepspace${randomUUID2().replace(/-/g, "")}`;
8061
+ const safeName = fileName.replace(/["\\\r\n]/g, "_");
8062
+ const head = `--${boundary}\r
8063
+ Content-Disposition: form-data; name="file"; filename="${safeName}"\r
8064
+ Content-Type: ${contentType}\r
8065
+ \r
8066
+ `;
8067
+ const file = await openAsBlob(localPath, { type: contentType });
8068
+ return {
8069
+ blob: new Blob([head, file, `\r
8070
+ --${boundary}--\r
8071
+ `]),
8072
+ contentType: `multipart/form-data; boundary=${boundary}`
8073
+ };
8074
+ }
8075
+ async function uploadAppFile(baseUrl, token, appId, localPath, key) {
8076
+ const path = filesPath(appId, `/upload?key=${encodeKeyPath(key)}`);
8077
+ const size = statSync(localPath).size;
8078
+ if (size > MAX_APP_FILE_BYTES) {
8079
+ throw new ApiError(oversizeMessage(size), 0, "file_too_large", path);
8080
+ }
8081
+ const { blob, contentType } = await multipartBody(
8082
+ localPath,
8083
+ basename3(localPath),
8084
+ contentTypeFor(localPath)
8085
+ );
8086
+ const res = await request(baseUrl, token, path, {
8087
+ method: "POST",
8088
+ headers: { "Content-Type": contentType },
8089
+ body: blob
8090
+ });
8091
+ const body = await readJson(res, path);
8092
+ return { key: body.key, name: body.name, size };
8093
+ }
8094
+ async function listAppFiles(baseUrl, token, appId, query) {
8095
+ const path = filesPath(appId, `?${query}`);
8096
+ return readJson(await request(baseUrl, token, path), path);
8097
+ }
8098
+ async function deleteAppFile(baseUrl, token, appId, key) {
8099
+ await request(baseUrl, token, filesPath(appId, `/${encodeKeyPath(key)}`), { method: "DELETE" });
8100
+ }
8101
+ async function downloadAppFile(baseUrl, token, appId, key, destination) {
8102
+ const path = filesPath(appId, `/${encodeKeyPath(key)}`);
8103
+ const res = await request(baseUrl, token, path);
8104
+ if (!res.body) {
8105
+ throw new ApiError("The server returned an empty response", 502, "invalid_response", path);
8106
+ }
8107
+ let bytes = 0;
8108
+ await pipeline(
8109
+ Readable.fromWeb(res.body),
8110
+ async function* (chunks) {
8111
+ for await (const chunk of chunks) {
8112
+ bytes += chunk.length;
8113
+ yield chunk;
8114
+ }
8115
+ },
8116
+ createWriteStream(destination)
8117
+ );
8118
+ return { bytes, contentType: res.headers.get("content-type") ?? "application/octet-stream" };
8119
+ }
8120
+
8121
+ // src/cli/commands/app-files.ts
8122
+ var DEPLOY_URL8 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
8123
+ var PLATFORM_URL = process.env.DEEPSPACE_PLATFORM_URL ?? PLATFORM_URLS.platform;
8124
+ async function target(app2) {
8125
+ const token = await ensureToken();
8126
+ const appId = await resolveAppTarget(DEPLOY_URL8, token, app2);
8127
+ return { token, appId };
8128
+ }
8129
+ var MAX_LIST_LIMIT = 1e3;
8130
+ function parseLimit(raw) {
8131
+ const limit = Number(raw ?? 100);
8132
+ if (!Number.isInteger(limit) || limit < 1 || limit > MAX_LIST_LIMIT) {
8133
+ throw new Refusal(`--limit must be a whole number from 1 to ${MAX_LIST_LIMIT}.`, "invalid_limit");
8134
+ }
8135
+ return limit;
8136
+ }
8137
+ function requireKey(raw) {
8138
+ const key = raw.trim().replace(/^\/+/, "");
8139
+ if (!key) throw new Refusal("A key is required (for example `logo.png`).", "invalid_key");
8140
+ if (key.split("/").some((segment) => segment === ".." || segment === ".")) {
8141
+ throw new Refusal(
8142
+ `"${raw}" contains a path traversal segment. Keys are relative to the app, e.g. \`img/hero.jpg\`.`,
8143
+ "invalid_key"
8144
+ );
8145
+ }
8146
+ return key;
8147
+ }
8148
+ function appPrefixOf(key) {
8149
+ const segments = key.split("/");
8150
+ return segments.length > 2 ? `${segments[0]}/${segments[1]}/` : "";
8151
+ }
8152
+ function relativeKey(key, prefix) {
8153
+ return prefix && key.startsWith(prefix) ? key.slice(prefix.length) : key;
8154
+ }
8155
+ function servedPath(key) {
8156
+ return `/api/files/${encodeKeyPath(key)}?scope=app`;
8157
+ }
8158
+ var put = defineDeepspaceCommand({
8159
+ meta: { name: "put", description: "Upload a file to the app\u2019s files" },
8160
+ args: {
8161
+ file: { type: "positional", description: "Local file to upload", required: true },
8162
+ key: { type: "string", description: "Key to store it under (default: the file name)" },
8163
+ app: { type: "string", description: "App id or live name (defaults to ./wrangler.toml)" }
8164
+ },
8165
+ async run({ args }) {
8166
+ const localPath = resolve15(String(args.file));
8167
+ if (!existsSync21(localPath) || !statSync2(localPath).isFile()) {
8168
+ throw new Refusal(`No such file: ${args.file}`, "file_not_found");
8169
+ }
8170
+ const key = requireKey(args.key ?? basename4(localPath));
8171
+ const { token, appId } = await target(args.app);
8172
+ const result = await uploadAppFile(PLATFORM_URL, token, appId, localPath, key);
8173
+ if (!args.json) {
8174
+ console.log(`\u2713 ${key} (${formatBytes(result.size)})`);
8175
+ console.log(` Served from your app at ${servedPath(result.key)}`);
8176
+ }
8177
+ return { data: { appId, key, storedKey: result.key, size: result.size, path: servedPath(result.key) } };
8178
+ }
8179
+ });
8180
+ var list3 = defineDeepspaceCommand({
8181
+ meta: { name: "list", description: "List the app\u2019s files" },
8182
+ args: {
8183
+ prefix: { type: "string", description: "Only keys starting with this prefix" },
8184
+ limit: {
8185
+ type: "string",
8186
+ description: `Max entries, 1-${MAX_LIST_LIMIT} (default 100)`,
8187
+ default: "100"
8188
+ },
8189
+ app: { type: "string", description: "App id or live name (defaults to ./wrangler.toml)" }
8190
+ },
8191
+ async run({ args }) {
8192
+ const limit = parseLimit(args.limit);
8193
+ const { token, appId } = await target(args.app);
8194
+ const query = new URLSearchParams({ limit: String(limit) });
8195
+ if (args.prefix) query.set("prefix", String(args.prefix));
8196
+ const result = await listAppFiles(PLATFORM_URL, token, appId, query);
8197
+ const prefix = result.files.length ? appPrefixOf(result.files[0].key) : "";
8198
+ const files = result.files.map((file) => ({
8199
+ key: relativeKey(file.key, prefix),
8200
+ size: file.size,
8201
+ uploaded: file.uploaded,
8202
+ path: servedPath(file.key)
8203
+ }));
8204
+ if (!args.json) {
8205
+ if (files.length === 0) {
8206
+ console.log("No files. Upload one with `deepspace app files put <file>`.");
8207
+ } else {
8208
+ const keyWidth = Math.max(3, ...files.map((file) => file.key.length));
8209
+ console.log(`${"KEY".padEnd(keyWidth)} ${"SIZE".padStart(9)} UPLOADED`);
8210
+ for (const file of files) {
8211
+ console.log(
8212
+ `${file.key.padEnd(keyWidth)} ${formatBytes(file.size).padStart(9)} ${file.uploaded.slice(0, 10)}`
8213
+ );
8214
+ }
8215
+ const more = limit < MAX_LIST_LIMIT ? "raise --limit or narrow with --prefix" : "narrow with --prefix";
8216
+ if (result.truncated) console.log(`(truncated at ${limit} \u2014 ${more})`);
8217
+ }
8218
+ }
8219
+ return { data: { appId, files, truncated: result.truncated } };
8220
+ }
8221
+ });
8222
+ var get = defineDeepspaceCommand({
8223
+ meta: { name: "get", description: "Download one file" },
8224
+ args: {
8225
+ key: { type: "positional", description: "Key to download", required: true },
8226
+ out: { type: "string", description: "Write here (default: the key\u2019s file name)" },
8227
+ app: { type: "string", description: "App id or live name (defaults to ./wrangler.toml)" }
8228
+ },
8229
+ async run({ args }) {
8230
+ const key = requireKey(String(args.key));
8231
+ const destination = resolve15(String(args.out ?? basename4(key)));
8232
+ const { token, appId } = await target(args.app);
8233
+ const result = await downloadAppFile(PLATFORM_URL, token, appId, key, destination);
8234
+ if (!args.json) console.log(`\u2713 ${key} \u2192 ${destination} (${formatBytes(result.bytes)})`);
8235
+ return { data: { appId, key, out: destination, size: result.bytes, contentType: result.contentType } };
8236
+ }
8237
+ });
8238
+ var rm = defineDeepspaceCommand({
8239
+ meta: { name: "rm", description: "Delete one file" },
8240
+ args: {
8241
+ key: { type: "positional", description: "Key to delete", required: true },
8242
+ app: { type: "string", description: "App id or live name (defaults to ./wrangler.toml)" }
8243
+ },
8244
+ async run({ args }) {
8245
+ const key = requireKey(String(args.key));
8246
+ const { token, appId } = await target(args.app);
8247
+ await deleteAppFile(PLATFORM_URL, token, appId, key);
8248
+ if (!args.json) console.log(`\u2713 deleted ${key}`);
8249
+ return { data: { appId, key, deleted: true } };
8250
+ }
8251
+ });
8252
+ var app_files_default = defineCommand6({
8253
+ meta: { name: "files", description: "Upload and manage the app\u2019s files (images, media)" },
8254
+ subCommands: { put, list: list3, get, rm }
8255
+ });
8256
+
8257
+ // src/cli/commands/collaborators.ts
8258
+ import { defineCommand as defineCommand7 } from "citty";
7918
8259
 
7919
8260
  // src/server/auth/testAccounts.ts
7920
8261
  var TEST_ACCOUNT_EMAIL_SUFFIX = "@deepspace.test";
@@ -7924,18 +8265,18 @@ function isTestAccountEmail(email) {
7924
8265
 
7925
8266
  // src/cli/commands/collaborators.ts
7926
8267
  var API_URL5 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
7927
- var DEPLOY_URL8 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
8268
+ var DEPLOY_URL9 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
7928
8269
  function api2(token, path, init) {
7929
8270
  return apiFetch(API_URL5, token, path, init);
7930
8271
  }
7931
- var list3 = defineDeepspaceCommand({
8272
+ var list4 = defineDeepspaceCommand({
7932
8273
  meta: { name: "list", description: "List collaborators on your app" },
7933
8274
  args: {
7934
8275
  app: { type: "string", alias: "a", description: "App id or name (defaults to ./wrangler.toml)" }
7935
8276
  },
7936
8277
  async run({ args }) {
7937
8278
  const token = await ensureToken();
7938
- const app2 = await resolveAppTarget(DEPLOY_URL8, token, args.app);
8279
+ const app2 = await resolveAppTarget(DEPLOY_URL9, token, args.app);
7939
8280
  const { collaborators, pending = [] } = await api2(token, `/api/app-collaborators/${encodeURIComponent(app2)}`);
7940
8281
  if (!collaborators.length && !pending.length) {
7941
8282
  if (!args.json) {
@@ -7973,7 +8314,7 @@ var add = defineDeepspaceCommand({
7973
8314
  async run({ args }) {
7974
8315
  const email = String(args.email);
7975
8316
  const token = await ensureToken();
7976
- const app2 = await resolveAppTarget(DEPLOY_URL8, token, args.app);
8317
+ const app2 = await resolveAppTarget(DEPLOY_URL9, token, args.app);
7977
8318
  if (isTestAccountEmail(email)) {
7978
8319
  throw new Refusal(
7979
8320
  "Test accounts (@deepspace.test) cannot be added as collaborators. Use a real DeepSpace account.",
@@ -8015,13 +8356,13 @@ var remove = defineDeepspaceCommand({
8015
8356
  async run({ args }) {
8016
8357
  const email = String(args.email);
8017
8358
  const token = await ensureToken();
8018
- const app2 = await resolveAppTarget(DEPLOY_URL8, token, args.app);
8359
+ const app2 = await resolveAppTarget(DEPLOY_URL9, token, args.app);
8019
8360
  const { collaborators } = await api2(
8020
8361
  token,
8021
8362
  `/api/app-collaborators/${encodeURIComponent(app2)}`
8022
8363
  );
8023
- const target = email.trim().toLowerCase();
8024
- const found = collaborators.find((c) => c.emailDisplay.toLowerCase() === target);
8364
+ const target2 = email.trim().toLowerCase();
8365
+ const found = collaborators.find((c) => c.emailDisplay.toLowerCase() === target2);
8025
8366
  if (!found) {
8026
8367
  throw new Refusal(`${email} is not a collaborator on ${app2}`, "not_a_collaborator", {
8027
8368
  action: cliAction("deepspace", "app", "collaborators", "list", "--app", app2)
@@ -8045,13 +8386,13 @@ var cancel3 = defineDeepspaceCommand({
8045
8386
  async run({ args }) {
8046
8387
  const email = String(args.email);
8047
8388
  const token = await ensureToken();
8048
- const app2 = await resolveAppTarget(DEPLOY_URL8, token, args.app);
8389
+ const app2 = await resolveAppTarget(DEPLOY_URL9, token, args.app);
8049
8390
  const { pending = [] } = await api2(
8050
8391
  token,
8051
8392
  `/api/app-collaborators/${encodeURIComponent(app2)}`
8052
8393
  );
8053
- const target = email.trim().toLowerCase();
8054
- const found = pending.find((p34) => p34.email.toLowerCase() === target);
8394
+ const target2 = email.trim().toLowerCase();
8395
+ const found = pending.find((p34) => p34.email.toLowerCase() === target2);
8055
8396
  if (!found) {
8056
8397
  throw new Refusal(`${email} has no pending invite on ${app2}`, "no_pending_invite", {
8057
8398
  action: cliAction("deepspace", "app", "collaborators", "list", "--app", app2)
@@ -8066,16 +8407,16 @@ var cancel3 = defineDeepspaceCommand({
8066
8407
  return { data: { cancelled: found } };
8067
8408
  }
8068
8409
  });
8069
- var collaborators_default = defineCommand6({
8410
+ var collaborators_default = defineCommand7({
8070
8411
  meta: { name: "collaborators", description: "Manage who can deploy your app" },
8071
- subCommands: { list: list3, add, remove, cancel: cancel3 }
8412
+ subCommands: { list: list4, add, remove, cancel: cancel3 }
8072
8413
  });
8073
8414
 
8074
8415
  // src/cli/commands/transfer.ts
8075
- import { defineCommand as defineCommand7 } from "citty";
8416
+ import { defineCommand as defineCommand8 } from "citty";
8076
8417
  import * as p14 from "@clack/prompts";
8077
8418
  var API_URL6 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
8078
- var DEPLOY_URL9 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
8419
+ var DEPLOY_URL10 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
8079
8420
  var offer = defineDeepspaceCommand({
8080
8421
  meta: { name: "offer", description: "Offer this app to another DeepSpace user" },
8081
8422
  args: {
@@ -8089,7 +8430,7 @@ var offer = defineDeepspaceCommand({
8089
8430
  async run({ args }) {
8090
8431
  const email = String(args.email);
8091
8432
  const token = await ensureToken();
8092
- const app2 = await resolveAppTarget(DEPLOY_URL9, token, args.app);
8433
+ const app2 = await resolveAppTarget(DEPLOY_URL10, token, args.app);
8093
8434
  const pending = await apiFetch(
8094
8435
  API_URL6,
8095
8436
  token,
@@ -8141,7 +8482,7 @@ var status2 = defineDeepspaceCommand({
8141
8482
  },
8142
8483
  async run({ args }) {
8143
8484
  const token = await ensureToken();
8144
- const app2 = await resolveAppTarget(DEPLOY_URL9, token, args.app);
8485
+ const app2 = await resolveAppTarget(DEPLOY_URL10, token, args.app);
8145
8486
  const { transfer } = await apiFetch(API_URL6, token, `/api/app-transfers/${encodeURIComponent(app2)}`);
8146
8487
  if (!transfer) {
8147
8488
  if (!args.json) console.log(`No pending transfer for ${app2}.`);
@@ -8168,7 +8509,7 @@ var accept = defineDeepspaceCommand({
8168
8509
  const msg = err instanceof Error ? err.message : String(err);
8169
8510
  throw new Refusal(msg, msg.startsWith("No app id") ? "not_in_app_repo" : "invalid_app");
8170
8511
  }
8171
- await apiFetch(DEPLOY_URL9, token, `/api/apps/${encodeURIComponent(app2)}/transfer/accept`, {
8512
+ await apiFetch(DEPLOY_URL10, token, `/api/apps/${encodeURIComponent(app2)}/transfer/accept`, {
8172
8513
  method: "POST"
8173
8514
  });
8174
8515
  if (!args.json) {
@@ -8186,37 +8527,37 @@ var cancel4 = defineDeepspaceCommand({
8186
8527
  },
8187
8528
  async run({ args }) {
8188
8529
  const token = await ensureToken();
8189
- const app2 = await resolveAppTarget(DEPLOY_URL9, token, args.app);
8530
+ const app2 = await resolveAppTarget(DEPLOY_URL10, token, args.app);
8190
8531
  await apiFetch(API_URL6, token, `/api/app-transfers/${encodeURIComponent(app2)}`, { method: "DELETE" });
8191
8532
  if (!args.json) console.log(`\u2713 Transfer offer for ${app2} cancelled.`);
8192
8533
  return { data: { app: app2, cancelled: true } };
8193
8534
  }
8194
8535
  });
8195
- var transfer_default = defineCommand7({
8536
+ var transfer_default = defineCommand8({
8196
8537
  meta: { name: "transfer", description: "Transfer app ownership to another user" },
8197
8538
  subCommands: { offer, status: status2, accept, cancel: cancel4 }
8198
8539
  });
8199
8540
 
8200
8541
  // src/cli/commands/integrations.ts
8201
- import { defineCommand as defineCommand8 } from "citty";
8542
+ import { defineCommand as defineCommand9 } from "citty";
8202
8543
 
8203
8544
  // src/cli/commands/_invoke-impl.ts
8204
8545
  import { readFileSync as readFileSync22 } from "fs";
8205
8546
  import * as p15 from "@clack/prompts";
8206
8547
  var API_URL7 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
8207
8548
  var DEFAULT_TIMEOUT_MS = 12e4;
8208
- function parseTarget(target) {
8209
- if (!target || typeof target !== "string") {
8549
+ function parseTarget(target2) {
8550
+ if (!target2 || typeof target2 !== "string") {
8210
8551
  throw new Refusal(
8211
8552
  "Missing target. Expected '<integration>/<endpoint>' (e.g. 'openai/chat-completion').",
8212
8553
  "missing_target",
8213
8554
  { action: cliAction("deepspace", "integrations", "list") }
8214
8555
  );
8215
8556
  }
8216
- const parts = target.split("/");
8557
+ const parts = target2.split("/");
8217
8558
  if (parts.length !== 2 || !parts[0] || !parts[1]) {
8218
8559
  throw new Refusal(
8219
- `Bad target '${target}'. Expected '<integration>/<endpoint>' (e.g. 'openai/chat-completion').`,
8560
+ `Bad target '${target2}'. Expected '<integration>/<endpoint>' (e.g. 'openai/chat-completion').`,
8220
8561
  "bad_target",
8221
8562
  { action: cliAction("deepspace", "integrations", "list") }
8222
8563
  );
@@ -8436,7 +8777,7 @@ async function runInvoke(args) {
8436
8777
  }
8437
8778
 
8438
8779
  // src/cli/commands/integrations.ts
8439
- var list4 = defineDeepspaceCommand({
8780
+ var list5 = defineDeepspaceCommand({
8440
8781
  meta: {
8441
8782
  name: "list",
8442
8783
  description: "List all available integrations"
@@ -8507,13 +8848,13 @@ var invoke = defineDeepspaceCommand({
8507
8848
  });
8508
8849
  }
8509
8850
  });
8510
- var integrations_default = defineCommand8({
8851
+ var integrations_default = defineCommand9({
8511
8852
  meta: {
8512
8853
  name: "integrations",
8513
8854
  description: "List, inspect, and invoke platform integrations"
8514
8855
  },
8515
8856
  subCommands: {
8516
- list: list4,
8857
+ list: list5,
8517
8858
  info,
8518
8859
  invoke
8519
8860
  }
@@ -8521,7 +8862,7 @@ var integrations_default = defineCommand8({
8521
8862
 
8522
8863
  // src/cli/commands/feedback.ts
8523
8864
  import * as p16 from "@clack/prompts";
8524
- import { existsSync as existsSync21, readFileSync as readFileSync23 } from "fs";
8865
+ import { existsSync as existsSync22, readFileSync as readFileSync23 } from "fs";
8525
8866
  import { dirname as dirname8, join as join20 } from "path";
8526
8867
  import { fileURLToPath as fileURLToPath2 } from "url";
8527
8868
  import { release } from "os";
@@ -8549,7 +8890,7 @@ function readCliVersion() {
8549
8890
  join20(here, "..", "..", "..", "package.json")
8550
8891
  ];
8551
8892
  for (const path of candidates) {
8552
- if (!existsSync21(path)) continue;
8893
+ if (!existsSync22(path)) continue;
8553
8894
  const pkg2 = JSON.parse(readFileSync23(path, "utf-8"));
8554
8895
  if (pkg2.name === "deepspace" && pkg2.version) return pkg2.version;
8555
8896
  }
@@ -8745,7 +9086,7 @@ Context: deepspace ${payload.cliVersion} \xB7 ${payload.nodeVersion} \xB7 ${payl
8745
9086
  });
8746
9087
 
8747
9088
  // src/cli/commands/secrets.ts
8748
- import { defineCommand as defineCommand9 } from "citty";
9089
+ import { defineCommand as defineCommand10 } from "citty";
8749
9090
  import { readFileSync as readFileSync24 } from "fs";
8750
9091
 
8751
9092
  // src/cli/lib/citty-args.ts
@@ -8779,7 +9120,7 @@ function parseCursorArg(raw) {
8779
9120
  }
8780
9121
 
8781
9122
  // src/cli/commands/secrets.ts
8782
- var DEPLOY_URL10 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
9123
+ var DEPLOY_URL11 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
8783
9124
  var COMMON_ARGS = {
8784
9125
  app: {
8785
9126
  type: "string",
@@ -8849,7 +9190,7 @@ function warnIfDebugRoutesEnabled(secrets) {
8849
9190
  }
8850
9191
  var APPLY_HINT = "Run `deepspace deploy` to apply \u2014 secrets take effect at deploy time.";
8851
9192
  var LOCAL_DEV_HINT = "Running `deepspace dev start`? Restart it to load the change locally.";
8852
- var list5 = defineCommand9({
9193
+ var list6 = defineCommand10({
8853
9194
  meta: { name: "list", description: "List masked secrets in a config" },
8854
9195
  args: {
8855
9196
  ...COMMON_ARGS,
@@ -8862,7 +9203,7 @@ var list5 = defineCommand9({
8862
9203
  async run({ args }) {
8863
9204
  try {
8864
9205
  const t = await resolveTarget(args);
8865
- const { secrets } = await listSecrets(DEPLOY_URL10, t.token, t.appId, t.configName);
9206
+ const { secrets } = await listSecrets(DEPLOY_URL11, t.token, t.appId, t.configName);
8866
9207
  if (args.json) {
8867
9208
  console.log(JSON.stringify({ ok: true, appId: t.appId, config: t.configName, secrets }));
8868
9209
  return;
@@ -8879,7 +9220,7 @@ var list5 = defineCommand9({
8879
9220
  }
8880
9221
  }
8881
9222
  });
8882
- var set = defineCommand9({
9223
+ var set = defineCommand10({
8883
9224
  meta: { name: "set", description: "Set secrets from KEY=value pairs" },
8884
9225
  args: {
8885
9226
  ...COMMON_ARGS,
@@ -8907,9 +9248,9 @@ var set = defineCommand9({
8907
9248
  }
8908
9249
  if (Object.keys(secrets).length === 1) {
8909
9250
  const [[k, v]] = Object.entries(secrets);
8910
- await setSecret(DEPLOY_URL10, t.token, t.appId, t.configName, k, v);
9251
+ await setSecret(DEPLOY_URL11, t.token, t.appId, t.configName, k, v);
8911
9252
  } else {
8912
- await uploadSecrets(DEPLOY_URL10, t.token, t.appId, t.configName, secrets, false);
9253
+ await uploadSecrets(DEPLOY_URL11, t.token, t.appId, t.configName, secrets, false);
8913
9254
  }
8914
9255
  const n = Object.keys(secrets).length;
8915
9256
  ok(
@@ -8928,7 +9269,7 @@ var set = defineCommand9({
8928
9269
  }
8929
9270
  }
8930
9271
  });
8931
- var get = defineCommand9({
9272
+ var get2 = defineCommand10({
8932
9273
  meta: { name: "get", description: "Get a secret; pass --plain to print the value" },
8933
9274
  args: {
8934
9275
  ...COMMON_ARGS,
@@ -8940,11 +9281,11 @@ var get = defineCommand9({
8940
9281
  const t = await resolveTarget(args);
8941
9282
  const key = validateSecretName(args.key);
8942
9283
  if (args.plain) {
8943
- const { value } = await getSecretPlain(DEPLOY_URL10, t.token, t.appId, t.configName, key);
9284
+ const { value } = await getSecretPlain(DEPLOY_URL11, t.token, t.appId, t.configName, key);
8944
9285
  process.stdout.write(process.stdout.isTTY ? value + "\n" : value);
8945
9286
  return;
8946
9287
  }
8947
- const { secrets } = await listSecrets(DEPLOY_URL10, t.token, t.appId, t.configName);
9288
+ const { secrets } = await listSecrets(DEPLOY_URL11, t.token, t.appId, t.configName);
8948
9289
  const item = secrets.find((s) => s.key === key);
8949
9290
  if (!item)
8950
9291
  fail(new InputError(`Secret "${key}" not found in ${t.configName}`, "secret_not_found"));
@@ -8964,7 +9305,7 @@ var get = defineCommand9({
8964
9305
  }
8965
9306
  }
8966
9307
  });
8967
- var del2 = defineCommand9({
9308
+ var del2 = defineCommand10({
8968
9309
  meta: { name: "delete", description: "Delete one or more secrets" },
8969
9310
  args: {
8970
9311
  ...COMMON_ARGS,
@@ -8979,7 +9320,7 @@ var del2 = defineCommand9({
8979
9320
  for (const key of keys) {
8980
9321
  const name = validateSecretName(key);
8981
9322
  try {
8982
- await deleteSecret(DEPLOY_URL10, t.token, t.appId, t.configName, name);
9323
+ await deleteSecret(DEPLOY_URL11, t.token, t.appId, t.configName, name);
8983
9324
  deleted++;
8984
9325
  } catch (err) {
8985
9326
  if (err?.status === 404) absent++;
@@ -8998,7 +9339,7 @@ var del2 = defineCommand9({
8998
9339
  }
8999
9340
  }
9000
9341
  });
9001
- var upload = defineCommand9({
9342
+ var upload = defineCommand10({
9002
9343
  meta: { name: "upload", description: "Upload secrets from a dotenv or JSON file" },
9003
9344
  args: {
9004
9345
  ...COMMON_ARGS,
@@ -9020,7 +9361,7 @@ var upload = defineCommand9({
9020
9361
  const secrets = parseSecretsUpload(content);
9021
9362
  if (Object.keys(secrets).length === 0)
9022
9363
  fail(new InputError("No secrets found in the input.", "empty_input"));
9023
- await uploadSecrets(DEPLOY_URL10, t.token, t.appId, t.configName, secrets, args.replace);
9364
+ await uploadSecrets(DEPLOY_URL11, t.token, t.appId, t.configName, secrets, args.replace);
9024
9365
  const uploaded = Object.keys(secrets);
9025
9366
  ok(
9026
9367
  args.json === true,
@@ -9035,7 +9376,7 @@ var upload = defineCommand9({
9035
9376
  }
9036
9377
  }
9037
9378
  });
9038
- var download = defineCommand9({
9379
+ var download = defineCommand10({
9039
9380
  meta: { name: "download", description: "Download a config\u2019s secrets (dotenv/json/shell)" },
9040
9381
  args: {
9041
9382
  ...COMMON_ARGS,
@@ -9057,14 +9398,14 @@ var download = defineCommand9({
9057
9398
  )
9058
9399
  );
9059
9400
  }
9060
- const { secrets } = await fetchSecretsValues(DEPLOY_URL10, t.token, t.appId, t.configName);
9401
+ const { secrets } = await fetchSecretsValues(DEPLOY_URL11, t.token, t.appId, t.configName);
9061
9402
  process.stdout.write(formatSecretsDownload(secrets, format));
9062
9403
  } catch (err) {
9063
9404
  fail(err);
9064
9405
  }
9065
9406
  }
9066
9407
  });
9067
- var pull = defineCommand9({
9408
+ var pull = defineCommand10({
9068
9409
  meta: { name: "pull", description: "Refresh the .dev.vars cache from the app store" },
9069
9410
  args: { ...COMMON_ARGS },
9070
9411
  async run({ args }) {
@@ -9080,7 +9421,7 @@ var pull = defineCommand9({
9080
9421
  );
9081
9422
  const t = await resolveTarget(args);
9082
9423
  const ownerId = decodeJwtPayload(t.token).sub;
9083
- const pulled = await pullAppSecretsCache(DEPLOY_URL10, t.token, t.appId, t.configName);
9424
+ const pulled = await pullAppSecretsCache(DEPLOY_URL11, t.token, t.appId, t.configName);
9084
9425
  if (!pulled) {
9085
9426
  ok(
9086
9427
  args.json === true,
@@ -9107,13 +9448,13 @@ var pull = defineCommand9({
9107
9448
  }
9108
9449
  }
9109
9450
  });
9110
- var configsList = defineCommand9({
9451
+ var configsList = defineCommand10({
9111
9452
  meta: { name: "list", description: "List the app\u2019s configs" },
9112
9453
  args: { ...COMMON_ARGS },
9113
9454
  async run({ args }) {
9114
9455
  try {
9115
9456
  const t = await resolveTarget(args);
9116
- const { configs: configs2 } = await listConfigs(DEPLOY_URL10, t.token, t.appId);
9457
+ const { configs: configs2 } = await listConfigs(DEPLOY_URL11, t.token, t.appId);
9117
9458
  ok(args.json === true, { appId: t.appId, configs: configs2 }, () => {
9118
9459
  if (configs2.length === 0) {
9119
9460
  console.log("No configs yet \u2014 the first `secrets set` creates one.");
@@ -9128,7 +9469,7 @@ var configsList = defineCommand9({
9128
9469
  }
9129
9470
  }
9130
9471
  });
9131
- var configsCreate = defineCommand9({
9472
+ var configsCreate = defineCommand10({
9132
9473
  meta: { name: "create", description: "Create a config (optionally copying another)" },
9133
9474
  args: {
9134
9475
  ...COMMON_ARGS,
@@ -9140,7 +9481,7 @@ var configsCreate = defineCommand9({
9140
9481
  const t = await resolveTarget(args);
9141
9482
  const name = validateConfigName(args.name);
9142
9483
  const copyFrom = args["copy-from"] || void 0;
9143
- const { configs: configs2 } = await listConfigs(DEPLOY_URL10, t.token, t.appId);
9484
+ const { configs: configs2 } = await listConfigs(DEPLOY_URL11, t.token, t.appId);
9144
9485
  if (configs2.some((c) => c.name === name)) {
9145
9486
  if (copyFrom) {
9146
9487
  fail(
@@ -9157,7 +9498,7 @@ var configsCreate = defineCommand9({
9157
9498
  );
9158
9499
  return;
9159
9500
  }
9160
- await createConfig(DEPLOY_URL10, t.token, t.appId, name, copyFrom);
9501
+ await createConfig(DEPLOY_URL11, t.token, t.appId, name, copyFrom);
9161
9502
  ok(
9162
9503
  args.json === true,
9163
9504
  { appId: t.appId, config: name, created: true, ...copyFrom ? { copyFrom } : {} },
@@ -9168,7 +9509,7 @@ var configsCreate = defineCommand9({
9168
9509
  }
9169
9510
  }
9170
9511
  });
9171
- var configsDelete = defineCommand9({
9512
+ var configsDelete = defineCommand10({
9172
9513
  meta: { name: "delete", description: "Delete a config and all of its secrets" },
9173
9514
  args: {
9174
9515
  ...COMMON_ARGS,
@@ -9178,7 +9519,7 @@ var configsDelete = defineCommand9({
9178
9519
  try {
9179
9520
  const t = await resolveTarget(args);
9180
9521
  const name = validateConfigName(args.name);
9181
- await deleteConfig(DEPLOY_URL10, t.token, t.appId, name);
9522
+ await deleteConfig(DEPLOY_URL11, t.token, t.appId, name);
9182
9523
  ok(
9183
9524
  args.json === true,
9184
9525
  { appId: t.appId, config: name, deleted: true },
@@ -9189,16 +9530,16 @@ var configsDelete = defineCommand9({
9189
9530
  }
9190
9531
  }
9191
9532
  });
9192
- var configs = defineCommand9({
9533
+ var configs = defineCommand10({
9193
9534
  meta: { name: "configs", description: "Manage the app\u2019s secrets configs" },
9194
9535
  subCommands: { list: configsList, create: configsCreate, delete: configsDelete }
9195
9536
  });
9196
- var secrets_default = defineCommand9({
9537
+ var secrets_default = defineCommand10({
9197
9538
  meta: { name: "secrets", description: "Manage the app\u2019s secrets store" },
9198
9539
  subCommands: {
9199
- list: list5,
9540
+ list: list6,
9200
9541
  set,
9201
- get,
9542
+ get: get2,
9202
9543
  delete: del2,
9203
9544
  upload,
9204
9545
  download,
@@ -9270,9 +9611,9 @@ var init_default = defineDeepspaceCommand({
9270
9611
  });
9271
9612
 
9272
9613
  // src/cli/commands/status.ts
9273
- import { defineCommand as defineCommand10 } from "citty";
9614
+ import { defineCommand as defineCommand11 } from "citty";
9274
9615
  import * as p17 from "@clack/prompts";
9275
- import { existsSync as existsSync22, readFileSync as readFileSync25 } from "fs";
9616
+ import { existsSync as existsSync23, readFileSync as readFileSync25 } from "fs";
9276
9617
  function timeAgo(iso) {
9277
9618
  const ms = Date.now() - Date.parse(iso);
9278
9619
  if (!Number.isFinite(ms) || ms < 0) return iso;
@@ -9305,7 +9646,7 @@ function resolveStatusApp(appDir, wranglerEnv) {
9305
9646
  if (!resolved.ok) throw new InputError(resolved.reason, "invalid_env");
9306
9647
  return { appName: resolved.name, appId: readAppId(appDir, wranglerEnv) };
9307
9648
  }
9308
- var status_default = defineCommand10({
9649
+ var status_default = defineCommand11({
9309
9650
  meta: {
9310
9651
  name: "status",
9311
9652
  description: "One screenful of current state: session, app, workspace, and live release"
@@ -9357,7 +9698,7 @@ var status_default = defineCommand10({
9357
9698
  json.services = urls;
9358
9699
  if (urlOverrides.length > 0) json.urlOverrides = urlOverrides;
9359
9700
  }
9360
- facts.loggedIn = existsSync22(SESSION_PATH);
9701
+ facts.loggedIn = existsSync23(SESSION_PATH);
9361
9702
  let userId = null;
9362
9703
  if (!facts.loggedIn) {
9363
9704
  lines.push(["Session", "not logged in"]);
@@ -9596,8 +9937,8 @@ var push_default = defineDeepspaceCommand({
9596
9937
  const branchArg = args.branch === void 0 ? void 0 : String(args.branch);
9597
9938
  const appArg = typeof args.app === "string" ? args.app : void 0;
9598
9939
  const force = args.force === true;
9940
+ const appDir = findAppDir();
9599
9941
  try {
9600
- const appDir = findAppDir();
9601
9942
  if (!appDir)
9602
9943
  throw new Refusal(
9603
9944
  "No wrangler.toml found \u2014 run from inside an app directory.",
@@ -9726,7 +10067,7 @@ var push_default = defineDeepspaceCommand({
9726
10067
  }
9727
10068
  const recoverable = isRecoverablePushFailure(result.status);
9728
10069
  const rejectReason = result.reason ?? result.summary;
9729
- const errorMsg = ok2 ? null : result.status === "ref_conflict" ? `The cloud repo's ${branch} moved while you worked (${rejectReason}). Run \`deepspace pull --app ${shQuote(appId)} --branch ${shQuote(branch)}\` to integrate, then push again.` : result.status === "rejected" ? `The cloud repo rejected ${branch}: ${rejectReason}.` + (OVERSIZED_PUSH_RE.test(rejectReason) ? ` ${oversizedPushFix(appDir)}` : "") : `The cloud repo's ${branch} has commit(s) your local ${branch} doesn't. Run \`deepspace pull --app ${shQuote(appId)} --branch ${shQuote(branch)}\` and merge (or rebase), then push. Avoid --force here \u2014 it's guarded and will refuse rather than silently drop a peer's commits.`;
10070
+ const errorMsg = ok2 ? null : result.status === "ref_conflict" ? `The cloud repo's ${branch} moved while you worked (${rejectReason}). Run \`deepspace pull --app ${shQuote(appId)} --branch ${shQuote(branch)}\` to integrate, then push again.` : result.status === "rejected" ? pushFailureMessage(`The cloud repo rejected ${branch}`, result, appDir ?? void 0) : `The cloud repo's ${branch} has commit(s) your local ${branch} doesn't. Run \`deepspace pull --app ${shQuote(appId)} --branch ${shQuote(branch)}\` and merge (or rebase), then push. Avoid --force here \u2014 it's guarded and will refuse rather than silently drop a peer's commits.`;
9730
10071
  if (!args.json && ok2) p18.log.info(`App: ${appId}`);
9731
10072
  const action = recoverable ? pullRecoveryAction(pullRecoveryCwd, appId, branch) : void 0;
9732
10073
  if (!ok2) {
@@ -9739,7 +10080,7 @@ var push_default = defineDeepspaceCommand({
9739
10080
  return { data: { status: result.status, appId, branch, oid: tipOid } };
9740
10081
  } catch (err) {
9741
10082
  if (err instanceof Refusal) throw err;
9742
- const transportFailure = classifyPushTransportFailure(err);
10083
+ const transportFailure = classifyPushTransportFailure(err, appDir ?? void 0);
9743
10084
  if (transportFailure) {
9744
10085
  throw new Refusal(transportFailure.error, transportFailure.code);
9745
10086
  }
@@ -10031,14 +10372,14 @@ var pull_default = defineDeepspaceCommand({
10031
10372
 
10032
10373
  // src/cli/commands/clone.ts
10033
10374
  import * as p20 from "@clack/prompts";
10034
- import { existsSync as existsSync23, readdirSync as readdirSync6, rmSync as rmSync5 } from "fs";
10035
- import { resolve as resolve15, basename as basename3, join as join21 } from "path";
10375
+ import { existsSync as existsSync24, readdirSync as readdirSync6, rmSync as rmSync5 } from "fs";
10376
+ import { resolve as resolve16, basename as basename5, join as join21 } from "path";
10036
10377
  function cleanFailedCloneDir(dir) {
10037
10378
  let entries;
10038
10379
  try {
10039
10380
  entries = readdirSync6(dir);
10040
10381
  } catch {
10041
- return existsSync23(dir) ? ["<unreadable>"] : [];
10382
+ return existsSync24(dir) ? ["<unreadable>"] : [];
10042
10383
  }
10043
10384
  if (entries.length === 1 && entries[0] === ".git") {
10044
10385
  try {
@@ -10051,7 +10392,7 @@ function cleanFailedCloneDir(dir) {
10051
10392
  return entries;
10052
10393
  }
10053
10394
  function validateCloneTarget(dir) {
10054
- if (!existsSync23(dir)) return false;
10395
+ if (!existsSync24(dir)) return false;
10055
10396
  let entries;
10056
10397
  try {
10057
10398
  entries = readdirSync6(dir);
@@ -10086,7 +10427,7 @@ var clone_default = defineDeepspaceCommand({
10086
10427
  async run({ args }) {
10087
10428
  assertAppTargetResolvable(String(args.app));
10088
10429
  const selector = String(args.app).trim();
10089
- const dir = resolve15(String(args.dir ?? selector));
10430
+ const dir = resolve16(String(args.dir ?? selector));
10090
10431
  const dirPreexisted = validateCloneTarget(dir);
10091
10432
  const spinner2 = args.json ? null : createSpinner();
10092
10433
  spinner2?.start(`Resolving ${selector}\u2026`);
@@ -10129,7 +10470,7 @@ var clone_default = defineDeepspaceCommand({
10129
10470
  const head = resolveCommit(dir, "HEAD");
10130
10471
  const branch = currentBranch(dir);
10131
10472
  spinner2?.stop(
10132
- `Cloned ${selector} (${branches.length} ${branches.length === 1 ? "branch" : "branches"}) into ${basename3(dir)}/ at ${head?.slice(0, 10) ?? "(unborn)"}.`
10473
+ `Cloned ${selector} (${branches.length} ${branches.length === 1 ? "branch" : "branches"}) into ${basename5(dir)}/ at ${head?.slice(0, 10) ?? "(unborn)"}.`
10133
10474
  );
10134
10475
  if (!args.json && !branch) {
10135
10476
  p20.log.warn("HEAD is detached \u2014 check `git branch -a`.");
@@ -10152,7 +10493,7 @@ var clone_default = defineDeepspaceCommand({
10152
10493
 
10153
10494
  // src/cli/commands/releases.ts
10154
10495
  import * as p21 from "@clack/prompts";
10155
- var DEPLOY_URL11 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
10496
+ var DEPLOY_URL12 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
10156
10497
  var releases_default = defineDeepspaceCommand({
10157
10498
  meta: {
10158
10499
  name: "releases",
@@ -10187,8 +10528,8 @@ var releases_default = defineDeepspaceCommand({
10187
10528
  const spinner2 = args.json ? null : createSpinner();
10188
10529
  spinner2?.start("Loading release history\u2026");
10189
10530
  const token = await ensureToken();
10190
- const appId = await resolveAppTarget(DEPLOY_URL11, token, appArg, { wranglerEnv: envArg });
10191
- const { releases } = await repoApi(DEPLOY_URL11, token, appId).listReleases(limit);
10531
+ const appId = await resolveAppTarget(DEPLOY_URL12, token, appArg, { wranglerEnv: envArg });
10532
+ const { releases } = await repoApi(DEPLOY_URL12, token, appId).listReleases(limit);
10192
10533
  spinner2?.stop(`Loaded ${releases.length} ${releases.length === 1 ? "release" : "releases"}.`);
10193
10534
  if (!args.json) {
10194
10535
  if (releases.length === 0) {
@@ -10220,7 +10561,7 @@ Retry in a moment, or re-run with --allow-do-deletion to accept the risk.`,
10220
10561
  "do_guard_unavailable"
10221
10562
  );
10222
10563
  }
10223
- var DEPLOY_URL12 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
10564
+ var DEPLOY_URL13 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
10224
10565
  function pickPreviousRelease(releases) {
10225
10566
  if (releases.length === 0) {
10226
10567
  return { error: "No releases recorded yet \u2014 nothing to roll back to.", code: "no_releases" };
@@ -10282,8 +10623,8 @@ var rollback_default = defineDeepspaceCommand({
10282
10623
  const spinner2 = args.json ? null : createSpinner();
10283
10624
  spinner2?.start("Preparing rollback\u2026");
10284
10625
  const token = await ensureToken();
10285
- const appId = await resolveAppTarget(DEPLOY_URL12, token, appArg, { wranglerEnv: envArg });
10286
- const api4 = repoApi(DEPLOY_URL12, token, appId);
10626
+ const appId = await resolveAppTarget(DEPLOY_URL13, token, appArg, { wranglerEnv: envArg });
10627
+ const api4 = repoApi(DEPLOY_URL13, token, appId);
10287
10628
  let releaseId = args.release ? String(args.release).trim() : "";
10288
10629
  if (!releaseId) {
10289
10630
  const { releases } = await api4.listReleases(2);
@@ -10294,7 +10635,7 @@ var rollback_default = defineDeepspaceCommand({
10294
10635
  spinner2?.message(`Rolling back to ${releaseId}\u2026`);
10295
10636
  let result;
10296
10637
  try {
10297
- result = await apiFetch(DEPLOY_URL12, token, `/api/deploy/${appId}/rollback`, {
10638
+ result = await apiFetch(DEPLOY_URL13, token, `/api/deploy/${appId}/rollback`, {
10298
10639
  method: "POST",
10299
10640
  body: JSON.stringify({
10300
10641
  releaseId,
@@ -10355,11 +10696,11 @@ Re-run with --allow-do-deletion to accept the data loss.`,
10355
10696
  });
10356
10697
 
10357
10698
  // src/cli/commands/workspace.ts
10358
- import { defineCommand as defineCommand11 } from "citty";
10699
+ import { defineCommand as defineCommand12 } from "citty";
10359
10700
 
10360
10701
  // src/cli/commands/workspace/new.ts
10361
10702
  import * as p25 from "@clack/prompts";
10362
- import { isAbsolute as isAbsolute6, resolve as resolve16 } from "path";
10703
+ import { isAbsolute as isAbsolute6, resolve as resolve17 } from "path";
10363
10704
 
10364
10705
  // src/cli/commands/workspace/analysis.ts
10365
10706
  import * as p23 from "@clack/prompts";
@@ -10484,7 +10825,7 @@ function printOverlaps(overlaps, json) {
10484
10825
 
10485
10826
  // src/cli/commands/workspace/local.ts
10486
10827
  import * as p24 from "@clack/prompts";
10487
- import { mkdirSync as mkdirSync7, readFileSync as readFileSync26, realpathSync as realpathSync5, renameSync as renameSync2, statSync, writeFileSync as writeFileSync9 } from "fs";
10828
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync26, realpathSync as realpathSync5, renameSync as renameSync2, statSync as statSync3, writeFileSync as writeFileSync9 } from "fs";
10488
10829
  import { dirname as dirname9, isAbsolute as isAbsolute5, join as join22, relative as relative2, sep as sep2 } from "path";
10489
10830
  var WORKSPACE_MARKER = "deepspace-workspace.json";
10490
10831
  function excludeWorktreeDir(appDir, dir) {
@@ -10573,7 +10914,7 @@ function materializeWorkspaceWorktree(appDir, worktreeRoot, branch, startPoint,
10573
10914
  function inOwnLinkedWorktree(appDir, id) {
10574
10915
  if (workspaceIdFromBranch(currentBranch(appDir)) !== id) return false;
10575
10916
  try {
10576
- return statSync(join22(repoToplevel(appDir), ".git")).isFile();
10917
+ return statSync3(join22(repoToplevel(appDir), ".git")).isFile();
10577
10918
  } catch {
10578
10919
  return false;
10579
10920
  }
@@ -10919,7 +11260,7 @@ var newWorkspaceCommand = defineDeepspaceCommand({
10919
11260
  idempotencyKey: mintIdempotencyKey()
10920
11261
  });
10921
11262
  const dirInput = typeof args.dir === "string" ? args.dir.trim() : "";
10922
- const worktreeRoot = dirInput ? isAbsolute6(dirInput) ? dirInput : resolve16(appDir, dirInput) : defaultWorkspaceRoot(appDir, id);
11263
+ const worktreeRoot = dirInput ? isAbsolute6(dirInput) ? dirInput : resolve17(appDir, dirInput) : defaultWorkspaceRoot(appDir, id);
10923
11264
  const dir = appDirInWorktree(appDir, worktreeRoot);
10924
11265
  try {
10925
11266
  materializeWorkspaceWorktree(appDir, worktreeRoot, branch, baseOid, id);
@@ -10957,8 +11298,8 @@ var newWorkspaceCommand = defineDeepspaceCommand({
10957
11298
 
10958
11299
  // src/cli/commands/workspace/attach.ts
10959
11300
  import * as p26 from "@clack/prompts";
10960
- import { existsSync as existsSync24, readdirSync as readdirSync7, rmSync as rmSync6 } from "fs";
10961
- import { isAbsolute as isAbsolute7, resolve as resolve17 } from "path";
11301
+ import { existsSync as existsSync25, readdirSync as readdirSync7, rmSync as rmSync6 } from "fs";
11302
+ import { isAbsolute as isAbsolute7, resolve as resolve18 } from "path";
10962
11303
  function finishedWorkspaceMessage(id, view) {
10963
11304
  const workspace = view.workspace;
10964
11305
  if (workspace.status === "landed") {
@@ -10967,7 +11308,7 @@ function finishedWorkspaceMessage(id, view) {
10967
11308
  return `Workspace ${id} is ${workspace.status} \u2014 its ref is retained briefly (fetch ${workspace.ref} to inspect), but attach only works on active workspaces.`;
10968
11309
  }
10969
11310
  function cleanFailedFreshAttachDir(dir) {
10970
- if (!existsSync24(dir)) return [];
11311
+ if (!existsSync25(dir)) return [];
10971
11312
  let entries;
10972
11313
  try {
10973
11314
  entries = readdirSync7(dir);
@@ -11043,7 +11384,7 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
11043
11384
  );
11044
11385
  }
11045
11386
  const requestedDir = typeof args.dir === "string" ? args.dir.trim() : "";
11046
- const worktreeRoot = requestedDir ? isAbsolute7(requestedDir) ? requestedDir : resolve17(appDir, requestedDir) : defaultWorkspaceRoot(appDir, id);
11387
+ const worktreeRoot = requestedDir ? isAbsolute7(requestedDir) ? requestedDir : resolve18(appDir, requestedDir) : defaultWorkspaceRoot(appDir, id);
11047
11388
  const dir2 = appDirInWorktree(appDir, worktreeRoot);
11048
11389
  if (resolveCommit(appDir, `refs/heads/${branch}`)) {
11049
11390
  throw new Refusal(
@@ -11082,11 +11423,11 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
11082
11423
  extra: { status: view.workspace.status }
11083
11424
  });
11084
11425
  }
11085
- const dir = resolve17(
11426
+ const dir = resolve18(
11086
11427
  process.cwd(),
11087
11428
  (typeof args.dir === "string" ? args.dir.trim() : "") || `ws-${id.slice(3).toLowerCase()}`
11088
11429
  );
11089
- if (existsSync24(dir)) {
11430
+ if (existsSync25(dir)) {
11090
11431
  spinner2?.stop("Target exists.");
11091
11432
  throw new Refusal(`${dir} already exists \u2014 pass a different directory.`, "dir_exists");
11092
11433
  }
@@ -11830,7 +12171,7 @@ var dropWorkspaceCommand = defineDeepspaceCommand({
11830
12171
  });
11831
12172
 
11832
12173
  // src/cli/commands/workspace.ts
11833
- var workspace_default = defineCommand11({
12174
+ var workspace_default = defineCommand12({
11834
12175
  meta: {
11835
12176
  name: "workspace",
11836
12177
  description: "Durable parallel-agent workspaces (new/attach/sync/list/status/land/drop)"
@@ -12001,7 +12342,7 @@ var activity_default = defineDeepspaceCommand({
12001
12342
  });
12002
12343
 
12003
12344
  // src/cli/commands/git-credential.ts
12004
- import { defineCommand as defineCommand12 } from "citty";
12345
+ import { defineCommand as defineCommand13 } from "citty";
12005
12346
  function parseFields(input) {
12006
12347
  const fields = /* @__PURE__ */ new Map();
12007
12348
  for (const line of input.split("\n")) {
@@ -12050,7 +12391,7 @@ async function readStdin() {
12050
12391
  for await (const chunk of process.stdin) data += chunk;
12051
12392
  return data;
12052
12393
  }
12053
- var git_credential_default = defineCommand12({
12394
+ var git_credential_default = defineCommand13({
12054
12395
  meta: {
12055
12396
  name: "git-credential",
12056
12397
  description: "Git credential helper (installed automatically; not for direct use)"
@@ -12168,8 +12509,8 @@ function reportSource(appId, state, json) {
12168
12509
  async function moveToGitHub(input) {
12169
12510
  const { appDir, appId, token, deployUrl, state, remoteArg } = input;
12170
12511
  const selected = requireGitHubRemote(appDir, remoteArg);
12171
- const target = { provider: "github", repository: selected.repository };
12172
- if (sameSource(state.source, target)) return { source: target, revision: state.revision };
12512
+ const target2 = { provider: "github", repository: selected.repository };
12513
+ if (sameSource(state.source, target2)) return { source: target2, revision: state.revision };
12173
12514
  if (state.source?.provider === "deepspace") {
12174
12515
  requireLocalHead(appDir);
12175
12516
  const api4 = repoApi(deployUrl, token, appId);
@@ -12225,7 +12566,7 @@ async function moveToGitHub(input) {
12225
12566
  );
12226
12567
  }
12227
12568
  return setAppSource(deployUrl, token, appId, {
12228
- source: target,
12569
+ source: target2,
12229
12570
  expectedRevision: state.revision,
12230
12571
  refs: wanted,
12231
12572
  ...releaseExpectation(latest)
@@ -12233,17 +12574,17 @@ async function moveToGitHub(input) {
12233
12574
  }
12234
12575
  requirePublishedHead(appDir, selected, appId);
12235
12576
  return setAppSource(deployUrl, token, appId, {
12236
- source: target,
12577
+ source: target2,
12237
12578
  expectedRevision: state.revision
12238
12579
  });
12239
12580
  }
12240
12581
  async function moveToDeepSpace(input) {
12241
12582
  const { appDir, appId, token, deployUrl, state, remoteArg } = input;
12242
- const target = { provider: "deepspace" };
12243
- if (sameSource(state.source, target)) return { source: target, revision: state.revision };
12583
+ const target2 = { provider: "deepspace" };
12584
+ if (sameSource(state.source, target2)) return { source: target2, revision: state.revision };
12244
12585
  if (state.source?.provider !== "github") {
12245
12586
  return setAppSource(deployUrl, token, appId, {
12246
- source: target,
12587
+ source: target2,
12247
12588
  expectedRevision: state.revision
12248
12589
  });
12249
12590
  }
@@ -12277,7 +12618,7 @@ async function moveToDeepSpace(input) {
12277
12618
  );
12278
12619
  }
12279
12620
  return setAppSource(deployUrl, token, appId, {
12280
- source: target,
12621
+ source: target2,
12281
12622
  expectedRevision: state.revision,
12282
12623
  branch,
12283
12624
  commitOid: oid,
@@ -12905,10 +13246,10 @@ function reportMigration(migration, json) {
12905
13246
  // src/cli/cli.ts
12906
13247
  var __dirname = dirname10(fileURLToPath3(import.meta.url));
12907
13248
  var pkg = JSON.parse(readFileSync28(join24(__dirname, "..", "package.json"), "utf-8"));
12908
- if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync25("/etc/ssl/certs/ca-certificates.crt")) {
13249
+ if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync26("/etc/ssl/certs/ca-certificates.crt")) {
12909
13250
  process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
12910
13251
  }
12911
- var create2 = defineCommand13({
13252
+ var create2 = defineCommand14({
12912
13253
  meta: {
12913
13254
  name: "create",
12914
13255
  description: "Create a new DeepSpace app (runs create-deepspace; all flags are forwarded)"
@@ -12947,19 +13288,20 @@ var create2 = defineCommand13({
12947
13288
  }
12948
13289
  });
12949
13290
  var rename = (cmd, name) => ({ ...cmd, meta: { ...cmd.meta ?? {}, name } });
12950
- var auth = defineCommand13({
13291
+ var auth = defineCommand14({
12951
13292
  meta: { name: "auth", description: "Session: login, logout, whoami" },
12952
13293
  subCommands: { login: login_default, logout: logout_default, whoami: whoami_default }
12953
13294
  });
12954
- var app = defineCommand13({
13295
+ var app = defineCommand14({
12955
13296
  meta: {
12956
13297
  name: "app",
12957
- description: "The app itself: create, init, list, source, migrate, undeploy, transfer, collaborators, domain, usage"
13298
+ description: "The app itself: create, init, list, files, source, migrate, undeploy, transfer, collaborators, domain, usage"
12958
13299
  },
12959
13300
  subCommands: {
12960
13301
  create: create2,
12961
13302
  init: init_default,
12962
13303
  list: rename(apps_default, "list"),
13304
+ files: app_files_default,
12963
13305
  undeploy: undeploy_default,
12964
13306
  transfer: transfer_default,
12965
13307
  collaborators: collaborators_default,
@@ -12969,11 +13311,11 @@ var app = defineCommand13({
12969
13311
  migrate: migrate_default
12970
13312
  }
12971
13313
  });
12972
- var devGroup = defineCommand13({
13314
+ var devGroup = defineCommand14({
12973
13315
  meta: { name: "dev", description: "Local development" },
12974
13316
  subCommands: { start: rename(dev_default, "start"), kill: kill_default }
12975
13317
  });
12976
- var testGroup = defineCommand13({
13318
+ var testGroup = defineCommand14({
12977
13319
  meta: { name: "test", description: "App tests and test tooling" },
12978
13320
  subCommands: {
12979
13321
  run: rename(test_default, "run"),
@@ -12981,7 +13323,7 @@ var testGroup = defineCommand13({
12981
13323
  accounts: rename(test_accounts_default, "accounts")
12982
13324
  }
12983
13325
  });
12984
- var main = defineCommand13({
13326
+ var main = defineCommand14({
12985
13327
  meta: {
12986
13328
  name: "deepspace",
12987
13329
  version: pkg.version,