deepspace 0.10.1 → 0.10.3

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 (75) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/chunk-ZJ43EK3N.js +1751 -0
  3. package/dist/chunk-ZJ43EK3N.js.map +1 -0
  4. package/dist/cli.js +848 -650
  5. package/dist/cli.js.map +1 -1
  6. package/dist/documentation-client-core.d.ts +18 -0
  7. package/dist/documentation-client-core.js +395 -0
  8. package/dist/documentation-client-core.js.map +1 -0
  9. package/dist/documentation-default-renderer.cjs +124 -0
  10. package/dist/documentation-react.d.ts +2 -0
  11. package/dist/documentation-react.js +35 -0
  12. package/dist/documentation-react.js.map +1 -0
  13. package/dist/documentation-runtime.js +71 -0
  14. package/dist/documentation-server-core.d.ts +11 -0
  15. package/dist/documentation-server-core.js +48 -0
  16. package/dist/documentation-server-core.js.map +1 -0
  17. package/dist/documentation.d.ts +367 -0
  18. package/dist/documentation.js +4122 -0
  19. package/dist/documentation.js.map +1 -0
  20. package/dist/feature-installer.js +145 -0
  21. package/dist/feature-installer.js.map +1 -1
  22. package/dist/fonts/geist-mono-OFL.txt +93 -0
  23. package/dist/fonts/geist-mono-variable.woff2 +0 -0
  24. package/dist/fonts/inter-OFL.txt +92 -0
  25. package/dist/fonts/inter-variable.woff2 +0 -0
  26. package/dist/index.d.ts +177 -1
  27. package/dist/index.js +133 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/public-BGRX_Cjd.d.ts +234 -0
  30. package/dist/schema.d.ts +186 -0
  31. package/dist/schema.js +610 -0
  32. package/dist/schema.js.map +1 -0
  33. package/dist/server.d.ts +83 -4
  34. package/dist/server.js +296 -1
  35. package/dist/server.js.map +1 -1
  36. package/dist/testing.d.ts +5 -8
  37. package/dist/testing.js +48 -14
  38. package/dist/testing.js.map +1 -1
  39. package/dist/worker.d.ts +249 -5
  40. package/dist/worker.js +1438 -19
  41. package/dist/worker.js.map +1 -1
  42. package/features/admin-page/src/admin-schema.ts +1 -1
  43. package/features/ai-chat/feature.json +2 -2
  44. package/features/ai-chat/src/ChatPanel.tsx +6 -11
  45. package/features/ai-chat/src/ai-chat-schema.ts +1 -2
  46. package/features/canvas/src/canvas-schema.ts +1 -1
  47. package/features/documentation/content/index.mdx +14 -0
  48. package/features/documentation/content/quickstart.mdx +12 -0
  49. package/features/documentation/documentation.json +18 -0
  50. package/features/documentation/feature.json +68 -0
  51. package/features/{docs → documents}/feature.json +27 -27
  52. package/features/{docs/src/DocsEditorPage.tsx → documents/src/DocumentsEditorPage.tsx} +74 -74
  53. package/features/{docs/src/DocsListPage.tsx → documents/src/DocumentsListPage.tsx} +102 -102
  54. package/features/{docs/src/DocsOutlinePanel.tsx → documents/src/DocumentsOutlinePanel.tsx} +9 -9
  55. package/features/{docs/src/DocsPresence.tsx → documents/src/DocumentsPresence.tsx} +18 -18
  56. package/features/{docs → documents}/src/InviteDialog.tsx +21 -21
  57. package/features/{docs → documents}/src/LibrarySidebar.tsx +25 -25
  58. package/features/documents/src/__tests__/documents-presence-access.test.ts +71 -0
  59. package/features/{docs/src/docs-library-types.ts → documents/src/documents-library-types.ts} +3 -3
  60. package/features/{docs/src/docs-schema.ts → documents/src/documents-schema.ts} +6 -6
  61. package/features/{docs/src/docs-ui.css → documents/src/documents-ui.css} +89 -89
  62. package/features/{docs → documents}/src/editor/DocEditorSurface.tsx +11 -11
  63. package/features/{docs/src/editor/DocsTiptapToolbar.tsx → documents/src/editor/DocumentsTiptapToolbar.tsx} +21 -21
  64. package/features/{docs → documents}/src/editor/useDocEditor.ts +4 -4
  65. package/features/{docs/src/use-docs-presence-access.ts → documents/src/use-documents-presence-access.ts} +51 -51
  66. package/features/file-attachments/src/attachments-schema.ts +1 -1
  67. package/features/items/src/items-schema.ts +1 -1
  68. package/features/leaderboard/src/leaderboard-schema.ts +1 -1
  69. package/features/messaging/src/ChatMultiPage.tsx +1 -1
  70. package/features/messaging/src/ChatPage.tsx +1 -1
  71. package/features/messaging/src/messaging-schema.ts +2 -2
  72. package/features/tasks/src/tasks-schema.ts +1 -1
  73. package/features/testing/src/test-schema.ts +2 -2
  74. package/package.json +26 -1
  75. package/features/docs/src/__tests__/docs-presence-access.test.ts +0 -71
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 defineCommand14, runMain, runCommand } from "citty";
4
+ import { defineCommand as defineCommand13, runMain, runCommand } from "citty";
5
5
  import { sync as spawnSync8 } from "cross-spawn";
6
- import { readFileSync as readFileSync26, existsSync as existsSync25 } from "fs";
6
+ import { readFileSync as readFileSync27, existsSync as existsSync25 } 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((resolve19) => setTimeout(resolve19, ms));
712
+ return new Promise((resolve18) => setTimeout(resolve18, ms));
713
713
  }
714
714
  function base64url(bytes) {
715
715
  return bytes.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
@@ -1267,7 +1267,7 @@ function upsertWorktreeLaunchConfig(mainRepoRoot, worktreeName, appDir, opts) {
1267
1267
  let port = opts.port;
1268
1268
  if (opts.probePort) {
1269
1269
  const taken = new Set(
1270
- config.configurations.map((c) => Number(c?.port)).filter((p35) => Number.isInteger(p35) && p35 > 0)
1270
+ config.configurations.map((c) => Number(c?.port)).filter((p34) => Number.isInteger(p34) && p34 > 0)
1271
1271
  );
1272
1272
  for (let i = 0; i < WORKTREE_PORT_RANGE && taken.has(port); i++) {
1273
1273
  port = WORKTREE_PORT_MIN + (port - WORKTREE_PORT_MIN + 1) % WORKTREE_PORT_RANGE;
@@ -1372,7 +1372,8 @@ var ALLOWED_BINDING_TYPES = /* @__PURE__ */ new Set([
1372
1372
  "queue",
1373
1373
  "browser_rendering",
1374
1374
  "analytics_engine",
1375
- "hyperdrive"
1375
+ "hyperdrive",
1376
+ "ratelimit"
1376
1377
  ]);
1377
1378
  var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
1378
1379
  "ASSETS",
@@ -1380,6 +1381,7 @@ var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
1380
1381
  "API_WORKER",
1381
1382
  "DEEPSPACE_APP_ID",
1382
1383
  "DEEPSPACE_RESOURCE_ID",
1384
+ "DEEPSPACE_RELEASE_OPERATION_ID",
1383
1385
  "APP_NAME",
1384
1386
  "OWNER_USER_ID",
1385
1387
  "AUTH_JWT_PUBLIC_KEY",
@@ -1470,6 +1472,17 @@ function requiredFieldError(b) {
1470
1472
  return `hyperdrive binding "${b.name}": auto-provisioning not yet supported (provide a real id)`;
1471
1473
  }
1472
1474
  return null;
1475
+ case "ratelimit":
1476
+ if (!/^\d+$/u.test(b.namespace_id) || Number(b.namespace_id) <= 0) {
1477
+ return `ratelimit binding "${b.name}" requires a positive integer namespace_id string`;
1478
+ }
1479
+ if (!Number.isInteger(b.simple?.limit) || b.simple.limit <= 0) {
1480
+ return `ratelimit binding "${b.name}" requires simple.limit (positive integer)`;
1481
+ }
1482
+ if (b.simple.period !== 10 && b.simple.period !== 60) {
1483
+ return `ratelimit binding "${b.name}" requires simple.period (10 or 60)`;
1484
+ }
1485
+ return null;
1473
1486
  case "ai":
1474
1487
  case "browser_rendering":
1475
1488
  case "analytics_engine":
@@ -1547,6 +1560,15 @@ function bindingManifestFromOutputConfig(outputConfig) {
1547
1560
  const id = pluckString(h, "id");
1548
1561
  if (name && id) out.push({ type: "hyperdrive", name, id });
1549
1562
  }
1563
+ for (const r of asObjectArray(outputConfig.ratelimits)) {
1564
+ const name = pluckString(r, "name");
1565
+ const namespace_id = pluckString(r, "namespace_id");
1566
+ const simple = isObject(r.simple) ? r.simple : null;
1567
+ const limit = pluckNumber(simple, "limit");
1568
+ const period = pluckNumber(simple, "period");
1569
+ if (!name || !namespace_id || limit === null || period !== 10 && period !== 60) continue;
1570
+ out.push({ type: "ratelimit", name, namespace_id, simple: { limit, period } });
1571
+ }
1550
1572
  return out;
1551
1573
  }
1552
1574
  function isObject(v) {
@@ -1816,7 +1838,8 @@ async function refreshSecretsCache(deployUrl, token, appId, wranglerEnv) {
1816
1838
  return {
1817
1839
  pulled,
1818
1840
  rendered: renderSecretsCache(pulled.values, pulled),
1819
- summary: `Secrets: ${pulled.configName} (${count} secret${count === 1 ? "" : "s"})`
1841
+ // An empty store is the common case and not worth a status line.
1842
+ summary: count ? `Secrets: ${pulled.configName} (${count} secret${count === 1 ? "" : "s"})` : null
1820
1843
  };
1821
1844
  }
1822
1845
 
@@ -1897,6 +1920,7 @@ var NON_INHERITABLE_ENV_KEYS = /* @__PURE__ */ new Set([
1897
1920
  "r2_buckets",
1898
1921
  "ai_search_namespaces",
1899
1922
  "ai_search",
1923
+ "ratelimits",
1900
1924
  "vectorize",
1901
1925
  "services",
1902
1926
  "queues",
@@ -1998,6 +2022,13 @@ function flattenWranglerEnvConfig(config, envName) {
1998
2022
  for (const [key, value] of Object.entries(envBlock)) {
1999
2023
  flattened[key] = value;
2000
2024
  }
2025
+ if (!("durable_objects" in envBlock) && config.durable_objects) {
2026
+ flattened.durable_objects = config.durable_objects;
2027
+ }
2028
+ const envVars = asTomlRecord(envBlock.vars) ?? {};
2029
+ if (!("APP_NAME" in envVars) && typeof envBlock.name === "string") {
2030
+ flattened.vars = { ...envVars, APP_NAME: envBlock.name };
2031
+ }
2001
2032
  return flattened;
2002
2033
  }
2003
2034
  function asTomlRecord(value) {
@@ -2259,18 +2290,18 @@ function ensureInstallReady(appDir) {
2259
2290
  const logPath = join9(sentinelDir, "install.log");
2260
2291
  if (existsSync9(errPath)) {
2261
2292
  const detail = readFileSync11(errPath, "utf-8").trimEnd();
2262
- const log29 = existsSync9(logPath) ? ` Full log: ${logPath}.` : "";
2293
+ const log28 = existsSync9(logPath) ? ` Full log: ${logPath}.` : "";
2263
2294
  throw new Refusal(
2264
- `Dependency install failed: ${detail}.${log29} Run \`npm install\` (or \`bun install\`) manually, then retry.`,
2295
+ `Dependency install failed: ${detail}.${log28} Run \`npm install\` (or \`bun install\`) manually, then retry.`,
2265
2296
  "install_failed",
2266
2297
  { action: { cwd: appDir, argv: [detectPackageManager(appDir), "install"] } }
2267
2298
  );
2268
2299
  }
2269
2300
  if (existsSync9(startedPath) && !existsSync9(donePath)) {
2270
2301
  if (!installWorkerAlive(sentinelDir)) {
2271
- const log29 = existsSync9(logPath) ? ` See what happened: ${logPath}.` : "";
2302
+ const log28 = existsSync9(logPath) ? ` See what happened: ${logPath}.` : "";
2272
2303
  throw new Refusal(
2273
- `The dependency install is no longer running and never finished.${log29} Run \`npm install\` (or \`bun install\`) manually, then retry.`,
2304
+ `The dependency install is no longer running and never finished.${log28} Run \`npm install\` (or \`bun install\`) manually, then retry.`,
2274
2305
  "install_failed",
2275
2306
  { action: { cwd: appDir, argv: [detectPackageManager(appDir), "install"] } }
2276
2307
  );
@@ -2419,7 +2450,7 @@ function lintSchema(schema) {
2419
2450
  const offendingRoles = Object.entries(schema.permissions).filter(([, perms]) => perms.read === true).map(([role]) => role);
2420
2451
  if (offendingRoles.length > 0) {
2421
2452
  const usesAware = Object.values(schema.permissions).some(
2422
- (p35) => typeof p35.read === "string" && VISIBILITY_AWARE.includes(p35.read)
2453
+ (p34) => typeof p34.read === "string" && VISIBILITY_AWARE.includes(p34.read)
2423
2454
  );
2424
2455
  if (!usesAware) {
2425
2456
  warnings.push(
@@ -2503,10 +2534,10 @@ function formatSchemaLintFindings(findings) {
2503
2534
  import { createServer } from "net";
2504
2535
  var DEFAULT_PORT = 5173;
2505
2536
  function checkPortAvailable(port, host = "0.0.0.0") {
2506
- return new Promise((resolve19) => {
2537
+ return new Promise((resolve18) => {
2507
2538
  const srv = createServer();
2508
- srv.once("error", () => resolve19(false));
2509
- srv.once("listening", () => srv.close(() => resolve19(true)));
2539
+ srv.once("error", () => resolve18(false));
2540
+ srv.once("listening", () => srv.close(() => resolve18(true)));
2510
2541
  srv.listen(port, host);
2511
2542
  });
2512
2543
  }
@@ -2532,6 +2563,9 @@ function noAppDirMessage(start, children) {
2532
2563
  }
2533
2564
  return lines.join("\n");
2534
2565
  }
2566
+ function devExitSucceeded(code, stopping) {
2567
+ return stopping || code === null || code === 0;
2568
+ }
2535
2569
  var dev_default = defineDeepspaceCommand({
2536
2570
  meta: {
2537
2571
  name: "dev",
@@ -2631,7 +2665,7 @@ var dev_default = defineDeepspaceCommand({
2631
2665
  const refreshed = await refreshSecretsCache(DEPLOY_URL, token, appIdForSecrets, wranglerEnv);
2632
2666
  if (refreshed) {
2633
2667
  generatedSecretsCache = refreshed.rendered;
2634
- say(refreshed.summary);
2668
+ if (refreshed.summary) say(refreshed.summary);
2635
2669
  }
2636
2670
  } catch (err) {
2637
2671
  throw new Refusal(
@@ -2668,7 +2702,9 @@ Free it with \`${killCmd}\`, or start on another port: \`deepspace dev start --p
2668
2702
  stdio: "inherit",
2669
2703
  env: wranglerViteEnv(process.env, wranglerConfig, { DEEPSPACE_PORT: String(port) })
2670
2704
  });
2705
+ let stopping = false;
2671
2706
  const stop = () => {
2707
+ stopping = true;
2672
2708
  wranglerConfig.cleanup();
2673
2709
  vite.kill();
2674
2710
  };
@@ -2680,7 +2716,9 @@ Free it with \`${killCmd}\`, or start on another port: \`deepspace dev start --p
2680
2716
  res(c);
2681
2717
  });
2682
2718
  });
2683
- if (code !== null && code !== 0) {
2719
+ process.off("SIGINT", stop);
2720
+ process.off("SIGTERM", stop);
2721
+ if (!devExitSucceeded(code, stopping)) {
2684
2722
  throw new Refusal(`Dev server exited with code ${code}.`, "dev_server_failed", {
2685
2723
  extra: { port, appDir, exitCode: code }
2686
2724
  });
@@ -3000,6 +3038,169 @@ function ensurePlaywright(appDir) {
3000
3038
  }
3001
3039
  }
3002
3040
 
3041
+ // src/cli/lib/test-account-service.ts
3042
+ import { readFileSync as readFileSync14 } from "fs";
3043
+
3044
+ // src/testing/accounts.ts
3045
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync13, writeFileSync as writeFileSync7 } from "fs";
3046
+ import { homedir as homedir3 } from "os";
3047
+ import { join as join13 } from "path";
3048
+ var TEST_ACCOUNTS_PATH = join13(homedir3(), ".deepspace", "test-accounts.json");
3049
+ var PRODUCTION_AUTH_ORIGIN = "https://auth.deep.space";
3050
+ var STAGING_AUTH_ORIGIN = "https://auth.deepspacesites.com";
3051
+ function currentTestAccountScope(env = process.env) {
3052
+ const selected = env.DEEPSPACE_AUTH_URL ?? (env.DEEPSPACE_ENV === "staging" ? STAGING_AUTH_ORIGIN : PRODUCTION_AUTH_ORIGIN);
3053
+ return normalizeTestAccountScope(selected);
3054
+ }
3055
+ function normalizeTestAccountScope(value) {
3056
+ try {
3057
+ return new URL(value).origin;
3058
+ } catch {
3059
+ return value;
3060
+ }
3061
+ }
3062
+ function loadAllTestAccounts(scope = currentTestAccountScope()) {
3063
+ const store = readTestAccountStore();
3064
+ return mergeTestAccounts(store.scopes[scope] ?? [], store.unscoped);
3065
+ }
3066
+ function reconcileTestAccountStore(remote, scope = currentTestAccountScope()) {
3067
+ const result = reconcileTestAccountScopes(readTestAccountStore(), remote, scope);
3068
+ saveTestAccountStore(result.store);
3069
+ return { accounts: result.accounts, removed: result.removed };
3070
+ }
3071
+ function reconcileTestAccountScopes(store, remote, scope) {
3072
+ const scoped = store.scopes[scope] ?? [];
3073
+ const current = reconcileTestAccounts(scoped, remote);
3074
+ const migrated = reconcileTestAccounts(store.unscoped, remote);
3075
+ const accounts = mergeTestAccounts(current, migrated);
3076
+ return {
3077
+ accounts,
3078
+ removed: Math.max(0, scoped.length - current.length),
3079
+ store: {
3080
+ version: 2,
3081
+ scopes: { ...store.scopes, [scope]: accounts },
3082
+ unscoped: store.unscoped.filter((account) => !remote.some((item) => accountMatchesRemote(account, item)))
3083
+ }
3084
+ };
3085
+ }
3086
+ function reconcileTestAccounts(local, remote) {
3087
+ const localById = new Map(local.filter((account) => account.id).map((account) => [account.id, account]));
3088
+ const legacyByEmail = new Map(local.filter((account) => !account.id).map((account) => [account.email, account]));
3089
+ return remote.flatMap((account) => {
3090
+ const stored = localById.get(account.id) ?? legacyByEmail.get(account.email);
3091
+ return stored ? [{ ...stored, ...account }] : [];
3092
+ });
3093
+ }
3094
+ function upsertTestAccount(account, scope = currentTestAccountScope()) {
3095
+ const store = readTestAccountStore();
3096
+ const accounts = (store.scopes[scope] ?? []).filter(
3097
+ (stored) => stored.id !== account.id && stored.email !== account.email
3098
+ );
3099
+ saveTestAccountStore({
3100
+ version: 2,
3101
+ scopes: { ...store.scopes, [scope]: [...accounts, account] },
3102
+ unscoped: store.unscoped.filter(
3103
+ (stored) => stored.id !== account.id && stored.email !== account.email
3104
+ )
3105
+ });
3106
+ }
3107
+ function removeTestAccounts(ids, emails = [], scope = currentTestAccountScope()) {
3108
+ const idSet = new Set(ids);
3109
+ const emailSet = new Set(emails);
3110
+ const keep = (account) => !idSet.has(account.id ?? "") && !emailSet.has(account.email);
3111
+ const store = readTestAccountStore();
3112
+ saveTestAccountStore({
3113
+ version: 2,
3114
+ scopes: { ...store.scopes, [scope]: (store.scopes[scope] ?? []).filter(keep) },
3115
+ unscoped: store.unscoped.filter(keep)
3116
+ });
3117
+ }
3118
+ function readTestAccountStore() {
3119
+ if (!existsSync12(TEST_ACCOUNTS_PATH)) return { version: 2, scopes: {}, unscoped: [] };
3120
+ try {
3121
+ const raw = JSON.parse(readFileSync13(TEST_ACCOUNTS_PATH, "utf-8"));
3122
+ if (Array.isArray(raw)) return { version: 2, scopes: {}, unscoped: validTestAccounts(raw) };
3123
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
3124
+ return { version: 2, scopes: {}, unscoped: [] };
3125
+ }
3126
+ const candidate = raw;
3127
+ const scopes = candidate.scopes && typeof candidate.scopes === "object" && !Array.isArray(candidate.scopes) ? Object.fromEntries(Object.entries(candidate.scopes).map(([key, value]) => [key, validTestAccounts(value)])) : {};
3128
+ return { version: 2, scopes, unscoped: validTestAccounts(candidate.unscoped) };
3129
+ } catch {
3130
+ return { version: 2, scopes: {}, unscoped: [] };
3131
+ }
3132
+ }
3133
+ function validTestAccounts(value) {
3134
+ if (!Array.isArray(value)) return [];
3135
+ return value.filter((item) => Boolean(
3136
+ item && typeof item === "object" && typeof item.email === "string" && typeof item.password === "string"
3137
+ ));
3138
+ }
3139
+ function mergeTestAccounts(...groups) {
3140
+ const accounts = /* @__PURE__ */ new Map();
3141
+ for (const account of groups.flat()) {
3142
+ const key = account.id ? `id:${account.id}` : `email:${account.email}`;
3143
+ if (!accounts.has(key)) accounts.set(key, account);
3144
+ }
3145
+ return [...accounts.values()];
3146
+ }
3147
+ function accountMatchesRemote(local, remote) {
3148
+ return local.id ? local.id === remote.id : local.email === remote.email;
3149
+ }
3150
+ function saveTestAccountStore(store) {
3151
+ mkdirSync5(join13(homedir3(), ".deepspace"), { recursive: true });
3152
+ writeFileSync7(TEST_ACCOUNTS_PATH, JSON.stringify(store, null, 2), { mode: 384 });
3153
+ }
3154
+
3155
+ // src/cli/lib/test-account-service.ts
3156
+ var SESSION_COOKIE2 = "__Secure-better-auth.session_token";
3157
+ var AUTH_URL4 = process.env.DEEPSPACE_AUTH_URL ?? PLATFORM_URLS.auth;
3158
+ function sessionCookie() {
3159
+ const token = readFileSync14(SESSION_PATH, "utf-8").trim();
3160
+ return `${SESSION_COOKIE2}=${encodeURIComponent(token)}`;
3161
+ }
3162
+ async function fetchRemoteTestAccounts() {
3163
+ const response = await fetch(`${AUTH_URL4}/api/auth/test-accounts`, {
3164
+ headers: { Cookie: sessionCookie(), Origin: AUTH_URL4 }
3165
+ });
3166
+ const data = await response.json().catch(() => ({}));
3167
+ if (!response.ok || !data.accounts) {
3168
+ throw new Error(data.error ?? "Failed to list test accounts");
3169
+ }
3170
+ return data.accounts;
3171
+ }
3172
+ async function createRemoteTestAccount(input) {
3173
+ const response = await fetch(`${AUTH_URL4}/api/auth/test-accounts`, {
3174
+ method: "POST",
3175
+ headers: {
3176
+ "Content-Type": "application/json",
3177
+ Cookie: sessionCookie(),
3178
+ Origin: AUTH_URL4
3179
+ },
3180
+ body: JSON.stringify(input)
3181
+ });
3182
+ const data = await response.json().catch(() => ({}));
3183
+ if (!response.ok || typeof data.id !== "string" || typeof data.email !== "string" || typeof data.userId !== "string" || typeof data.createdAt !== "number") {
3184
+ throw new Error(data.error ?? "Unknown error");
3185
+ }
3186
+ return { ...data, label: data.label ?? null };
3187
+ }
3188
+ async function deleteRemoteTestAccount(id) {
3189
+ const response = await fetch(`${AUTH_URL4}/api/auth/test-accounts/${id}`, {
3190
+ method: "DELETE",
3191
+ headers: { Cookie: sessionCookie(), Origin: AUTH_URL4 }
3192
+ });
3193
+ if (!response.ok) {
3194
+ const data = await response.json().catch(() => ({}));
3195
+ throw new Error(data.error ?? `DELETE returned ${response.status}`);
3196
+ }
3197
+ }
3198
+ async function syncTestAccountStore() {
3199
+ const accounts = await fetchRemoteTestAccounts();
3200
+ const { removed } = reconcileTestAccountStore(accounts, normalizeTestAccountScope(AUTH_URL4));
3201
+ return { accounts, removed };
3202
+ }
3203
+
3003
3204
  // src/cli/commands/test.ts
3004
3205
  var DEPLOY_URL2 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
3005
3206
  var test_default = defineDeepspaceCommand({
@@ -3061,7 +3262,7 @@ var test_default = defineDeepspaceCommand({
3061
3262
  const refreshed = await refreshSecretsCache(DEPLOY_URL2, token, appIdForSecrets, wranglerEnv);
3062
3263
  if (refreshed) {
3063
3264
  generatedSecretsCache = refreshed.rendered;
3064
- say(refreshed.summary);
3265
+ if (refreshed.summary) say(refreshed.summary);
3065
3266
  }
3066
3267
  } catch (err) {
3067
3268
  throw new Refusal(
@@ -3075,6 +3276,14 @@ var test_default = defineDeepspaceCommand({
3075
3276
  generatedSecretsCache,
3076
3277
  sharedDevVarsCache
3077
3278
  });
3279
+ if (suite !== "unit") {
3280
+ try {
3281
+ const { removed } = await syncTestAccountStore();
3282
+ if (removed > 0) say(`Removed ${removed} stale test account credential(s).`);
3283
+ } catch (error) {
3284
+ say(`Warning: could not reconcile test accounts: ${error instanceof Error ? error.message : String(error)}`);
3285
+ }
3286
+ }
3078
3287
  if (suite !== "unit") {
3079
3288
  preflightWindowsWorkerd(appDir);
3080
3289
  ensurePlaywright(appDir);
@@ -3226,49 +3435,6 @@ var screenshot_default = defineDeepspaceCommand({
3226
3435
  // src/cli/commands/test-accounts.ts
3227
3436
  import { defineCommand as defineCommand2 } from "citty";
3228
3437
  import * as p5 from "@clack/prompts";
3229
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync7, existsSync as existsSync12, mkdirSync as mkdirSync5 } from "fs";
3230
- import { join as join13 } from "path";
3231
- import { homedir as homedir3 } from "os";
3232
- var SESSION_COOKIE2 = "__Secure-better-auth.session_token";
3233
- var AUTH_URL4 = process.env.DEEPSPACE_AUTH_URL ?? PLATFORM_URLS.auth;
3234
- var DIR2 = join13(homedir3(), ".deepspace");
3235
- var ACCOUNTS_PATH = join13(DIR2, "test-accounts.json");
3236
- function loadAccounts() {
3237
- if (!existsSync12(ACCOUNTS_PATH)) return [];
3238
- try {
3239
- return JSON.parse(readFileSync13(ACCOUNTS_PATH, "utf-8"));
3240
- } catch {
3241
- return [];
3242
- }
3243
- }
3244
- function saveAccounts(accounts) {
3245
- mkdirSync5(DIR2, { recursive: true });
3246
- writeFileSync7(ACCOUNTS_PATH, JSON.stringify(accounts, null, 2), { mode: 384 });
3247
- }
3248
- function sessionCookie() {
3249
- const token = readFileSync13(SESSION_PATH, "utf-8").trim();
3250
- return `${SESSION_COOKIE2}=${encodeURIComponent(token)}`;
3251
- }
3252
- async function fetchRemoteAccounts() {
3253
- const res = await fetch(`${AUTH_URL4}/api/auth/test-accounts`, {
3254
- headers: { Cookie: sessionCookie(), Origin: AUTH_URL4 }
3255
- });
3256
- const data = await res.json().catch(() => ({}));
3257
- if (!res.ok || !data.accounts) {
3258
- throw new Error(data.error ?? "Failed to list test accounts");
3259
- }
3260
- return data.accounts;
3261
- }
3262
- async function deleteRemote(id) {
3263
- const res = await fetch(`${AUTH_URL4}/api/auth/test-accounts/${id}`, {
3264
- method: "DELETE",
3265
- headers: { Cookie: sessionCookie(), Origin: AUTH_URL4 }
3266
- });
3267
- if (!res.ok) {
3268
- const data = await res.json().catch(() => ({}));
3269
- throw new Error(data.error ?? `DELETE returned ${res.status}`);
3270
- }
3271
- }
3272
3438
  var create = defineDeepspaceCommand({
3273
3439
  meta: {
3274
3440
  name: "create",
@@ -3298,45 +3464,37 @@ var create = defineDeepspaceCommand({
3298
3464
  },
3299
3465
  async run({ args }) {
3300
3466
  await ensureToken();
3301
- const res = await fetch(`${AUTH_URL4}/api/auth/test-accounts`, {
3302
- method: "POST",
3303
- headers: {
3304
- "Content-Type": "application/json",
3305
- Cookie: sessionCookie(),
3306
- Origin: AUTH_URL4
3307
- },
3308
- body: JSON.stringify({
3309
- email: args.email,
3310
- password: args.password,
3311
- name: args.name,
3312
- label: args.label
3313
- })
3314
- });
3315
- const data = await res.json().catch(() => ({}));
3316
- if (!res.ok || !data.id) {
3317
- throw new Refusal(`Failed: ${data.error ?? "Unknown error"}`, "test_account_create_failed");
3467
+ const email = args.email;
3468
+ const password = args.password;
3469
+ const name = args.name;
3470
+ const label = args.label;
3471
+ let account;
3472
+ try {
3473
+ account = await createRemoteTestAccount({ email, password, name, label });
3474
+ } catch (error) {
3475
+ throw new Refusal(
3476
+ `Failed: ${error instanceof Error ? error.message : String(error)}`,
3477
+ "test_account_create_failed"
3478
+ );
3318
3479
  }
3319
- const account = data;
3320
- const accounts = loadAccounts();
3321
- accounts.push({
3480
+ upsertTestAccount({
3322
3481
  id: account.id,
3323
- email: args.email,
3324
- password: args.password,
3482
+ email,
3483
+ password,
3325
3484
  userId: account.userId,
3326
- name: args.name,
3485
+ name,
3327
3486
  label: account.label,
3328
3487
  createdAt: account.createdAt
3329
3488
  });
3330
- saveAccounts(accounts);
3331
3489
  if (!args.json) {
3332
3490
  console.log(`Created test account:`);
3333
3491
  console.log(` ID: ${account.id}`);
3334
3492
  console.log(` Email: ${account.email}`);
3335
- console.log(` Password: ${args.password}`);
3493
+ console.log(` Password: ${password}`);
3336
3494
  console.log(` UserID: ${account.userId}`);
3337
3495
  if (account.label) console.log(` Label: ${account.label}`);
3338
3496
  console.log(`
3339
- Saved to ${ACCOUNTS_PATH}`);
3497
+ Saved to ${TEST_ACCOUNTS_PATH}`);
3340
3498
  }
3341
3499
  return {
3342
3500
  data: {
@@ -3345,11 +3503,11 @@ Saved to ${ACCOUNTS_PATH}`);
3345
3503
  // The password is already echoed on the human path and stored 0600 in
3346
3504
  // ACCOUNTS_PATH — withholding it from --json would only force scripts
3347
3505
  // to parse the file themselves.
3348
- password: args.password,
3506
+ password,
3349
3507
  userId: account.userId,
3350
3508
  label: account.label ?? null,
3351
3509
  createdAt: account.createdAt,
3352
- savedTo: ACCOUNTS_PATH
3510
+ savedTo: TEST_ACCOUNTS_PATH
3353
3511
  }
3354
3512
  };
3355
3513
  }
@@ -3363,11 +3521,11 @@ var list = defineDeepspaceCommand({
3363
3521
  await ensureToken();
3364
3522
  let remote;
3365
3523
  try {
3366
- remote = await fetchRemoteAccounts();
3524
+ remote = (await syncTestAccountStore()).accounts;
3367
3525
  } catch (err) {
3368
3526
  throw new Refusal(`Failed: ${err.message}`, "test_accounts_list_failed");
3369
3527
  }
3370
- const local = loadAccounts();
3528
+ const local = loadAllTestAccounts();
3371
3529
  const localByEmail = new Map(local.map((a) => [a.email, a]));
3372
3530
  if (remote.length === 0) {
3373
3531
  if (!args.json) {
@@ -3438,7 +3596,7 @@ var del = defineDeepspaceCommand({
3438
3596
  if (targetEmail && !targetId) {
3439
3597
  let remote;
3440
3598
  try {
3441
- remote = await fetchRemoteAccounts();
3599
+ remote = await fetchRemoteTestAccounts();
3442
3600
  } catch (err) {
3443
3601
  throw new Refusal(`Failed: ${err.message}`, "test_accounts_list_failed");
3444
3602
  }
@@ -3451,14 +3609,11 @@ var del = defineDeepspaceCommand({
3451
3609
  targetId = match.id;
3452
3610
  }
3453
3611
  try {
3454
- await deleteRemote(targetId);
3612
+ await deleteRemoteTestAccount(targetId);
3455
3613
  } catch (err) {
3456
3614
  throw new Refusal(`Failed: ${err.message}`, "test_account_delete_failed");
3457
3615
  }
3458
- const accounts = loadAccounts().filter(
3459
- (a) => a.id !== targetId && (!targetEmail || a.email !== targetEmail)
3460
- );
3461
- saveAccounts(accounts);
3616
+ removeTestAccounts([targetId], targetEmail ? [targetEmail] : []);
3462
3617
  if (!args.json) {
3463
3618
  console.log(`Test account deleted${targetEmail ? `: ${targetEmail}` : `: ${targetId}`}`);
3464
3619
  }
@@ -3487,7 +3642,7 @@ var clear = defineDeepspaceCommand({
3487
3642
  await ensureToken();
3488
3643
  let remote;
3489
3644
  try {
3490
- remote = await fetchRemoteAccounts();
3645
+ remote = await fetchRemoteTestAccounts();
3491
3646
  } catch (err) {
3492
3647
  throw new Refusal(`Failed: ${err.message}`, "test_accounts_list_failed");
3493
3648
  }
@@ -3518,16 +3673,14 @@ var clear = defineDeepspaceCommand({
3518
3673
  const failures = [];
3519
3674
  for (const a of targets) {
3520
3675
  try {
3521
- await deleteRemote(a.id);
3676
+ await deleteRemoteTestAccount(a.id);
3522
3677
  ok2++;
3523
3678
  } catch (err) {
3524
3679
  failures.push({ email: a.email, error: err.message });
3525
3680
  }
3526
3681
  }
3527
- const deletedIds = new Set(
3528
- targets.filter((t) => !failures.find((f) => f.email === t.email)).map((t) => t.id)
3529
- );
3530
- saveAccounts(loadAccounts().filter((a) => !deletedIds.has(a.id)));
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));
3531
3684
  if (failures.length > 0) {
3532
3685
  throw new Refusal(
3533
3686
  `Deleted ${ok2}/${targets.length} test account(s).
@@ -3562,22 +3715,36 @@ import { resolve as resolve12 } from "path";
3562
3715
  // src/cli/commands/deploy/build.ts
3563
3716
  import * as p6 from "@clack/prompts";
3564
3717
  import { execSync as execSync2 } from "child_process";
3565
- import { existsSync as existsSync14, readFileSync as readFileSync15, readdirSync as readdirSync4 } from "fs";
3566
- import { dirname as dirname5, join as join15, resolve as resolve10 } from "path";
3718
+ import { existsSync as existsSync15, readFileSync as readFileSync17, readdirSync as readdirSync4 } from "fs";
3719
+ import { dirname as dirname5, join as join16, resolve as resolve10 } from "path";
3720
+
3721
+ // src/documentation/deploy.ts
3722
+ import { existsSync as existsSync13, readFileSync as readFileSync15 } from "fs";
3723
+ import { join as join14 } from "path";
3724
+ function readDocumentationDeployManifest(appDir, clientDir) {
3725
+ if (!existsSync13(join14(appDir, "documentation.json"))) return void 0;
3726
+ const manifestPath = join14(clientDir, "_documentation", "manifest.json");
3727
+ if (!existsSync13(manifestPath)) {
3728
+ throw new Error(
3729
+ "documentation.json exists but the build produced no documentation manifest. Ensure vite.config.ts includes deepSpaceDocumentation() from deepspace/documentation."
3730
+ );
3731
+ }
3732
+ return JSON.parse(readFileSync15(manifestPath, "utf8"));
3733
+ }
3567
3734
 
3568
3735
  // src/cli/commands/migrate/app-migrations.ts
3569
3736
  import {
3570
3737
  closeSync,
3571
3738
  constants,
3572
- existsSync as existsSync13,
3739
+ existsSync as existsSync14,
3573
3740
  fstatSync,
3574
3741
  ftruncateSync,
3575
3742
  lstatSync,
3576
3743
  openSync,
3577
- readFileSync as readFileSync14,
3744
+ readFileSync as readFileSync16,
3578
3745
  writeSync
3579
3746
  } from "fs";
3580
- import { join as join14 } from "path";
3747
+ import { join as join15 } from "path";
3581
3748
 
3582
3749
  // src/shared/protocol/app-migrations.ts
3583
3750
  var APP_MIGRATION_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/;
@@ -3622,8 +3789,8 @@ var APP_MIGRATION_DEFINITIONS = SOURCE_MIGRATIONS.map(
3622
3789
  ({ id, description }) => ({ id, description })
3623
3790
  );
3624
3791
  function readAppliedAppMigrations(appDir) {
3625
- const path = join14(appDir, APP_MIGRATIONS_MANIFEST);
3626
- if (!existsSync13(path)) return [];
3792
+ const path = join15(appDir, APP_MIGRATIONS_MANIFEST);
3793
+ if (!existsSync14(path)) return [];
3627
3794
  const source = readRegularFile(path);
3628
3795
  let parsed;
3629
3796
  try {
@@ -3649,7 +3816,7 @@ function planAppMigrations(appDir) {
3649
3816
  const edits = [];
3650
3817
  for (const repoPath of tracked) {
3651
3818
  if (!isSourcePath(repoPath, appPrefix)) continue;
3652
- const absolutePath = join14(repo, repoPath);
3819
+ const absolutePath = join15(repo, repoPath);
3653
3820
  let stat;
3654
3821
  try {
3655
3822
  stat = lstatSync(absolutePath);
@@ -3683,8 +3850,8 @@ function planAppMigrations(appDir) {
3683
3850
  });
3684
3851
  if (pending.length > 0 && blockers.length === 0) {
3685
3852
  const manifestPath = `${appPrefix}${APP_MIGRATIONS_MANIFEST}`;
3686
- const absoluteManifestPath = join14(repo, manifestPath);
3687
- const before = existsSync13(absoluteManifestPath) ? readRegularFile(absoluteManifestPath) : null;
3853
+ const absoluteManifestPath = join15(repo, manifestPath);
3854
+ const before = existsSync14(absoluteManifestPath) ? readRegularFile(absoluteManifestPath) : null;
3688
3855
  const after = `${JSON.stringify([...appliedIds, ...pending.map(({ id }) => id)], null, 2)}
3689
3856
  `;
3690
3857
  if (before !== after) edits.push({ path: manifestPath, before, after });
@@ -3702,13 +3869,13 @@ function applyAppMigrationPlan(appDir, plan) {
3702
3869
  }
3703
3870
  const repo = repoToplevel(appDir);
3704
3871
  for (const edit of plan.edits) {
3705
- const absolutePath = join14(repo, edit.path);
3706
- const current = existsSync13(absolutePath) ? readRegularFile(absolutePath) : null;
3872
+ const absolutePath = join15(repo, edit.path);
3873
+ const current = existsSync14(absolutePath) ? readRegularFile(absolutePath) : null;
3707
3874
  if (current !== edit.before) {
3708
3875
  throw new Error(`Migration input changed after planning: ${edit.path}`);
3709
3876
  }
3710
3877
  }
3711
- for (const edit of plan.edits) writeRegularFile(join14(repo, edit.path), edit.before, edit.after);
3878
+ for (const edit of plan.edits) writeRegularFile(join15(repo, edit.path), edit.before, edit.after);
3712
3879
  }
3713
3880
  function readRegularFile(path) {
3714
3881
  const before = lstatSync(path);
@@ -3722,7 +3889,7 @@ function readRegularFile(path) {
3722
3889
  if (!opened.isFile() || opened.dev !== after.dev || opened.ino !== after.ino) {
3723
3890
  throw new Error(`Migration input changed while opening: ${path}`);
3724
3891
  }
3725
- return readFileSync14(fd, "utf-8");
3892
+ return readFileSync16(fd, "utf-8");
3726
3893
  } finally {
3727
3894
  closeSync(fd);
3728
3895
  }
@@ -3748,7 +3915,7 @@ function writeRegularFile(path, before, after) {
3748
3915
  if (!opened.isFile() || opened.dev !== currentPath.dev || opened.ino !== currentPath.ino) {
3749
3916
  throw new Error(`Migration input changed while opening: ${path}`);
3750
3917
  }
3751
- if (readFileSync14(fd, "utf-8") !== before) {
3918
+ if (readFileSync16(fd, "utf-8") !== before) {
3752
3919
  throw new Error(`Migration input changed after planning: ${path}`);
3753
3920
  }
3754
3921
  ftruncateSync(fd, 0);
@@ -3859,10 +4026,14 @@ var RESERVED_RUN_WORKER_FIRST = /* @__PURE__ */ new Set([
3859
4026
  "/ws/*",
3860
4027
  "/internal/*",
3861
4028
  "/v1/*",
3862
- "/_deepspace/*"
4029
+ "/_deepspace/*",
4030
+ "/_documentation",
4031
+ "/_documentation/*",
4032
+ "/_documentation-root",
4033
+ "/_documentation-root/*"
3863
4034
  ]);
3864
4035
  async function buildDeployBundle(options) {
3865
- const { appDir, appName, envName, sharedDevVarsCache, wranglerConfig, output, spinner: spinner2 } = options;
4036
+ const { appDir, appName, envName, sharedDevVarsCache, output, spinner: spinner2 } = options;
3866
4037
  const junk = removeMacosJunk(appDir);
3867
4038
  if (junk > 0) p6.log.info(`Removed ${junk} macOS metadata file(s) (._*, .DS_Store)`);
3868
4039
  const lintFindings = await lintProjectSchemas(appDir);
@@ -3875,10 +4046,15 @@ async function buildDeployBundle(options) {
3875
4046
  preparedWranglerConfig = prepareWranglerEnvConfig(appDir, envName, {
3876
4047
  sharedDevVarsCache
3877
4048
  });
4049
+ const appDomain = DEEPSPACE_ENV === "staging" ? "spacestest.com" : "app.space";
3878
4050
  execSync2("npx vite build", {
3879
4051
  cwd: appDir,
3880
4052
  stdio: "pipe",
3881
- env: wranglerViteEnv(process.env, preparedWranglerConfig)
4053
+ env: {
4054
+ ...wranglerViteEnv(process.env, preparedWranglerConfig),
4055
+ // The Vite plugin's one documentation compile stamps release URLs.
4056
+ DEEPSPACE_DOCUMENTATION_BASE_URL: `https://${appName}.${appDomain}/docs`
4057
+ }
3882
4058
  });
3883
4059
  } catch (error) {
3884
4060
  spinner2.stop("Build failed");
@@ -3889,19 +4065,19 @@ async function buildDeployBundle(options) {
3889
4065
  preparedWranglerConfig?.cleanup();
3890
4066
  }
3891
4067
  spinner2.stop("Built");
3892
- const deployConfigPath = join15(appDir, ".wrangler", "deploy", "config.json");
3893
- if (!existsSync14(deployConfigPath)) {
4068
+ const deployConfigPath = join16(appDir, ".wrangler", "deploy", "config.json");
4069
+ if (!existsSync15(deployConfigPath)) {
3894
4070
  output.die(
3895
4071
  "Build output config not found at .wrangler/deploy/config.json",
3896
4072
  "build_output_missing"
3897
4073
  );
3898
4074
  }
3899
- const deployConfig = JSON.parse(readFileSync15(deployConfigPath, "utf-8"));
4075
+ const deployConfig = JSON.parse(readFileSync17(deployConfigPath, "utf-8"));
3900
4076
  const outputWranglerPath = resolve10(dirname5(deployConfigPath), deployConfig.configPath);
3901
- if (!existsSync14(outputWranglerPath)) {
4077
+ if (!existsSync15(outputWranglerPath)) {
3902
4078
  output.die(`Output wrangler.json not found at ${outputWranglerPath}`, "build_output_missing");
3903
4079
  }
3904
- const outputConfig = JSON.parse(readFileSync15(outputWranglerPath, "utf-8"));
4080
+ const outputConfig = JSON.parse(readFileSync17(outputWranglerPath, "utf-8"));
3905
4081
  if (outputConfig.name && outputConfig.name !== appName) {
3906
4082
  const envHint = envName ? ` (--env ${envName})` : "";
3907
4083
  output.die(
@@ -3910,18 +4086,25 @@ async function buildDeployBundle(options) {
3910
4086
  );
3911
4087
  }
3912
4088
  const workerDir = dirname5(outputWranglerPath);
3913
- const workerBundlePath = join15(workerDir, outputConfig.main);
4089
+ const workerBundlePath = join16(workerDir, outputConfig.main);
3914
4090
  const clientDir = outputConfig.assets?.directory ? resolve10(workerDir, outputConfig.assets.directory) : null;
3915
- if (!existsSync14(workerBundlePath)) {
4091
+ if (!existsSync15(workerBundlePath)) {
3916
4092
  output.die(`Worker bundle not found at ${workerBundlePath}`, "build_output_missing");
3917
4093
  }
3918
- if (!clientDir || !existsSync14(clientDir)) {
4094
+ if (!clientDir || !existsSync15(clientDir)) {
3919
4095
  return output.die(`Client assets not found at ${clientDir}`, "build_output_missing");
3920
4096
  }
4097
+ try {
4098
+ const manifest = readDocumentationDeployManifest(appDir, clientDir);
4099
+ if (manifest) p6.log.info(`Documentation: ${manifest.pageCount} page(s)`);
4100
+ } catch (error) {
4101
+ output.die(errorMessage(error), "documentation_build_failed");
4102
+ }
3921
4103
  spinner2.start("Collecting assets...");
3922
4104
  const assets = collectAssets(clientDir);
4105
+ const assetConfig = readDeployAssetConfig(clientDir);
3923
4106
  spinner2.stop(`Collected ${assets.length} assets`);
3924
- const workerJs = readFileSync15(workerBundlePath, "utf-8");
4107
+ const workerJs = readFileSync17(workerBundlePath, "utf-8");
3925
4108
  const doBindings = outputConfig.durable_objects?.bindings;
3926
4109
  const sqliteClasses = new Set(
3927
4110
  outputConfig.migrations?.flatMap((migration) => migration.new_sqlite_classes ?? []) ?? []
@@ -3946,7 +4129,7 @@ async function buildDeployBundle(options) {
3946
4129
  `Custom bindings: ${customBindings.map((binding) => `${binding.name} (${binding.type})`).join(", ")}`
3947
4130
  );
3948
4131
  }
3949
- const extraRoutes = extractRunWorkerFirst(wranglerConfig);
4132
+ const extraRoutes = resolveDeployRunWorkerFirst(outputConfig);
3950
4133
  let appMigrations = [];
3951
4134
  try {
3952
4135
  appMigrations = readAppliedAppMigrations(appDir);
@@ -3955,6 +4138,7 @@ async function buildDeployBundle(options) {
3955
4138
  }
3956
4139
  return {
3957
4140
  assets,
4141
+ assetConfig,
3958
4142
  workerJs,
3959
4143
  appMigrations,
3960
4144
  doManifest,
@@ -3963,8 +4147,7 @@ async function buildDeployBundle(options) {
3963
4147
  };
3964
4148
  }
3965
4149
  function extractRunWorkerFirst(config) {
3966
- const raw = config.assets?.run_worker_first;
3967
- if (!Array.isArray(raw)) return [];
4150
+ const raw = Array.isArray(config.assets?.run_worker_first) ? config.assets.run_worker_first : [];
3968
4151
  const routes = [];
3969
4152
  const seen = /* @__PURE__ */ new Set();
3970
4153
  for (const entry of raw) {
@@ -3976,19 +4159,24 @@ function extractRunWorkerFirst(config) {
3976
4159
  }
3977
4160
  return routes;
3978
4161
  }
4162
+ function resolveDeployRunWorkerFirst(config) {
4163
+ if (config.assets?.run_worker_first === true) return true;
4164
+ const routes = extractRunWorkerFirst(config);
4165
+ return routes;
4166
+ }
3979
4167
  function collectAssets(dir) {
3980
4168
  const assets = [];
3981
4169
  const walk = (currentDir, prefix) => {
3982
4170
  for (const entry of readdirSync4(currentDir, { withFileTypes: true })) {
3983
- if (entry.name === ".assetsignore") continue;
3984
- const fullPath = join15(currentDir, entry.name);
4171
+ if (isDeployAssetControlFile(entry.name)) continue;
4172
+ const fullPath = join16(currentDir, entry.name);
3985
4173
  const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
3986
4174
  if (entry.isDirectory()) {
3987
4175
  walk(fullPath, relativePath);
3988
4176
  } else {
3989
4177
  assets.push({
3990
4178
  path: `/${relativePath}`,
3991
- contentBase64: readFileSync15(fullPath).toString("base64")
4179
+ contentBase64: readFileSync17(fullPath).toString("base64")
3992
4180
  });
3993
4181
  }
3994
4182
  }
@@ -3996,24 +4184,37 @@ function collectAssets(dir) {
3996
4184
  walk(dir, "");
3997
4185
  return assets;
3998
4186
  }
4187
+ function readDeployAssetConfig(clientDir) {
4188
+ const config = {};
4189
+ for (const [file, key] of [["_headers", "_headers"], ["_redirects", "_redirects"]]) {
4190
+ const filePath = join16(clientDir, file);
4191
+ if (!existsSync15(filePath)) continue;
4192
+ const content = readFileSync17(filePath, "utf8");
4193
+ if (content.trim()) config[key] = content;
4194
+ }
4195
+ return config;
4196
+ }
4197
+ function isDeployAssetControlFile(name) {
4198
+ return name === ".assetsignore" || name === "_headers" || name === "_redirects";
4199
+ }
3999
4200
  function errorMessage(error) {
4000
4201
  return error instanceof Error ? error.message : String(error);
4001
4202
  }
4002
4203
 
4003
4204
  // src/cli/commands/deploy/commerce.ts
4004
4205
  import * as p7 from "@clack/prompts";
4005
- import { existsSync as existsSync15 } from "fs";
4006
- import { join as join16 } from "path";
4206
+ import { existsSync as existsSync16 } from "fs";
4207
+ import { join as join17 } from "path";
4007
4208
  import { pathToFileURL as pathToFileURL2 } from "url";
4008
4209
  var API_URL2 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
4009
4210
  function errorMessage2(error) {
4010
4211
  return error instanceof Error ? error.message : String(error);
4011
4212
  }
4012
4213
  async function loadDeclaration(appDir, sourceFile, outputFile, exportName) {
4013
- const sourcePath = join16(appDir, "src", sourceFile);
4014
- if (!existsSync15(sourcePath)) return void 0;
4214
+ const sourcePath = join17(appDir, "src", sourceFile);
4215
+ if (!existsSync16(sourcePath)) return void 0;
4015
4216
  const esbuild = await import("esbuild");
4016
- const outputPath = join16(appDir, ".wrangler", "deploy", outputFile);
4217
+ const outputPath = join17(appDir, ".wrangler", "deploy", outputFile);
4017
4218
  await esbuild.build({
4018
4219
  entryPoints: [sourcePath],
4019
4220
  bundle: true,
@@ -4106,7 +4307,7 @@ Stripe will keep charging them until you cancel. Cancel at the end of their curr
4106
4307
  }
4107
4308
  }
4108
4309
  async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
4109
- if (!existsSync15(join16(appDir, "src", "subscriptions.ts"))) return;
4310
+ if (!existsSync16(join17(appDir, "src", "subscriptions.ts"))) return;
4110
4311
  let plans;
4111
4312
  try {
4112
4313
  plans = await loadDeclaration(
@@ -4161,7 +4362,7 @@ async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
4161
4362
  }
4162
4363
  }
4163
4364
  async function syncOneTimeProducts(appDir, appName, token) {
4164
- if (!existsSync15(join16(appDir, "src", "products.ts"))) return;
4365
+ if (!existsSync16(join17(appDir, "src", "products.ts"))) return;
4165
4366
  let products;
4166
4367
  try {
4167
4368
  products = await loadDeclaration(
@@ -4453,10 +4654,22 @@ function repoApi(deployUrl, token, appId) {
4453
4654
  }
4454
4655
 
4455
4656
  // src/cli/lib/vc-remote.ts
4456
- import { existsSync as existsSync16 } from "fs";
4657
+ import { existsSync as existsSync17 } from "fs";
4457
4658
  import { tmpdir } from "os";
4458
4659
  import { isAbsolute as isAbsolute3, relative, resolve as resolve11 } from "path";
4459
- var SPACE_REMOTE = "space";
4660
+ function spaceRemoteName(environment = DEEPSPACE_ENV) {
4661
+ if (environment === "staging") return "space-staging";
4662
+ if (environment === "production") return "space";
4663
+ return "space-invalid";
4664
+ }
4665
+ var SPACE_REMOTE = spaceRemoteName();
4666
+ function spaceTrackingRef(branch, remote = SPACE_REMOTE) {
4667
+ return `refs/remotes/${remote}/${branch}`;
4668
+ }
4669
+ function spacePrivateRef(path, environment = DEEPSPACE_ENV) {
4670
+ const root = environment === "production" ? "refs/deepspace" : `refs/deepspace/${environment}`;
4671
+ return `${root}/${path}`;
4672
+ }
4460
4673
  function deployBaseUrl() {
4461
4674
  return process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
4462
4675
  }
@@ -4493,13 +4706,16 @@ function entryIsTransient(cwd) {
4493
4706
  return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
4494
4707
  }
4495
4708
  function helperEntryUsable(command) {
4496
- const match = /^!'([^']*)' '([^']*)' git-credential$/.exec(command);
4497
- return match !== null && existsSync16(match[1]) && existsSync16(match[2]);
4709
+ const match = /^!(?:DEEPSPACE_ENV=(production|staging) )?'([^']*)' '([^']*)' git-credential$/.exec(command);
4710
+ const expectedEnvironment = DEEPSPACE_ENV === "production" ? void 0 : DEEPSPACE_ENV;
4711
+ if (!match || match[1] !== expectedEnvironment) return false;
4712
+ return existsSync17(match[2]) && existsSync17(match[3]);
4498
4713
  }
4499
- function credentialHelperCommand() {
4714
+ function credentialHelperCommand(environment = DEEPSPACE_ENV) {
4500
4715
  const entry = process.argv[1];
4501
4716
  if (!entry) throw new Error("CLI entry path is unavailable");
4502
- return `!${shQuote(process.execPath)} ${shQuote(entry)} git-credential`;
4717
+ const environmentPrefix = environment === "staging" ? "DEEPSPACE_ENV=staging " : "";
4718
+ return `!${environmentPrefix}${shQuote(process.execPath)} ${shQuote(entry)} git-credential`;
4503
4719
  }
4504
4720
  function installCredentialHelper(cwd, url) {
4505
4721
  try {
@@ -4538,22 +4754,22 @@ function installCredentialHelper(cwd, url) {
4538
4754
  } catch {
4539
4755
  }
4540
4756
  }
4541
- function ensureSpaceRemote(cwd, appId) {
4757
+ function ensureSpaceRemote(cwd, appId, remote = SPACE_REMOTE) {
4542
4758
  const url = repoUrl(appId);
4543
- const current = runGit(cwd, ["remote", "get-url", SPACE_REMOTE], { allowFail: true });
4759
+ const current = runGit(cwd, ["remote", "get-url", remote], { allowFail: true });
4544
4760
  if (current.status !== 0) {
4545
- runGit(cwd, ["remote", "add", SPACE_REMOTE, url]);
4761
+ runGit(cwd, ["remote", "add", remote, url]);
4546
4762
  } else {
4547
4763
  const existing = current.stdout.toString("utf-8").trim();
4548
4764
  if (existing !== url) {
4549
4765
  const priorApp = appIdFromRepoUrl(existing);
4550
4766
  if (priorApp && priorApp !== appId && !process.argv.includes("--json")) {
4551
4767
  process.stderr.write(
4552
- `warning: remote '${SPACE_REMOTE}' was pointed at app ${priorApp}; re-aiming it at ${appId}.
4768
+ `warning: remote '${remote}' was pointed at app ${priorApp}; re-aiming it at ${appId}.
4553
4769
  `
4554
4770
  );
4555
4771
  }
4556
- runGit(cwd, ["remote", "set-url", SPACE_REMOTE, url]);
4772
+ runGit(cwd, ["remote", "set-url", remote, url]);
4557
4773
  }
4558
4774
  }
4559
4775
  installCredentialHelper(cwd, url);
@@ -4628,7 +4844,7 @@ function pushToSpace(cwd, token, refspec, opts = {}) {
4628
4844
  const result = runGitRemote(
4629
4845
  cwd,
4630
4846
  token,
4631
- ["push", "--porcelain", ...opts.force ? ["--force"] : [], ...extra, SPACE_REMOTE, refspec],
4847
+ ["push", "--porcelain", ...opts.force ? ["--force"] : [], ...extra, opts.remote ?? SPACE_REMOTE, refspec],
4632
4848
  { allowFail: true }
4633
4849
  );
4634
4850
  const parsed = parsePushPorcelain(result.stdout.toString("utf-8"));
@@ -4811,7 +5027,8 @@ async function syncDeployRepository(options) {
4811
5027
  "source_unclaimed"
4812
5028
  );
4813
5029
  }
4814
- ensureSpaceRemote(appDir, appId);
5030
+ const sourceRemote = spaceRemoteName();
5031
+ ensureSpaceRemote(appDir, appId, sourceRemote);
4815
5032
  const branch = currentBranch(appDir);
4816
5033
  const workspaceBranchId = workspaceIdFromBranch(branch);
4817
5034
  if (!isWorkTreeClean(appDir)) {
@@ -4855,7 +5072,9 @@ async function syncDeployRepository(options) {
4855
5072
  );
4856
5073
  } else {
4857
5074
  const pushResult = await pushWithTransientRetry(
4858
- () => pushToSpace(appDir, token, `refs/heads/${branch}:refs/heads/${branch}`)
5075
+ () => pushToSpace(appDir, token, `refs/heads/${branch}:refs/heads/${branch}`, {
5076
+ remote: sourceRemote
5077
+ })
4859
5078
  );
4860
5079
  if (pushResult.status === "committed" || pushResult.status === "up_to_date") {
4861
5080
  recoverable = true;
@@ -4868,7 +5087,7 @@ async function syncDeployRepository(options) {
4868
5087
  "vc_push_rejected"
4869
5088
  );
4870
5089
  } else {
4871
- const remoteState = classifyRemoteState(appDir, token, branch, tip);
5090
+ const remoteState = classifyRemoteState(appDir, token, branch, tip, sourceRemote);
4872
5091
  if (remoteState.strictlyBehind && !ignoreStale) {
4873
5092
  let behind = "several";
4874
5093
  try {
@@ -4913,16 +5132,17 @@ async function syncDeployRepository(options) {
4913
5132
  }
4914
5133
  return { commitOid, recoverable, deployKey, source, sourceRevision, baseReleaseId };
4915
5134
  }
4916
- function classifyRemoteState(appDir, token, branch, localTip) {
5135
+ function classifyRemoteState(appDir, token, branch, localTip, remote) {
4917
5136
  let remoteTip = null;
5137
+ const remoteRef = `refs/remotes/${remote}/${branch}`;
4918
5138
  try {
4919
5139
  runGitRemote(appDir, token, [
4920
5140
  "fetch",
4921
5141
  "--quiet",
4922
- SPACE_REMOTE,
4923
- `+refs/heads/${branch}:refs/remotes/space/${branch}`
5142
+ remote,
5143
+ `+refs/heads/${branch}:${remoteRef}`
4924
5144
  ]);
4925
- remoteTip = resolveCommit(appDir, `refs/remotes/space/${branch}`);
5145
+ remoteTip = resolveCommit(appDir, remoteRef);
4926
5146
  return {
4927
5147
  remoteTip,
4928
5148
  strictlyBehind: remoteTip !== null && remoteTip !== localTip && isAncestor(appDir, localTip, remoteTip)
@@ -4953,7 +5173,7 @@ async function pushWithTransientRetry(push) {
4953
5173
  } catch (error) {
4954
5174
  const message = errorMessage3(error);
4955
5175
  if (/(?:HTTP |error: )(?:429|503)\b/i.test(message) && attempt < backoffMs.length) {
4956
- await new Promise((resolve19) => setTimeout(resolve19, backoffMs[attempt]));
5176
+ await new Promise((resolve18) => setTimeout(resolve18, backoffMs[attempt]));
4957
5177
  continue;
4958
5178
  }
4959
5179
  throw error;
@@ -5004,7 +5224,9 @@ async function deployBuiltBundle(options) {
5004
5224
  spinner: spinner2
5005
5225
  } = options;
5006
5226
  spinner2.start(`Deploying ${appName}...`);
5007
- if (bundle.extraRoutes.length) {
5227
+ if (bundle.extraRoutes === true) {
5228
+ p10.log.info("Worker-first routing: all requests");
5229
+ } else if (bundle.extraRoutes.length) {
5008
5230
  p10.log.info(`Custom worker-first routes: ${bundle.extraRoutes.join(", ")}`);
5009
5231
  }
5010
5232
  const assetGroups = packAssetGroups(bundle.assets, GROUP_BYTES);
@@ -5074,9 +5296,12 @@ async function deployBuiltBundle(options) {
5074
5296
  form.append("bindingManifest", JSON.stringify(bundle.customBindings));
5075
5297
  }
5076
5298
  if (secrets.names.length) form.append("userSecrets", JSON.stringify(secrets.values));
5077
- if (bundle.extraRoutes.length) {
5299
+ if (bundle.extraRoutes === true || bundle.extraRoutes.length) {
5078
5300
  form.append("extraRunWorkerFirst", JSON.stringify(bundle.extraRoutes));
5079
5301
  }
5302
+ if (Object.keys(bundle.assetConfig).length) {
5303
+ form.append("assetConfig", JSON.stringify(bundle.assetConfig));
5304
+ }
5080
5305
  form.append("name", appName);
5081
5306
  if (confirmRename) form.append("confirmRename", "true");
5082
5307
  if (shouldSendLineage(repository.commitOid, repository.recoverable)) {
@@ -5115,11 +5340,6 @@ async function deployBuiltBundle(options) {
5115
5340
  throw error;
5116
5341
  }
5117
5342
  let body = await response.json().catch(() => ({}));
5118
- for (let attempt = 0; response.status === 503 && body.code === "release_finalization_pending" && attempt < 3; attempt++) {
5119
- await new Promise((resolve19) => setTimeout(resolve19, 250 * 2 ** attempt));
5120
- response = await postCommit();
5121
- body = await response.json().catch(() => ({}));
5122
- }
5123
5343
  if (response.status === 409 && body.code === "rename_required" && !confirmRename) {
5124
5344
  spinner2.stop("Rename confirmation needed");
5125
5345
  if (output.nonInteractive) {
@@ -5225,7 +5445,7 @@ async function postWithRetry(url, makeInit, {
5225
5445
  lastError = error;
5226
5446
  }
5227
5447
  if (attempt < attempts) {
5228
- await new Promise((resolve19) => setTimeout(resolve19, Math.min(8e3, 500 * 2 ** (attempt - 1))));
5448
+ await new Promise((resolve18) => setTimeout(resolve18, Math.min(8e3, 500 * 2 ** (attempt - 1))));
5229
5449
  }
5230
5450
  }
5231
5451
  if (lastResponse) return lastResponse;
@@ -5234,7 +5454,7 @@ async function postWithRetry(url, makeInit, {
5234
5454
 
5235
5455
  // src/cli/commands/deploy/secrets.ts
5236
5456
  import * as p11 from "@clack/prompts";
5237
- import { existsSync as existsSync17, readFileSync as readFileSync16 } from "fs";
5457
+ import { existsSync as existsSync18, readFileSync as readFileSync18 } from "fs";
5238
5458
  function classifyDevVarsSecrets(options) {
5239
5459
  const store = new Set(options.storeSecretNames);
5240
5460
  const strayKeys = options.handEditedDevVarKeys.filter((key) => !store.has(key));
@@ -5261,7 +5481,7 @@ async function loadDeploySecrets(options) {
5261
5481
  generatedSecretsCache: refreshed.rendered,
5262
5482
  sharedDevVarsCache: true
5263
5483
  });
5264
- p11.log.info(refreshed.summary);
5484
+ if (refreshed.summary) p11.log.info(refreshed.summary);
5265
5485
  }
5266
5486
  } catch (error) {
5267
5487
  if (error?.status === 403) {
@@ -5332,8 +5552,8 @@ function prepareDeploySecrets(options) {
5332
5552
  return { values, names };
5333
5553
  }
5334
5554
  function readCustomDevVars(path, output) {
5335
- if (!existsSync17(path)) return {};
5336
- const section = extractCustomDevVars(stripGeneratedSecretsCache(readFileSync16(path, "utf-8")));
5555
+ if (!existsSync18(path)) return {};
5556
+ const section = extractCustomDevVars(stripGeneratedSecretsCache(readFileSync18(path, "utf-8")));
5337
5557
  if (!section) return {};
5338
5558
  try {
5339
5559
  return parseDevVars(section);
@@ -5502,7 +5722,6 @@ var deploy_default = defineCommand3({
5502
5722
  appName,
5503
5723
  envName,
5504
5724
  sharedDevVarsCache: secretsCache.linked !== null,
5505
- wranglerConfig,
5506
5725
  output,
5507
5726
  spinner: spinner2
5508
5727
  });
@@ -5608,7 +5827,7 @@ async function waitForAssetsReady(url, timeoutMs) {
5608
5827
  if (!body.includes("Assets have not yet deployed") && !body.includes("No app configured")) {
5609
5828
  streak++;
5610
5829
  if (streak >= 3) return true;
5611
- await new Promise((resolve19) => setTimeout(resolve19, 1500));
5830
+ await new Promise((resolve18) => setTimeout(resolve18, 1500));
5612
5831
  continue;
5613
5832
  }
5614
5833
  streak = 0;
@@ -5616,7 +5835,7 @@ async function waitForAssetsReady(url, timeoutMs) {
5616
5835
  streak = 0;
5617
5836
  }
5618
5837
  const waitMs = Math.min(8e3, 1e3 * 2 ** (attempt - 1));
5619
- await new Promise((resolve19) => setTimeout(resolve19, waitMs));
5838
+ await new Promise((resolve18) => setTimeout(resolve18, waitMs));
5620
5839
  }
5621
5840
  return false;
5622
5841
  }
@@ -5684,18 +5903,15 @@ async function resolveAppSelector(deployUrl, token, selector) {
5684
5903
  "app_not_found"
5685
5904
  );
5686
5905
  }
5687
- async function resolveAppTarget(deployUrl, token, explicit) {
5906
+ async function resolveAppTarget(deployUrl, token, explicit, options = {}) {
5907
+ assertAppTargetResolvable(explicit, options);
5688
5908
  const { app: app2, error } = parseAppArg(explicit);
5689
5909
  if (error) throw new InputError(error, "invalid_app");
5690
5910
  if (app2 !== void 0) return resolveAppSelector(deployUrl, token, app2);
5911
+ const { wranglerEnv } = parseWranglerEnvArg(options.wranglerEnv);
5691
5912
  const appDir = findAppDir();
5692
- const id = appDir ? readAppId(appDir) : null;
5693
- if (!id) {
5694
- throw new InputError(
5695
- "No app id. Run from an app directory whose wrangler.toml carries DEEPSPACE_APP_ID, or pass --app <id or name>.",
5696
- "not_in_app_repo"
5697
- );
5698
- }
5913
+ const id = appDir ? readAppId(appDir, wranglerEnv) : null;
5914
+ if (!id) throw appTargetMissingError(wranglerEnv);
5699
5915
  return id;
5700
5916
  }
5701
5917
  function parseAppArg(raw) {
@@ -5721,17 +5937,42 @@ function parseAppArg(raw) {
5721
5937
  }
5722
5938
  return { app: selector };
5723
5939
  }
5724
- function assertAppTargetResolvable(appArg) {
5940
+ function parseWranglerEnvArg(raw) {
5941
+ if (raw === void 0) return {};
5942
+ const wranglerEnv = raw.trim();
5943
+ if (!wranglerEnv) {
5944
+ return {
5945
+ error: "--env was given an empty environment name \u2014 pass a wrangler.toml environment name, or omit --env to use the top-level app."
5946
+ };
5947
+ }
5948
+ return { wranglerEnv };
5949
+ }
5950
+ function appTargetMissingError(wranglerEnv) {
5951
+ if (wranglerEnv) {
5952
+ return new InputError(
5953
+ `No app id for env "${wranglerEnv}" \u2014 wrangler.toml has no [env.${wranglerEnv}] block with its own DEEPSPACE_APP_ID.`,
5954
+ "no_app_id_for_env"
5955
+ );
5956
+ }
5957
+ return new InputError(
5958
+ "No app id. Run from an app directory whose wrangler.toml carries DEEPSPACE_APP_ID, or pass --app <id or name>.",
5959
+ "not_in_app_repo"
5960
+ );
5961
+ }
5962
+ function assertAppTargetResolvable(appArg, options = {}) {
5725
5963
  const { app: app2, error } = parseAppArg(appArg);
5726
5964
  if (error) throw new InputError(error, "invalid_app");
5727
- if (app2 !== void 0) return;
5728
- const appDir = findAppDir();
5729
- if (!appDir || !readAppId(appDir)) {
5965
+ const { wranglerEnv, error: envError } = parseWranglerEnvArg(options.wranglerEnv);
5966
+ if (envError) throw new InputError(envError, "invalid_env");
5967
+ if (app2 !== void 0 && wranglerEnv) {
5730
5968
  throw new InputError(
5731
- "No app id. Run from an app directory whose wrangler.toml carries DEEPSPACE_APP_ID, or pass --app <id or name>.",
5732
- "not_in_app_repo"
5969
+ "Pass either --app or --env, not both \u2014 --env reads the [env.<name>] block's own app id, while --app names an app directly.",
5970
+ "ambiguous_target"
5733
5971
  );
5734
5972
  }
5973
+ if (app2 !== void 0) return;
5974
+ const appDir = findAppDir();
5975
+ if (!appDir || !readAppId(appDir, wranglerEnv)) throw appTargetMissingError(wranglerEnv);
5735
5976
  }
5736
5977
  async function warnIfPhantomApp(deployUrl, token, appId, explicit) {
5737
5978
  if (!explicit || !STRICT_APP_ID_RE.test(explicit.trim())) return;
@@ -5887,12 +6128,20 @@ var apps_default = defineDeepspaceCommand({
5887
6128
  if (!apps.length) {
5888
6129
  console.log("No apps yet. Create one with `npx create-deepspace <name>` and `deepspace deploy`.");
5889
6130
  } else {
5890
- const nameWidth = Math.max(4, ...apps.map((a) => (a.name ?? "\u2014").length));
5891
- const idWidth = Math.max(6, ...apps.map((a) => a.appId.length));
5892
- console.log(`${"NAME".padEnd(nameWidth)} ${"APP ID".padEnd(idWidth)} URL`);
5893
- for (const a of apps) {
6131
+ const rows = [...apps].sort(
6132
+ (left, right) => Number(right.status !== "undeployed") - Number(left.status !== "undeployed")
6133
+ );
6134
+ const nameWidth = Math.max(4, ...rows.map((a) => (a.name ?? "\u2014").length));
6135
+ const statusWidth = Math.max(6, ...rows.map((a) => a.status.length));
6136
+ const idWidth = Math.max(6, ...rows.map((a) => a.appId.length));
6137
+ console.log(
6138
+ `${"NAME".padEnd(nameWidth)} ${"STATUS".padEnd(statusWidth)} ${"APP ID".padEnd(idWidth)} URL`
6139
+ );
6140
+ for (const a of rows) {
5894
6141
  const url = a.url ?? "(not deployed)";
5895
- console.log(`${(a.name ?? "\u2014").padEnd(nameWidth)} ${a.appId.padEnd(idWidth)} ${url}`);
6142
+ console.log(
6143
+ `${(a.name ?? "\u2014").padEnd(nameWidth)} ${a.status.padEnd(statusWidth)} ${a.appId.padEnd(idWidth)} ${url}`
6144
+ );
5896
6145
  }
5897
6146
  }
5898
6147
  }
@@ -6058,28 +6307,10 @@ var logs_default = defineCommand4({
6058
6307
  }
6059
6308
  },
6060
6309
  async run({ args }) {
6310
+ const envArg = args.env;
6311
+ assertAppTargetResolvable(args.app, { wranglerEnv: envArg });
6061
6312
  const token = await ensureToken();
6062
- const wranglerEnv = args.env?.trim() || void 0;
6063
- if (args.app && wranglerEnv) {
6064
- throw new InputError(
6065
- "Pass either --app or --env, not both \u2014 --env reads the [env.<name>] block\u2019s own app id, --app names an app directly.",
6066
- "ambiguous_target"
6067
- );
6068
- }
6069
- let appId;
6070
- if (!args.app && wranglerEnv) {
6071
- const appDir = findAppDir();
6072
- const id = appDir ? readAppId(appDir, wranglerEnv) : null;
6073
- if (!id) {
6074
- throw new InputError(
6075
- `No app id for env "${wranglerEnv}" \u2014 wrangler.toml has no [env.${wranglerEnv}] block with its own DEEPSPACE_APP_ID.`,
6076
- "no_app_id_for_env"
6077
- );
6078
- }
6079
- appId = id;
6080
- } else {
6081
- appId = await resolveAppTarget(DEPLOY_URL6, token, args.app);
6082
- }
6313
+ const appId = await resolveAppTarget(DEPLOY_URL6, token, args.app, { wranglerEnv: envArg });
6083
6314
  if (args.level && !LOG_LEVELS.includes(args.level)) {
6084
6315
  throw new InputError(`Invalid --level "${args.level}". Use: ${LOG_LEVELS.join(", ")}`, "invalid_level");
6085
6316
  }
@@ -6248,20 +6479,20 @@ var usage_default = defineDeepspaceCommand({
6248
6479
  });
6249
6480
 
6250
6481
  // src/cli/commands/add.ts
6251
- import { existsSync as existsSync19, readFileSync as readFileSync18, realpathSync as realpathSync3 } from "fs";
6252
- import { dirname as dirname7, join as join18, resolve as resolve14 } from "path";
6482
+ import { existsSync as existsSync20, readFileSync as readFileSync20, realpathSync as realpathSync3 } from "fs";
6483
+ import { dirname as dirname7, join as join19, resolve as resolve14 } from "path";
6253
6484
  import { pathToFileURL as pathToFileURL3 } from "url";
6254
6485
 
6255
6486
  // src/cli/commands/feature-installer.ts
6256
6487
  import {
6257
6488
  closeSync as closeSync2,
6258
6489
  copyFileSync,
6259
- existsSync as existsSync18,
6490
+ existsSync as existsSync19,
6260
6491
  fstatSync as fstatSync2,
6261
6492
  mkdirSync as mkdirSync6,
6262
6493
  mkdtempSync,
6263
6494
  openSync as openSync2,
6264
- readFileSync as readFileSync17,
6495
+ readFileSync as readFileSync19,
6265
6496
  readSync,
6266
6497
  readdirSync as readdirSync5,
6267
6498
  rmSync as rmSync4,
@@ -6269,16 +6500,16 @@ import {
6269
6500
  } from "fs";
6270
6501
  import { spawnSync as spawnSync6 } from "child_process";
6271
6502
  import { tmpdir as tmpdir2 } from "os";
6272
- import { dirname as dirname6, isAbsolute as isAbsolute4, join as join17, resolve as resolve13 } from "path";
6503
+ import { dirname as dirname6, isAbsolute as isAbsolute4, join as join18, resolve as resolve13 } from "path";
6273
6504
  import { fileURLToPath } from "url";
6274
6505
  var NAV_MARKER = "// \u2500\u2500 Features add nav items below this line \u2500\u2500";
6275
6506
  function featurePackageRoot(moduleUrl = import.meta.url) {
6276
6507
  let dir = dirname6(fileURLToPath(moduleUrl));
6277
6508
  for (; ; ) {
6278
- const packagePath = join17(dir, "package.json");
6279
- if (existsSync18(packagePath)) {
6509
+ const packagePath = join18(dir, "package.json");
6510
+ if (existsSync19(packagePath)) {
6280
6511
  try {
6281
- const pkg2 = JSON.parse(readFileSync17(packagePath, "utf-8"));
6512
+ const pkg2 = JSON.parse(readFileSync19(packagePath, "utf-8"));
6282
6513
  if (pkg2.name === "deepspace") return dir;
6283
6514
  } catch {
6284
6515
  }
@@ -6289,13 +6520,13 @@ function featurePackageRoot(moduleUrl = import.meta.url) {
6289
6520
  }
6290
6521
  }
6291
6522
  function hasFeatureCatalog(packageRoot = featurePackageRoot()) {
6292
- return existsSync18(join17(packageRoot, "features"));
6523
+ return existsSync19(join18(packageRoot, "features"));
6293
6524
  }
6294
6525
  function readFeatureConfig(id, packageRoot = featurePackageRoot()) {
6295
- const configPath = join17(packageRoot, "features", id, "feature.json");
6296
- if (!existsSync18(configPath)) return null;
6526
+ const configPath = join18(packageRoot, "features", id, "feature.json");
6527
+ if (!existsSync19(configPath)) return null;
6297
6528
  try {
6298
- const config = JSON.parse(readFileSync17(configPath, "utf-8"));
6529
+ const config = JSON.parse(readFileSync19(configPath, "utf-8"));
6299
6530
  if (typeof config.id !== "string" || typeof config.name !== "string" || !Array.isArray(config.files)) {
6300
6531
  return null;
6301
6532
  }
@@ -6305,8 +6536,8 @@ function readFeatureConfig(id, packageRoot = featurePackageRoot()) {
6305
6536
  }
6306
6537
  }
6307
6538
  function listFeatureConfigs(packageRoot = featurePackageRoot()) {
6308
- const featuresDir = join17(packageRoot, "features");
6309
- if (!existsSync18(featuresDir)) return [];
6539
+ const featuresDir = join18(packageRoot, "features");
6540
+ if (!existsSync19(featuresDir)) return [];
6310
6541
  return readdirSync5(featuresDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => readFeatureConfig(entry.name, packageRoot)).filter((config) => config !== null && !config.internal).sort((a, b) => a.id.localeCompare(b.id));
6311
6542
  }
6312
6543
  function featureCatalog(packageRoot = featurePackageRoot()) {
@@ -6360,7 +6591,7 @@ function copyFile(src, dest) {
6360
6591
  function copyFileWithImportShift(src, dest, levels) {
6361
6592
  mkdirSync6(dirname6(dest), { recursive: true });
6362
6593
  const prefix = "../".repeat(levels);
6363
- const shifted = readFileSync17(src, "utf-8").replace(
6594
+ const shifted = readFileSync19(src, "utf-8").replace(
6364
6595
  /(from\s+|import\s+|import\()(['"])(\.\.\/)/g,
6365
6596
  `$1$2${prefix}$3`
6366
6597
  );
@@ -6373,12 +6604,12 @@ function installFiles(packageRoot, config, targetDir, emit) {
6373
6604
  const preexisting = [];
6374
6605
  const missing = [];
6375
6606
  for (const file of config.files) {
6376
- const srcPath = join17(packageRoot, "features", config.id, file.src);
6607
+ const srcPath = join18(packageRoot, "features", config.id, file.src);
6377
6608
  const resolvedDest = resolveFeatureDestPath(file.dest, config.route);
6378
- const destPath = join17(targetDir, resolvedDest);
6379
- const existed = existsSync18(destPath);
6609
+ const destPath = join18(targetDir, resolvedDest);
6610
+ const existed = existsSync19(destPath);
6380
6611
  if (existed) preexisting.push(resolvedDest);
6381
- if (!existsSync18(srcPath)) {
6612
+ if (!existsSync19(srcPath)) {
6382
6613
  emit(` Warning: source not found: ${file.src}`, "stderr");
6383
6614
  if (!existed) missing.push(resolvedDest);
6384
6615
  continue;
@@ -6415,11 +6646,11 @@ function unresolvedSchema(schema, reason, emit) {
6415
6646
  }
6416
6647
  function integrateSchema(config, targetDir, emit) {
6417
6648
  if (!config.schema) return [];
6418
- const schemasPath = join17(targetDir, "src", "schemas.ts");
6419
- if (!existsSync18(schemasPath)) {
6649
+ const schemasPath = join18(targetDir, "src", "schemas.ts");
6650
+ if (!existsSync19(schemasPath)) {
6420
6651
  return [unresolvedSchema(config.schema, "src/schemas.ts was not found.", emit)];
6421
6652
  }
6422
- let content = readFileSync17(schemasPath, "utf-8");
6653
+ let content = readFileSync19(schemasPath, "utf-8");
6423
6654
  const { exportName, importPath, spreadOperator } = config.schema;
6424
6655
  const importPattern = new RegExp(`^import\\s+\\{[^}]*\\b${exportName}\\b`, "m");
6425
6656
  if (importPattern.test(content)) {
@@ -6472,11 +6703,11 @@ function unresolvedActions(actions, reason, emit) {
6472
6703
  }
6473
6704
  function integrateActions(config, targetDir, emit) {
6474
6705
  if (!config.actions?.length) return [];
6475
- const actionsPath = join17(targetDir, "src", "actions", "index.ts");
6476
- if (!existsSync18(actionsPath)) {
6706
+ const actionsPath = join18(targetDir, "src", "actions", "index.ts");
6707
+ if (!existsSync19(actionsPath)) {
6477
6708
  return [unresolvedActions(config.actions, "src/actions/index.ts was not found.", emit)];
6478
6709
  }
6479
- let content = readFileSync17(actionsPath, "utf-8");
6710
+ let content = readFileSync19(actionsPath, "utf-8");
6480
6711
  const registryPattern = /export const actions:\s*Record<string,\s*ActionHandler(?:<[^>]*>)?>\s*=\s*\{/;
6481
6712
  if (!registryPattern.test(content)) {
6482
6713
  const reason = "src/actions/index.ts has an unexpected structure.";
@@ -6519,10 +6750,152 @@ ${registryEntry}`);
6519
6750
  if (integrated > 0) writeFileSync8(actionsPath, content);
6520
6751
  return unresolved;
6521
6752
  }
6753
+ function integrateCode(config, targetDir, emit) {
6754
+ const unresolved = [];
6755
+ for (const insertion of config.code ?? []) {
6756
+ const targetPath = join18(targetDir, insertion.file);
6757
+ if (!existsSync19(targetPath)) {
6758
+ unresolved.push({
6759
+ area: "code",
6760
+ reason: `${insertion.file} was not found.`,
6761
+ steps: [`Add ${insertion.insert.trim()} to ${insertion.file}.`]
6762
+ });
6763
+ continue;
6764
+ }
6765
+ const original = readFileSync19(targetPath, "utf-8");
6766
+ if (original.includes(insertion.insert.trim())) {
6767
+ emit(` Integration already present: ${insertion.file}`);
6768
+ continue;
6769
+ }
6770
+ const markerIndex = original.indexOf(insertion.marker);
6771
+ if (markerIndex < 0) {
6772
+ unresolved.push({
6773
+ area: "code",
6774
+ reason: `${insertion.file} does not contain the expected insertion point.`,
6775
+ steps: [
6776
+ ...insertion.import ? [`Add ${insertion.import} to ${insertion.file}.`] : [],
6777
+ `Add ${insertion.insert.trim()} to ${insertion.file}.`
6778
+ ]
6779
+ });
6780
+ continue;
6781
+ }
6782
+ let content = original;
6783
+ if (insertion.import && !content.includes(insertion.import)) {
6784
+ const importIndex = content.search(/^import\s/m);
6785
+ content = `${content.slice(0, importIndex < 0 ? 0 : importIndex)}${insertion.import}
6786
+ ${content.slice(importIndex < 0 ? 0 : importIndex)}`;
6787
+ }
6788
+ const currentMarkerIndex = content.indexOf(insertion.marker);
6789
+ const insertAt = insertion.placement === "before" ? currentMarkerIndex : currentMarkerIndex + insertion.marker.length;
6790
+ const prefix = insertion.placement === "before" ? "" : "\n";
6791
+ const suffix = insertion.placement === "before" ? "\n" : "";
6792
+ content = `${content.slice(0, insertAt)}${prefix}${insertion.insert}${suffix}${content.slice(insertAt)}`;
6793
+ writeFileSync8(targetPath, content);
6794
+ emit(` Integrated: ${insertion.file}`);
6795
+ }
6796
+ return unresolved;
6797
+ }
6798
+ function integrateIgnore(config, targetDir) {
6799
+ if (!config.ignore?.length) return;
6800
+ const targetPath = join18(targetDir, ".gitignore");
6801
+ const current = existsSync19(targetPath) ? readFileSync19(targetPath, "utf-8") : "";
6802
+ const present = new Set(current.split(/\r?\n/).map((line) => line.trim()));
6803
+ const missing = config.ignore.filter((pattern) => !present.has(pattern));
6804
+ if (missing.length === 0) return;
6805
+ const separator = current === "" || current.endsWith("\n") ? "" : "\n";
6806
+ writeFileSync8(targetPath, `${current}${separator}${missing.join("\n")}
6807
+ `);
6808
+ }
6809
+ function integrateWrangler(config, targetDir, emit) {
6810
+ const runWorkerFirst = config.wrangler?.runWorkerFirst === true;
6811
+ const rateLimits = config.wrangler?.rateLimits ?? [];
6812
+ if (!runWorkerFirst && rateLimits.length === 0) return [];
6813
+ const targetPath = join18(targetDir, "wrangler.toml");
6814
+ const steps = [
6815
+ ...runWorkerFirst ? ["Set assets.run_worker_first = true in wrangler.toml."] : [],
6816
+ ...rateLimits.map((binding) => `Add the ${binding.name} rate-limit binding to wrangler.toml.`)
6817
+ ];
6818
+ if (!existsSync19(targetPath)) {
6819
+ return [{ area: "wrangler", reason: "wrangler.toml was not found.", steps }];
6820
+ }
6821
+ let source = readFileSync19(targetPath, "utf-8");
6822
+ if (runWorkerFirst && !/^\[assets\]\s*$/m.test(source)) {
6823
+ return [{ area: "wrangler", reason: "wrangler.toml has no [assets] section.", steps }];
6824
+ }
6825
+ let changed = false;
6826
+ if (runWorkerFirst) {
6827
+ const updated = setRunWorkerFirst(source);
6828
+ changed ||= updated !== source;
6829
+ source = updated;
6830
+ }
6831
+ const scopes = ["", ...wranglerEnvironmentNames(source)];
6832
+ for (const scope of scopes) {
6833
+ for (const binding of rateLimits) {
6834
+ if (hasRateLimitBinding(source, scope, binding.name)) continue;
6835
+ source = `${source.trimEnd()}
6836
+
6837
+ ${rateLimitBindingBlock(scope, binding)}
6838
+ `;
6839
+ changed = true;
6840
+ }
6841
+ }
6842
+ if (changed) {
6843
+ writeFileSync8(targetPath, source);
6844
+ emit(" Integrated: wrangler.toml");
6845
+ } else emit(" Integration already present: wrangler.toml");
6846
+ return [];
6847
+ }
6848
+ function setRunWorkerFirst(source) {
6849
+ const headers = [...source.matchAll(/^\[(?:env\.[^\]]+\.)?assets\]\s*$/gm)];
6850
+ for (const header of headers.reverse()) {
6851
+ const start = header.index;
6852
+ const bodyStart = start + header[0].length;
6853
+ const nextHeader = source.slice(bodyStart).search(/^\[\[?[^\]]+\]\]?\s*$/m);
6854
+ const end = nextHeader < 0 ? source.length : bodyStart + nextHeader;
6855
+ const section = source.slice(start, end);
6856
+ if (/^\s*run_worker_first\s*=\s*true(?:\s*#.*)?$/m.test(section)) continue;
6857
+ const setting = /^(\s*)run_worker_first\s*=\s*(?:false|\[(?:[^\]"']|"(?:[^"\\]|\\[\s\S])*"|'[^']*')*\])(\s*(?:#.*)?)$/m;
6858
+ const updated = setting.test(section) ? section.replace(setting, "$1run_worker_first = true$2") : section.replace(/^\[(?:env\.[^\]]+\.)?assets\]\s*$/m, "$&\nrun_worker_first = true");
6859
+ source = source.slice(0, start) + updated + source.slice(end);
6860
+ }
6861
+ return source;
6862
+ }
6863
+ function wranglerEnvironmentNames(source) {
6864
+ return [
6865
+ ...new Set(
6866
+ [...source.matchAll(/^\[\[?env\.([^.\]]+)(?:\.|\]\]?\s*$)/gm)].map((match) => match[1])
6867
+ )
6868
+ ];
6869
+ }
6870
+ function hasRateLimitBinding(source, scope, name) {
6871
+ const header = scope ? `[[env.${scope}.ratelimits]]` : "[[ratelimits]]";
6872
+ const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6873
+ const namePattern = new RegExp(
6874
+ `^\\s*name\\s*=\\s*(?:"${escaped}"|'${escaped}')\\s*(?:#.*)?$`,
6875
+ "m"
6876
+ );
6877
+ let offset = 0;
6878
+ while ((offset = source.indexOf(header, offset)) >= 0) {
6879
+ const end = source.slice(offset + header.length).search(/^\[/m);
6880
+ const blockEnd = end < 0 ? source.length : offset + header.length + end;
6881
+ if (namePattern.test(source.slice(offset, blockEnd))) return true;
6882
+ offset += header.length;
6883
+ }
6884
+ return false;
6885
+ }
6886
+ function rateLimitBindingBlock(scope, binding) {
6887
+ const header = scope ? `[[env.${scope}.ratelimits]]` : "[[ratelimits]]";
6888
+ return [
6889
+ header,
6890
+ `name = ${JSON.stringify(binding.name)}`,
6891
+ `namespace_id = ${JSON.stringify(binding.namespaceId)}`,
6892
+ `simple = { limit = ${binding.limit}, period = ${binding.period} }`
6893
+ ].join("\n");
6894
+ }
6522
6895
  function integrateCss(packageRoot, config, targetDir, emit) {
6523
6896
  if (!config.css?.length) return [];
6524
- const stylesPath = join17(targetDir, "src", "styles.css");
6525
- if (!existsSync18(stylesPath)) {
6897
+ const stylesPath = join18(targetDir, "src", "styles.css");
6898
+ if (!existsSync19(stylesPath)) {
6526
6899
  emit(" Warning: Cannot integrate CSS \u2014 src/styles.css not found");
6527
6900
  return [
6528
6901
  {
@@ -6534,12 +6907,12 @@ function integrateCss(packageRoot, config, targetDir, emit) {
6534
6907
  }
6535
6908
  ];
6536
6909
  }
6537
- let styles = readFileSync17(stylesPath, "utf-8");
6910
+ let styles = readFileSync19(stylesPath, "utf-8");
6538
6911
  let integrated = 0;
6539
6912
  const unresolved = [];
6540
6913
  for (const cssFile of config.css) {
6541
- const srcPath = join17(packageRoot, "features", config.id, cssFile);
6542
- if (!existsSync18(srcPath)) {
6914
+ const srcPath = join18(packageRoot, "features", config.id, cssFile);
6915
+ if (!existsSync19(srcPath)) {
6543
6916
  emit(` Warning: CSS source not found: ${cssFile}`, "stderr");
6544
6917
  unresolved.push({
6545
6918
  area: "css",
@@ -6548,7 +6921,7 @@ function integrateCss(packageRoot, config, targetDir, emit) {
6548
6921
  });
6549
6922
  continue;
6550
6923
  }
6551
- const css = readFileSync17(srcPath, "utf-8");
6924
+ const css = readFileSync19(srcPath, "utf-8");
6552
6925
  const fingerprint = css.match(/\.([\w-]+)\s*\{|@keyframes\s+([\w-]+)/);
6553
6926
  const marker = fingerprint?.[1] ?? fingerprint?.[2];
6554
6927
  if (marker && styles.includes(marker)) {
@@ -6575,11 +6948,11 @@ function unresolvedRoute(route, config, reason, emit) {
6575
6948
  }
6576
6949
  function integrateRoute(config, targetDir, emit) {
6577
6950
  if (!config.route) return [];
6578
- const navPath = join17(targetDir, "src", "nav.ts");
6579
- if (!existsSync18(navPath)) {
6951
+ const navPath = join18(targetDir, "src", "nav.ts");
6952
+ if (!existsSync19(navPath)) {
6580
6953
  return [unresolvedRoute(config.route, config, "src/nav.ts was not found.", emit)];
6581
6954
  }
6582
- let content = readFileSync17(navPath, "utf-8");
6955
+ let content = readFileSync19(navPath, "utf-8");
6583
6956
  const routePath = config.route.path;
6584
6957
  if (content.includes(`'${routePath}'`)) {
6585
6958
  emit(` Nav already present: ${routePath}`);
@@ -6626,10 +6999,10 @@ function runPackageInstall(packageManager, targetDir, outputVisible) {
6626
6999
  const child = spawnSync6(packageManager, ["install"], { ...options, stdio: "inherit" });
6627
7000
  return { error: child.error, status: child.status };
6628
7001
  }
6629
- const captureDir = mkdtempSync(join17(tmpdir2(), "deepspace-add-install-"));
7002
+ const captureDir = mkdtempSync(join18(tmpdir2(), "deepspace-add-install-"));
6630
7003
  let outputFd;
6631
7004
  try {
6632
- outputFd = openSync2(join17(captureDir, "output.log"), "w+");
7005
+ outputFd = openSync2(join18(captureDir, "output.log"), "w+");
6633
7006
  const child = spawnSync6(packageManager, ["install"], {
6634
7007
  ...options,
6635
7008
  stdio: ["ignore", outputFd, outputFd]
@@ -6653,14 +7026,14 @@ function integrateDependencies(config, targetDir, runInstall, outputVisible, emi
6653
7026
  if (declarations.every(({ values }) => !values || Object.keys(values).length === 0)) {
6654
7027
  return { added: [], installRan: false, packageManager };
6655
7028
  }
6656
- const packagePath = join17(targetDir, "package.json");
6657
- if (!existsSync18(packagePath)) {
7029
+ const packagePath = join18(targetDir, "package.json");
7030
+ if (!existsSync19(packagePath)) {
6658
7031
  emit(" Could not patch package.json (file not found)");
6659
7032
  return { added: [], installRan: false, packageManager };
6660
7033
  }
6661
7034
  let pkg2;
6662
7035
  try {
6663
- pkg2 = JSON.parse(readFileSync17(packagePath, "utf-8"));
7036
+ pkg2 = JSON.parse(readFileSync19(packagePath, "utf-8"));
6664
7037
  } catch (error) {
6665
7038
  emit(
6666
7039
  ` Could not patch package.json (parse error: ${error instanceof Error ? error.message : String(error)})`
@@ -6720,7 +7093,7 @@ ${child.diagnosticTail}` : "";
6720
7093
  }
6721
7094
  function dependencySnapshot(targetDir) {
6722
7095
  try {
6723
- const pkg2 = JSON.parse(readFileSync17(join17(targetDir, "package.json"), "utf-8"));
7096
+ const pkg2 = JSON.parse(readFileSync19(join18(targetDir, "package.json"), "utf-8"));
6724
7097
  return {
6725
7098
  dependencies: pkg2.dependencies ?? {},
6726
7099
  devDependencies: pkg2.devDependencies ?? {}
@@ -6732,7 +7105,7 @@ function dependencySnapshot(targetDir) {
6732
7105
  function installedNavFeatures(packageRoot, targetDir) {
6733
7106
  return listFeatureConfigs(packageRoot).filter((feature) => feature.category === "nav" && feature.files.length > 0).filter((feature) => {
6734
7107
  const signature = resolveFeatureDestPath(feature.files[0].dest, feature.route);
6735
- return existsSync18(join17(targetDir, signature));
7108
+ return existsSync19(join18(targetDir, signature));
6736
7109
  }).map((feature) => feature.id);
6737
7110
  }
6738
7111
  function warnNavConflicts(packageRoot, config, targetDir, emit) {
@@ -6770,6 +7143,7 @@ function installFeature(options) {
6770
7143
  emit(` ${config.description ?? ""}`);
6771
7144
  emit(` Target: ${targetDir}`);
6772
7145
  const installedFiles = installFiles(packageRoot, config, targetDir, emit);
7146
+ integrateIgnore(config, targetDir);
6773
7147
  emit("");
6774
7148
  emit(
6775
7149
  `Copied ${installedFiles.copied} file(s)${installedFiles.skipped > 0 ? `, skipped ${installedFiles.skipped} existing` : ""}`
@@ -6777,6 +7151,8 @@ function installFeature(options) {
6777
7151
  const unresolvedIntegrations = [
6778
7152
  ...integrateSchema(config, targetDir, emit),
6779
7153
  ...integrateActions(config, targetDir, emit),
7154
+ ...integrateWrangler(config, targetDir, emit),
7155
+ ...integrateCode(config, targetDir, emit),
6780
7156
  ...integrateCss(packageRoot, config, targetDir, emit),
6781
7157
  ...integrateRoute(config, targetDir, emit)
6782
7158
  ];
@@ -6869,9 +7245,10 @@ var CATEGORY_LABELS = {
6869
7245
  nav: "Navigation",
6870
7246
  layout: "Layouts",
6871
7247
  display: "Display",
6872
- landing: "Landing Page Sections"
7248
+ landing: "Landing Page Sections",
7249
+ publishing: "Publishing"
6873
7250
  };
6874
- var CATEGORY_ORDER = ["assistant", "data", "nav", "layout", "display", "landing"];
7251
+ var CATEGORY_ORDER = ["assistant", "data", "publishing", "nav", "layout", "display", "landing"];
6875
7252
  function groupByCategory(features) {
6876
7253
  const groups = /* @__PURE__ */ new Map();
6877
7254
  for (const feature of features) {
@@ -6961,11 +7338,11 @@ function renderFeatureInfo(config) {
6961
7338
  return lines.join("\n");
6962
7339
  }
6963
7340
  function isDeepSpaceApp(dir) {
6964
- if (existsSync19(join18(dir, "wrangler.toml"))) return true;
6965
- const packagePath = join18(dir, "package.json");
6966
- if (!existsSync19(packagePath)) return false;
7341
+ if (existsSync20(join19(dir, "wrangler.toml"))) return true;
7342
+ const packagePath = join19(dir, "package.json");
7343
+ if (!existsSync20(packagePath)) return false;
6967
7344
  try {
6968
- const pkg2 = JSON.parse(readFileSync18(packagePath, "utf-8"));
7345
+ const pkg2 = JSON.parse(readFileSync20(packagePath, "utf-8"));
6969
7346
  return Boolean(pkg2.dependencies?.deepspace || pkg2.devDependencies?.deepspace);
6970
7347
  } catch {
6971
7348
  return false;
@@ -6974,8 +7351,8 @@ function isDeepSpaceApp(dir) {
6974
7351
  function installedPackageRoot(fromDir) {
6975
7352
  let dir = fromDir;
6976
7353
  for (; ; ) {
6977
- const candidate = join18(dir, "node_modules", "deepspace");
6978
- if (existsSync19(join18(candidate, "package.json"))) return candidate;
7354
+ const candidate = join19(dir, "node_modules", "deepspace");
7355
+ if (existsSync20(join19(candidate, "package.json"))) return candidate;
6979
7356
  const parent = dirname7(dir);
6980
7357
  if (parent === dir) return null;
6981
7358
  dir = parent;
@@ -6992,9 +7369,9 @@ async function targetInstaller(appDir) {
6992
7369
  if (realpathSync3(installedRoot) === realpathSync3(featurePackageRoot())) {
6993
7370
  return { installFeature };
6994
7371
  }
6995
- const entry = join18(installedRoot, "dist", "feature-installer.js");
6996
- const catalog = join18(installedRoot, "features");
6997
- if (!existsSync19(entry) || !existsSync19(catalog)) {
7372
+ const entry = join19(installedRoot, "dist", "feature-installer.js");
7373
+ const catalog = join19(installedRoot, "features");
7374
+ if (!existsSync20(entry) || !existsSync20(catalog)) {
6998
7375
  throw new Refusal(
6999
7376
  `The app's installed deepspace package does not contain the typed feature installer.
7000
7377
  Expected: ${entry}
@@ -7087,7 +7464,7 @@ var add_default = defineDeepspaceCommand({
7087
7464
  return { data: { features: featureCatalog(root) } };
7088
7465
  }
7089
7466
  const appDir = resolve14(args.dir ?? ".");
7090
- if (!existsSync19(appDir)) {
7467
+ if (!existsSync20(appDir)) {
7091
7468
  throw new Refusal(
7092
7469
  `Target directory not found: ${appDir}
7093
7470
  Create a new app first: deepspace app create <name>`,
@@ -7168,11 +7545,11 @@ function fmtDate(iso) {
7168
7545
  }
7169
7546
  async function confirm4(message) {
7170
7547
  process.stdout.write(`${message} [y/N] `);
7171
- return new Promise((resolve19) => {
7548
+ return new Promise((resolve18) => {
7172
7549
  process.stdin.setEncoding("utf-8");
7173
7550
  process.stdin.once("data", (data) => {
7174
7551
  const answer = data.toString().trim().toLowerCase();
7175
- resolve19(answer === "y" || answer === "yes");
7552
+ resolve18(answer === "y" || answer === "yes");
7176
7553
  });
7177
7554
  });
7178
7555
  }
@@ -7499,8 +7876,8 @@ var list3 = defineDeepspaceCommand({
7499
7876
  }
7500
7877
  if (pending.length) {
7501
7878
  console.log(`PENDING INVITES ON ${app2}`);
7502
- for (const p35 of pending) {
7503
- console.log(` ${p35.email} (expires ${new Date(p35.expiresAt).toLocaleDateString()})`);
7879
+ for (const p34 of pending) {
7880
+ console.log(` ${p34.email} (expires ${new Date(p34.expiresAt).toLocaleDateString()})`);
7504
7881
  }
7505
7882
  }
7506
7883
  }
@@ -7598,7 +7975,7 @@ var cancel3 = defineDeepspaceCommand({
7598
7975
  `/api/app-collaborators/${encodeURIComponent(app2)}`
7599
7976
  );
7600
7977
  const target = email.trim().toLowerCase();
7601
- const found = pending.find((p35) => p35.email.toLowerCase() === target);
7978
+ const found = pending.find((p34) => p34.email.toLowerCase() === target);
7602
7979
  if (!found) {
7603
7980
  throw new Refusal(`${email} has no pending invite on ${app2}`, "no_pending_invite", {
7604
7981
  action: cliAction("deepspace", "app", "collaborators", "list", "--app", app2)
@@ -7748,7 +8125,7 @@ var transfer_default = defineCommand7({
7748
8125
  import { defineCommand as defineCommand8 } from "citty";
7749
8126
 
7750
8127
  // src/cli/commands/_invoke-impl.ts
7751
- import { readFileSync as readFileSync19 } from "fs";
8128
+ import { readFileSync as readFileSync21 } from "fs";
7752
8129
  import * as p15 from "@clack/prompts";
7753
8130
  var API_URL7 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
7754
8131
  var DEFAULT_TIMEOUT_MS = 12e4;
@@ -7789,7 +8166,7 @@ function resolveBody(opts) {
7789
8166
  if (opts.body != null) {
7790
8167
  raw = opts.body;
7791
8168
  } else if (opts.bodyFile != null) {
7792
- raw = opts.bodyFile === "-" ? readFileSync19(0, "utf-8") : readFileSync19(opts.bodyFile, "utf-8");
8169
+ raw = opts.bodyFile === "-" ? readFileSync21(0, "utf-8") : readFileSync21(opts.bodyFile, "utf-8");
7793
8170
  } else {
7794
8171
  return "{}";
7795
8172
  }
@@ -8066,226 +8443,9 @@ var integrations_default = defineCommand8({
8066
8443
  }
8067
8444
  });
8068
8445
 
8069
- // src/cli/commands/library.ts
8070
- import { defineCommand as defineCommand9 } from "citty";
8071
- import { existsSync as existsSync20, readFileSync as readFileSync20 } from "fs";
8072
- import { join as join19, resolve as resolve15 } from "path";
8073
- import { parse as parseToml4 } from "smol-toml";
8074
- import * as p16 from "@clack/prompts";
8075
- var DEFAULT_LIBRARY_APP = "deepdotspace-site";
8076
- var str = (v) => typeof v === "string" && v ? v : void 0;
8077
- var publish = defineDeepspaceCommand({
8078
- meta: {
8079
- name: "publish",
8080
- description: "Publish the current app to the DeepSpace library"
8081
- },
8082
- args: {
8083
- dir: {
8084
- type: "string",
8085
- description: "App directory (default: current directory)",
8086
- required: false
8087
- },
8088
- app: {
8089
- type: "string",
8090
- description: "App name (default: read from wrangler.toml)",
8091
- required: false
8092
- },
8093
- name: {
8094
- type: "string",
8095
- description: "Display name (default: app name)",
8096
- required: false
8097
- },
8098
- description: {
8099
- type: "string",
8100
- description: "Short description",
8101
- required: false
8102
- },
8103
- category: {
8104
- type: "string",
8105
- description: "Category (e.g. Productivity, Finance, AI). Default: General",
8106
- required: false
8107
- },
8108
- tags: {
8109
- type: "string",
8110
- description: "Comma-separated tags",
8111
- required: false
8112
- },
8113
- visibility: {
8114
- type: "string",
8115
- description: "'public' (default), 'unlisted', or 'private'",
8116
- required: false
8117
- },
8118
- screenshot: {
8119
- type: "string",
8120
- description: "Override the auto-captured preview with a public PNG URL",
8121
- required: false
8122
- },
8123
- "library-app": {
8124
- type: "string",
8125
- description: `Target library app subdomain (default: ${DEFAULT_LIBRARY_APP})`,
8126
- required: false
8127
- }
8128
- },
8129
- async run({ args }) {
8130
- const json = args.json;
8131
- const intro6 = (s) => {
8132
- if (!json) p16.intro(s);
8133
- };
8134
- const info2 = (s) => {
8135
- if (!json) p16.log.info(s);
8136
- };
8137
- intro6("Publishing to the DeepSpace library");
8138
- const appErr = parseAppArg(str(args.app)).error;
8139
- if (appErr) throw new Refusal(appErr, "invalid_app");
8140
- let appName = str(args.app)?.trim();
8141
- if (!appName) {
8142
- const appDir = resolve15(str(args.dir) ?? ".");
8143
- const wranglerPath = join19(appDir, "wrangler.toml");
8144
- if (!existsSync20(wranglerPath)) {
8145
- throw new Refusal(
8146
- "No --app provided and no wrangler.toml found. Either pass --app <name> or run from your app's directory.",
8147
- "not_in_app_repo"
8148
- );
8149
- }
8150
- const cfg = parseToml4(readFileSync20(wranglerPath, "utf-8"));
8151
- const nameRes = resolveAppName(cfg.name);
8152
- if (!nameRes.ok) throw new Refusal(`wrangler.toml: ${nameRes.reason}`, "invalid_config");
8153
- appName = nameRes.name;
8154
- }
8155
- info2(`App: ${appName}`);
8156
- const visibility = str(args.visibility) ?? "public";
8157
- if (!["public", "private", "unlisted"].includes(visibility)) {
8158
- throw new Refusal("--visibility must be 'public', 'unlisted', or 'private'.", "invalid_visibility");
8159
- }
8160
- const tags = str(args.tags) ? String(args.tags).split(",").map((t) => t.trim()).filter(Boolean) : void 0;
8161
- const libraryApp = str(args["library-app"]) ?? DEFAULT_LIBRARY_APP;
8162
- const libraryHost = process.env.DEEPSPACE_LIBRARY_HOST ?? `https://${libraryApp}.app.space`;
8163
- let token;
8164
- try {
8165
- token = await ensureToken();
8166
- } catch (err) {
8167
- throw new Refusal(err instanceof Error ? err.message : String(err), "not_authenticated", {
8168
- action: cliAction("deepspace", "auth", "login")
8169
- });
8170
- }
8171
- const payload = {
8172
- appName,
8173
- ...args.name ? { name: args.name } : {},
8174
- ...args.description ? { description: args.description } : {},
8175
- ...args.category ? { category: args.category } : {},
8176
- ...tags ? { tags } : {},
8177
- visibility,
8178
- ...args.screenshot ? { screenshotUrl: args.screenshot } : {}
8179
- };
8180
- const url = `${libraryHost}/api/actions/library.publish`;
8181
- let res;
8182
- try {
8183
- res = await fetch(url, {
8184
- method: "POST",
8185
- headers: {
8186
- "Content-Type": "application/json",
8187
- Authorization: `Bearer ${token}`
8188
- },
8189
- body: JSON.stringify(payload)
8190
- });
8191
- } catch (err) {
8192
- throw new Refusal(
8193
- `Network error reaching ${url}: ${err instanceof Error ? err.message : String(err)}`,
8194
- "network_error"
8195
- );
8196
- }
8197
- const body = await res.text();
8198
- let parsed;
8199
- try {
8200
- parsed = JSON.parse(body);
8201
- } catch {
8202
- throw new Refusal(`Server returned non-JSON (${res.status}): ${body.slice(0, 400)}`, "invalid_response");
8203
- }
8204
- if (!parsed.success) {
8205
- throw new Refusal(parsed.error ?? `Publish failed (HTTP ${res.status}).`, "publish_failed");
8206
- }
8207
- if (!json) {
8208
- p16.log.success(`Published ${appName} as ${parsed.data?.templateHandle} (v${parsed.data?.version})`);
8209
- if (parsed.data?.url) p16.log.message(parsed.data.url);
8210
- p16.outro("Done");
8211
- }
8212
- return {
8213
- data: {
8214
- appName,
8215
- templateHandle: parsed.data?.templateHandle ?? null,
8216
- url: parsed.data?.url ?? null,
8217
- version: parsed.data?.version ?? null,
8218
- visibility
8219
- }
8220
- };
8221
- }
8222
- });
8223
- var unpublish = defineDeepspaceCommand({
8224
- meta: {
8225
- name: "unpublish",
8226
- description: "Remove a library entry (owner or admin only)"
8227
- },
8228
- args: {
8229
- handle: {
8230
- type: "positional",
8231
- description: "templateHandle to remove (usually your app name)",
8232
- required: true
8233
- },
8234
- "library-app": {
8235
- type: "string",
8236
- description: `Target library app subdomain (default: ${DEFAULT_LIBRARY_APP})`,
8237
- required: false
8238
- }
8239
- },
8240
- async run({ args }) {
8241
- const json = args.json;
8242
- if (!json) p16.intro("Unpublishing library entry");
8243
- let token;
8244
- try {
8245
- token = await ensureToken();
8246
- } catch (err) {
8247
- throw new Refusal(err instanceof Error ? err.message : String(err), "not_authenticated", {
8248
- action: cliAction("deepspace", "auth", "login")
8249
- });
8250
- }
8251
- const libraryApp = str(args["library-app"]) ?? DEFAULT_LIBRARY_APP;
8252
- const libraryHost = process.env.DEEPSPACE_LIBRARY_HOST ?? `https://${libraryApp}.app.space`;
8253
- const res = await fetch(`${libraryHost}/api/actions/library.unpublish`, {
8254
- method: "POST",
8255
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
8256
- body: JSON.stringify({ templateHandle: String(args.handle) })
8257
- });
8258
- const body = await res.text();
8259
- let parsed;
8260
- try {
8261
- parsed = JSON.parse(body);
8262
- } catch {
8263
- throw new Refusal(`Server returned non-JSON (${res.status}): ${body.slice(0, 400)}`, "invalid_response");
8264
- }
8265
- if (!parsed.success) {
8266
- throw new Refusal(parsed.error ?? `HTTP ${res.status}`, "unpublish_failed");
8267
- }
8268
- if (!json) {
8269
- p16.log.success(`Unpublished ${parsed.data?.templateHandle}`);
8270
- p16.outro("Done");
8271
- }
8272
- return { data: { templateHandle: parsed.data?.templateHandle ?? String(args.handle) } };
8273
- }
8274
- });
8275
- var library_default = defineCommand9({
8276
- meta: {
8277
- name: "library",
8278
- description: "Manage your DeepSpace library entries"
8279
- },
8280
- subCommands: {
8281
- publish,
8282
- unpublish
8283
- }
8284
- });
8285
-
8286
8446
  // src/cli/commands/feedback.ts
8287
- import * as p17 from "@clack/prompts";
8288
- import { existsSync as existsSync21, readFileSync as readFileSync21 } from "fs";
8447
+ import * as p16 from "@clack/prompts";
8448
+ import { existsSync as existsSync21, readFileSync as readFileSync22 } from "fs";
8289
8449
  import { dirname as dirname8, join as join20 } from "path";
8290
8450
  import { fileURLToPath as fileURLToPath2 } from "url";
8291
8451
  import { release } from "os";
@@ -8314,7 +8474,7 @@ function readCliVersion() {
8314
8474
  ];
8315
8475
  for (const path of candidates) {
8316
8476
  if (!existsSync21(path)) continue;
8317
- const pkg2 = JSON.parse(readFileSync21(path, "utf-8"));
8477
+ const pkg2 = JSON.parse(readFileSync22(path, "utf-8"));
8318
8478
  if (pkg2.name === "deepspace" && pkg2.version) return pkg2.version;
8319
8479
  }
8320
8480
  } catch {
@@ -8407,21 +8567,21 @@ var feedback_default = defineDeepspaceCommand({
8407
8567
  );
8408
8568
  }
8409
8569
  if (interactive && (!args.type || !title || !body)) {
8410
- p17.intro("DeepSpace Feedback");
8570
+ p16.intro("DeepSpace Feedback");
8411
8571
  if (!args.type) {
8412
- const picked = await p17.select({
8572
+ const picked = await p16.select({
8413
8573
  message: "What kind of feedback is this?",
8414
8574
  options: TYPES.map((t) => ({ value: t, label: TYPE_LABELS[t] })),
8415
8575
  initialValue: type
8416
8576
  });
8417
- if (p17.isCancel(picked)) {
8418
- p17.cancel("Cancelled.");
8577
+ if (p16.isCancel(picked)) {
8578
+ p16.cancel("Cancelled.");
8419
8579
  return { data: { submitted: false, cancelled: true } };
8420
8580
  }
8421
8581
  type = picked;
8422
8582
  }
8423
8583
  if (!title) {
8424
- const t = await p17.text({
8584
+ const t = await p16.text({
8425
8585
  message: "Title",
8426
8586
  placeholder: "Short summary of the issue",
8427
8587
  validate: (v) => {
@@ -8431,14 +8591,14 @@ var feedback_default = defineDeepspaceCommand({
8431
8591
  return void 0;
8432
8592
  }
8433
8593
  });
8434
- if (p17.isCancel(t)) {
8435
- p17.cancel("Cancelled.");
8594
+ if (p16.isCancel(t)) {
8595
+ p16.cancel("Cancelled.");
8436
8596
  return { data: { submitted: false, cancelled: true } };
8437
8597
  }
8438
8598
  title = String(t);
8439
8599
  }
8440
8600
  if (!body) {
8441
- const m = await p17.text({
8601
+ const m = await p16.text({
8442
8602
  message: "Details",
8443
8603
  placeholder: "What happened? Steps to reproduce, what you expected\u2026",
8444
8604
  validate: (v) => {
@@ -8448,8 +8608,8 @@ var feedback_default = defineDeepspaceCommand({
8448
8608
  return void 0;
8449
8609
  }
8450
8610
  });
8451
- if (p17.isCancel(m)) {
8452
- p17.cancel("Cancelled.");
8611
+ if (p16.isCancel(m)) {
8612
+ p16.cancel("Cancelled.");
8453
8613
  return { data: { submitted: false, cancelled: true } };
8454
8614
  }
8455
8615
  body = String(m);
@@ -8467,7 +8627,7 @@ var feedback_default = defineDeepspaceCommand({
8467
8627
  throw new Refusal(err.message, "invalid_input");
8468
8628
  }
8469
8629
  if (interactive && !args.yes) {
8470
- p17.note(
8630
+ p16.note(
8471
8631
  `${TYPE_LABELS[payload.type]}
8472
8632
  ${payload.title}
8473
8633
 
@@ -8476,9 +8636,9 @@ ${payload.body}
8476
8636
  Context: deepspace ${payload.cliVersion} \xB7 ${payload.nodeVersion} \xB7 ${payload.platform}` + (payload.appName ? ` \xB7 app ${payload.appName}` : ""),
8477
8637
  "Review"
8478
8638
  );
8479
- const ok2 = await p17.confirm({ message: "Send this to the DeepSpace team?" });
8480
- if (p17.isCancel(ok2) || !ok2) {
8481
- p17.cancel("Not sent.");
8639
+ const ok2 = await p16.confirm({ message: "Send this to the DeepSpace team?" });
8640
+ if (p16.isCancel(ok2) || !ok2) {
8641
+ p16.cancel("Not sent.");
8482
8642
  return { data: { submitted: false, cancelled: true } };
8483
8643
  }
8484
8644
  }
@@ -8498,7 +8658,7 @@ Context: deepspace ${payload.cliVersion} \xB7 ${payload.nodeVersion} \xB7 ${payl
8498
8658
  });
8499
8659
  if (!args.json) {
8500
8660
  if (interactive) {
8501
- p17.outro(`Thanks! Submitted as ${result.report.id}.`);
8661
+ p16.outro(`Thanks! Submitted as ${result.report.id}.`);
8502
8662
  } else {
8503
8663
  console.log(`Submitted feedback ${result.report.id} (${result.report.type}).`);
8504
8664
  }
@@ -8509,8 +8669,8 @@ Context: deepspace ${payload.cliVersion} \xB7 ${payload.nodeVersion} \xB7 ${payl
8509
8669
  });
8510
8670
 
8511
8671
  // src/cli/commands/secrets.ts
8512
- import { defineCommand as defineCommand10 } from "citty";
8513
- import { readFileSync as readFileSync22 } from "fs";
8672
+ import { defineCommand as defineCommand9 } from "citty";
8673
+ import { readFileSync as readFileSync23 } from "fs";
8514
8674
 
8515
8675
  // src/cli/lib/citty-args.ts
8516
8676
  function dedupePositionals(bound, rest) {
@@ -8613,7 +8773,7 @@ function warnIfDebugRoutesEnabled(secrets) {
8613
8773
  }
8614
8774
  var APPLY_HINT = "Run `deepspace deploy` to apply \u2014 secrets take effect at deploy time.";
8615
8775
  var LOCAL_DEV_HINT = "Running `deepspace dev start`? Restart it to load the change locally.";
8616
- var list5 = defineCommand10({
8776
+ var list5 = defineCommand9({
8617
8777
  meta: { name: "list", description: "List masked secrets in a config" },
8618
8778
  args: {
8619
8779
  ...COMMON_ARGS,
@@ -8643,7 +8803,7 @@ var list5 = defineCommand10({
8643
8803
  }
8644
8804
  }
8645
8805
  });
8646
- var set = defineCommand10({
8806
+ var set = defineCommand9({
8647
8807
  meta: { name: "set", description: "Set secrets from KEY=value pairs" },
8648
8808
  args: {
8649
8809
  ...COMMON_ARGS,
@@ -8692,7 +8852,7 @@ var set = defineCommand10({
8692
8852
  }
8693
8853
  }
8694
8854
  });
8695
- var get = defineCommand10({
8855
+ var get = defineCommand9({
8696
8856
  meta: { name: "get", description: "Get a secret; pass --plain to print the value" },
8697
8857
  args: {
8698
8858
  ...COMMON_ARGS,
@@ -8728,7 +8888,7 @@ var get = defineCommand10({
8728
8888
  }
8729
8889
  }
8730
8890
  });
8731
- var del2 = defineCommand10({
8891
+ var del2 = defineCommand9({
8732
8892
  meta: { name: "delete", description: "Delete one or more secrets" },
8733
8893
  args: {
8734
8894
  ...COMMON_ARGS,
@@ -8762,7 +8922,7 @@ var del2 = defineCommand10({
8762
8922
  }
8763
8923
  }
8764
8924
  });
8765
- var upload = defineCommand10({
8925
+ var upload = defineCommand9({
8766
8926
  meta: { name: "upload", description: "Upload secrets from a dotenv or JSON file" },
8767
8927
  args: {
8768
8928
  ...COMMON_ARGS,
@@ -8780,7 +8940,7 @@ var upload = defineCommand10({
8780
8940
  async run({ args }) {
8781
8941
  try {
8782
8942
  const t = await resolveTarget(args);
8783
- const content = args.file === "-" ? readFileSync22(0, "utf-8") : readFileSync22(args.file, "utf-8");
8943
+ const content = args.file === "-" ? readFileSync23(0, "utf-8") : readFileSync23(args.file, "utf-8");
8784
8944
  const secrets = parseSecretsUpload(content);
8785
8945
  if (Object.keys(secrets).length === 0)
8786
8946
  fail(new InputError("No secrets found in the input.", "empty_input"));
@@ -8799,7 +8959,7 @@ var upload = defineCommand10({
8799
8959
  }
8800
8960
  }
8801
8961
  });
8802
- var download = defineCommand10({
8962
+ var download = defineCommand9({
8803
8963
  meta: { name: "download", description: "Download a config\u2019s secrets (dotenv/json/shell)" },
8804
8964
  args: {
8805
8965
  ...COMMON_ARGS,
@@ -8828,7 +8988,7 @@ var download = defineCommand10({
8828
8988
  }
8829
8989
  }
8830
8990
  });
8831
- var pull = defineCommand10({
8991
+ var pull = defineCommand9({
8832
8992
  meta: { name: "pull", description: "Refresh the .dev.vars cache from the app store" },
8833
8993
  args: { ...COMMON_ARGS },
8834
8994
  async run({ args }) {
@@ -8871,7 +9031,7 @@ var pull = defineCommand10({
8871
9031
  }
8872
9032
  }
8873
9033
  });
8874
- var configsList = defineCommand10({
9034
+ var configsList = defineCommand9({
8875
9035
  meta: { name: "list", description: "List the app\u2019s configs" },
8876
9036
  args: { ...COMMON_ARGS },
8877
9037
  async run({ args }) {
@@ -8892,7 +9052,7 @@ var configsList = defineCommand10({
8892
9052
  }
8893
9053
  }
8894
9054
  });
8895
- var configsCreate = defineCommand10({
9055
+ var configsCreate = defineCommand9({
8896
9056
  meta: { name: "create", description: "Create a config (optionally copying another)" },
8897
9057
  args: {
8898
9058
  ...COMMON_ARGS,
@@ -8932,7 +9092,7 @@ var configsCreate = defineCommand10({
8932
9092
  }
8933
9093
  }
8934
9094
  });
8935
- var configsDelete = defineCommand10({
9095
+ var configsDelete = defineCommand9({
8936
9096
  meta: { name: "delete", description: "Delete a config and all of its secrets" },
8937
9097
  args: {
8938
9098
  ...COMMON_ARGS,
@@ -8953,11 +9113,11 @@ var configsDelete = defineCommand10({
8953
9113
  }
8954
9114
  }
8955
9115
  });
8956
- var configs = defineCommand10({
9116
+ var configs = defineCommand9({
8957
9117
  meta: { name: "configs", description: "Manage the app\u2019s secrets configs" },
8958
9118
  subCommands: { list: configsList, create: configsCreate, delete: configsDelete }
8959
9119
  });
8960
- var secrets_default = defineCommand10({
9120
+ var secrets_default = defineCommand9({
8961
9121
  meta: { name: "secrets", description: "Manage the app\u2019s secrets store" },
8962
9122
  subCommands: {
8963
9123
  list: list5,
@@ -9034,9 +9194,9 @@ var init_default = defineDeepspaceCommand({
9034
9194
  });
9035
9195
 
9036
9196
  // src/cli/commands/status.ts
9037
- import { defineCommand as defineCommand11 } from "citty";
9038
- import * as p18 from "@clack/prompts";
9039
- import { existsSync as existsSync22, readFileSync as readFileSync23 } from "fs";
9197
+ import { defineCommand as defineCommand10 } from "citty";
9198
+ import * as p17 from "@clack/prompts";
9199
+ import { existsSync as existsSync22, readFileSync as readFileSync24 } from "fs";
9040
9200
  function timeAgo(iso) {
9041
9201
  const ms = Date.now() - Date.parse(iso);
9042
9202
  if (!Number.isFinite(ms) || ms < 0) return iso;
@@ -9061,7 +9221,15 @@ function statusRemoteFailure(error) {
9061
9221
  json: { state: "unavailable", error: message, ...code ? { code } : {} }
9062
9222
  };
9063
9223
  }
9064
- var status_default = defineCommand11({
9224
+ function resolveStatusApp(appDir, wranglerEnv) {
9225
+ if (!wranglerEnv) {
9226
+ return { appName: detectAppName(appDir) ?? "(unnamed)", appId: readAppId(appDir) };
9227
+ }
9228
+ const resolved = resolveAppNameForEnv(readWranglerConfig(appDir), wranglerEnv);
9229
+ if (!resolved.ok) throw new InputError(resolved.reason, "invalid_env");
9230
+ return { appName: resolved.name, appId: readAppId(appDir, wranglerEnv) };
9231
+ }
9232
+ var status_default = defineCommand10({
9065
9233
  meta: {
9066
9234
  name: "status",
9067
9235
  description: "One screenful of current state: session, app, workspace, and live release"
@@ -9071,9 +9239,17 @@ var status_default = defineCommand11({
9071
9239
  type: "boolean",
9072
9240
  description: "Emit a single-line JSON result for scripts/agents",
9073
9241
  default: false
9242
+ },
9243
+ env: {
9244
+ type: "string",
9245
+ alias: "e",
9246
+ description: "wrangler.toml [env.<name>] slot \u2014 reads that environment\u2019s app identity",
9247
+ required: false
9074
9248
  }
9075
9249
  },
9076
9250
  async run({ args }) {
9251
+ const { wranglerEnv, error: envError } = parseWranglerEnvArg(args.env);
9252
+ if (envError) throw new InputError(envError, "invalid_env");
9077
9253
  const lines = [];
9078
9254
  const facts = {
9079
9255
  loggedIn: false,
@@ -9114,7 +9290,7 @@ var status_default = defineCommand11({
9114
9290
  let who = "logged in";
9115
9291
  try {
9116
9292
  const payload = decodeJwtPayload(
9117
- readFileSync23(TOKEN_PATH, "utf-8").trim()
9293
+ readFileSync24(TOKEN_PATH, "utf-8").trim()
9118
9294
  );
9119
9295
  if (payload.email) who = payload.email;
9120
9296
  userId = payload.sub ?? null;
@@ -9129,15 +9305,15 @@ var status_default = defineCommand11({
9129
9305
  lines.push(["App", "not inside a DeepSpace app directory"]);
9130
9306
  json.inApp = false;
9131
9307
  } else {
9132
- const appName = detectAppName(appDir) ?? "(unnamed)";
9133
- const appId = readAppId(appDir);
9308
+ const { appName, appId } = resolveStatusApp(appDir, wranglerEnv);
9134
9309
  lines.push([
9135
9310
  "App",
9136
- `${appName}${appId ? ` (${appId})` : " \u2014 no DEEPSPACE_APP_ID; run `deepspace app init`"}`
9311
+ `${appName}${wranglerEnv ? ` [env.${wranglerEnv}]` : ""}${appId ? ` (${appId})` : " \u2014 no DEEPSPACE_APP_ID; run `deepspace app init`"}`
9137
9312
  ]);
9138
9313
  json.inApp = true;
9139
9314
  json.appName = appName;
9140
9315
  json.appId = appId;
9316
+ if (wranglerEnv) json.wranglerEnv = wranglerEnv;
9141
9317
  const deps = installState(appDir);
9142
9318
  const depsInstallCommand = installCommand(appDir);
9143
9319
  lines.push([
@@ -9286,12 +9462,12 @@ var status_default = defineCommand11({
9286
9462
  return;
9287
9463
  }
9288
9464
  const width = Math.max(...lines.map(([k]) => k.length));
9289
- for (const [k, v] of lines) p18.log.message(`${k.padEnd(width)} ${v}`);
9465
+ for (const [k, v] of lines) p17.log.message(`${k.padEnd(width)} ${v}`);
9290
9466
  }
9291
9467
  });
9292
9468
 
9293
9469
  // src/cli/commands/push.ts
9294
- import * as p19 from "@clack/prompts";
9470
+ import * as p18 from "@clack/prompts";
9295
9471
  function pullRecoveryAction(cwd, appId, branch) {
9296
9472
  return {
9297
9473
  cwd,
@@ -9425,7 +9601,7 @@ var push_default = defineDeepspaceCommand({
9425
9601
  ensureSpaceRemote(appDir, appId);
9426
9602
  if (force) {
9427
9603
  spinner2?.message(`Checking the cloud ${branch} tip before force-push\u2026`);
9428
- const lastPushed = resolveCommit(appDir, `refs/deepspace/pushed/${branch}`);
9604
+ const lastPushed = resolveCommit(appDir, spacePrivateRef(`pushed/${branch}`));
9429
9605
  let remoteTip = null;
9430
9606
  try {
9431
9607
  runGitRemote(appDir, token, [
@@ -9470,12 +9646,12 @@ var push_default = defineDeepspaceCommand({
9470
9646
  );
9471
9647
  const ok2 = result.status === "up_to_date" || result.status === "committed";
9472
9648
  if (ok2) {
9473
- updateRef(appDir, `refs/deepspace/pushed/${branch}`, tipOid);
9649
+ updateRef(appDir, spacePrivateRef(`pushed/${branch}`), tipOid);
9474
9650
  }
9475
9651
  const recoverable = isRecoverablePushFailure(result.status);
9476
9652
  const rejectReason = result.reason ?? result.summary;
9477
9653
  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.`;
9478
- if (!args.json && ok2) p19.log.info(`App: ${appId}`);
9654
+ if (!args.json && ok2) p18.log.info(`App: ${appId}`);
9479
9655
  const action = recoverable ? pullRecoveryAction(pullRecoveryCwd, appId, branch) : void 0;
9480
9656
  if (!ok2) {
9481
9657
  throw new Refusal(errorMsg ?? `Push did not complete.`, result.status, {
@@ -9497,7 +9673,7 @@ var push_default = defineDeepspaceCommand({
9497
9673
  });
9498
9674
 
9499
9675
  // src/cli/commands/pull.ts
9500
- import * as p20 from "@clack/prompts";
9676
+ import * as p19 from "@clack/prompts";
9501
9677
  import { realpathSync as realpathSync4 } from "fs";
9502
9678
  function targetedVcAction(command, cwd, appId, branch) {
9503
9679
  return {
@@ -9512,7 +9688,7 @@ function workspaceBranchPullRefusal(branch, trunkBranch) {
9512
9688
  const id = workspaceIdFromBranch(branch);
9513
9689
  if (!id) return null;
9514
9690
  const trunk = trunkBranch || "main";
9515
- const merge = `git merge refs/remotes/space/${shQuote(trunk)}`;
9691
+ const merge = `git merge ${spaceTrackingRef(shQuote(trunk))}`;
9516
9692
  return {
9517
9693
  code: "workspace_branch",
9518
9694
  trunk,
@@ -9535,7 +9711,7 @@ function worktreeHoldingBranch(worktrees, branch, selfPath) {
9535
9711
  }
9536
9712
  function divergedMergeAdvice(branch, hasSelectedCheckout) {
9537
9713
  const b = shQuote(branch);
9538
- return hasSelectedCheckout ? `git merge refs/remotes/space/${b}` : `git checkout ${b} && git merge refs/remotes/space/${b}`;
9714
+ return hasSelectedCheckout ? `git merge ${spaceTrackingRef(b)}` : `git checkout ${b} && git merge ${spaceTrackingRef(b)}`;
9539
9715
  }
9540
9716
  var pull_default = defineDeepspaceCommand({
9541
9717
  meta: {
@@ -9608,11 +9784,12 @@ var pull_default = defineDeepspaceCommand({
9608
9784
  const wsRefusal = workspaceBranchPullRefusal(branch, trunkBranch);
9609
9785
  if (wsRefusal) {
9610
9786
  const trunkRef = `refs/heads/${wsRefusal.trunk}`;
9787
+ const trunkTrackingRef = spaceTrackingRef(wsRefusal.trunk);
9611
9788
  runGitRemote(appDir, token, [
9612
9789
  "fetch",
9613
9790
  "--quiet",
9614
9791
  SPACE_REMOTE,
9615
- `+${trunkRef}:refs/remotes/space/${wsRefusal.trunk}`
9792
+ `+${trunkRef}:${trunkTrackingRef}`
9616
9793
  ]);
9617
9794
  spinner2?.stop("Workspace branch.");
9618
9795
  const { path: branchWorktreePath2 } = selectedBranchCheckout2();
@@ -9625,7 +9802,7 @@ var pull_default = defineDeepspaceCommand({
9625
9802
  const step = branchWorktreePath2 ? selectedCheckoutClean2 ? `Merge the fetched trunk in the checkout that owns ${branch} (${branchWorktreePath2}).` : `The checkout that owns ${branch} (${branchWorktreePath2}) is dirty. Commit or stash there, then rerun \`${pullCommand2}\` from that checkout.` : localBranchExists ? canCheckoutHere2 ? `The branch is not checked out. Check it out here, then rerun \`${pullCommand2}\` to receive the merge step.` : `The branch is not checked out and this checkout is dirty. Commit or stash first (or use a clean worktree), check out ${shQuote(branch)}, then rerun \`${pullCommand2}\`.` : `This clone has not attached the workspace. Attach ${workspaceId}, then rerun \`${pullCommand2}\` from its checkout.`;
9626
9803
  const action = branchWorktreePath2 && selectedCheckoutClean2 ? {
9627
9804
  cwd: branchWorktreePath2,
9628
- argv: ["git", "merge", `refs/remotes/space/${wsRefusal.trunk}`]
9805
+ argv: ["git", "merge", trunkTrackingRef]
9629
9806
  } : canCheckoutHere2 ? { cwd: appDir, argv: ["git", "checkout", branch] } : !localBranchExists ? {
9630
9807
  cwd: appDir,
9631
9808
  argv: ["deepspace", "workspace", "attach", workspaceId, "--app", appId]
@@ -9649,17 +9826,18 @@ var pull_default = defineDeepspaceCommand({
9649
9826
  );
9650
9827
  }
9651
9828
  spinner2?.message(`Fetching ${branch} from the cloud repo\u2026`);
9829
+ const trackingRef = spaceTrackingRef(branch);
9652
9830
  runGitRemote(appDir, token, [
9653
9831
  "fetch",
9654
9832
  "--quiet",
9655
9833
  SPACE_REMOTE,
9656
- `+${refName}:refs/remotes/space/${branch}`
9834
+ `+${refName}:${trackingRef}`
9657
9835
  ]);
9658
- const remoteOid = resolveCommit(appDir, `refs/remotes/space/${branch}`);
9836
+ const remoteOid = resolveCommit(appDir, trackingRef);
9659
9837
  if (!remoteOid) {
9660
9838
  spinner2?.stop("Fetch failed.");
9661
9839
  throw new Refusal(
9662
- `Fetched, but refs/remotes/space/${branch} did not materialize \u2014 retry; if it persists, report it with \`deepspace feedback\`.`,
9840
+ `Fetched, but ${trackingRef} did not materialize \u2014 retry; if it persists, report it with \`deepspace feedback\`.`,
9663
9841
  "fetch_incomplete"
9664
9842
  );
9665
9843
  }
@@ -9696,8 +9874,8 @@ var pull_default = defineDeepspaceCommand({
9696
9874
  integration = "fetched_only_diverged";
9697
9875
  }
9698
9876
  const qBranch = shQuote(branch);
9699
- const ffRef = `git merge --ff-only refs/remotes/space/${qBranch}`;
9700
- const mergeRef = `git merge refs/remotes/space/${qBranch}`;
9877
+ const ffRef = `git merge --ff-only ${spaceTrackingRef(qBranch)}`;
9878
+ const mergeRef = `git merge ${spaceTrackingRef(qBranch)}`;
9701
9879
  const pullCommand = targetedVcCommand("pull", appId, branch);
9702
9880
  const pushCommand = targetedVcCommand("push", appId, branch);
9703
9881
  const selectedCheckoutClean = branchWorktreePath !== null && isWorkTreeClean(branchWorktreePath);
@@ -9719,35 +9897,35 @@ var pull_default = defineDeepspaceCommand({
9719
9897
  codeForState = "diverged";
9720
9898
  if (branchWorktreePath && selectedCheckoutClean) {
9721
9899
  const mergeAdvice = divergedMergeAdvice(branch, true);
9722
- line = `Fetched space/${branch}. ${branch} and the cloud repo diverged \u2014 run \`${mergeAdvice}\` in ${branchWorktreePath} (no network needed), then \`${pushCommand}\`.`;
9900
+ line = `Fetched ${SPACE_REMOTE}/${branch}. ${branch} and the cloud repo diverged \u2014 run \`${mergeAdvice}\` in ${branchWorktreePath} (no network needed), then \`${pushCommand}\`.`;
9723
9901
  recoveryAction = {
9724
9902
  cwd: branchWorktreePath,
9725
- argv: ["git", "merge", `refs/remotes/space/${branch}`]
9903
+ argv: ["git", "merge", trackingRef]
9726
9904
  };
9727
9905
  } else if (branchWorktreePath) {
9728
- line = `Fetched space/${branch}. ${branch} and the cloud repo diverged, but its checkout (${branchWorktreePath}) is dirty. Commit or stash there, then rerun \`${pullCommand}\` from that checkout.`;
9906
+ line = `Fetched ${SPACE_REMOTE}/${branch}. ${branch} and the cloud repo diverged, but its checkout (${branchWorktreePath}) is dirty. Commit or stash there, then rerun \`${pullCommand}\` from that checkout.`;
9729
9907
  } else if (canCheckoutHere) {
9730
- line = `Fetched space/${branch}. ${branch} and the cloud repo diverged, but ${branch} is not checked out. Check it out here, then rerun \`${pullCommand}\` to receive the merge step.`;
9908
+ line = `Fetched ${SPACE_REMOTE}/${branch}. ${branch} and the cloud repo diverged, but ${branch} is not checked out. Check it out here, then rerun \`${pullCommand}\` to receive the merge step.`;
9731
9909
  recoveryAction = { cwd: appDir, argv: ["git", "checkout", branch] };
9732
9910
  } else {
9733
- line = `Fetched space/${branch}. ${branch} and the cloud repo diverged, but ${branch} is not checked out and this checkout is dirty. Commit or stash first (or use a clean worktree), check out ${qBranch}, then rerun \`${pullCommand}\`.`;
9911
+ line = `Fetched ${SPACE_REMOTE}/${branch}. ${branch} and the cloud repo diverged, but ${branch} is not checked out and this checkout is dirty. Commit or stash first (or use a clean worktree), check out ${qBranch}, then rerun \`${pullCommand}\`.`;
9734
9912
  }
9735
9913
  break;
9736
9914
  case "fetched_only_dirty":
9737
9915
  codeForState = "dirty_worktree";
9738
- line = `Fetched space/${branch}, but the worktree is dirty. Either stash \u2192 \`${ffRef}\` \u2192 \`git stash pop\`, or commit first \u2192 \`${mergeRef}\` (or \`git rebase\`).`;
9916
+ line = `Fetched ${SPACE_REMOTE}/${branch}, but the worktree is dirty. Either stash \u2192 \`${ffRef}\` \u2192 \`git stash pop\`, or commit first \u2192 \`${mergeRef}\` (or \`git rebase\`).`;
9739
9917
  break;
9740
9918
  case "fetched_only_unborn":
9741
9919
  codeForState = "dirty_worktree";
9742
- line = `Fetched space/${branch} and created ${branch}, but the worktree has uncommitted files. Preserve them in a commit or stash (including untracked files), then restore the checkout; do not run reset --hard until \`git status\` confirms nothing worth keeping remains.`;
9920
+ line = `Fetched ${SPACE_REMOTE}/${branch} and created ${branch}, but the worktree has uncommitted files. Preserve them in a commit or stash (including untracked files), then restore the checkout; do not run reset --hard until \`git status\` confirms nothing worth keeping remains.`;
9743
9921
  break;
9744
9922
  case "fetched_only_worktree":
9745
9923
  codeForState = "branch_in_worktree";
9746
9924
  if (branchWorktreePath && selectedCheckoutClean) {
9747
- line = `Fetched space/${branch}, but ${branch} is checked out in another worktree (${branchWorktreePath}) \u2014 its ref was left untouched to keep that worktree in sync. Run \`${pullCommand}\` from there.`;
9925
+ line = `Fetched ${SPACE_REMOTE}/${branch}, but ${branch} is checked out in another worktree (${branchWorktreePath}) \u2014 its ref was left untouched to keep that worktree in sync. Run \`${pullCommand}\` from there.`;
9748
9926
  recoveryAction = targetedVcAction("pull", branchWorktreePath, appId, branch);
9749
9927
  } else {
9750
- line = `Fetched space/${branch}, but its checkout (${branchWorktreePath ?? "a linked worktree"}) is dirty, so the ref and worktree were left untouched. Commit or stash there, then run \`${pullCommand}\` from that checkout.`;
9928
+ line = `Fetched ${SPACE_REMOTE}/${branch}, but its checkout (${branchWorktreePath ?? "a linked worktree"}) is dirty, so the ref and worktree were left untouched. Commit or stash there, then run \`${pullCommand}\` from that checkout.`;
9751
9929
  }
9752
9930
  break;
9753
9931
  default: {
@@ -9756,8 +9934,8 @@ var pull_default = defineDeepspaceCommand({
9756
9934
  }
9757
9935
  }
9758
9936
  const actionRequired = recoveryAction !== void 0;
9759
- spinner2?.stop(codeForState ? `Fetched space/${branch}.` : line);
9760
- if (!args.json) p20.log.info(`App: ${appId} \xB7 space/${branch} at ${remoteOid.slice(0, 10)}`);
9937
+ spinner2?.stop(codeForState ? `Fetched ${SPACE_REMOTE}/${branch}.` : line);
9938
+ if (!args.json) p19.log.info(`App: ${appId} \xB7 ${SPACE_REMOTE}/${branch} at ${remoteOid.slice(0, 10)}`);
9761
9939
  if (codeForState) {
9762
9940
  throw new Refusal(line, codeForState, {
9763
9941
  actionRequired,
@@ -9776,9 +9954,9 @@ var pull_default = defineDeepspaceCommand({
9776
9954
  });
9777
9955
 
9778
9956
  // src/cli/commands/clone.ts
9779
- import * as p21 from "@clack/prompts";
9957
+ import * as p20 from "@clack/prompts";
9780
9958
  import { existsSync as existsSync23, readdirSync as readdirSync6, rmSync as rmSync5 } from "fs";
9781
- import { resolve as resolve16, basename as basename3, join as join21 } from "path";
9959
+ import { resolve as resolve15, basename as basename3, join as join21 } from "path";
9782
9960
  function cleanFailedCloneDir(dir) {
9783
9961
  let entries;
9784
9962
  try {
@@ -9832,7 +10010,7 @@ var clone_default = defineDeepspaceCommand({
9832
10010
  async run({ args }) {
9833
10011
  assertAppTargetResolvable(String(args.app));
9834
10012
  const selector = String(args.app).trim();
9835
- const dir = resolve16(String(args.dir ?? selector));
10013
+ const dir = resolve15(String(args.dir ?? selector));
9836
10014
  const dirPreexisted = validateCloneTarget(dir);
9837
10015
  const spinner2 = args.json ? null : createSpinner();
9838
10016
  spinner2?.start(`Resolving ${selector}\u2026`);
@@ -9878,7 +10056,7 @@ var clone_default = defineDeepspaceCommand({
9878
10056
  `Cloned ${selector} (${branches.length} ${branches.length === 1 ? "branch" : "branches"}) into ${basename3(dir)}/ at ${head?.slice(0, 10) ?? "(unborn)"}.`
9879
10057
  );
9880
10058
  if (!args.json && !branch) {
9881
- p21.log.warn("HEAD is detached \u2014 check `git branch -a`.");
10059
+ p20.log.warn("HEAD is detached \u2014 check `git branch -a`.");
9882
10060
  }
9883
10061
  return {
9884
10062
  data: {
@@ -9897,7 +10075,7 @@ var clone_default = defineDeepspaceCommand({
9897
10075
  });
9898
10076
 
9899
10077
  // src/cli/commands/releases.ts
9900
- import * as p22 from "@clack/prompts";
10078
+ import * as p21 from "@clack/prompts";
9901
10079
  var DEPLOY_URL11 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
9902
10080
  var releases_default = defineDeepspaceCommand({
9903
10081
  meta: {
@@ -9911,6 +10089,12 @@ var releases_default = defineDeepspaceCommand({
9911
10089
  description: "App id or subdomain name (default: the surrounding app directory)",
9912
10090
  required: false
9913
10091
  },
10092
+ env: {
10093
+ type: "string",
10094
+ alias: "e",
10095
+ description: "wrangler.toml [env.<name>] slot \u2014 selects that environment's app id",
10096
+ required: false
10097
+ },
9914
10098
  limit: {
9915
10099
  type: "string",
9916
10100
  description: "Max entries (default 20)",
@@ -9919,18 +10103,20 @@ var releases_default = defineDeepspaceCommand({
9919
10103
  },
9920
10104
  async run({ args }) {
9921
10105
  const appArg = args.app;
10106
+ const envArg = args.env;
9922
10107
  const { limit, error: limitError } = parseLimitArg(args.limit);
9923
10108
  if (limitError) throw new Refusal(limitError, "invalid_limit");
9924
- assertAppTargetResolvable(appArg);
10109
+ assertAppTargetResolvable(appArg, { wranglerEnv: envArg });
10110
+ const { wranglerEnv } = parseWranglerEnvArg(envArg);
9925
10111
  const spinner2 = args.json ? null : createSpinner();
9926
10112
  spinner2?.start("Loading release history\u2026");
9927
10113
  const token = await ensureToken();
9928
- const appId = await resolveAppTarget(DEPLOY_URL11, token, appArg);
10114
+ const appId = await resolveAppTarget(DEPLOY_URL11, token, appArg, { wranglerEnv: envArg });
9929
10115
  const { releases } = await repoApi(DEPLOY_URL11, token, appId).listReleases(limit);
9930
10116
  spinner2?.stop(`Loaded ${releases.length} ${releases.length === 1 ? "release" : "releases"}.`);
9931
10117
  if (!args.json) {
9932
10118
  if (releases.length === 0) {
9933
- p22.log.info("No releases yet \u2014 the history starts with the next `deepspace deploy`.");
10119
+ p21.log.info("No releases yet \u2014 the history starts with the next `deepspace deploy`.");
9934
10120
  } else {
9935
10121
  for (const r of releases) {
9936
10122
  const source = r.commitOid ? `commit ${r.commitOid.slice(0, 10)}` : "(no source recorded)";
@@ -9939,15 +10125,17 @@ var releases_default = defineDeepspaceCommand({
9939
10125
  `#${r.seq} ${r.id} ${r.kind.padEnd(8)} ${r.createdAt} ${r.actor} ${source} ${rollback}`
9940
10126
  );
9941
10127
  }
9942
- p22.log.info("Roll back an available release with: deepspace rollback <release-id>");
10128
+ p21.log.info(
10129
+ `Roll back an available release with: deepspace rollback <release-id>${wranglerEnv ? ` --env ${wranglerEnv}` : ""}`
10130
+ );
9943
10131
  }
9944
10132
  }
9945
- return { data: { appId, releases } };
10133
+ return { data: { appId, wranglerEnv: wranglerEnv ?? null, releases } };
9946
10134
  }
9947
10135
  });
9948
10136
 
9949
10137
  // src/cli/commands/rollback.ts
9950
- import * as p23 from "@clack/prompts";
10138
+ import * as p22 from "@clack/prompts";
9951
10139
  var REL_ID_RE = /^rel_[0-9A-HJKMNP-TV-Z]{26}$/;
9952
10140
  function unavailableDoGuardRefusal(message) {
9953
10141
  return new Refusal(
@@ -9986,6 +10174,12 @@ var rollback_default = defineDeepspaceCommand({
9986
10174
  description: "App id or subdomain name (default: the surrounding app directory)",
9987
10175
  required: false
9988
10176
  },
10177
+ env: {
10178
+ type: "string",
10179
+ alias: "e",
10180
+ description: "wrangler.toml [env.<name>] slot \u2014 selects that environment's app id",
10181
+ required: false
10182
+ },
9989
10183
  "allow-do-deletion": {
9990
10184
  type: "boolean",
9991
10185
  description: "Proceed even though the target release declares fewer Durable Object classes than the current one \u2014 THEIR STORED DATA IS DELETED.",
@@ -9994,6 +10188,7 @@ var rollback_default = defineDeepspaceCommand({
9994
10188
  },
9995
10189
  async run({ args }) {
9996
10190
  const appArg = args.app;
10191
+ const envArg = args.env;
9997
10192
  if (args.release !== void 0) {
9998
10193
  const r = String(args.release).trim();
9999
10194
  if (!r) {
@@ -10006,11 +10201,12 @@ var rollback_default = defineDeepspaceCommand({
10006
10201
  throw new Refusal(`Invalid release id: ${r} (expected rel_<ULID>).`, "invalid_release");
10007
10202
  }
10008
10203
  }
10009
- assertAppTargetResolvable(appArg);
10204
+ assertAppTargetResolvable(appArg, { wranglerEnv: envArg });
10205
+ const { wranglerEnv } = parseWranglerEnvArg(envArg);
10010
10206
  const spinner2 = args.json ? null : createSpinner();
10011
10207
  spinner2?.start("Preparing rollback\u2026");
10012
10208
  const token = await ensureToken();
10013
- const appId = await resolveAppTarget(DEPLOY_URL12, token, appArg);
10209
+ const appId = await resolveAppTarget(DEPLOY_URL12, token, appArg, { wranglerEnv: envArg });
10014
10210
  const api4 = repoApi(DEPLOY_URL12, token, appId);
10015
10211
  let releaseId = args.release ? String(args.release).trim() : "";
10016
10212
  if (!releaseId) {
@@ -10052,18 +10248,18 @@ Re-run with --allow-do-deletion to accept the data loss.`,
10052
10248
  spinner2?.stop(`Rolled back to ${result.rolledBackTo ?? releaseId}.`);
10053
10249
  const doClassGuard = result.doClassGuard === "unverified" ? "unverified" : void 0;
10054
10250
  if (!args.json) {
10055
- if (result.url) p23.log.success(`Live at: ${result.url}`);
10251
+ if (result.url) p22.log.success(`Live at: ${result.url}`);
10056
10252
  if (result.bundleRetained === false) {
10057
- p23.log.warn(
10253
+ p22.log.warn(
10058
10254
  "The rollback is live, but its bundle was concurrently retired and is not available for a future rollback."
10059
10255
  );
10060
10256
  }
10061
10257
  if (doClassGuard === "unverified") {
10062
- p23.log.warn(
10258
+ p22.log.warn(
10063
10259
  "DO-class guard bypassed (--allow-do-deletion): this rollback was NOT checked against the live Durable Object classes \u2014 any class the target doesn't declare has been deleted with its data."
10064
10260
  );
10065
10261
  }
10066
- p23.log.info(
10262
+ p22.log.info(
10067
10263
  "This rollback is itself the newest release \u2014 `deepspace releases` shows the full history."
10068
10264
  );
10069
10265
  }
@@ -10075,6 +10271,7 @@ Re-run with --allow-do-deletion to accept the data loss.`,
10075
10271
  bundleRetained: result.bundleRetained ?? null,
10076
10272
  url: result.url ?? null,
10077
10273
  versionId: result.versionId ?? null,
10274
+ wranglerEnv: wranglerEnv ?? null,
10078
10275
  ...doClassGuard ? { doClassGuard } : {}
10079
10276
  }
10080
10277
  };
@@ -10082,16 +10279,16 @@ Re-run with --allow-do-deletion to accept the data loss.`,
10082
10279
  });
10083
10280
 
10084
10281
  // src/cli/commands/workspace.ts
10085
- import { defineCommand as defineCommand12 } from "citty";
10282
+ import { defineCommand as defineCommand11 } from "citty";
10086
10283
 
10087
10284
  // src/cli/commands/workspace/new.ts
10088
- import * as p26 from "@clack/prompts";
10089
- import { isAbsolute as isAbsolute6, resolve as resolve17 } from "path";
10285
+ import * as p25 from "@clack/prompts";
10286
+ import { isAbsolute as isAbsolute6, resolve as resolve16 } from "path";
10090
10287
 
10091
10288
  // src/cli/commands/workspace/analysis.ts
10092
- import * as p24 from "@clack/prompts";
10289
+ import * as p23 from "@clack/prompts";
10093
10290
  var MAX_OVERLAP_PATHS = 20;
10094
- var PEER_REF_PREFIX = "refs/deepspace/peers/";
10291
+ var PEER_REF_PREFIX = `${spacePrivateRef("peers")}/`;
10095
10292
  function peerWorkspaceRef(workspaceId) {
10096
10293
  return `${PEER_REF_PREFIX}${workspaceId}`;
10097
10294
  }
@@ -10102,7 +10299,7 @@ function formatCount(count) {
10102
10299
  function fetchTrunk(appDir, token, head) {
10103
10300
  if (!head?.startsWith("refs/heads/")) return null;
10104
10301
  const branch = head.slice("refs/heads/".length);
10105
- const remoteRef = `refs/remotes/space/${branch}`;
10302
+ const remoteRef = spaceTrackingRef(branch);
10106
10303
  runGitRemote(appDir, token, ["fetch", "--quiet", SPACE_REMOTE, `+${head}:${remoteRef}`]);
10107
10304
  const oid = resolveCommit(appDir, remoteRef);
10108
10305
  return oid ? { branch, ref: remoteRef, oid } : null;
@@ -10112,7 +10309,7 @@ function localTrunkOid(appDir, refs) {
10112
10309
  if (!head?.startsWith("refs/heads/")) return null;
10113
10310
  const advertised = refs?.refs.find((ref) => ref.name === head)?.oid ?? null;
10114
10311
  if (advertised && resolveCommit(appDir, advertised)) return advertised;
10115
- return resolveCommit(appDir, `refs/remotes/space/${head.slice("refs/heads/".length)}`);
10312
+ return resolveCommit(appDir, spaceTrackingRef(head.slice("refs/heads/".length)));
10116
10313
  }
10117
10314
  function fetchPeerWorkspaceRefs(appDir, token) {
10118
10315
  try {
@@ -10203,15 +10400,15 @@ function printOverlaps(overlaps, json) {
10203
10400
  for (const overlap of overlaps) {
10204
10401
  const sample = overlap.paths.slice(0, 5).join(", ");
10205
10402
  const more = overlap.paths.length - 5 + overlap.morePaths;
10206
- p24.log.warn(
10403
+ p23.log.warn(
10207
10404
  `Overlap: ${overlap.workspaceId} ("${overlap.task}") also modified ${sample}${more > 0 ? ` (+${more} more)` : ""} \u2014 coordinate before landing.`
10208
10405
  );
10209
10406
  }
10210
10407
  }
10211
10408
 
10212
10409
  // src/cli/commands/workspace/local.ts
10213
- import * as p25 from "@clack/prompts";
10214
- import { mkdirSync as mkdirSync7, readFileSync as readFileSync24, realpathSync as realpathSync5, renameSync as renameSync2, statSync, writeFileSync as writeFileSync9 } from "fs";
10410
+ import * as p24 from "@clack/prompts";
10411
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync25, realpathSync as realpathSync5, renameSync as renameSync2, statSync, writeFileSync as writeFileSync9 } from "fs";
10215
10412
  import { dirname as dirname9, isAbsolute as isAbsolute5, join as join22, relative as relative2, sep as sep2 } from "path";
10216
10413
  var WORKSPACE_MARKER = "deepspace-workspace.json";
10217
10414
  function excludeWorktreeDir(appDir, dir) {
@@ -10269,7 +10466,7 @@ function isManagedWorkspaceWorktree(worktreeRoot, workspaceId) {
10269
10466
  const markerPath = worktreeMarkerPath(worktreeRoot);
10270
10467
  if (!markerPath) return false;
10271
10468
  try {
10272
- const marker = JSON.parse(readFileSync24(markerPath, "utf-8"));
10469
+ const marker = JSON.parse(readFileSync25(markerPath, "utf-8"));
10273
10470
  return marker.version === 1 && marker.workspaceId === workspaceId && marker.mode === "managed-linked";
10274
10471
  } catch {
10275
10472
  return false;
@@ -10371,7 +10568,7 @@ function switchOffWsBranch(mainDir, trunkBranch) {
10371
10568
  if (resolveCommit(mainDir, `refs/heads/${trunkBranch}`)) {
10372
10569
  return runGit(mainDir, ["switch", "--quiet", trunkBranch], { allowFail: true }).status === 0;
10373
10570
  }
10374
- const remote = resolveCommit(mainDir, `refs/remotes/space/${trunkBranch}`);
10571
+ const remote = resolveCommit(mainDir, spaceTrackingRef(trunkBranch));
10375
10572
  if (remote) {
10376
10573
  return runGit(mainDir, ["switch", "--quiet", "-c", trunkBranch, remote], { allowFail: true }).status === 0;
10377
10574
  }
@@ -10446,7 +10643,7 @@ function cleanupAction(outcome) {
10446
10643
  }
10447
10644
  function reportCleanupHuman(outcome) {
10448
10645
  if (outcome.error) {
10449
- p25.log.warn(
10646
+ p24.log.warn(
10450
10647
  cleanupRefusalMessage({
10451
10648
  error: outcome.error,
10452
10649
  worktreeDir: outcome.worktreeDir,
@@ -10459,12 +10656,12 @@ function reportCleanupHuman(outcome) {
10459
10656
  outcome.worktreeRemoved ? "worktree" : null,
10460
10657
  outcome.branchDeleted ? `branch ${outcome.branch}` : null
10461
10658
  ].filter(Boolean);
10462
- if (parts.length) p25.log.success(`Removed ${parts.join(" + ")}.`);
10659
+ if (parts.length) p24.log.success(`Removed ${parts.join(" + ")}.`);
10463
10660
  if (outcome.worktreeRetained) {
10464
- p25.log.info(`Retained externally managed worktree: ${outcome.worktreeRetained}`);
10661
+ p24.log.info(`Retained externally managed worktree: ${outcome.worktreeRetained}`);
10465
10662
  }
10466
10663
  if (outcome.worktreeRemoved && outcome.mainDir) {
10467
- p25.log.info(`This directory was removed \u2014 run: cd ${shQuote(outcome.mainDir)}`);
10664
+ p24.log.info(`This directory was removed \u2014 run: cd ${shQuote(outcome.mainDir)}`);
10468
10665
  }
10469
10666
  }
10470
10667
 
@@ -10646,7 +10843,7 @@ var newWorkspaceCommand = defineDeepspaceCommand({
10646
10843
  idempotencyKey: mintIdempotencyKey()
10647
10844
  });
10648
10845
  const dirInput = typeof args.dir === "string" ? args.dir.trim() : "";
10649
- const worktreeRoot = dirInput ? isAbsolute6(dirInput) ? dirInput : resolve17(appDir, dirInput) : defaultWorkspaceRoot(appDir, id);
10846
+ const worktreeRoot = dirInput ? isAbsolute6(dirInput) ? dirInput : resolve16(appDir, dirInput) : defaultWorkspaceRoot(appDir, id);
10650
10847
  const dir = appDirInWorktree(appDir, worktreeRoot);
10651
10848
  try {
10652
10849
  materializeWorkspaceWorktree(appDir, worktreeRoot, branch, baseOid, id);
@@ -10661,8 +10858,8 @@ var newWorkspaceCommand = defineDeepspaceCommand({
10661
10858
  }
10662
10859
  spinner2?.stop(`Workspace ${id} created.`);
10663
10860
  if (!args.json) {
10664
- p26.log.info(`Work in: cd ${dir}`);
10665
- p26.log.info(
10861
+ p25.log.info(`Work in: cd ${dir}`);
10862
+ p25.log.info(
10666
10863
  `Then: commit as usual, \`deepspace workspace sync\` to publish, \`deepspace workspace land\` when done.`
10667
10864
  );
10668
10865
  }
@@ -10683,9 +10880,9 @@ var newWorkspaceCommand = defineDeepspaceCommand({
10683
10880
  });
10684
10881
 
10685
10882
  // src/cli/commands/workspace/attach.ts
10686
- import * as p27 from "@clack/prompts";
10883
+ import * as p26 from "@clack/prompts";
10687
10884
  import { existsSync as existsSync24, readdirSync as readdirSync7, rmSync as rmSync6 } from "fs";
10688
- import { isAbsolute as isAbsolute7, resolve as resolve18 } from "path";
10885
+ import { isAbsolute as isAbsolute7, resolve as resolve17 } from "path";
10689
10886
  function finishedWorkspaceMessage(id, view) {
10690
10887
  const workspace = view.workspace;
10691
10888
  if (workspace.status === "landed") {
@@ -10754,11 +10951,12 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
10754
10951
  });
10755
10952
  }
10756
10953
  ensureSpaceRemote(appDir, appId2);
10954
+ const localWorkspaceRef = spacePrivateRef(`workspaces/${id}`);
10757
10955
  runGitRemote(appDir, token2, [
10758
10956
  "fetch",
10759
10957
  "--quiet",
10760
10958
  SPACE_REMOTE,
10761
- `+${view2.workspace.ref}:${view2.workspace.ref}`
10959
+ `+${view2.workspace.ref}:${localWorkspaceRef}`
10762
10960
  ]);
10763
10961
  const tip2 = view2.tipOid ?? view2.workspace.baseOid;
10764
10962
  if (!resolveCommit(appDir, tip2)) {
@@ -10769,7 +10967,7 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
10769
10967
  );
10770
10968
  }
10771
10969
  const requestedDir = typeof args.dir === "string" ? args.dir.trim() : "";
10772
- const worktreeRoot = requestedDir ? isAbsolute7(requestedDir) ? requestedDir : resolve18(appDir, requestedDir) : defaultWorkspaceRoot(appDir, id);
10970
+ const worktreeRoot = requestedDir ? isAbsolute7(requestedDir) ? requestedDir : resolve17(appDir, requestedDir) : defaultWorkspaceRoot(appDir, id);
10773
10971
  const dir2 = appDirInWorktree(appDir, worktreeRoot);
10774
10972
  if (resolveCommit(appDir, `refs/heads/${branch}`)) {
10775
10973
  throw new Refusal(
@@ -10780,8 +10978,8 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
10780
10978
  materializeWorkspaceWorktree(appDir, worktreeRoot, branch, tip2, id);
10781
10979
  spinner2?.stop(`Attached ${id}.`);
10782
10980
  if (!args.json) {
10783
- p27.log.info(`Task: ${view2.workspace.task}`);
10784
- p27.log.info(`Work in: cd ${dir2}`);
10981
+ p26.log.info(`Task: ${view2.workspace.task}`);
10982
+ p26.log.info(`Work in: cd ${dir2}`);
10785
10983
  }
10786
10984
  const action2 = { cwd: dir2, argv: [detectPackageManager(dir2), "install"] };
10787
10985
  return {
@@ -10808,7 +11006,7 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
10808
11006
  extra: { status: view.workspace.status }
10809
11007
  });
10810
11008
  }
10811
- const dir = resolve18(
11009
+ const dir = resolve17(
10812
11010
  process.cwd(),
10813
11011
  (typeof args.dir === "string" ? args.dir.trim() : "") || `ws-${id.slice(3).toLowerCase()}`
10814
11012
  );
@@ -10827,11 +11025,12 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
10827
11025
  try {
10828
11026
  runGit(process.cwd(), ["init", "--quiet", dir]);
10829
11027
  ensureSpaceRemote(dir, appId);
11028
+ const localWorkspaceRef = spacePrivateRef(`workspaces/${id}`);
10830
11029
  runGitRemote(dir, token, [
10831
11030
  "fetch",
10832
11031
  "--quiet",
10833
11032
  SPACE_REMOTE,
10834
- `+${view.workspace.ref}:${view.workspace.ref}`
11033
+ `+${view.workspace.ref}:${localWorkspaceRef}`
10835
11034
  ]);
10836
11035
  tip = view.tipOid ?? view.workspace.baseOid;
10837
11036
  if (!resolveCommit(dir, tip)) {
@@ -10852,8 +11051,8 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
10852
11051
  spinner2?.stop(`Attached ${id}.`);
10853
11052
  const action = { cwd: dir, argv: [detectPackageManager(dir), "install"] };
10854
11053
  if (!args.json) {
10855
- p27.log.info(`Task: ${view.workspace.task}`);
10856
- p27.log.info("Pull app secrets after install if this workspace needs them.");
11054
+ p26.log.info(`Task: ${view.workspace.task}`);
11055
+ p26.log.info("Pull app secrets after install if this workspace needs them.");
10857
11056
  }
10858
11057
  return {
10859
11058
  data: {
@@ -10870,7 +11069,7 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
10870
11069
  });
10871
11070
 
10872
11071
  // src/cli/commands/workspace/sync.ts
10873
- import * as p28 from "@clack/prompts";
11072
+ import * as p27 from "@clack/prompts";
10874
11073
  var syncWorkspaceCommand = defineDeepspaceCommand({
10875
11074
  meta: {
10876
11075
  name: "sync",
@@ -10931,12 +11130,12 @@ var syncWorkspaceCommand = defineDeepspaceCommand({
10931
11130
  );
10932
11131
  printOverlaps(overlaps, args.json);
10933
11132
  if (!args.json && trunkOverlap.length > 0) {
10934
- p28.log.warn(
11133
+ p27.log.warn(
10935
11134
  `Trunk already changed ${trunkOverlap.length} file(s) you also touch \u2014 landed work you haven't merged: ${trunkOverlap.slice(0, 5).join(", ")}${trunkOverlap.length > 5 ? ` (+${trunkOverlap.length - 5} more)` : ""}. Merge trunk before landing (\`deepspace workspace status\` shows the full picture).`
10936
11135
  );
10937
11136
  }
10938
11137
  if (!args.json && dirty.length > 0) {
10939
- p28.log.warn(
11138
+ p27.log.warn(
10940
11139
  `${dirty.length} uncommitted ${dirty.length === 1 ? "file" : "files"} NOT synced \u2014 commit them to this workspace branch (WIP commits are fine \u2014 they land with the merge).`
10941
11140
  );
10942
11141
  }
@@ -10956,7 +11155,7 @@ var syncWorkspaceCommand = defineDeepspaceCommand({
10956
11155
  });
10957
11156
 
10958
11157
  // src/cli/commands/workspace/list.ts
10959
- import * as p29 from "@clack/prompts";
11158
+ import * as p28 from "@clack/prompts";
10960
11159
 
10961
11160
  // src/cli/lib/actor-labels.ts
10962
11161
  var API_URL9 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
@@ -11004,7 +11203,7 @@ var listWorkspacesCommand = defineDeepspaceCommand({
11004
11203
  if (views.length === 0) {
11005
11204
  spinner2?.stop("No workspaces found.");
11006
11205
  if (!args.json) {
11007
- p29.log.info(
11206
+ p28.log.info(
11008
11207
  all ? "No workspaces." : 'No active workspaces. Create one with `deepspace workspace new -t "\u2026"`.'
11009
11208
  );
11010
11209
  }
@@ -11033,13 +11232,13 @@ var listWorkspacesCommand = defineDeepspaceCommand({
11033
11232
  );
11034
11233
  }
11035
11234
  }
11036
- p29.log.info("\u2191 commits ahead of base \xB7 \u2193 trunk commits since base (staleness)");
11235
+ p28.log.info("\u2191 commits ahead of base \xB7 \u2193 trunk commits since base (staleness)");
11037
11236
  return { data: { workspaces } };
11038
11237
  }
11039
11238
  });
11040
11239
 
11041
11240
  // src/cli/commands/workspace/status.ts
11042
- import * as p30 from "@clack/prompts";
11241
+ import * as p29 from "@clack/prompts";
11043
11242
  var workspaceStatusCommand = defineDeepspaceCommand({
11044
11243
  meta: { name: "status", description: "This workspace: base staleness, overlaps, sync state" },
11045
11244
  args: {
@@ -11099,22 +11298,22 @@ var workspaceStatusCommand = defineDeepspaceCommand({
11099
11298
  overlaps
11100
11299
  };
11101
11300
  if (!args.json) {
11102
- p30.log.info(`Task: ${workspace.task}`);
11103
- p30.log.info(
11301
+ p29.log.info(`Task: ${workspace.task}`);
11302
+ p29.log.info(
11104
11303
  `Base: ${workspace.baseOid.slice(0, 10)} \xB7 ahead ${aheadExact ?? "?"} commit(s) \xB7 trunk moved ${behindExact ?? "?"} commit(s) since your base`
11105
11304
  );
11106
11305
  if (!attached) {
11107
- p30.log.info(
11306
+ p29.log.info(
11108
11307
  `Not checked out in this clone \u2014 showing the published state (local HEAD/uncommitted unavailable). Attach it with \`deepspace workspace attach ${id} <dir>\`.`
11109
11308
  );
11110
11309
  }
11111
11310
  if (unsynced)
11112
- p30.log.warn(
11311
+ p29.log.warn(
11113
11312
  `Local HEAD ${headOid?.slice(0, 10)} is not synced (server has ${view.tipOid?.slice(0, 10) ?? "nothing"}) \u2014 run \`deepspace workspace sync\`.`
11114
11313
  );
11115
- if (dirty.length > 0) p30.log.warn(`${dirty.length} uncommitted file(s) in the worktree.`);
11314
+ if (dirty.length > 0) p29.log.warn(`${dirty.length} uncommitted file(s) in the worktree.`);
11116
11315
  if (trunkOverlap.length > 0) {
11117
- p30.log.warn(
11316
+ p29.log.warn(
11118
11317
  `Trunk also modified: ${trunkOverlap.slice(0, 5).join(", ")}${trunkOverlap.length > 5 ? ` (+${trunkOverlap.length - 5} more)` : ""} \u2014 expect merge attention at land time.`
11119
11318
  );
11120
11319
  }
@@ -11125,17 +11324,17 @@ var workspaceStatusCommand = defineDeepspaceCommand({
11125
11324
  });
11126
11325
 
11127
11326
  // src/cli/commands/workspace/land.ts
11128
- import * as p31 from "@clack/prompts";
11327
+ import * as p30 from "@clack/prompts";
11129
11328
 
11130
11329
  // src/cli/lib/validation.ts
11131
11330
  import { sync as spawnSync7 } from "cross-spawn";
11132
- import { readFileSync as readFileSync25 } from "fs";
11331
+ import { readFileSync as readFileSync26 } from "fs";
11133
11332
  import { join as join23 } from "path";
11134
11333
  var MAX_SUMMARY_CHARS = 4e3;
11135
11334
  var MAX_OUTPUT_BUFFER = 64 * 1024 * 1024;
11136
11335
  function configuredCommand(appDir) {
11137
11336
  try {
11138
- const pkg2 = JSON.parse(readFileSync25(join23(appDir, "package.json"), "utf-8"));
11337
+ const pkg2 = JSON.parse(readFileSync26(join23(appDir, "package.json"), "utf-8"));
11139
11338
  if (pkg2.scripts?.validate) return `${detectPackageManager(appDir)} run --silent validate`;
11140
11339
  } catch {
11141
11340
  }
@@ -11266,7 +11465,7 @@ var landWorkspaceCommand = defineDeepspaceCommand({
11266
11465
  );
11267
11466
  }
11268
11467
  const intoRef = `refs/heads/${intoBranch}`;
11269
- const remoteRef = `refs/remotes/space/${intoBranch}`;
11468
+ const remoteRef = spaceTrackingRef(intoBranch);
11270
11469
  runGitRemote(appDir, token, ["fetch", "--quiet", SPACE_REMOTE, `+${intoRef}:${remoteRef}`], {
11271
11470
  allowFail: true
11272
11471
  });
@@ -11411,7 +11610,7 @@ var landWorkspaceCommand = defineDeepspaceCommand({
11411
11610
  if (!args.json && cleanup && !cleanup.error) {
11412
11611
  reportCleanupHuman(cleanup);
11413
11612
  } else if (!args.json && retainedWorktree) {
11414
- p31.log.info(`Retained checkout: ${retainedWorktree}`);
11613
+ p30.log.info(`Retained checkout: ${retainedWorktree}`);
11415
11614
  }
11416
11615
  if (cleanup?.error) {
11417
11616
  const action = cleanupAction({
@@ -11433,7 +11632,7 @@ var landWorkspaceCommand = defineDeepspaceCommand({
11433
11632
  });
11434
11633
 
11435
11634
  // src/cli/commands/workspace/drop.ts
11436
- import * as p32 from "@clack/prompts";
11635
+ import * as p31 from "@clack/prompts";
11437
11636
  function isWorkspaceTipPublished(appDir, localTip, publishedOids) {
11438
11637
  return publishedOids.some(
11439
11638
  (publishedOid) => publishedOid === localTip || isAncestor(appDir, localTip, publishedOid)
@@ -11442,9 +11641,9 @@ function isWorkspaceTipPublished(appDir, localTip, publishedOids) {
11442
11641
  function workspaceUnsyncedRefusal(args) {
11443
11642
  const syncArgv = ["deepspace", "workspace", "sync", "--app", args.appId, "--workspace", args.id];
11444
11643
  const dropArgv = ["deepspace", "workspace", "drop", args.id, "--app", args.appId];
11445
- const publish2 = args.workspaceDir ? `Publish them first with \`${humanCommand(syncArgv)}\`, then re-run \`${humanCommand(dropArgv)}\`.` : `Recreate a worktree from the branch (\`git worktree add <dir> ${args.branch}\`), publish from there with \`${humanCommand(syncArgv)}\`, then re-run \`${humanCommand(dropArgv)}\`.`;
11644
+ const publish = args.workspaceDir ? `Publish them first with \`${humanCommand(syncArgv)}\`, then re-run \`${humanCommand(dropArgv)}\`.` : `Recreate a worktree from the branch (\`git worktree add <dir> ${args.branch}\`), publish from there with \`${humanCommand(syncArgv)}\`, then re-run \`${humanCommand(dropArgv)}\`.`;
11446
11645
  return new Refusal(
11447
- `${args.id} has committed work that isn't published yet. ${publish2} Alternatively, pass \`--keep-worktree\` to drop the cloud workspace while retaining the local branch/worktree for manual disposal. Nothing was dropped.`,
11646
+ `${args.id} has committed work that isn't published yet. ${publish} Alternatively, pass \`--keep-worktree\` to drop the cloud workspace while retaining the local branch/worktree for manual disposal. Nothing was dropped.`,
11448
11647
  "workspace_unsynced",
11449
11648
  {
11450
11649
  ...args.workspaceDir ? { action: { cwd: args.workspaceDir, argv: syncArgv } } : {},
@@ -11479,7 +11678,7 @@ var dropWorkspaceCommand = defineDeepspaceCommand({
11479
11678
  const { view: view2 } = await api5.dropWorkspace(explicitId);
11480
11679
  spinner2?.stop();
11481
11680
  if (!args.json) {
11482
- p32.log.success(
11681
+ p31.log.success(
11483
11682
  `Dropped ${explicitId}. Its ref sticks around briefly for undo, then is reaped.`
11484
11683
  );
11485
11684
  }
@@ -11531,9 +11730,9 @@ var dropWorkspaceCommand = defineDeepspaceCommand({
11531
11730
  };
11532
11731
  spinner2?.stop();
11533
11732
  if (!args.json) {
11534
- p32.log.success(`Dropped ${id}. Its ref sticks around briefly for undo, then is reaped.`);
11733
+ p31.log.success(`Dropped ${id}. Its ref sticks around briefly for undo, then is reaped.`);
11535
11734
  if (cleanup && !cleanup.error) reportCleanupHuman(cleanup);
11536
- else if (retainedWorktree) p32.log.info(`Retained checkout: ${retainedWorktree}`);
11735
+ else if (retainedWorktree) p31.log.info(`Retained checkout: ${retainedWorktree}`);
11537
11736
  }
11538
11737
  if (cleanup?.error) {
11539
11738
  const action = cleanupAction({
@@ -11555,7 +11754,7 @@ var dropWorkspaceCommand = defineDeepspaceCommand({
11555
11754
  });
11556
11755
 
11557
11756
  // src/cli/commands/workspace.ts
11558
- var workspace_default = defineCommand12({
11757
+ var workspace_default = defineCommand11({
11559
11758
  meta: {
11560
11759
  name: "workspace",
11561
11760
  description: "Durable parallel-agent workspaces (new/attach/sync/list/status/land/drop)"
@@ -11572,7 +11771,7 @@ var workspace_default = defineCommand12({
11572
11771
  });
11573
11772
 
11574
11773
  // src/cli/commands/activity.ts
11575
- import * as p33 from "@clack/prompts";
11774
+ import * as p32 from "@clack/prompts";
11576
11775
  var FOLLOW_INTERVAL_MS = 5e3;
11577
11776
  function landIndex(events) {
11578
11777
  const m = /* @__PURE__ */ new Map();
@@ -11685,7 +11884,7 @@ var activity_default = defineDeepspaceCommand({
11685
11884
  const lands = landIndex(page.events);
11686
11885
  for (const e of page.events) console.log(formatEvent2(e, lands, actors));
11687
11886
  if (page.events.length === 0 && !args.follow) {
11688
- p33.log.info(cursor > 0 ? `No activity after #${cursor}.` : "No activity yet.");
11887
+ p32.log.info(cursor > 0 ? `No activity after #${cursor}.` : "No activity yet.");
11689
11888
  }
11690
11889
  }
11691
11890
  cursor = page.cursor;
@@ -11695,7 +11894,7 @@ var activity_default = defineDeepspaceCommand({
11695
11894
  }
11696
11895
  if (!args.follow) {
11697
11896
  if (page.events.length > 0) {
11698
- p33.log.info(
11897
+ p32.log.info(
11699
11898
  `Cursor: ${cursor}` + (page.hasMore ? ` \xB7 more available (next page: --since ${cursor})` : ` (resume later with --since ${cursor})`)
11700
11899
  );
11701
11900
  }
@@ -11717,7 +11916,7 @@ var activity_default = defineDeepspaceCommand({
11717
11916
  })
11718
11917
  );
11719
11918
  } else {
11720
- p33.log.warn(`Activity feed interrupted (${code}); retrying in 5s.`);
11919
+ p32.log.warn(`Activity feed interrupted (${code}); retrying in 5s.`);
11721
11920
  }
11722
11921
  }
11723
11922
  await new Promise((resolveSleep) => setTimeout(resolveSleep, FOLLOW_INTERVAL_MS));
@@ -11726,7 +11925,7 @@ var activity_default = defineDeepspaceCommand({
11726
11925
  });
11727
11926
 
11728
11927
  // src/cli/commands/git-credential.ts
11729
- import { defineCommand as defineCommand13 } from "citty";
11928
+ import { defineCommand as defineCommand12 } from "citty";
11730
11929
  function parseFields(input) {
11731
11930
  const fields = /* @__PURE__ */ new Map();
11732
11931
  for (const line of input.split("\n")) {
@@ -11775,7 +11974,7 @@ async function readStdin() {
11775
11974
  for await (const chunk of process.stdin) data += chunk;
11776
11975
  return data;
11777
11976
  }
11778
- var git_credential_default = defineCommand13({
11977
+ var git_credential_default = defineCommand12({
11779
11978
  meta: {
11780
11979
  name: "git-credential",
11781
11980
  description: "Git credential helper (installed automatically; not for direct use)"
@@ -11810,8 +12009,8 @@ var git_credential_default = defineCommand13({
11810
12009
  });
11811
12010
 
11812
12011
  // src/cli/commands/source.ts
11813
- import * as p34 from "@clack/prompts";
11814
- var TRANSFER_PREFIX = "refs/deepspace/source-transfer";
12012
+ import * as p33 from "@clack/prompts";
12013
+ var TRANSFER_PREFIX = spacePrivateRef("source-transfer");
11815
12014
  var source_default = defineDeepspaceCommand({
11816
12015
  meta: {
11817
12016
  name: "source",
@@ -11863,7 +12062,7 @@ var source_default = defineDeepspaceCommand({
11863
12062
  const remoteArg = typeof args.remote === "string" ? args.remote.trim() : void 0;
11864
12063
  const result = requested === "github" ? await moveToGitHub({ appDir, appId, token, deployUrl, state, remoteArg }) : await moveToDeepSpace({ appDir, appId, token, deployUrl, state, remoteArg });
11865
12064
  if (!args.json) {
11866
- p34.log.success(
12065
+ p33.log.success(
11867
12066
  result.source.provider === "github" ? `Source: GitHub \xB7 ${result.source.repository} (revision ${result.revision})` : `Source: DeepSpace (revision ${result.revision})`
11868
12067
  );
11869
12068
  }
@@ -12629,11 +12828,11 @@ function reportMigration(migration, json) {
12629
12828
 
12630
12829
  // src/cli/cli.ts
12631
12830
  var __dirname = dirname10(fileURLToPath3(import.meta.url));
12632
- var pkg = JSON.parse(readFileSync26(join24(__dirname, "..", "package.json"), "utf-8"));
12831
+ var pkg = JSON.parse(readFileSync27(join24(__dirname, "..", "package.json"), "utf-8"));
12633
12832
  if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync25("/etc/ssl/certs/ca-certificates.crt")) {
12634
12833
  process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
12635
12834
  }
12636
- var create2 = defineCommand14({
12835
+ var create2 = defineCommand13({
12637
12836
  meta: {
12638
12837
  name: "create",
12639
12838
  description: "Create a new DeepSpace app (runs create-deepspace; all flags are forwarded)"
@@ -12672,14 +12871,14 @@ var create2 = defineCommand14({
12672
12871
  }
12673
12872
  });
12674
12873
  var rename = (cmd, name) => ({ ...cmd, meta: { ...cmd.meta ?? {}, name } });
12675
- var auth = defineCommand14({
12874
+ var auth = defineCommand13({
12676
12875
  meta: { name: "auth", description: "Session: login, logout, whoami" },
12677
12876
  subCommands: { login: login_default, logout: logout_default, whoami: whoami_default }
12678
12877
  });
12679
- var app = defineCommand14({
12878
+ var app = defineCommand13({
12680
12879
  meta: {
12681
12880
  name: "app",
12682
- description: "The app itself: create, init, list, source, migrate, undeploy, transfer, collaborators, domain, library, usage"
12881
+ description: "The app itself: create, init, list, source, migrate, undeploy, transfer, collaborators, domain, usage"
12683
12882
  },
12684
12883
  subCommands: {
12685
12884
  create: create2,
@@ -12689,17 +12888,16 @@ var app = defineCommand14({
12689
12888
  transfer: transfer_default,
12690
12889
  collaborators: collaborators_default,
12691
12890
  domain: domain_default,
12692
- library: library_default,
12693
12891
  usage: usage_default,
12694
12892
  source: source_default,
12695
12893
  migrate: migrate_default
12696
12894
  }
12697
12895
  });
12698
- var devGroup = defineCommand14({
12896
+ var devGroup = defineCommand13({
12699
12897
  meta: { name: "dev", description: "Local development" },
12700
12898
  subCommands: { start: rename(dev_default, "start"), kill: kill_default }
12701
12899
  });
12702
- var testGroup = defineCommand14({
12900
+ var testGroup = defineCommand13({
12703
12901
  meta: { name: "test", description: "App tests and test tooling" },
12704
12902
  subCommands: {
12705
12903
  run: rename(test_default, "run"),
@@ -12707,7 +12905,7 @@ var testGroup = defineCommand14({
12707
12905
  accounts: rename(test_accounts_default, "accounts")
12708
12906
  }
12709
12907
  });
12710
- var main = defineCommand14({
12908
+ var main = defineCommand13({
12711
12909
  meta: {
12712
12910
  name: "deepspace",
12713
12911
  version: pkg.version,