deepspace 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -3,8 +3,8 @@
3
3
  // src/cli/cli.ts
4
4
  import { defineCommand as defineCommand14, runMain, runCommand, renderUsage } from "citty";
5
5
  import { sync as spawnSync8 } from "cross-spawn";
6
- import { readFileSync as readFileSync29, existsSync as existsSync27 } from "fs";
7
- import { dirname as dirname10, join as join25 } from "path";
6
+ import { readFileSync as readFileSync30, existsSync as existsSync27 } from "fs";
7
+ import { dirname as dirname11, join as join25 } from "path";
8
8
  import { fileURLToPath as fileURLToPath3 } from "url";
9
9
 
10
10
  // src/cli/lib/spinner.ts
@@ -41,7 +41,8 @@ function stopActiveSpinner() {
41
41
 
42
42
  // src/cli/lib/output.ts
43
43
  import * as p2 from "@clack/prompts";
44
- import { isAbsolute } from "path";
44
+ import { readFileSync, realpathSync } from "fs";
45
+ import { dirname, isAbsolute, resolve } from "path";
45
46
 
46
47
  // src/cli/lib/cli-format.ts
47
48
  var ANSI_RE = /\[[0-9;]*m/g;
@@ -59,10 +60,35 @@ function humanCommand(argv) {
59
60
  }
60
61
 
61
62
  // src/cli/lib/output.ts
63
+ function resolveActionArgv(argv) {
64
+ if (argv[0] !== "deepspace") return argv;
65
+ const entry = cliEntryPath();
66
+ return entry ? [process.execPath, entry, ...argv.slice(1)] : argv;
67
+ }
68
+ function cliEntryPath() {
69
+ const entry = process.argv[1];
70
+ if (!entry || !isAbsolute(entry)) return null;
71
+ let real;
72
+ try {
73
+ real = realpathSync(entry);
74
+ } catch {
75
+ return null;
76
+ }
77
+ if (!/(^|[\\/])cli\.[cm]?js$/.test(real)) return null;
78
+ try {
79
+ const pkg2 = JSON.parse(readFileSync(resolve(dirname(real), "..", "package.json"), "utf-8"));
80
+ return pkg2.name === "deepspace" ? real : null;
81
+ } catch {
82
+ return null;
83
+ }
84
+ }
85
+ function executableAction(action) {
86
+ const pinned = { ...action, argv: resolveActionArgv(action.argv) };
87
+ assertExecutableAction(pinned);
88
+ return pinned;
89
+ }
62
90
  function cliAction(...argv) {
63
- const action = { cwd: process.cwd(), argv };
64
- assertExecutableAction(action);
65
- return action;
91
+ return executableAction({ cwd: process.cwd(), argv });
66
92
  }
67
93
  function assertExecutableAction(action) {
68
94
  if (!isAbsolute(action.cwd)) throw new TypeError("CLI action cwd must be absolute");
@@ -78,7 +104,9 @@ function withSlug(msg, code) {
78
104
  return msg.trimEnd().endsWith(tag) ? msg : `${msg.trimEnd()} ${tag}`;
79
105
  }
80
106
  function displayArg(arg) {
81
- return /^[A-Za-z0-9_./:@%+=,-]+$/.test(arg) ? arg : shQuote(arg);
107
+ if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(arg)) return arg;
108
+ if (process.platform === "win32") return `"${arg.replaceAll('"', '""')}"`;
109
+ return shQuote(arg);
82
110
  }
83
111
  function printAction(action) {
84
112
  assertExecutableAction(action);
@@ -342,7 +370,7 @@ function effectivePlatformUrls(env = process.env) {
342
370
  var DASHBOARD_URL = DEEPSPACE_ENV === "production" ? "https://dashboard.deep.space" : DEEPSPACE_ENV === "staging" ? "https://dashboard.deepspacesites.com" : "http://127.0.0.1:9";
343
371
 
344
372
  // src/cli/auth.ts
345
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
373
+ import { readFileSync as readFileSync2, writeFileSync, existsSync, mkdirSync } from "fs";
346
374
  import { join } from "path";
347
375
  import { homedir } from "os";
348
376
 
@@ -382,8 +410,7 @@ var Refusal = class extends Error {
382
410
  super(message);
383
411
  this.name = "Refusal";
384
412
  this.code = code;
385
- if (opts.action) assertExecutableAction(opts.action);
386
- this.action = opts.action;
413
+ this.action = opts.action ? executableAction(opts.action) : void 0;
387
414
  this.actionRequired = opts.actionRequired ?? false;
388
415
  this.extra = opts.extra ?? {};
389
416
  }
@@ -428,7 +455,7 @@ function defineDeepspaceCommand(def) {
428
455
  let succeeded = false;
429
456
  try {
430
457
  const out = await def.run({ args }) ?? {};
431
- if (out.action) assertExecutableAction(out.action);
458
+ if (out.action) out.action = executableAction(out.action);
432
459
  if (json) {
433
460
  console.log(
434
461
  JSON.stringify({
@@ -492,7 +519,7 @@ function credentialPaths(authUrl = AUTH_URL) {
492
519
  var { sessionPath: SESSION_PATH, tokenPath: TOKEN_PATH } = credentialPaths();
493
520
  async function ensureToken() {
494
521
  if (existsSync(TOKEN_PATH)) {
495
- const existing = readFileSync(TOKEN_PATH, "utf-8").trim();
522
+ const existing = readFileSync2(TOKEN_PATH, "utf-8").trim();
496
523
  if (isTokenValid(existing)) {
497
524
  return existing;
498
525
  }
@@ -502,7 +529,7 @@ async function ensureToken() {
502
529
  action: cliAction("deepspace", "auth", "login")
503
530
  });
504
531
  }
505
- const sessionToken = readFileSync(SESSION_PATH, "utf-8").trim();
532
+ const sessionToken = readFileSync2(SESSION_PATH, "utf-8").trim();
506
533
  const token = await exchangeSession(AUTH_URL, sessionToken);
507
534
  if (!token) {
508
535
  throw new Refusal(
@@ -526,7 +553,7 @@ function isTokenValid(jwt) {
526
553
  registerAuthRefresh(async () => {
527
554
  try {
528
555
  if (!existsSync(SESSION_PATH)) return null;
529
- const sessionToken = readFileSync(SESSION_PATH, "utf-8").trim();
556
+ const sessionToken = readFileSync2(SESSION_PATH, "utf-8").trim();
530
557
  const token = await exchangeSession(AUTH_URL, sessionToken);
531
558
  if (!token) return null;
532
559
  mkdirSync(DIR, { recursive: true, mode: 448 });
@@ -538,7 +565,7 @@ registerAuthRefresh(async () => {
538
565
  });
539
566
 
540
567
  // src/cli/commands/login.ts
541
- import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2 } from "fs";
568
+ import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3 } from "fs";
542
569
  import { join as join2 } from "path";
543
570
  import { homedir as homedir2 } from "os";
544
571
  import { randomBytes, createHash } from "crypto";
@@ -697,7 +724,7 @@ function readPasswordFromStdin() {
697
724
  "password_stdin_needs_pipe"
698
725
  );
699
726
  }
700
- return readFileSync2(0, "utf-8").replace(/\r?\n$/, "");
727
+ return readFileSync3(0, "utf-8").replace(/\r?\n$/, "");
701
728
  }
702
729
  async function pollForCompletion(sessionId, codeVerifier) {
703
730
  const maxAttempts = 120;
@@ -738,7 +765,7 @@ async function provisionProfile(jwt) {
738
765
  }
739
766
  }
740
767
  function sleep(ms) {
741
- return new Promise((resolve20) => setTimeout(resolve20, ms));
768
+ return new Promise((resolve21) => setTimeout(resolve21, ms));
742
769
  }
743
770
  function base64url(bytes) {
744
771
  return bytes.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
@@ -775,7 +802,7 @@ async function doEmailLogin(email, password) {
775
802
  }
776
803
 
777
804
  // src/cli/commands/logout.ts
778
- import { existsSync as existsSync2, readFileSync as readFileSync3, rmSync } from "fs";
805
+ import { existsSync as existsSync2, readFileSync as readFileSync4, rmSync } from "fs";
779
806
  var AUTH_URL3 = process.env.DEEPSPACE_AUTH_URL ?? PLATFORM_URLS.auth;
780
807
  var { sessionPath: SESSION_PATH2, tokenPath: TOKEN_PATH2 } = credentialPaths(AUTH_URL3);
781
808
  var logout_default = defineDeepspaceCommand({
@@ -784,7 +811,7 @@ var logout_default = defineDeepspaceCommand({
784
811
  description: "Sign out and remove cached credentials"
785
812
  },
786
813
  async run({ args }) {
787
- const sessionToken = existsSync2(SESSION_PATH2) ? readFileSync3(SESSION_PATH2, "utf-8").trim() : null;
814
+ const sessionToken = existsSync2(SESSION_PATH2) ? readFileSync4(SESSION_PATH2, "utf-8").trim() : null;
788
815
  if (sessionToken) {
789
816
  try {
790
817
  await fetch(`${AUTH_URL3}/api/auth/sign-out`, {
@@ -810,8 +837,8 @@ var logout_default = defineDeepspaceCommand({
810
837
  });
811
838
 
812
839
  // src/cli/lib/app-identity.ts
813
- import { existsSync as existsSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
814
- import { join as join3, resolve } from "path";
840
+ import { existsSync as existsSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
841
+ import { join as join3, resolve as resolve2 } from "path";
815
842
  import { parse as parseToml } from "smol-toml";
816
843
 
817
844
  // src/server/utils/registry-client.ts
@@ -846,10 +873,10 @@ function mintAppId(now = Date.now()) {
846
873
  return `app_${mintUlid(now)}`;
847
874
  }
848
875
  function readWranglerIdentityConfig(cwd) {
849
- const wranglerPath = join3(resolve(cwd), "wrangler.toml");
876
+ const wranglerPath = join3(resolve2(cwd), "wrangler.toml");
850
877
  if (!existsSync3(wranglerPath)) return null;
851
878
  try {
852
- return parseToml(readFileSync4(wranglerPath, "utf-8"));
879
+ return parseToml(readFileSync5(wranglerPath, "utf-8"));
853
880
  } catch (err) {
854
881
  throw new InputError(
855
882
  `Could not parse ${wranglerPath}: ${err instanceof Error ? err.message : String(err)}`,
@@ -865,9 +892,9 @@ function readAppId(cwd = process.cwd(), wranglerEnv) {
865
892
  return typeof id === "string" && APP_ID_RE.test(id) ? id : null;
866
893
  }
867
894
  function writeAppId(cwd, appId, opts = {}) {
868
- const wranglerPath = join3(resolve(cwd), "wrangler.toml");
895
+ const wranglerPath = join3(resolve2(cwd), "wrangler.toml");
869
896
  if (!existsSync3(wranglerPath)) {
870
- throw new Error(`No wrangler.toml in ${resolve(cwd)}`);
897
+ throw new Error(`No wrangler.toml in ${resolve2(cwd)}`);
871
898
  }
872
899
  const existing = readAppId(cwd, opts.wranglerEnv);
873
900
  if (existing && !opts.force) {
@@ -876,7 +903,7 @@ function writeAppId(cwd, appId, opts = {}) {
876
903
  `wrangler.toml already carries ${existing}. The app id is immutable \u2014 use --new-id only to fork this repo as a separate app.`
877
904
  );
878
905
  }
879
- let src = readFileSync4(wranglerPath, "utf-8");
906
+ let src = readFileSync5(wranglerPath, "utf-8");
880
907
  const header = opts.wranglerEnv ? `[env.${opts.wranglerEnv}.vars]` : "[vars]";
881
908
  const line = `DEEPSPACE_APP_ID = "${appId}"`;
882
909
  const headerRe = new RegExp(`^\\${header.replace(/\./g, "\\.")}[ \\t]*$`, "m");
@@ -902,18 +929,18 @@ ${line}
902
929
  }
903
930
 
904
931
  // src/cli/commands/dev.ts
905
- import { resolve as resolve7, basename as basename2, join as join12 } from "path";
932
+ import { resolve as resolve8, basename as basename2, join as join12 } from "path";
906
933
  import spawn from "cross-spawn";
907
934
 
908
935
  // src/cli/lib/app-context.ts
909
- import { existsSync as existsSync4, readFileSync as readFileSync5, readdirSync } from "fs";
910
- import { dirname, join as join4, resolve as resolve2 } from "path";
936
+ import { existsSync as existsSync4, readFileSync as readFileSync6, readdirSync } from "fs";
937
+ import { dirname as dirname2, join as join4, resolve as resolve3 } from "path";
911
938
  import { parse as parseToml2 } from "smol-toml";
912
939
  function detectAppName(cwd = process.cwd()) {
913
- const wranglerPath = join4(resolve2(cwd), "wrangler.toml");
940
+ const wranglerPath = join4(resolve3(cwd), "wrangler.toml");
914
941
  if (!existsSync4(wranglerPath)) return null;
915
942
  try {
916
- const config = parseToml2(readFileSync5(wranglerPath, "utf-8"));
943
+ const config = parseToml2(readFileSync6(wranglerPath, "utf-8"));
917
944
  return typeof config.name === "string" && config.name.length > 0 ? config.name : null;
918
945
  } catch (error) {
919
946
  throw new InputError(
@@ -923,10 +950,10 @@ function detectAppName(cwd = process.cwd()) {
923
950
  }
924
951
  }
925
952
  function findAppDir(start = process.cwd()) {
926
- let dir = resolve2(start);
953
+ let dir = resolve3(start);
927
954
  for (; ; ) {
928
955
  if (existsSync4(join4(dir, "wrangler.toml"))) return dir;
929
- const parent = dirname(dir);
956
+ const parent = dirname2(dir);
930
957
  if (parent === dir) return null;
931
958
  dir = parent;
932
959
  }
@@ -942,18 +969,18 @@ function findChildApps(dir) {
942
969
  // src/cli/lib/launch-config.ts
943
970
  import {
944
971
  existsSync as existsSync5,
945
- readFileSync as readFileSync7,
946
- realpathSync,
972
+ readFileSync as readFileSync8,
973
+ realpathSync as realpathSync2,
947
974
  writeFileSync as writeFileSync4,
948
975
  mkdirSync as mkdirSync4,
949
976
  renameSync,
950
977
  unlinkSync
951
978
  } from "fs";
952
- import { join as join6, resolve as resolve4, dirname as dirname3, basename, isAbsolute as isAbsolute2, sep } from "path";
979
+ import { join as join6, resolve as resolve5, dirname as dirname4, basename, isAbsolute as isAbsolute2, sep } from "path";
953
980
 
954
981
  // src/cli/lib/git/repository.ts
955
- import { appendFileSync, mkdirSync as mkdirSync3, readFileSync as readFileSync6 } from "fs";
956
- import { dirname as dirname2, join as join5, resolve as resolve3 } from "path";
982
+ import { appendFileSync, mkdirSync as mkdirSync3, readFileSync as readFileSync7 } from "fs";
983
+ import { dirname as dirname3, join as join5, resolve as resolve4 } from "path";
957
984
 
958
985
  // src/cli/lib/git/process.ts
959
986
  import { sync as spawnSync } from "cross-spawn";
@@ -1156,16 +1183,16 @@ function listWorktrees(cwd) {
1156
1183
  return worktrees;
1157
1184
  }
1158
1185
  function ensureLocalExclude(cwd, pattern) {
1159
- const commonDir = resolve3(cwd, gitLine(cwd, ["rev-parse", "--git-common-dir"]));
1186
+ const commonDir = resolve4(cwd, gitLine(cwd, ["rev-parse", "--git-common-dir"]));
1160
1187
  const excludePath = join5(commonDir, "info", "exclude");
1161
1188
  let current = "";
1162
1189
  try {
1163
- current = readFileSync6(excludePath, "utf-8");
1190
+ current = readFileSync7(excludePath, "utf-8");
1164
1191
  } catch {
1165
1192
  }
1166
1193
  if (current.split("\n").some((line) => line.trim() === pattern)) return;
1167
1194
  try {
1168
- mkdirSync3(dirname2(excludePath), { recursive: true });
1195
+ mkdirSync3(dirname3(excludePath), { recursive: true });
1169
1196
  appendFileSync(
1170
1197
  excludePath,
1171
1198
  `${current.endsWith("\n") || current === "" ? "" : "\n"}${pattern}
@@ -1177,7 +1204,7 @@ function ensureLocalExclude(cwd, pattern) {
1177
1204
 
1178
1205
  // src/cli/lib/launch-config.ts
1179
1206
  function writeLaunchFile(launchPath, config) {
1180
- mkdirSync4(dirname3(launchPath), { recursive: true });
1207
+ mkdirSync4(dirname4(launchPath), { recursive: true });
1181
1208
  const tmpPath = `${launchPath}.${process.pid}.tmp`;
1182
1209
  writeFileSync4(tmpPath, JSON.stringify(config, null, 2) + "\n");
1183
1210
  try {
@@ -1193,7 +1220,7 @@ function writeLaunchFile(launchPath, config) {
1193
1220
  function readLaunchFile(launchPath) {
1194
1221
  const empty = { version: "0.0.1", configurations: [] };
1195
1222
  if (!existsSync5(launchPath)) return empty;
1196
- const raw = readFileSync7(launchPath, "utf-8").trim();
1223
+ const raw = readFileSync8(launchPath, "utf-8").trim();
1197
1224
  if (!raw) return empty;
1198
1225
  const parsed = JSON.parse(raw);
1199
1226
  if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.configurations)) return null;
@@ -1258,10 +1285,10 @@ function detectClaudeWorktree(appDir) {
1258
1285
  listWorktrees(appDir).map((worktree) => canonicalPath(worktree.path))
1259
1286
  );
1260
1287
  if (!registered.has(worktreeRoot)) return null;
1261
- const worktreesDir = dirname3(worktreeRoot);
1262
- const claudeDir = dirname3(worktreesDir);
1288
+ const worktreesDir = dirname4(worktreeRoot);
1289
+ const claudeDir = dirname4(worktreesDir);
1263
1290
  if (basename(worktreesDir) !== "worktrees" || basename(claudeDir) !== ".claude") return null;
1264
- const ownerRoot = canonicalPath(dirname3(claudeDir));
1291
+ const ownerRoot = canonicalPath(dirname4(claudeDir));
1265
1292
  if (!registered.has(ownerRoot)) return null;
1266
1293
  return { mainRepoRoot: ownerRoot, worktreeName: basename(worktreeRoot) };
1267
1294
  } catch {
@@ -1270,9 +1297,9 @@ function detectClaudeWorktree(appDir) {
1270
1297
  }
1271
1298
  function canonicalPath(path) {
1272
1299
  try {
1273
- return realpathSync(path);
1300
+ return realpathSync2(path);
1274
1301
  } catch {
1275
- return resolve4(path);
1302
+ return resolve5(path);
1276
1303
  }
1277
1304
  }
1278
1305
  function detectLinkedWorktree(appDir) {
@@ -1324,7 +1351,7 @@ function upsertWorktreeLaunchConfig(mainRepoRoot, worktreeName, appDir, opts) {
1324
1351
  // the Claude Code desktop launch.json reference alongside `port`) and
1325
1352
  // verified end-to-end: with it set, the preview server builds from the
1326
1353
  // worktree instead of the session's main repo.
1327
- cwd: resolve4(appDir)
1354
+ cwd: resolve5(appDir)
1328
1355
  });
1329
1356
  writeLaunchFile(launchPath, config);
1330
1357
  return { entryName, port };
@@ -1351,7 +1378,7 @@ function resolveWorktreePort(appDir) {
1351
1378
  }
1352
1379
 
1353
1380
  // src/cli/lib/dev-vars.ts
1354
- import { existsSync as existsSync7, readFileSync as readFileSync9 } from "fs";
1381
+ import { existsSync as existsSync7, readFileSync as readFileSync10 } from "fs";
1355
1382
 
1356
1383
  // src/cli/lib/app-tokens.ts
1357
1384
  async function fetchPublicKey(authUrl) {
@@ -1896,7 +1923,7 @@ function writeSecretFileSync(path, content) {
1896
1923
  }
1897
1924
 
1898
1925
  // src/cli/lib/wrangler-env.ts
1899
- import { existsSync as existsSync6, readFileSync as readFileSync8, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
1926
+ import { existsSync as existsSync6, readFileSync as readFileSync9, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
1900
1927
  import { join as join7 } from "path";
1901
1928
  import { parse as parseToml3, stringify as stringifyToml } from "smol-toml";
1902
1929
 
@@ -2090,7 +2117,7 @@ var WranglerConfigError = class extends Error {
2090
2117
  };
2091
2118
  function readWranglerConfig(appDir) {
2092
2119
  const path = join7(appDir, "wrangler.toml");
2093
- const raw = readFileSync8(path, "utf-8");
2120
+ const raw = readFileSync9(path, "utf-8");
2094
2121
  try {
2095
2122
  return parseToml3(raw);
2096
2123
  } catch (err) {
@@ -2263,7 +2290,7 @@ async function writeDevVars(appDir, ownerId, callerJwt, wranglerEnv, opts = {})
2263
2290
  const devVarsPath = devVarsPathFor(appDir, wranglerEnv, {
2264
2291
  sharedDevVarsCache: useSharedDevVarsCache
2265
2292
  });
2266
- const existing = existsSync7(devVarsPath) ? readFileSync9(devVarsPath, "utf-8") : "";
2293
+ const existing = existsSync7(devVarsPath) ? readFileSync10(devVarsPath, "utf-8") : "";
2267
2294
  const custom = opts.generatedSecretsCache ?? extractCustomDevVars(stripGeneratedSecretsCache(existing).trimEnd());
2268
2295
  let body;
2269
2296
  if (!custom) {
@@ -2285,15 +2312,15 @@ ${custom}
2285
2312
  }
2286
2313
 
2287
2314
  // src/cli/lib/install-status.ts
2288
- import { existsSync as existsSync9, readFileSync as readFileSync11, realpathSync as realpathSync2 } from "fs";
2289
- import { dirname as dirname4, join as join9, resolve as resolve5 } from "path";
2315
+ import { existsSync as existsSync9, readFileSync as readFileSync12, realpathSync as realpathSync3 } from "fs";
2316
+ import { dirname as dirname5, join as join9, resolve as resolve6 } from "path";
2290
2317
 
2291
2318
  // src/cli/lib/package-manager.ts
2292
- import { existsSync as existsSync8, readFileSync as readFileSync10 } from "fs";
2319
+ import { existsSync as existsSync8, readFileSync as readFileSync11 } from "fs";
2293
2320
  import { join as join8 } from "path";
2294
2321
  function detectPackageManager(appDir) {
2295
2322
  try {
2296
- const pkg2 = JSON.parse(readFileSync10(join8(appDir, "package.json"), "utf-8"));
2323
+ const pkg2 = JSON.parse(readFileSync11(join8(appDir, "package.json"), "utf-8"));
2297
2324
  const name = typeof pkg2.packageManager === "string" ? pkg2.packageManager.split("@")[0] : "";
2298
2325
  if (name === "npm" || name === "pnpm" || name === "yarn" || name === "bun") return name;
2299
2326
  } catch {
@@ -2331,7 +2358,7 @@ function ensureInstallReady(appDir) {
2331
2358
  const donePath = join9(sentinelDir, "install.done");
2332
2359
  const logPath = join9(sentinelDir, "install.log");
2333
2360
  if (existsSync9(errPath)) {
2334
- const detail = readFileSync11(errPath, "utf-8").trimEnd();
2361
+ const detail = readFileSync12(errPath, "utf-8").trimEnd();
2335
2362
  const log29 = existsSync9(logPath) ? ` Full log: ${logPath}.` : "";
2336
2363
  throw new Refusal(
2337
2364
  `Dependency install failed: ${detail}.${log29} Run \`npm install\` (or \`bun install\`) manually, then retry.`,
@@ -2372,22 +2399,22 @@ function resolvesDeepspace(fromDir) {
2372
2399
  for (; ; ) {
2373
2400
  if (existsSync9(join9(dir, "node_modules", "deepspace", "package.json"))) return true;
2374
2401
  if (dir === checkoutRoot) return false;
2375
- const parent = dirname4(dir);
2402
+ const parent = dirname5(dir);
2376
2403
  if (parent === dir) return false;
2377
2404
  dir = parent;
2378
2405
  }
2379
2406
  }
2380
2407
  function canonicalPath2(path) {
2381
2408
  try {
2382
- return realpathSync2(path);
2409
+ return realpathSync3(path);
2383
2410
  } catch {
2384
- return resolve5(path);
2411
+ return resolve6(path);
2385
2412
  }
2386
2413
  }
2387
2414
  function installWorkerAlive(sentinelDir) {
2388
2415
  const pidPath = join9(sentinelDir, "install.pid");
2389
2416
  if (!existsSync9(pidPath)) return false;
2390
- const pid = Number(readFileSync11(pidPath, "utf-8").trim());
2417
+ const pid = Number(readFileSync12(pidPath, "utf-8").trim());
2391
2418
  if (!Number.isSafeInteger(pid) || pid <= 0) return false;
2392
2419
  try {
2393
2420
  process.kill(pid, 0);
@@ -2401,7 +2428,7 @@ function installWorkerAlive(sentinelDir) {
2401
2428
  import { execFileSync } from "child_process";
2402
2429
  import { existsSync as existsSync10 } from "fs";
2403
2430
  import * as nodeModule from "module";
2404
- import { resolve as resolve6 } from "path";
2431
+ import { resolve as resolve7 } from "path";
2405
2432
  function preflightNodeVersion(command, hasRegisterHooks = typeof nodeModule.registerHooks === "function") {
2406
2433
  if (hasRegisterHooks) return;
2407
2434
  throw new Refusal(
@@ -2412,7 +2439,7 @@ function preflightNodeVersion(command, hasRegisterHooks = typeof nodeModule.regi
2412
2439
  var DLL_NOT_FOUND = -1073741515;
2413
2440
  function preflightWindowsWorkerd(appDir) {
2414
2441
  if (process.platform !== "win32") return;
2415
- const workerdExe = resolve6(
2442
+ const workerdExe = resolve7(
2416
2443
  appDir,
2417
2444
  "node_modules",
2418
2445
  "@cloudflare",
@@ -2577,11 +2604,11 @@ import { createServer, Socket } from "net";
2577
2604
  var DEFAULT_PORT = 5173;
2578
2605
  function isPortListening(port, host, timeoutMs = 500) {
2579
2606
  const target2 = host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host;
2580
- return new Promise((resolve20) => {
2607
+ return new Promise((resolve21) => {
2581
2608
  const socket = new Socket();
2582
2609
  const done = (listening) => {
2583
2610
  socket.destroy();
2584
- resolve20(listening);
2611
+ resolve21(listening);
2585
2612
  };
2586
2613
  socket.setTimeout(timeoutMs);
2587
2614
  socket.once("connect", () => done(true));
@@ -2594,7 +2621,7 @@ async function waitForPortListening(port, host, timeoutMs) {
2594
2621
  const deadline = Date.now() + timeoutMs;
2595
2622
  while (Date.now() < deadline) {
2596
2623
  if (await isPortListening(port, host)) return true;
2597
- await new Promise((resolve20) => setTimeout(resolve20, 250));
2624
+ await new Promise((resolve21) => setTimeout(resolve21, 250));
2598
2625
  }
2599
2626
  return false;
2600
2627
  }
@@ -2660,7 +2687,7 @@ var dev_default = defineDeepspaceCommand({
2660
2687
  preflightNodeVersion("dev start");
2661
2688
  const wranglerEnv = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
2662
2689
  const host = typeof args.host === "string" && args.host.trim() ? args.host.trim() : DEFAULT_DEV_HOST;
2663
- const start = resolve7(args.dir ?? ".");
2690
+ const start = resolve8(args.dir ?? ".");
2664
2691
  const appDir = findAppDir(start);
2665
2692
  if (!appDir) {
2666
2693
  throw new Refusal(noAppDirMessage(start, findChildApps(start)), "no_app_dir");
@@ -2817,7 +2844,7 @@ Free it with \`${killCmd}\`, or start on another port: \`deepspace dev start --p
2817
2844
 
2818
2845
  // src/cli/commands/kill.ts
2819
2846
  import { spawnSync as spawnSync2 } from "child_process";
2820
- import { readFileSync as readFileSync12, readdirSync as readdirSync3, readlinkSync } from "fs";
2847
+ import { readFileSync as readFileSync13, readdirSync as readdirSync3, readlinkSync } from "fs";
2821
2848
  import { setTimeout as wait } from "timers/promises";
2822
2849
  var SIGTERM_GRACE_MS = 1500;
2823
2850
  var IS_WIN = process.platform === "win32";
@@ -2968,7 +2995,7 @@ function listenerPidsViaProc(port) {
2968
2995
  for (const f of ["/proc/net/tcp", "/proc/net/tcp6"]) {
2969
2996
  let data;
2970
2997
  try {
2971
- data = readFileSync12(f, "utf-8");
2998
+ data = readFileSync13(f, "utf-8");
2972
2999
  } catch {
2973
3000
  continue;
2974
3001
  }
@@ -3098,7 +3125,7 @@ function isAlive(pid) {
3098
3125
  }
3099
3126
 
3100
3127
  // src/cli/commands/test.ts
3101
- import { resolve as resolve8 } from "path";
3128
+ import { resolve as resolve9 } from "path";
3102
3129
  import { sync as spawnSync4 } from "cross-spawn";
3103
3130
 
3104
3131
  // src/cli/lib/playwright.ts
@@ -3127,10 +3154,10 @@ function ensurePlaywright(appDir) {
3127
3154
  }
3128
3155
 
3129
3156
  // src/cli/lib/test-account-service.ts
3130
- import { readFileSync as readFileSync14 } from "fs";
3157
+ import { readFileSync as readFileSync15 } from "fs";
3131
3158
 
3132
3159
  // src/testing/accounts.ts
3133
- import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync13, writeFileSync as writeFileSync7 } from "fs";
3160
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
3134
3161
  import { homedir as homedir3 } from "os";
3135
3162
  import { join as join13 } from "path";
3136
3163
  var TEST_ACCOUNTS_PATH = join13(homedir3(), ".deepspace", "test-accounts.json");
@@ -3206,7 +3233,7 @@ function removeTestAccounts(ids, emails = [], scope = currentTestAccountScope())
3206
3233
  function readTestAccountStore() {
3207
3234
  if (!existsSync12(TEST_ACCOUNTS_PATH)) return { version: 2, scopes: {}, unscoped: [] };
3208
3235
  try {
3209
- const raw = JSON.parse(readFileSync13(TEST_ACCOUNTS_PATH, "utf-8"));
3236
+ const raw = JSON.parse(readFileSync14(TEST_ACCOUNTS_PATH, "utf-8"));
3210
3237
  if (Array.isArray(raw)) return { version: 2, scopes: {}, unscoped: validTestAccounts(raw) };
3211
3238
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
3212
3239
  return { version: 2, scopes: {}, unscoped: [] };
@@ -3244,7 +3271,7 @@ function saveTestAccountStore(store) {
3244
3271
  var SESSION_COOKIE2 = "__Secure-better-auth.session_token";
3245
3272
  var AUTH_URL4 = process.env.DEEPSPACE_AUTH_URL ?? PLATFORM_URLS.auth;
3246
3273
  function sessionCookie() {
3247
- const token = readFileSync14(SESSION_PATH, "utf-8").trim();
3274
+ const token = readFileSync15(SESSION_PATH, "utf-8").trim();
3248
3275
  return `${SESSION_COOKIE2}=${encodeURIComponent(token)}`;
3249
3276
  }
3250
3277
  async function fetchRemoteTestAccounts() {
@@ -3321,7 +3348,7 @@ var test_default = defineDeepspaceCommand({
3321
3348
  preflightNodeVersion("test run");
3322
3349
  const suite = args.suite ?? "default";
3323
3350
  const wranglerEnv = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
3324
- const start = resolve8(".");
3351
+ const start = resolve9(".");
3325
3352
  const appDir = findAppDir(start);
3326
3353
  if (!appDir) {
3327
3354
  throw new Refusal(noAppDirMessage(start, findChildApps(start)), "no_app_dir");
@@ -3468,7 +3495,7 @@ function runVitest(appDir) {
3468
3495
 
3469
3496
  // src/cli/commands/screenshot.ts
3470
3497
  import { sync as spawnSync5 } from "cross-spawn";
3471
- import { resolve as resolve9 } from "path";
3498
+ import { resolve as resolve10 } from "path";
3472
3499
  var screenshot_default = defineDeepspaceCommand({
3473
3500
  meta: {
3474
3501
  name: "screenshot",
@@ -3497,7 +3524,7 @@ var screenshot_default = defineDeepspaceCommand({
3497
3524
  }
3498
3525
  },
3499
3526
  async run({ args }) {
3500
- const appDir = resolve9(".");
3527
+ const appDir = resolve10(".");
3501
3528
  ensurePlaywright(appDir);
3502
3529
  const url = String(args.url);
3503
3530
  const output = String(args.output);
@@ -3798,7 +3825,7 @@ var test_accounts_default = defineCommand2({
3798
3825
  // src/cli/commands/deploy.ts
3799
3826
  import * as p12 from "@clack/prompts";
3800
3827
  import { defineCommand as defineCommand3 } from "citty";
3801
- import { resolve as resolve12 } from "path";
3828
+ import { resolve as resolve13 } from "path";
3802
3829
 
3803
3830
  // src/cli/lib/edge-propagation.ts
3804
3831
  import { request as httpRequest } from "http";
@@ -3863,14 +3890,14 @@ async function servesRelease(probe, releaseStamp) {
3863
3890
  }
3864
3891
  function getOnFreshConnection(url, timeoutMs = 5e3) {
3865
3892
  const send = url.protocol === "http:" ? httpRequest : httpsRequest;
3866
- return new Promise((resolve20) => {
3893
+ return new Promise((resolve21) => {
3867
3894
  const request2 = send(
3868
3895
  url,
3869
3896
  { method: "GET", agent: false, timeout: timeoutMs, headers: { "cache-control": "no-cache" } },
3870
3897
  (response) => {
3871
3898
  if (response.statusCode !== 200) {
3872
3899
  response.resume();
3873
- resolve20(null);
3900
+ resolve21(null);
3874
3901
  return;
3875
3902
  }
3876
3903
  let body = "";
@@ -3878,20 +3905,20 @@ function getOnFreshConnection(url, timeoutMs = 5e3) {
3878
3905
  response.on("data", (chunk) => {
3879
3906
  if (body.length < 4096) body += chunk;
3880
3907
  });
3881
- response.on("end", () => resolve20(body));
3882
- response.on("error", () => resolve20(null));
3908
+ response.on("end", () => resolve21(body));
3909
+ response.on("error", () => resolve21(null));
3883
3910
  }
3884
3911
  );
3885
3912
  request2.on("timeout", () => {
3886
3913
  request2.destroy();
3887
- resolve20(null);
3914
+ resolve21(null);
3888
3915
  });
3889
- request2.on("error", () => resolve20(null));
3916
+ request2.on("error", () => resolve21(null));
3890
3917
  request2.end();
3891
3918
  });
3892
3919
  }
3893
3920
  function sleep2(ms) {
3894
- return new Promise((resolve20) => setTimeout(resolve20, ms));
3921
+ return new Promise((resolve21) => setTimeout(resolve21, ms));
3895
3922
  }
3896
3923
 
3897
3924
  // src/shared/app-files.ts
@@ -3911,13 +3938,15 @@ function planUploadParts(totalBytes, partSize) {
3911
3938
  }
3912
3939
  return parts;
3913
3940
  }
3914
- var APP_STORAGE_LIMIT_BYTES = {
3941
+ var GIB = 1024 * 1024 * 1024;
3942
+ var ACCOUNT_STORAGE_LIMIT_BYTES = {
3915
3943
  test: 0,
3916
- free: 128 * 1024 * 1024,
3917
- starter: 512 * 1024 * 1024,
3918
- premium: 2 * 1024 * 1024 * 1024,
3919
- admin: 10 * 1024 * 1024 * 1024
3944
+ free: 1 * GIB,
3945
+ starter: 10 * GIB,
3946
+ premium: 30 * GIB,
3947
+ admin: 100 * GIB
3920
3948
  };
3949
+ var REPO_STORE_LIMIT_BYTES = 256 * 1024 * 1024;
3921
3950
  var MAX_DEPLOY_ASSET_FILE_BYTES = 25 * 1024 * 1024;
3922
3951
  var DANGEROUS_MIME_TYPES = /* @__PURE__ */ new Set([
3923
3952
  "text/html",
@@ -3968,11 +3997,11 @@ function describeFilesFailure(status3, body) {
3968
3997
  import * as p6 from "@clack/prompts";
3969
3998
  import { execSync as execSync2 } from "child_process";
3970
3999
  import { createHash as createHash2 } from "crypto";
3971
- import { existsSync as existsSync15, readFileSync as readFileSync17, readdirSync as readdirSync4 } from "fs";
3972
- import { dirname as dirname5, join as join16, resolve as resolve10 } from "path";
4000
+ import { existsSync as existsSync15, readFileSync as readFileSync18, readdirSync as readdirSync4 } from "fs";
4001
+ import { dirname as dirname6, join as join16, resolve as resolve11 } from "path";
3973
4002
 
3974
4003
  // src/documentation/deploy.ts
3975
- import { existsSync as existsSync13, readFileSync as readFileSync15 } from "fs";
4004
+ import { existsSync as existsSync13, readFileSync as readFileSync16 } from "fs";
3976
4005
  import { join as join14 } from "path";
3977
4006
  function readDocumentationDeployManifest(appDir, clientDir) {
3978
4007
  if (!existsSync13(join14(appDir, "documentation.json"))) return void 0;
@@ -3982,7 +4011,7 @@ function readDocumentationDeployManifest(appDir, clientDir) {
3982
4011
  "documentation.json exists but the build produced no documentation manifest. Ensure vite.config.ts includes deepSpaceDocumentation() from deepspace/documentation."
3983
4012
  );
3984
4013
  }
3985
- return JSON.parse(readFileSync15(manifestPath, "utf8"));
4014
+ return JSON.parse(readFileSync16(manifestPath, "utf8"));
3986
4015
  }
3987
4016
 
3988
4017
  // src/cli/commands/update/app-migrations.ts
@@ -3994,7 +4023,7 @@ import {
3994
4023
  ftruncateSync,
3995
4024
  lstatSync,
3996
4025
  openSync,
3997
- readFileSync as readFileSync16,
4026
+ readFileSync as readFileSync17,
3998
4027
  writeSync
3999
4028
  } from "fs";
4000
4029
  import { join as join15 } from "path";
@@ -4139,7 +4168,7 @@ function readRegularFile(path) {
4139
4168
  if (!opened.isFile() || opened.dev !== after.dev || opened.ino !== after.ino) {
4140
4169
  throw new Error(`Migration input changed while opening: ${path}`);
4141
4170
  }
4142
- return readFileSync16(fd, "utf-8");
4171
+ return readFileSync17(fd, "utf-8");
4143
4172
  } finally {
4144
4173
  closeSync(fd);
4145
4174
  }
@@ -4165,7 +4194,7 @@ function writeRegularFile(path, before, after) {
4165
4194
  if (!opened.isFile() || opened.dev !== currentPath.dev || opened.ino !== currentPath.ino) {
4166
4195
  throw new Error(`Migration input changed while opening: ${path}`);
4167
4196
  }
4168
- if (readFileSync16(fd, "utf-8") !== before) {
4197
+ if (readFileSync17(fd, "utf-8") !== before) {
4169
4198
  throw new Error(`Migration input changed after planning: ${path}`);
4170
4199
  }
4171
4200
  ftruncateSync(fd, 0);
@@ -4288,12 +4317,12 @@ async function buildDeployBundle(options) {
4288
4317
  "build_output_missing"
4289
4318
  );
4290
4319
  }
4291
- const deployConfig = JSON.parse(readFileSync17(deployConfigPath, "utf-8"));
4292
- const outputWranglerPath = resolve10(dirname5(deployConfigPath), deployConfig.configPath);
4320
+ const deployConfig = JSON.parse(readFileSync18(deployConfigPath, "utf-8"));
4321
+ const outputWranglerPath = resolve11(dirname6(deployConfigPath), deployConfig.configPath);
4293
4322
  if (!existsSync15(outputWranglerPath)) {
4294
4323
  output.die(`Output wrangler.json not found at ${outputWranglerPath}`, "build_output_missing");
4295
4324
  }
4296
- const outputConfig = JSON.parse(readFileSync17(outputWranglerPath, "utf-8"));
4325
+ const outputConfig = JSON.parse(readFileSync18(outputWranglerPath, "utf-8"));
4297
4326
  if (outputConfig.name && outputConfig.name !== appName) {
4298
4327
  const envHint = envName ? ` (--env ${envName})` : "";
4299
4328
  output.die(
@@ -4301,9 +4330,9 @@ async function buildDeployBundle(options) {
4301
4330
  "build_output_mismatch"
4302
4331
  );
4303
4332
  }
4304
- const workerDir = dirname5(outputWranglerPath);
4333
+ const workerDir = dirname6(outputWranglerPath);
4305
4334
  const workerBundlePath = join16(workerDir, outputConfig.main);
4306
- const clientDir = outputConfig.assets?.directory ? resolve10(workerDir, outputConfig.assets.directory) : null;
4335
+ const clientDir = outputConfig.assets?.directory ? resolve11(workerDir, outputConfig.assets.directory) : null;
4307
4336
  if (!existsSync15(workerBundlePath)) {
4308
4337
  output.die(`Worker bundle not found at ${workerBundlePath}`, "build_output_missing");
4309
4338
  }
@@ -4320,7 +4349,7 @@ async function buildDeployBundle(options) {
4320
4349
  const assets = collectAssets(clientDir);
4321
4350
  const assetConfig = readDeployAssetConfig(clientDir);
4322
4351
  spinner2.stop(`Collected ${assets.length} assets`);
4323
- const workerJs = readFileSync17(workerBundlePath, "utf-8");
4352
+ const workerJs = readFileSync18(workerBundlePath, "utf-8");
4324
4353
  const doBindings = outputConfig.durable_objects?.bindings;
4325
4354
  const sqliteClasses = new Set(
4326
4355
  outputConfig.migrations?.flatMap((migration) => migration.new_sqlite_classes ?? []) ?? []
@@ -4399,7 +4428,7 @@ function collectAssets(dir) {
4399
4428
  if (entry.isDirectory()) {
4400
4429
  walk(fullPath, relativePath);
4401
4430
  } else {
4402
- const bytes = readFileSync17(fullPath);
4431
+ const bytes = readFileSync18(fullPath);
4403
4432
  assets.push({
4404
4433
  path: `/${relativePath}`,
4405
4434
  hash: createHash2("sha256").update(bytes).digest("hex"),
@@ -4417,7 +4446,7 @@ function readDeployAssetConfig(clientDir) {
4417
4446
  for (const [file, key] of [["_headers", "_headers"], ["_redirects", "_redirects"]]) {
4418
4447
  const filePath = join16(clientDir, file);
4419
4448
  if (!existsSync15(filePath)) continue;
4420
- const content = readFileSync17(filePath, "utf8");
4449
+ const content = readFileSync18(filePath, "utf8");
4421
4450
  if (content.trim()) config[key] = content;
4422
4451
  }
4423
4452
  return config;
@@ -4670,19 +4699,20 @@ function createDeployOutput(json) {
4670
4699
  introShown = true;
4671
4700
  },
4672
4701
  die(message, code, opts = {}) {
4702
+ const action = opts.action ? executableAction(opts.action) : void 0;
4673
4703
  if (json) {
4674
4704
  emitJson({
4675
4705
  ok: false,
4676
4706
  code,
4677
4707
  error: message,
4678
4708
  ...opts.actionRequired ? { actionRequired: true } : {},
4679
- ...opts.action ? { action: opts.action } : {}
4709
+ ...action ? { action } : {}
4680
4710
  });
4681
4711
  } else {
4682
4712
  if (introShown && opts.actionRequired) p8.log.warn(withSlug(message, code));
4683
4713
  else if (introShown) p8.cancel(withSlug(message, code));
4684
4714
  else console.error(withSlug(message, code));
4685
- if (opts.action) printAction(opts.action);
4715
+ if (action) printAction(action);
4686
4716
  }
4687
4717
  process.exit(opts.actionRequired ? 2 : 1);
4688
4718
  }
@@ -4691,7 +4721,7 @@ function createDeployOutput(json) {
4691
4721
 
4692
4722
  // src/cli/commands/deploy/request.ts
4693
4723
  import * as p10 from "@clack/prompts";
4694
- import { readFileSync as readFileSync18 } from "fs";
4724
+ import { readFileSync as readFileSync19 } from "fs";
4695
4725
 
4696
4726
  // src/cli/commands/deploy/repository.ts
4697
4727
  import * as p9 from "@clack/prompts";
@@ -4884,7 +4914,7 @@ function repoApi(deployUrl, token, appId) {
4884
4914
  // src/cli/lib/vc-remote.ts
4885
4915
  import { existsSync as existsSync17 } from "fs";
4886
4916
  import { tmpdir } from "os";
4887
- import { isAbsolute as isAbsolute3, relative, resolve as resolve11 } from "path";
4917
+ import { isAbsolute as isAbsolute3, relative, resolve as resolve12 } from "path";
4888
4918
  function spaceRemoteName(environment = DEEPSPACE_ENV) {
4889
4919
  if (environment === "staging") return "space-staging";
4890
4920
  if (environment === "production") return "space";
@@ -4930,7 +4960,7 @@ function entryIsTransient(cwd) {
4930
4960
  const entry = process.argv[1];
4931
4961
  if (!entry) return true;
4932
4962
  if (/[\\/]_npx[\\/]/.test(entry) || entry.startsWith(tmpdir())) return true;
4933
- const rel = relative(resolve11(cwd), resolve11(entry));
4963
+ const rel = relative(resolve12(cwd), resolve12(entry));
4934
4964
  return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
4935
4965
  }
4936
4966
  function helperEntryUsable(command) {
@@ -5003,6 +5033,25 @@ function ensureSpaceRemote(cwd, appId, remote = SPACE_REMOTE) {
5003
5033
  installCredentialHelper(cwd, url);
5004
5034
  return url;
5005
5035
  }
5036
+ function ensureGitIdentity(cwd, token) {
5037
+ try {
5038
+ const configured = (key) => runGit(cwd, ["config", "--get", key], { allowFail: true }).stdout.toString("utf-8").trim() !== "";
5039
+ const missingName = !configured("user.name");
5040
+ const missingEmail = !configured("user.email");
5041
+ if (!missingName && !missingEmail) return;
5042
+ const payload = decodeJwtPayload(token);
5043
+ if (!payload.email) return;
5044
+ if (missingName) {
5045
+ runGit(cwd, ["config", "--local", "user.name", payload.name || payload.email], {
5046
+ allowFail: true
5047
+ });
5048
+ }
5049
+ if (missingEmail) {
5050
+ runGit(cwd, ["config", "--local", "user.email", payload.email], { allowFail: true });
5051
+ }
5052
+ } catch {
5053
+ }
5054
+ }
5006
5055
  function appIdFromRepoUrl(url) {
5007
5056
  const match = /\/api\/repo\/([^/]+)\/?$/.exec(url);
5008
5057
  return match ? decodeURIComponent(match[1]) : null;
@@ -5418,7 +5467,7 @@ async function pushWithTransientRetry(push) {
5418
5467
  const message = errorMessage3(error);
5419
5468
  const transient = /(?:HTTP |error: )(?:429|503)\b/i.test(message);
5420
5469
  if (transient && attempt < backoffMs.length && Date.now() + backoffMs[attempt] < deadline) {
5421
- await new Promise((resolve20) => setTimeout(resolve20, backoffMs[attempt]));
5470
+ await new Promise((resolve21) => setTimeout(resolve21, backoffMs[attempt]));
5422
5471
  continue;
5423
5472
  }
5424
5473
  throw error;
@@ -5458,6 +5507,7 @@ async function deployBuiltBundle(options) {
5458
5507
  appId,
5459
5508
  appName,
5460
5509
  token,
5510
+ claimReleased,
5461
5511
  ignoreStale,
5462
5512
  bundle,
5463
5513
  secrets,
@@ -5475,7 +5525,8 @@ async function deployBuiltBundle(options) {
5475
5525
  await uploadDeployAssets({ deployUrl, appId, token, assets: bundle.assets, output, spinner: spinner2 });
5476
5526
  spinner2.message(`Deploying ${appName}...`);
5477
5527
  let confirmRename = options.rename;
5478
- const bail = async (message, stopLabel = "Deploy failed", code, actionRequired = false, action) => {
5528
+ const bail = async (message, stopLabel = "Deploy failed", code, actionRequired = false, rawAction) => {
5529
+ const action = rawAction ? executableAction(rawAction) : void 0;
5479
5530
  if (stopLabel !== null) spinner2.stop(stopLabel);
5480
5531
  p10.cancel(code ? withSlug(message, code) : message);
5481
5532
  if (action) printAction(action);
@@ -5521,6 +5572,7 @@ async function deployBuiltBundle(options) {
5521
5572
  }
5522
5573
  form.append("name", appName);
5523
5574
  if (confirmRename) form.append("confirmRename", "true");
5575
+ if (claimReleased) form.append("claimReleased", "true");
5524
5576
  if (shouldSendLineage(repository.commitOid, repository.recoverable)) {
5525
5577
  form.append("commitOid", repository.commitOid);
5526
5578
  }
@@ -5694,7 +5746,7 @@ async function uploadDeployAssets(options) {
5694
5746
  // against the plan before it touches storage, and a stream body
5695
5747
  // would be sent chunked with no length at all; a retry also needs a
5696
5748
  // body it can send again.
5697
- body: readFileSync18(asset.sourcePath)
5749
+ body: readFileSync19(asset.sourcePath)
5698
5750
  })
5699
5751
  );
5700
5752
  if (!response.ok) {
@@ -5772,7 +5824,7 @@ async function postWithRetry(url, makeInit, {
5772
5824
  lastError = error;
5773
5825
  }
5774
5826
  if (attempt < attempts) {
5775
- await new Promise((resolve20) => setTimeout(resolve20, Math.min(8e3, 500 * 2 ** (attempt - 1))));
5827
+ await new Promise((resolve21) => setTimeout(resolve21, Math.min(8e3, 500 * 2 ** (attempt - 1))));
5776
5828
  }
5777
5829
  }
5778
5830
  if (lastResponse) return lastResponse;
@@ -5781,7 +5833,7 @@ async function postWithRetry(url, makeInit, {
5781
5833
 
5782
5834
  // src/cli/commands/deploy/secrets.ts
5783
5835
  import * as p11 from "@clack/prompts";
5784
- import { existsSync as existsSync18, readFileSync as readFileSync19 } from "fs";
5836
+ import { existsSync as existsSync18, readFileSync as readFileSync20 } from "fs";
5785
5837
  function classifyDevVarsSecrets(options) {
5786
5838
  const store = new Set(options.storeSecretNames);
5787
5839
  const strayKeys = options.handEditedDevVarKeys.filter((key) => !store.has(key));
@@ -5880,7 +5932,7 @@ function prepareDeploySecrets(options) {
5880
5932
  }
5881
5933
  function readCustomDevVars(path, output) {
5882
5934
  if (!existsSync18(path)) return {};
5883
- const section = extractCustomDevVars(stripGeneratedSecretsCache(readFileSync19(path, "utf-8")));
5935
+ const section = extractCustomDevVars(stripGeneratedSecretsCache(readFileSync20(path, "utf-8")));
5884
5936
  if (!section) return {};
5885
5937
  try {
5886
5938
  return parseDevVars(section);
@@ -5920,6 +5972,11 @@ var deploy_default = defineCommand3({
5920
5972
  description: "Confirm that a changed wrangler `name` renames this app (its URL moves; data, secrets, and collaborators travel). Without this flag an interactive prompt asks.",
5921
5973
  default: false
5922
5974
  },
5975
+ "claim-released": {
5976
+ type: "boolean",
5977
+ description: "Platform admins only: claim an app name still inside its 30-day release cooldown. The previous owner permanently loses their reserved reclaim. Non-admin accounts are refused.",
5978
+ default: false
5979
+ },
5923
5980
  "allow-missing-secrets": {
5924
5981
  type: "boolean",
5925
5982
  description: "Deploy even though hand-edited .dev.vars secrets are absent from the store (they will NOT be deployed, and any a previous deploy set are dropped).",
@@ -5945,7 +6002,7 @@ var deploy_default = defineCommand3({
5945
6002
  async run({ args }) {
5946
6003
  const output = createDeployOutput(args.json === true);
5947
6004
  preflightNodeVersion("deploy");
5948
- const appDir = resolve12(args.dir ?? ".");
6005
+ const appDir = resolve13(args.dir ?? ".");
5949
6006
  const selectorRefusal = blankSelectorRefusal(args);
5950
6007
  if (selectorRefusal) output.die(selectorRefusal.error, selectorRefusal.code);
5951
6008
  const envName = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
@@ -6033,6 +6090,7 @@ var deploy_default = defineCommand3({
6033
6090
  appName,
6034
6091
  token,
6035
6092
  rename: args.rename === true,
6093
+ claimReleased: args["claim-released"] === true,
6036
6094
  ignoreStale: Boolean(args["ignore-stale"]),
6037
6095
  bundle,
6038
6096
  secrets,
@@ -6061,10 +6119,7 @@ var deploy_default = defineCommand3({
6061
6119
  url: body.url,
6062
6120
  releaseId: body.releaseId ?? null,
6063
6121
  bundleRetained: body.bundleRetained ?? null,
6064
- // `serving` says what was actually established; `edgePropagating`
6065
- // stays for callers written against the old shape.
6066
6122
  serving,
6067
- edgePropagating: serving === "unconfirmed",
6068
6123
  recoverable: repository.recoverable,
6069
6124
  ...staleBaseGuardFields(body)
6070
6125
  });
@@ -6083,7 +6138,7 @@ var deploy_default = defineCommand3({
6083
6138
  appName,
6084
6139
  url: body.url ?? null,
6085
6140
  // Always present, so a caller branches on one field instead of
6086
- // inferring success from the absence of `edgePropagating`.
6141
+ // inferring success from an absent one.
6087
6142
  serving,
6088
6143
  releaseId: body.releaseId ?? null,
6089
6144
  bundleRetained: body.bundleRetained ?? null,
@@ -6763,8 +6818,8 @@ var usage_default = defineDeepspaceCommand({
6763
6818
  });
6764
6819
 
6765
6820
  // src/cli/commands/add.ts
6766
- import { existsSync as existsSync20, readFileSync as readFileSync21, realpathSync as realpathSync3 } from "fs";
6767
- import { dirname as dirname7, join as join19, resolve as resolve14 } from "path";
6821
+ import { existsSync as existsSync20, readFileSync as readFileSync22, realpathSync as realpathSync4 } from "fs";
6822
+ import { dirname as dirname8, join as join19, resolve as resolve15 } from "path";
6768
6823
  import { pathToFileURL as pathToFileURL3 } from "url";
6769
6824
 
6770
6825
  // src/cli/commands/feature-installer.ts
@@ -6776,7 +6831,7 @@ import {
6776
6831
  mkdirSync as mkdirSync6,
6777
6832
  mkdtempSync,
6778
6833
  openSync as openSync2,
6779
- readFileSync as readFileSync20,
6834
+ readFileSync as readFileSync21,
6780
6835
  readSync,
6781
6836
  readdirSync as readdirSync5,
6782
6837
  rmSync as rmSync4,
@@ -6784,21 +6839,21 @@ import {
6784
6839
  } from "fs";
6785
6840
  import { spawnSync as spawnSync6 } from "child_process";
6786
6841
  import { tmpdir as tmpdir2 } from "os";
6787
- import { dirname as dirname6, isAbsolute as isAbsolute4, join as join18, resolve as resolve13 } from "path";
6842
+ import { dirname as dirname7, isAbsolute as isAbsolute4, join as join18, resolve as resolve14 } from "path";
6788
6843
  import { fileURLToPath } from "url";
6789
6844
  var NAV_MARKER = "// \u2500\u2500 Features add nav items below this line \u2500\u2500";
6790
6845
  function featurePackageRoot(moduleUrl = import.meta.url) {
6791
- let dir = dirname6(fileURLToPath(moduleUrl));
6846
+ let dir = dirname7(fileURLToPath(moduleUrl));
6792
6847
  for (; ; ) {
6793
6848
  const packagePath = join18(dir, "package.json");
6794
6849
  if (existsSync19(packagePath)) {
6795
6850
  try {
6796
- const pkg2 = JSON.parse(readFileSync20(packagePath, "utf-8"));
6851
+ const pkg2 = JSON.parse(readFileSync21(packagePath, "utf-8"));
6797
6852
  if (pkg2.name === "deepspace") return dir;
6798
6853
  } catch {
6799
6854
  }
6800
6855
  }
6801
- const parent = dirname6(dir);
6856
+ const parent = dirname7(dir);
6802
6857
  if (parent === dir) throw new Error("Could not locate the deepspace package root");
6803
6858
  dir = parent;
6804
6859
  }
@@ -6810,7 +6865,7 @@ function readFeatureConfig(id, packageRoot = featurePackageRoot()) {
6810
6865
  const configPath = join18(packageRoot, "features", id, "feature.json");
6811
6866
  if (!existsSync19(configPath)) return null;
6812
6867
  try {
6813
- const config = JSON.parse(readFileSync20(configPath, "utf-8"));
6868
+ const config = JSON.parse(readFileSync21(configPath, "utf-8"));
6814
6869
  if (typeof config.id !== "string" || typeof config.name !== "string" || !Array.isArray(config.files)) {
6815
6870
  return null;
6816
6871
  }
@@ -6859,7 +6914,7 @@ function suggestFeature(featureId, packageRoot = featurePackageRoot()) {
6859
6914
  return closest !== null && closestDistance <= threshold ? closest : null;
6860
6915
  }
6861
6916
  function resolveTargetDir(raw) {
6862
- return isAbsolute4(raw) ? raw : resolve13(raw);
6917
+ return isAbsolute4(raw) ? raw : resolve14(raw);
6863
6918
  }
6864
6919
  function resolveFeatureDestPath(fileDest, route) {
6865
6920
  const pages = "src/pages/";
@@ -6869,13 +6924,13 @@ function resolveFeatureDestPath(fileDest, route) {
6869
6924
  return route?.protected === false ? `${appPages}${rest}` : `${appPages}(protected)/${rest}`;
6870
6925
  }
6871
6926
  function copyFile(src, dest) {
6872
- mkdirSync6(dirname6(dest), { recursive: true });
6927
+ mkdirSync6(dirname7(dest), { recursive: true });
6873
6928
  copyFileSync(src, dest);
6874
6929
  }
6875
6930
  function copyFileWithImportShift(src, dest, levels) {
6876
- mkdirSync6(dirname6(dest), { recursive: true });
6931
+ mkdirSync6(dirname7(dest), { recursive: true });
6877
6932
  const prefix = "../".repeat(levels);
6878
- const shifted = readFileSync20(src, "utf-8").replace(
6933
+ const shifted = readFileSync21(src, "utf-8").replace(
6879
6934
  /(from\s+|import\s+|import\()(['"])(\.\.\/)/g,
6880
6935
  `$1$2${prefix}$3`
6881
6936
  );
@@ -6934,7 +6989,7 @@ function integrateSchema(config, targetDir, emit) {
6934
6989
  if (!existsSync19(schemasPath)) {
6935
6990
  return [unresolvedSchema(config.schema, "src/schemas.ts was not found.", emit)];
6936
6991
  }
6937
- let content = readFileSync20(schemasPath, "utf-8");
6992
+ let content = readFileSync21(schemasPath, "utf-8");
6938
6993
  const { exportName, importPath, spreadOperator } = config.schema;
6939
6994
  const importPattern = new RegExp(`^import\\s+\\{[^}]*\\b${exportName}\\b`, "m");
6940
6995
  if (importPattern.test(content)) {
@@ -6991,7 +7046,7 @@ function integrateActions(config, targetDir, emit) {
6991
7046
  if (!existsSync19(actionsPath)) {
6992
7047
  return [unresolvedActions(config.actions, "src/actions/index.ts was not found.", emit)];
6993
7048
  }
6994
- let content = readFileSync20(actionsPath, "utf-8");
7049
+ let content = readFileSync21(actionsPath, "utf-8");
6995
7050
  const registryPattern = /export const actions:\s*Record<string,\s*ActionHandler(?:<[^>]*>)?>\s*=\s*\{/;
6996
7051
  if (!registryPattern.test(content)) {
6997
7052
  const reason = "src/actions/index.ts has an unexpected structure.";
@@ -7046,7 +7101,7 @@ function integrateCode(config, targetDir, emit) {
7046
7101
  });
7047
7102
  continue;
7048
7103
  }
7049
- const original = readFileSync20(targetPath, "utf-8");
7104
+ const original = readFileSync21(targetPath, "utf-8");
7050
7105
  if (original.includes(insertion.insert.trim())) {
7051
7106
  emit(` Integration already present: ${insertion.file}`);
7052
7107
  continue;
@@ -7082,7 +7137,7 @@ ${content.slice(importIndex < 0 ? 0 : importIndex)}`;
7082
7137
  function integrateIgnore(config, targetDir) {
7083
7138
  if (!config.ignore?.length) return;
7084
7139
  const targetPath = join18(targetDir, ".gitignore");
7085
- const current = existsSync19(targetPath) ? readFileSync20(targetPath, "utf-8") : "";
7140
+ const current = existsSync19(targetPath) ? readFileSync21(targetPath, "utf-8") : "";
7086
7141
  const present = new Set(current.split(/\r?\n/).map((line) => line.trim()));
7087
7142
  const missing = config.ignore.filter((pattern) => !present.has(pattern));
7088
7143
  if (missing.length === 0) return;
@@ -7102,7 +7157,7 @@ function integrateWrangler(config, targetDir, emit) {
7102
7157
  if (!existsSync19(targetPath)) {
7103
7158
  return [{ area: "wrangler", reason: "wrangler.toml was not found.", steps }];
7104
7159
  }
7105
- let source = readFileSync20(targetPath, "utf-8");
7160
+ let source = readFileSync21(targetPath, "utf-8");
7106
7161
  if (runWorkerFirst && !/^\[assets\]\s*$/m.test(source)) {
7107
7162
  return [{ area: "wrangler", reason: "wrangler.toml has no [assets] section.", steps }];
7108
7163
  }
@@ -7191,7 +7246,7 @@ function integrateCss(packageRoot, config, targetDir, emit) {
7191
7246
  }
7192
7247
  ];
7193
7248
  }
7194
- let styles = readFileSync20(stylesPath, "utf-8");
7249
+ let styles = readFileSync21(stylesPath, "utf-8");
7195
7250
  let integrated = 0;
7196
7251
  const unresolved = [];
7197
7252
  for (const cssFile of config.css) {
@@ -7205,7 +7260,7 @@ function integrateCss(packageRoot, config, targetDir, emit) {
7205
7260
  });
7206
7261
  continue;
7207
7262
  }
7208
- const css = readFileSync20(srcPath, "utf-8");
7263
+ const css = readFileSync21(srcPath, "utf-8");
7209
7264
  const fingerprint = css.match(/\.([\w-]+)\s*\{|@keyframes\s+([\w-]+)/);
7210
7265
  const marker = fingerprint?.[1] ?? fingerprint?.[2];
7211
7266
  if (marker && styles.includes(marker)) {
@@ -7236,7 +7291,7 @@ function integrateRoute(config, targetDir, emit) {
7236
7291
  if (!existsSync19(navPath)) {
7237
7292
  return [unresolvedRoute(config.route, config, "src/nav.ts was not found.", emit)];
7238
7293
  }
7239
- let content = readFileSync20(navPath, "utf-8");
7294
+ let content = readFileSync21(navPath, "utf-8");
7240
7295
  const routePath = config.route.path;
7241
7296
  if (content.includes(`'${routePath}'`)) {
7242
7297
  emit(` Nav already present: ${routePath}`);
@@ -7317,7 +7372,7 @@ function integrateDependencies(config, targetDir, runInstall, outputVisible, emi
7317
7372
  }
7318
7373
  let pkg2;
7319
7374
  try {
7320
- pkg2 = JSON.parse(readFileSync20(packagePath, "utf-8"));
7375
+ pkg2 = JSON.parse(readFileSync21(packagePath, "utf-8"));
7321
7376
  } catch (error) {
7322
7377
  emit(
7323
7378
  ` Could not patch package.json (parse error: ${error instanceof Error ? error.message : String(error)})`
@@ -7377,7 +7432,7 @@ ${child.diagnosticTail}` : "";
7377
7432
  }
7378
7433
  function dependencySnapshot(targetDir) {
7379
7434
  try {
7380
- const pkg2 = JSON.parse(readFileSync20(join18(targetDir, "package.json"), "utf-8"));
7435
+ const pkg2 = JSON.parse(readFileSync21(join18(targetDir, "package.json"), "utf-8"));
7381
7436
  return {
7382
7437
  dependencies: pkg2.dependencies ?? {},
7383
7438
  devDependencies: pkg2.devDependencies ?? {}
@@ -7626,7 +7681,7 @@ function isDeepSpaceApp(dir) {
7626
7681
  const packagePath = join19(dir, "package.json");
7627
7682
  if (!existsSync20(packagePath)) return false;
7628
7683
  try {
7629
- const pkg2 = JSON.parse(readFileSync21(packagePath, "utf-8"));
7684
+ const pkg2 = JSON.parse(readFileSync22(packagePath, "utf-8"));
7630
7685
  return Boolean(pkg2.dependencies?.deepspace || pkg2.devDependencies?.deepspace);
7631
7686
  } catch {
7632
7687
  return false;
@@ -7637,7 +7692,7 @@ function installedPackageRoot(fromDir) {
7637
7692
  for (; ; ) {
7638
7693
  const candidate = join19(dir, "node_modules", "deepspace");
7639
7694
  if (existsSync20(join19(candidate, "package.json"))) return candidate;
7640
- const parent = dirname7(dir);
7695
+ const parent = dirname8(dir);
7641
7696
  if (parent === dir) return null;
7642
7697
  dir = parent;
7643
7698
  }
@@ -7650,7 +7705,7 @@ async function targetInstaller(appDir) {
7650
7705
  "feature_installer_missing"
7651
7706
  );
7652
7707
  }
7653
- if (realpathSync3(installedRoot) === realpathSync3(featurePackageRoot())) {
7708
+ if (realpathSync4(installedRoot) === realpathSync4(featurePackageRoot())) {
7654
7709
  return { installFeature };
7655
7710
  }
7656
7711
  const entry = join19(installedRoot, "dist", "feature-installer.js");
@@ -7747,7 +7802,7 @@ var add_default = defineDeepspaceCommand({
7747
7802
  }
7748
7803
  return { data: { features: featureCatalog(root) } };
7749
7804
  }
7750
- const appDir = resolve14(args.dir ?? ".");
7805
+ const appDir = resolve15(args.dir ?? ".");
7751
7806
  if (!existsSync20(appDir)) {
7752
7807
  throw new Refusal(
7753
7808
  `Target directory not found: ${appDir}
@@ -7829,11 +7884,11 @@ function fmtDate(iso) {
7829
7884
  }
7830
7885
  async function confirm4(message) {
7831
7886
  process.stdout.write(`${message} [y/N] `);
7832
- return new Promise((resolve20) => {
7887
+ return new Promise((resolve21) => {
7833
7888
  process.stdin.setEncoding("utf-8");
7834
7889
  process.stdin.once("data", (data) => {
7835
7890
  const answer = data.toString().trim().toLowerCase();
7836
- resolve20(answer === "y" || answer === "yes");
7891
+ resolve21(answer === "y" || answer === "yes");
7837
7892
  });
7838
7893
  });
7839
7894
  }
@@ -8123,7 +8178,7 @@ var domain_default = defineCommand5({
8123
8178
 
8124
8179
  // src/cli/commands/app-files.ts
8125
8180
  import { defineCommand as defineCommand6 } from "citty";
8126
- import { basename as basename4, resolve as resolve15 } from "path";
8181
+ import { basename as basename4, resolve as resolve16 } from "path";
8127
8182
  import { existsSync as existsSync21, statSync as statSync2 } from "fs";
8128
8183
 
8129
8184
  // src/cli/lib/app-files-api.ts
@@ -8324,7 +8379,7 @@ async function deleteAppFile(baseUrl, token, appId, key) {
8324
8379
  const path = filesPath(appId, `/${encodeKeyPath(key)}`);
8325
8380
  const res = await request(baseUrl, token, path, { method: "DELETE" });
8326
8381
  const body = await readJson(res, path);
8327
- return { existed: body.existed !== false };
8382
+ return { existed: body.existed };
8328
8383
  }
8329
8384
  async function downloadAppFile(baseUrl, token, appId, key, destination) {
8330
8385
  const path = filesPath(appId, `/${encodeKeyPath(key)}`);
@@ -8394,7 +8449,7 @@ var put = defineDeepspaceCommand({
8394
8449
  app: { type: "string", description: "App id or live name (defaults to ./wrangler.toml)" }
8395
8450
  },
8396
8451
  async run({ args }) {
8397
- const localPath = resolve15(String(args.file));
8452
+ const localPath = resolve16(String(args.file));
8398
8453
  if (!existsSync21(localPath) || !statSync2(localPath).isFile()) {
8399
8454
  throw new Refusal(`No such file: ${args.file}`, "file_not_found");
8400
8455
  }
@@ -8495,7 +8550,7 @@ var get = defineDeepspaceCommand({
8495
8550
  },
8496
8551
  async run({ args }) {
8497
8552
  const key = requireKey(String(args.key));
8498
- const destination = resolve15(String(args.out ?? basename4(key)));
8553
+ const destination = resolve16(String(args.out ?? basename4(key)));
8499
8554
  const { token, appId } = await target(args.app);
8500
8555
  const result = await downloadAppFile(PLATFORM_URL, token, appId, key, destination);
8501
8556
  if (!args.json) console.log(`\u2713 ${key} \u2192 ${destination} (${formatBytes(result.bytes)})`);
@@ -8865,7 +8920,7 @@ var transfer_default = defineCommand8({
8865
8920
  import { defineCommand as defineCommand9 } from "citty";
8866
8921
 
8867
8922
  // src/cli/commands/_invoke-impl.ts
8868
- import { readFileSync as readFileSync22 } from "fs";
8923
+ import { readFileSync as readFileSync23 } from "fs";
8869
8924
  import * as p15 from "@clack/prompts";
8870
8925
  var API_URL7 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
8871
8926
  var DEFAULT_TIMEOUT_MS = 12e4;
@@ -8906,7 +8961,7 @@ function resolveBody(opts) {
8906
8961
  if (opts.body != null) {
8907
8962
  raw = opts.body;
8908
8963
  } else if (opts.bodyFile != null) {
8909
- raw = opts.bodyFile === "-" ? readFileSync22(0, "utf-8") : readFileSync22(opts.bodyFile, "utf-8");
8964
+ raw = opts.bodyFile === "-" ? readFileSync23(0, "utf-8") : readFileSync23(opts.bodyFile, "utf-8");
8910
8965
  } else {
8911
8966
  return "{}";
8912
8967
  }
@@ -9185,8 +9240,8 @@ var integrations_default = defineCommand9({
9185
9240
 
9186
9241
  // src/cli/commands/feedback.ts
9187
9242
  import * as p16 from "@clack/prompts";
9188
- import { existsSync as existsSync22, readFileSync as readFileSync23 } from "fs";
9189
- import { dirname as dirname8, join as join20 } from "path";
9243
+ import { existsSync as existsSync22, readFileSync as readFileSync24 } from "fs";
9244
+ import { dirname as dirname9, join as join20 } from "path";
9190
9245
  import { fileURLToPath as fileURLToPath2 } from "url";
9191
9246
  import { release } from "os";
9192
9247
  var API_URL8 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
@@ -9207,14 +9262,14 @@ function normalizeType(raw) {
9207
9262
  }
9208
9263
  function readCliVersion() {
9209
9264
  try {
9210
- const here = dirname8(fileURLToPath2(import.meta.url));
9265
+ const here = dirname9(fileURLToPath2(import.meta.url));
9211
9266
  const candidates = [
9212
9267
  join20(here, "..", "package.json"),
9213
9268
  join20(here, "..", "..", "..", "package.json")
9214
9269
  ];
9215
9270
  for (const path of candidates) {
9216
9271
  if (!existsSync22(path)) continue;
9217
- const pkg2 = JSON.parse(readFileSync23(path, "utf-8"));
9272
+ const pkg2 = JSON.parse(readFileSync24(path, "utf-8"));
9218
9273
  if (pkg2.name === "deepspace" && pkg2.version) return pkg2.version;
9219
9274
  }
9220
9275
  } catch {
@@ -9410,7 +9465,7 @@ Context: deepspace ${payload.cliVersion} \xB7 ${payload.nodeVersion} \xB7 ${payl
9410
9465
 
9411
9466
  // src/cli/commands/secrets.ts
9412
9467
  import { defineCommand as defineCommand10 } from "citty";
9413
- import { readFileSync as readFileSync24 } from "fs";
9468
+ import { readFileSync as readFileSync25 } from "fs";
9414
9469
 
9415
9470
  // src/cli/lib/citty-args.ts
9416
9471
  function dedupePositionals(bound, rest) {
@@ -9680,7 +9735,7 @@ var upload = defineCommand10({
9680
9735
  async run({ args }) {
9681
9736
  try {
9682
9737
  const t = await resolveTarget(args);
9683
- const content = args.file === "-" ? readFileSync24(0, "utf-8") : readFileSync24(args.file, "utf-8");
9738
+ const content = args.file === "-" ? readFileSync25(0, "utf-8") : readFileSync25(args.file, "utf-8");
9684
9739
  const secrets = parseSecretsUpload(content);
9685
9740
  if (Object.keys(secrets).length === 0)
9686
9741
  fail(new InputError("No secrets found in the input.", "empty_input"));
@@ -9936,7 +9991,7 @@ var init_default = defineDeepspaceCommand({
9936
9991
  // src/cli/commands/status.ts
9937
9992
  import { defineCommand as defineCommand11 } from "citty";
9938
9993
  import * as p17 from "@clack/prompts";
9939
- import { existsSync as existsSync23, readFileSync as readFileSync25 } from "fs";
9994
+ import { existsSync as existsSync23, readFileSync as readFileSync26 } from "fs";
9940
9995
  function timeAgo(iso) {
9941
9996
  const ms = Date.now() - Date.parse(iso);
9942
9997
  if (!Number.isFinite(ms) || ms < 0) return iso;
@@ -10030,7 +10085,7 @@ var status_default = defineCommand11({
10030
10085
  let who = "logged in";
10031
10086
  try {
10032
10087
  const payload = decodeJwtPayload(
10033
- readFileSync25(TOKEN_PATH, "utf-8").trim()
10088
+ readFileSync26(TOKEN_PATH, "utf-8").trim()
10034
10089
  );
10035
10090
  if (payload.email) who = payload.email;
10036
10091
  userId = payload.sub ?? null;
@@ -10182,21 +10237,24 @@ var status_default = defineCommand11({
10182
10237
  }
10183
10238
  const refs = refsResult.ok ? refsResult.value : void 0;
10184
10239
  const trunkRef = refs?.refs.find((r) => `refs/heads/${branch}` === r.name);
10240
+ const defaultBranch = refs?.head && refs.head.startsWith("refs/heads/") ? refs.head.slice("refs/heads/".length) : null;
10185
10241
  if (!facts.workspaceId && branch) {
10242
+ const isTrunk = defaultBranch === null ? null : branch === defaultBranch;
10243
+ const label = isTrunk === false ? "Branch sync" : "Trunk";
10186
10244
  if (githubSource) {
10187
- lines.push(["Trunk", `${branch} is owned through normal Git/GitHub`]);
10188
- json.trunk = { state: "external", provider: "github", branch };
10189
- } else if (!refsResult.ok) reportRemoteFailure2("Trunk", "trunk", refsResult.error);
10245
+ lines.push([label, `${branch} is owned through normal Git/GitHub`]);
10246
+ json.trunk = { state: "external", provider: "github", branch, isTrunk };
10247
+ } else if (!refsResult.ok) reportRemoteFailure2(label, "trunk", refsResult.error);
10190
10248
  else {
10191
10249
  const remoteOid = trunkRef?.oid ?? null;
10192
10250
  const localOid = resolveCommit(appDir, `refs/heads/${branch}`);
10193
10251
  const remoteKnownLocally = remoteOid ? resolveCommit(appDir, remoteOid) !== null : false;
10194
10252
  facts.trunkState = remoteOid === null && localOid === null ? "unknown" : remoteOid === localOid ? "in_sync" : remoteOid && localOid && remoteKnownLocally && isAncestor(appDir, remoteOid, localOid) ? "local_ahead" : remoteOid && localOid && remoteKnownLocally && isAncestor(appDir, localOid, remoteOid) ? "local_behind" : remoteOid && localOid && remoteKnownLocally ? "diverged" : "differs";
10195
10253
  lines.push([
10196
- "Trunk",
10254
+ label,
10197
10255
  remoteOid === null ? `cloud repo has no ${branch} yet` : facts.trunkState === "in_sync" ? `${branch} in sync with the cloud repo (${remoteOid.slice(0, 10)})` : `${branch} ${facts.trunkState.replace("_", " ")} relative to the cloud repo (local ${localOid?.slice(0, 10) ?? "none"}, cloud ${remoteOid.slice(0, 10)})`
10198
10256
  ]);
10199
- json.trunk = { state: facts.trunkState, localOid, remoteOid };
10257
+ json.trunk = { state: facts.trunkState, localOid, remoteOid, branch, isTrunk };
10200
10258
  }
10201
10259
  }
10202
10260
  if (!releaseResult.ok) reportRemoteFailure2("Live", "live", releaseResult.error);
@@ -10423,7 +10481,7 @@ var push_default = defineDeepspaceCommand({
10423
10481
 
10424
10482
  // src/cli/commands/pull.ts
10425
10483
  import * as p19 from "@clack/prompts";
10426
- import { realpathSync as realpathSync4 } from "fs";
10484
+ import { realpathSync as realpathSync5 } from "fs";
10427
10485
  function targetedVcAction(command, cwd, appId, branch) {
10428
10486
  return {
10429
10487
  cwd,
@@ -10447,7 +10505,7 @@ function workspaceBranchPullRefusal(branch, trunkBranch) {
10447
10505
  function worktreeHoldingBranch(worktrees, branch, selfPath) {
10448
10506
  const real = (pth) => {
10449
10507
  try {
10450
- return realpathSync4(pth);
10508
+ return realpathSync5(pth);
10451
10509
  } catch {
10452
10510
  return pth;
10453
10511
  }
@@ -10705,7 +10763,7 @@ var pull_default = defineDeepspaceCommand({
10705
10763
  // src/cli/commands/clone.ts
10706
10764
  import * as p20 from "@clack/prompts";
10707
10765
  import { existsSync as existsSync24, readdirSync as readdirSync6, rmSync as rmSync5 } from "fs";
10708
- import { resolve as resolve16, basename as basename5, join as join21 } from "path";
10766
+ import { resolve as resolve17, basename as basename5, join as join21 } from "path";
10709
10767
  function cleanFailedCloneDir(dir) {
10710
10768
  let entries;
10711
10769
  try {
@@ -10759,7 +10817,7 @@ var clone_default = defineDeepspaceCommand({
10759
10817
  async run({ args }) {
10760
10818
  assertAppTargetResolvable(String(args.app));
10761
10819
  const selector = String(args.app).trim();
10762
- const dir = resolve16(String(args.dir ?? selector));
10820
+ const dir = resolve17(String(args.dir ?? selector));
10763
10821
  const dirPreexisted = validateCloneTarget(dir);
10764
10822
  const spinner2 = args.json ? null : createSpinner();
10765
10823
  spinner2?.start(`Resolving ${selector}\u2026`);
@@ -10799,6 +10857,7 @@ var clone_default = defineDeepspaceCommand({
10799
10857
  throw err;
10800
10858
  }
10801
10859
  ensureSpaceRemote(dir, appId);
10860
+ ensureGitIdentity(dir, token);
10802
10861
  const head = resolveCommit(dir, "HEAD");
10803
10862
  const branch = currentBranch(dir);
10804
10863
  spinner2?.stop(
@@ -11021,14 +11080,13 @@ Re-run with --allow-do-deletion to accept the data loss.`,
11021
11080
  spinner2?.message("Waiting for the edge to serve this release...");
11022
11081
  serving = await waitForLiveRelease(result.url, result.releaseStamp, 9e4);
11023
11082
  }
11024
- const edgePropagating = serving === "unconfirmed";
11025
11083
  spinner2?.stop(
11026
11084
  serving === "confirmed" ? `Rolled back to ${result.rolledBackTo ?? releaseId}.` : serving === "unconfirmed" ? `Rolled back to ${result.rolledBackTo ?? releaseId} \u2014 the edge is still rolling it out.` : `Rolled back to ${result.rolledBackTo ?? releaseId} \u2014 serving could not be verified from here.`
11027
11085
  );
11028
11086
  const doClassGuard = result.doClassGuard === "unverified" ? "unverified" : void 0;
11029
11087
  if (!args.json) {
11030
11088
  if (result.url) {
11031
- if (edgePropagating) p22.log.warn(`URL: ${result.url} (accepted; serving not yet verified)`);
11089
+ if (serving === "unconfirmed") p22.log.warn(`URL: ${result.url} (accepted; serving not yet verified)`);
11032
11090
  else p22.log.success(`Live at: ${result.url}`);
11033
11091
  }
11034
11092
  if (result.bundleRetained === false) {
@@ -11056,7 +11114,6 @@ Re-run with --allow-do-deletion to accept the data loss.`,
11056
11114
  wranglerEnv: wranglerEnv ?? null,
11057
11115
  // Always present, matching `deploy`: one field to branch on.
11058
11116
  serving,
11059
- ...edgePropagating ? { edgePropagating: true } : {},
11060
11117
  ...doClassGuard ? { doClassGuard } : {}
11061
11118
  }
11062
11119
  };
@@ -11068,7 +11125,7 @@ import { defineCommand as defineCommand12 } from "citty";
11068
11125
 
11069
11126
  // src/cli/commands/workspace/new.ts
11070
11127
  import * as p25 from "@clack/prompts";
11071
- import { isAbsolute as isAbsolute6, resolve as resolve17 } from "path";
11128
+ import { isAbsolute as isAbsolute6, resolve as resolve18 } from "path";
11072
11129
 
11073
11130
  // src/cli/commands/workspace/analysis.ts
11074
11131
  import * as p23 from "@clack/prompts";
@@ -11077,6 +11134,14 @@ var PEER_REF_PREFIX = `${spacePrivateRef("peers")}/`;
11077
11134
  function peerWorkspaceRef(workspaceId) {
11078
11135
  return `${PEER_REF_PREFIX}${workspaceId}`;
11079
11136
  }
11137
+ function workspaceSyncRelation(dir, localOid, publishedOid) {
11138
+ if (localOid !== null && localOid === publishedOid) return "in_sync";
11139
+ if (localOid === null || publishedOid === null) return "unknown";
11140
+ if (resolveCommit(dir, publishedOid) === null) return "unknown";
11141
+ if (isAncestor(dir, localOid, publishedOid)) return "behind";
11142
+ if (isAncestor(dir, publishedOid, localOid)) return "ahead";
11143
+ return "diverged";
11144
+ }
11080
11145
  function formatCount(count) {
11081
11146
  if (!count) return "?";
11082
11147
  return count.capped ? `${count.count}+` : String(count.count);
@@ -11193,18 +11258,18 @@ function printOverlaps(overlaps, json) {
11193
11258
 
11194
11259
  // src/cli/commands/workspace/local.ts
11195
11260
  import * as p24 from "@clack/prompts";
11196
- import { mkdirSync as mkdirSync7, readFileSync as readFileSync26, realpathSync as realpathSync5, renameSync as renameSync2, statSync as statSync3, writeFileSync as writeFileSync9 } from "fs";
11197
- import { dirname as dirname9, isAbsolute as isAbsolute5, join as join22, relative as relative2, sep as sep2 } from "path";
11261
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync27, realpathSync as realpathSync6, renameSync as renameSync2, statSync as statSync3, writeFileSync as writeFileSync9 } from "fs";
11262
+ import { dirname as dirname10, isAbsolute as isAbsolute5, join as join22, relative as relative2, sep as sep2 } from "path";
11198
11263
  var WORKSPACE_MARKER = "deepspace-workspace.json";
11199
11264
  function excludeWorktreeDir(appDir, dir) {
11200
11265
  try {
11201
11266
  ensureLocalExclude(appDir, ".deepspace/");
11202
- const realDir = realpathSync5(dir);
11203
- const rel = relative2(realpathSync5(repoToplevel(appDir)), realDir);
11267
+ const realDir = realpathSync6(dir);
11268
+ const rel = relative2(realpathSync6(repoToplevel(appDir)), realDir);
11204
11269
  if (!rel || rel === ".." || rel.startsWith(`..${sep2}`) || isAbsolute5(rel)) return;
11205
11270
  const pattern = rel.split(sep2).join("/");
11206
11271
  if (pattern === ".deepspace" || pattern.startsWith(".deepspace/")) return;
11207
- const relApp = relative2(realpathSync5(appDir), realDir).split(sep2).join("/");
11272
+ const relApp = relative2(realpathSync6(appDir), realDir).split(sep2).join("/");
11208
11273
  if (relApp === ".deepspace" || relApp.startsWith(".deepspace/")) return;
11209
11274
  ensureLocalExclude(appDir, `/${pattern}/`);
11210
11275
  } catch {
@@ -11226,7 +11291,7 @@ function markManagedWorkspace(worktreeRoot, workspaceId) {
11226
11291
  const marker = { version: 1, workspaceId, mode: "managed-linked" };
11227
11292
  const tempPath = `${markerPath}.${process.pid}.tmp`;
11228
11293
  try {
11229
- mkdirSync7(dirname9(markerPath), { recursive: true });
11294
+ mkdirSync7(dirname10(markerPath), { recursive: true });
11230
11295
  writeFileSync9(tempPath, JSON.stringify(marker) + "\n");
11231
11296
  renameSync2(tempPath, markerPath);
11232
11297
  return true;
@@ -11251,7 +11316,7 @@ function isManagedWorkspaceWorktree(worktreeRoot, workspaceId) {
11251
11316
  const markerPath = worktreeMarkerPath(worktreeRoot);
11252
11317
  if (!markerPath) return false;
11253
11318
  try {
11254
- const marker = JSON.parse(readFileSync26(markerPath, "utf-8"));
11319
+ const marker = JSON.parse(readFileSync27(markerPath, "utf-8"));
11255
11320
  return marker.version === 1 && marker.workspaceId === workspaceId && marker.mode === "managed-linked";
11256
11321
  } catch {
11257
11322
  return false;
@@ -11263,7 +11328,7 @@ function defaultWorkspaceRoot(appDir, workspaceId) {
11263
11328
  return join22(primary.path, ".deepspace", "ws", workspaceId.slice(3).toLowerCase());
11264
11329
  }
11265
11330
  function appDirInWorktree(appDir, worktreeRoot) {
11266
- const appRelative = relative2(realpathSync5(repoToplevel(appDir)), realpathSync5(appDir));
11331
+ const appRelative = relative2(realpathSync6(repoToplevel(appDir)), realpathSync6(appDir));
11267
11332
  return appRelative ? join22(worktreeRoot, appRelative) : worktreeRoot;
11268
11333
  }
11269
11334
  function materializeWorkspaceWorktree(appDir, worktreeRoot, branch, startPoint, workspaceId, options = {}) {
@@ -11279,6 +11344,18 @@ function materializeWorkspaceWorktree(appDir, worktreeRoot, branch, startPoint,
11279
11344
  }
11280
11345
  excludeWorktreeDir(appDir, worktreeRoot);
11281
11346
  }
11347
+ function rematerializeWorkspaceWorktree(appDir, worktreeRoot, branch, workspaceId, options = {}) {
11348
+ const added = runGit(appDir, ["worktree", "add", worktreeRoot, branch], { allowFail: true });
11349
+ if (added.status !== 0) {
11350
+ return added.stderr.toString("utf-8").trim() || "unknown git error";
11351
+ }
11352
+ if (!(options.markManaged ?? markManagedWorkspace)(worktreeRoot, workspaceId)) {
11353
+ runGit(appDir, ["worktree", "remove", worktreeRoot], { allowFail: true });
11354
+ return `could not record DeepSpace ownership for ${worktreeRoot}`;
11355
+ }
11356
+ excludeWorktreeDir(appDir, worktreeRoot);
11357
+ return null;
11358
+ }
11282
11359
  function inOwnLinkedWorktree(appDir, id) {
11283
11360
  if (workspaceIdFromBranch(currentBranch(appDir)) !== id) return false;
11284
11361
  try {
@@ -11387,7 +11464,7 @@ function cleanupWorkspaceLocal(appDir, id, trunkBranch, options = {}) {
11387
11464
  }
11388
11465
  let inside = false;
11389
11466
  try {
11390
- inside = realpathSync5(repoToplevel(appDir)) === realpathSync5(linked.path);
11467
+ inside = realpathSync6(repoToplevel(appDir)) === realpathSync6(linked.path);
11391
11468
  } catch {
11392
11469
  inside = false;
11393
11470
  }
@@ -11483,7 +11560,7 @@ function inferWorkspaceId(appDir, explicit) {
11483
11560
  const id = explicit?.trim() || workspaceIdFromBranch(currentBranch(appDir));
11484
11561
  if (!id) {
11485
11562
  throw new Refusal(
11486
- 'Not inside a workspace worktree (the branch is not ws/<id>). Pass the workspace id, or create one with `deepspace workspace new -t "\u2026"`.',
11563
+ 'Not inside a workspace worktree (the branch is not ws/<id>). Select one with `-w ws_\u2026` (for `workspace drop`, pass the id as the argument), or create one with `deepspace workspace new -t "\u2026"`.',
11487
11564
  "not_in_workspace"
11488
11565
  );
11489
11566
  }
@@ -11492,17 +11569,28 @@ function inferWorkspaceId(appDir, explicit) {
11492
11569
  }
11493
11570
  return id;
11494
11571
  }
11572
+ function workspaceNotActiveRefusal(id, status3, dropAction) {
11573
+ return new Refusal(
11574
+ `Workspace ${id} is already ${status3}.` + (dropAction ? " This checkout is a leftover \u2014 `deepspace workspace drop` cleans it up." : ""),
11575
+ "workspace_not_active",
11576
+ {
11577
+ ...dropAction ? { action: dropAction } : {},
11578
+ extra: { workspaceId: id, status: status3 }
11579
+ }
11580
+ );
11581
+ }
11495
11582
  function assertSelectedWorkspaceCheckout(appDir, id, resumeArgv) {
11496
11583
  const branch = currentBranch(appDir);
11497
11584
  if (isSelectedWorkspaceCheckout(branch, id)) return;
11498
11585
  const worktree = resolveWorkspaceWorktree(listWorktrees(appDir), id);
11499
- const action = worktree ? { cwd: worktree, argv: resumeArgv } : { cwd: appDir, argv: ["deepspace", "workspace", "attach", id] };
11586
+ const worktreeAppDir = worktree ? appDirInWorktree(appDir, worktree) : null;
11587
+ const action = worktreeAppDir ? { cwd: worktreeAppDir, argv: resumeArgv } : { cwd: appDir, argv: ["deepspace", "workspace", "attach", id] };
11500
11588
  throw new Refusal(
11501
- `Workspace ${id} is not checked out here (current branch: ${branch ?? "detached"}). ` + (worktree ? `Run this command from its checkout at ${worktree}.` : "Attach it in this clone before mutating its published line."),
11589
+ `Workspace ${id} is not checked out here (current branch: ${branch ?? "detached"}). ` + (worktreeAppDir ? `Run this command from its checkout at ${worktreeAppDir}.` : "Attach it in this clone before mutating its published line."),
11502
11590
  "workspace_checkout_mismatch",
11503
11591
  {
11504
11592
  action,
11505
- extra: worktree ? { workspaceDir: worktree } : void 0
11593
+ extra: worktreeAppDir ? { workspaceDir: worktreeAppDir } : void 0
11506
11594
  }
11507
11595
  );
11508
11596
  }
@@ -11526,10 +11614,11 @@ function pushWorkspaceRef(appDir, token, ref, headOid) {
11526
11614
  if (push.status === "committed" || push.status === "up_to_date") return;
11527
11615
  if (push.status === "ref_conflict" || push.status === "non_fast_forward") {
11528
11616
  throw new Refusal(
11529
- `Another checkout advanced this workspace's line, so publishing yours is not a fast-forward \u2014 the push was refused rather than drop that work. Integrate its tip (\`git pull ${SPACE_REMOTE} ${ref}\`, or re-attach the workspace in a fresh dir), resolve any conflicts, then re-run the command. (Amended or rebased your own commits? Same refusal \u2014 merge your old tip back in.)`,
11617
+ `Another checkout advanced this workspace's line, so publishing yours is not a fast-forward \u2014 the push was refused rather than drop that work. Integrate its tip (\`git pull --no-rebase ${SPACE_REMOTE} ${ref}\`, or re-attach the workspace in a fresh dir), resolve any conflicts, then re-run the command. (Amended or rebased your own commits? Same refusal \u2014 merge your old tip back in.)`,
11530
11618
  push.status,
11531
11619
  {
11532
- action: { cwd: appDir, argv: ["git", "pull", SPACE_REMOTE, ref] }
11620
+ actionRequired: true,
11621
+ action: { cwd: appDir, argv: ["git", "pull", "--no-rebase", SPACE_REMOTE, ref] }
11533
11622
  }
11534
11623
  );
11535
11624
  }
@@ -11585,6 +11674,7 @@ var newWorkspaceCommand = defineDeepspaceCommand({
11585
11674
  );
11586
11675
  spinner2?.message("Creating workspace\u2026");
11587
11676
  ensureSpaceRemote(appDir, appId);
11677
+ ensureGitIdentity(appDir, token);
11588
11678
  let baseOid = null;
11589
11679
  const refs = await api4.getRefs();
11590
11680
  if (baseArg) {
@@ -11628,7 +11718,7 @@ var newWorkspaceCommand = defineDeepspaceCommand({
11628
11718
  idempotencyKey: mintIdempotencyKey()
11629
11719
  });
11630
11720
  const dirInput = typeof args.dir === "string" ? args.dir.trim() : "";
11631
- const worktreeRoot = dirInput ? isAbsolute6(dirInput) ? dirInput : resolve17(appDir, dirInput) : defaultWorkspaceRoot(appDir, id);
11721
+ const worktreeRoot = dirInput ? isAbsolute6(dirInput) ? dirInput : resolve18(appDir, dirInput) : defaultWorkspaceRoot(appDir, id);
11632
11722
  const dir = appDirInWorktree(appDir, worktreeRoot);
11633
11723
  try {
11634
11724
  materializeWorkspaceWorktree(appDir, worktreeRoot, branch, baseOid, id);
@@ -11666,8 +11756,18 @@ var newWorkspaceCommand = defineDeepspaceCommand({
11666
11756
 
11667
11757
  // src/cli/commands/workspace/attach.ts
11668
11758
  import * as p26 from "@clack/prompts";
11669
- import { existsSync as existsSync25, readdirSync as readdirSync7, rmSync as rmSync6 } from "fs";
11670
- import { isAbsolute as isAbsolute7, resolve as resolve18 } from "path";
11759
+ import { existsSync as existsSync25, readdirSync as readdirSync7, realpathSync as realpathSync7, rmSync as rmSync6 } from "fs";
11760
+ import { isAbsolute as isAbsolute7, resolve as resolve19 } from "path";
11761
+ function samePath(a, b) {
11762
+ const real = (p35) => {
11763
+ try {
11764
+ return realpathSync7(p35);
11765
+ } catch {
11766
+ return resolve19(p35);
11767
+ }
11768
+ };
11769
+ return real(a) === real(b);
11770
+ }
11671
11771
  function finishedWorkspaceMessage(id, view) {
11672
11772
  const workspace = view.workspace;
11673
11773
  if (workspace.status === "landed") {
@@ -11736,6 +11836,7 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
11736
11836
  });
11737
11837
  }
11738
11838
  ensureSpaceRemote(appDir, appId2);
11839
+ ensureGitIdentity(appDir, token2);
11739
11840
  const localWorkspaceRef = spacePrivateRef(`workspaces/${id}`);
11740
11841
  runGitRemote(appDir, token2, [
11741
11842
  "fetch",
@@ -11752,11 +11853,87 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
11752
11853
  );
11753
11854
  }
11754
11855
  const requestedDir = typeof args.dir === "string" ? args.dir.trim() : "";
11755
- const worktreeRoot = requestedDir ? isAbsolute7(requestedDir) ? requestedDir : resolve18(appDir, requestedDir) : defaultWorkspaceRoot(appDir, id);
11856
+ const worktreeRoot = requestedDir ? isAbsolute7(requestedDir) ? requestedDir : resolve19(appDir, requestedDir) : defaultWorkspaceRoot(appDir, id);
11756
11857
  const dir2 = appDirInWorktree(appDir, worktreeRoot);
11757
- if (resolveCommit(appDir, `refs/heads/${branch}`)) {
11858
+ const localTip = resolveCommit(appDir, `refs/heads/${branch}`);
11859
+ if (localTip) {
11860
+ let existing = resolveWorkspaceWorktree(listWorktrees(appDir), id);
11861
+ if (existing && !existsSync25(existing)) {
11862
+ runGit(appDir, ["worktree", "prune"], { allowFail: true });
11863
+ existing = null;
11864
+ }
11865
+ const relation = workspaceSyncRelation(appDir, localTip, tip2);
11866
+ const inSync = relation === "in_sync";
11867
+ const behindOrDiverged = relation === "behind" || relation === "diverged";
11868
+ const syncAdvice = behindOrDiverged ? `integrate the published tip first (\`git pull --no-rebase ${SPACE_REMOTE} ${view2.workspace.ref}\`)` : `run \`deepspace workspace sync\` from it`;
11869
+ const syncAction = (cwd) => behindOrDiverged ? { cwd, argv: ["git", "pull", "--no-rebase", SPACE_REMOTE, view2.workspace.ref] } : { cwd, argv: ["deepspace", "workspace", "sync"] };
11870
+ if (existing && (!requestedDir || samePath(existing, worktreeRoot))) {
11871
+ const existingDir = appDirInWorktree(appDir, existing);
11872
+ spinner2?.stop(`Already attached: ${id}.`);
11873
+ if (!args.json) {
11874
+ p26.log.info(`Task: ${view2.workspace.task}`);
11875
+ p26.log.info(`Work in: cd ${existingDir}`);
11876
+ if (!inSync) {
11877
+ p26.log.warn(
11878
+ `The checkout is at ${localTip.slice(0, 10)} but the workspace's published tip is ${tip2.slice(0, 10)} \u2014 ${syncAdvice}.`
11879
+ );
11880
+ }
11881
+ }
11882
+ return {
11883
+ data: {
11884
+ workspaceId: id,
11885
+ mode: "worktree",
11886
+ dir: existingDir,
11887
+ worktreeRoot: existing,
11888
+ branch,
11889
+ tipOid: localTip,
11890
+ remoteTip: tip2,
11891
+ syncRelation: relation,
11892
+ task: view2.workspace.task,
11893
+ alreadyAttached: true
11894
+ },
11895
+ ...inSync ? {} : { action: syncAction(existingDir) }
11896
+ };
11897
+ }
11898
+ if (!existing) {
11899
+ const failure = rematerializeWorkspaceWorktree(appDir, worktreeRoot, branch, id);
11900
+ if (failure) {
11901
+ spinner2?.stop("Branch exists.");
11902
+ throw new Refusal(
11903
+ `Branch ${branch} exists locally with no worktree, and re-materializing it failed: ${failure}. Inspect it (\`git log ${branch}\`) and check it out yourself.`,
11904
+ "branch_exists"
11905
+ );
11906
+ }
11907
+ spinner2?.stop(`Re-attached ${id}.`);
11908
+ if (!args.json) {
11909
+ p26.log.info(`Task: ${view2.workspace.task}`);
11910
+ p26.log.info(`Work in: cd ${dir2}`);
11911
+ if (!inSync) {
11912
+ p26.log.warn(
11913
+ `The re-materialized checkout is at ${localTip.slice(0, 10)} but the workspace's published tip is ${tip2.slice(0, 10)} \u2014 after installing, ${syncAdvice}.`
11914
+ );
11915
+ }
11916
+ }
11917
+ const action3 = { cwd: dir2, argv: [detectPackageManager(dir2), "install"] };
11918
+ return {
11919
+ data: {
11920
+ workspaceId: id,
11921
+ mode: "worktree",
11922
+ dir: dir2,
11923
+ worktreeRoot,
11924
+ branch,
11925
+ tipOid: localTip,
11926
+ remoteTip: tip2,
11927
+ syncRelation: relation,
11928
+ task: view2.workspace.task,
11929
+ rematerialized: true
11930
+ },
11931
+ action: action3
11932
+ };
11933
+ }
11934
+ spinner2?.stop("Branch exists.");
11758
11935
  throw new Refusal(
11759
- `Branch ${branch} already exists locally (this workspace has a worktree here). Find it with \`git worktree list\` and continue there.`,
11936
+ `Workspace ${id} is already checked out at ${existing} \u2014 continue there (or drop that worktree before attaching it elsewhere).`,
11760
11937
  "branch_exists"
11761
11938
  );
11762
11939
  }
@@ -11775,6 +11952,10 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
11775
11952
  worktreeRoot,
11776
11953
  branch,
11777
11954
  tipOid: tip2,
11955
+ remoteTip: tip2,
11956
+ // Freshly materialized at the published tip — trivially in sync;
11957
+ // every attach outcome carries the same shape.
11958
+ syncRelation: "in_sync",
11778
11959
  task: view2.workspace.task
11779
11960
  },
11780
11961
  action: action2
@@ -11791,7 +11972,7 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
11791
11972
  extra: { status: view.workspace.status }
11792
11973
  });
11793
11974
  }
11794
- const dir = resolve18(
11975
+ const dir = resolve19(
11795
11976
  process.cwd(),
11796
11977
  (typeof args.dir === "string" ? args.dir.trim() : "") || `ws-${id.slice(3).toLowerCase()}`
11797
11978
  );
@@ -11810,6 +11991,7 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
11810
11991
  try {
11811
11992
  runGit(process.cwd(), ["init", "--quiet", dir]);
11812
11993
  ensureSpaceRemote(dir, appId);
11994
+ ensureGitIdentity(dir, token);
11813
11995
  const localWorkspaceRef = spacePrivateRef(`workspaces/${id}`);
11814
11996
  runGitRemote(dir, token, [
11815
11997
  "fetch",
@@ -11846,6 +12028,8 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
11846
12028
  dir,
11847
12029
  branch,
11848
12030
  tipOid: tip,
12031
+ remoteTip: tip,
12032
+ syncRelation: "in_sync",
11849
12033
  task: view.workspace.task
11850
12034
  },
11851
12035
  action
@@ -11878,7 +12062,19 @@ var syncWorkspaceCommand = defineDeepspaceCommand({
11878
12062
  typeof args.app === "string" ? args.app : void 0
11879
12063
  );
11880
12064
  const id = inferWorkspaceId(appDir, workspaceArg);
11881
- assertSelectedWorkspaceCheckout(appDir, id, ["deepspace", "workspace", "sync"]);
12065
+ try {
12066
+ assertSelectedWorkspaceCheckout(appDir, id, ["deepspace", "workspace", "sync"]);
12067
+ } catch (mismatch) {
12068
+ const current = await api4.getWorkspace(id).then((r) => r.view).catch(() => null);
12069
+ if (current && current.workspace.status !== "active") {
12070
+ spinner2?.stop("Workspace finished.");
12071
+ throw workspaceNotActiveRefusal(id, current.workspace.status, {
12072
+ cwd: appDir,
12073
+ argv: ["deepspace", "workspace", "drop", id, "--app", appId]
12074
+ });
12075
+ }
12076
+ throw mismatch;
12077
+ }
11882
12078
  const headOid = resolveCommit(appDir, "HEAD");
11883
12079
  if (!headOid) {
11884
12080
  throw new Refusal("The workspace has no commits yet \u2014 commit first, then sync.", "no_commits");
@@ -11897,6 +12093,7 @@ var syncWorkspaceCommand = defineDeepspaceCommand({
11897
12093
  );
11898
12094
  }
11899
12095
  ensureSpaceRemote(appDir, appId);
12096
+ ensureGitIdentity(appDir, token);
11900
12097
  pushWorkspaceRef(appDir, token, before.workspace.ref, headOid);
11901
12098
  try {
11902
12099
  fetchTrunk(appDir, token, refs?.head ?? null);
@@ -12037,6 +12234,7 @@ var workspaceStatusCommand = defineDeepspaceCommand({
12037
12234
  const aheadExact = headOid && baseLocal ? revListCount(appDir, workspace.baseOid, headOid) : null;
12038
12235
  const unsynced = headOid !== null && view.tipOid !== headOid;
12039
12236
  const dirty = workspaceDir ? statusFiles(workspaceDir) : [];
12237
+ const relation = !unsynced ? "in_sync" : workspace.status !== "active" || !workspaceDir ? "unknown" : workspaceSyncRelation(workspaceDir, headOid, view.tipOid);
12040
12238
  const lines = await loadWorkspaceLines(appDir, token, api4, trunkOid);
12041
12239
  const myTip = headOid ?? resolveCommit(appDir, peerWorkspaceRef(id)) ?? view.tipOid;
12042
12240
  const line = myTip ? lineChangedPaths(appDir, workspace.baseOid, myTip, trunkOid) : null;
@@ -12056,6 +12254,10 @@ var workspaceStatusCommand = defineDeepspaceCommand({
12056
12254
  ...attached ? {
12057
12255
  headOid,
12058
12256
  unsynced,
12257
+ // The human line distinguishes behind/diverged/ahead; the machine
12258
+ // mirror must too, or a JSON caller can't tell a fast-forward from
12259
+ // a real divergence.
12260
+ syncRelation: relation,
12059
12261
  uncommitted: dirty,
12060
12262
  trunkOverlap
12061
12263
  } : {},
@@ -12071,10 +12273,29 @@ var workspaceStatusCommand = defineDeepspaceCommand({
12071
12273
  `Not checked out in this clone \u2014 showing the published state (local HEAD/uncommitted unavailable). Attach it with \`deepspace workspace attach ${id} <dir>\`.`
12072
12274
  );
12073
12275
  }
12074
- if (unsynced)
12075
- p29.log.warn(
12076
- `Local HEAD ${headOid?.slice(0, 10)} is not synced (server has ${view.tipOid?.slice(0, 10) ?? "nothing"}) \u2014 run \`deepspace workspace sync\`.`
12077
- );
12276
+ if (unsynced) {
12277
+ if (workspace.status === "landed") {
12278
+ p29.log.info(
12279
+ `Already landed${workspace.landedOid ? ` at ${workspace.landedOid.slice(0, 10)}` : ""} \u2014 this checkout is a leftover; \`deepspace workspace drop ${id}\` cleans it up.`
12280
+ );
12281
+ } else if (workspace.status === "dropped") {
12282
+ p29.log.info(
12283
+ `Already dropped \u2014 this checkout is a leftover; \`deepspace workspace drop ${id}\` cleans it up.`
12284
+ );
12285
+ } else if (relation === "behind") {
12286
+ p29.log.warn(
12287
+ `Local HEAD ${headOid?.slice(0, 10)} is BEHIND the published tip ${view.tipOid?.slice(0, 10)} (another checkout synced ahead) \u2014 fast-forward first (\`git pull --no-rebase ${SPACE_REMOTE} ${workspace.ref}\`).`
12288
+ );
12289
+ } else if (relation === "diverged") {
12290
+ p29.log.warn(
12291
+ `Local HEAD ${headOid?.slice(0, 10)} has DIVERGED from the published tip ${view.tipOid?.slice(0, 10)} \u2014 integrate it first (\`git pull --no-rebase ${SPACE_REMOTE} ${workspace.ref}\`), then \`deepspace workspace sync\`.`
12292
+ );
12293
+ } else {
12294
+ p29.log.warn(
12295
+ `Local HEAD ${headOid?.slice(0, 10)} is not synced (server has ${view.tipOid?.slice(0, 10) ?? "nothing"}) \u2014 run \`deepspace workspace sync\`.`
12296
+ );
12297
+ }
12298
+ }
12078
12299
  if (dirty.length > 0) p29.log.warn(`${dirty.length} uncommitted file(s) in the worktree.`);
12079
12300
  if (trunkOverlap.length > 0) {
12080
12301
  p29.log.warn(
@@ -12092,13 +12313,13 @@ import * as p30 from "@clack/prompts";
12092
12313
 
12093
12314
  // src/cli/lib/validation.ts
12094
12315
  import { sync as spawnSync7 } from "cross-spawn";
12095
- import { readFileSync as readFileSync27 } from "fs";
12316
+ import { readFileSync as readFileSync28 } from "fs";
12096
12317
  import { join as join23 } from "path";
12097
12318
  var MAX_SUMMARY_CHARS = 4e3;
12098
12319
  var MAX_OUTPUT_BUFFER = 64 * 1024 * 1024;
12099
12320
  function configuredCommand(appDir) {
12100
12321
  try {
12101
- const pkg2 = JSON.parse(readFileSync27(join23(appDir, "package.json"), "utf-8"));
12322
+ const pkg2 = JSON.parse(readFileSync28(join23(appDir, "package.json"), "utf-8"));
12102
12323
  if (pkg2.scripts?.validate) return `${detectPackageManager(appDir)} run --silent validate`;
12103
12324
  } catch {
12104
12325
  }
@@ -12128,6 +12349,14 @@ ${res.stderr?.toString("utf-8") ?? ""}`.trim();
12128
12349
  function hasLeftoverConflictMarkers(diffCheckOutput) {
12129
12350
  return /leftover conflict marker/.test(diffCheckOutput);
12130
12351
  }
12352
+ function conflictMarkerFiles(diffCheckOutput) {
12353
+ const files = /* @__PURE__ */ new Set();
12354
+ for (const line of diffCheckOutput.split("\n")) {
12355
+ const match = /^(.+?):\d+: leftover conflict marker/.exec(line);
12356
+ if (match) files.add(match[1]);
12357
+ }
12358
+ return [...files];
12359
+ }
12131
12360
  function landResumeArgv(args) {
12132
12361
  const argv = ["deepspace", "workspace", "land"];
12133
12362
  const workspace = args.workspace?.trim();
@@ -12193,7 +12422,20 @@ var landWorkspaceCommand = defineDeepspaceCommand({
12193
12422
  });
12194
12423
  const recoveryAction = { cwd: appDir, argv: resumeArgv };
12195
12424
  const resumeNext = humanCommand(resumeArgv);
12196
- assertSelectedWorkspaceCheckout(appDir, id, resumeArgv);
12425
+ const dropAction = {
12426
+ cwd: appDir,
12427
+ argv: ["deepspace", "workspace", "drop", id, "--app", appId]
12428
+ };
12429
+ try {
12430
+ assertSelectedWorkspaceCheckout(appDir, id, resumeArgv);
12431
+ } catch (mismatch) {
12432
+ const current = await api4.getWorkspace(id).then((r) => r.view).catch(() => null);
12433
+ if (current && current.workspace.status !== "active") {
12434
+ spinner2?.stop("Workspace finished.");
12435
+ throw workspaceNotActiveRefusal(id, current.workspace.status, dropAction);
12436
+ }
12437
+ throw mismatch;
12438
+ }
12197
12439
  const headBefore = resolveCommit(appDir, "HEAD");
12198
12440
  if (!headBefore) {
12199
12441
  throw new Refusal("The workspace has no commits \u2014 nothing to land.", "no_commits");
@@ -12209,13 +12451,10 @@ var landWorkspaceCommand = defineDeepspaceCommand({
12209
12451
  const [{ view }, refs] = await Promise.all([api4.getWorkspace(id), api4.getRefs()]);
12210
12452
  if (view.workspace.status !== "active") {
12211
12453
  spinner2?.stop("Workspace finished.");
12212
- throw new Refusal(
12213
- `Workspace ${id} is already ${view.workspace.status}.`,
12214
- "workspace_not_active",
12215
- { extra: { status: view.workspace.status } }
12216
- );
12454
+ throw workspaceNotActiveRefusal(id, view.workspace.status, dropAction);
12217
12455
  }
12218
12456
  ensureSpaceRemote(appDir, appId);
12457
+ ensureGitIdentity(appDir, token);
12219
12458
  const isResumedMerge = resolveCommit(appDir, "HEAD^2") !== null && view.tipOid !== null && resolveCommit(appDir, "HEAD^") === view.tipOid;
12220
12459
  if (!isResumedMerge) {
12221
12460
  pushWorkspaceRef(appDir, token, view.workspace.ref, headBefore);
@@ -12262,6 +12501,7 @@ var landWorkspaceCommand = defineDeepspaceCommand({
12262
12501
  "merge_conflict",
12263
12502
  {
12264
12503
  actionRequired: true,
12504
+ action: recoveryAction,
12265
12505
  extra: { workspaceId: id, conflict: true }
12266
12506
  }
12267
12507
  );
@@ -12271,14 +12511,17 @@ var landWorkspaceCommand = defineDeepspaceCommand({
12271
12511
  const markerCheck = runGit(appDir, ["diff", "--check", "HEAD^", "HEAD"], {
12272
12512
  allowFail: true
12273
12513
  });
12274
- if (hasLeftoverConflictMarkers(markerCheck.stdout.toString("utf-8"))) {
12514
+ const markerOutput = markerCheck.stdout.toString("utf-8");
12515
+ if (hasLeftoverConflictMarkers(markerOutput)) {
12516
+ const files = conflictMarkerFiles(markerOutput);
12275
12517
  spinner2?.stop("Conflict markers in merge.");
12276
12518
  throw new Refusal(
12277
- `The merge commit still contains unresolved conflict markers (<<<<<<< / ======= / >>>>>>>). Fix the marked files, commit the correction, then re-run \`${resumeNext}\`. Nothing was pushed to ${intoBranch}.`,
12519
+ `The merge commit still contains unresolved conflict markers (<<<<<<< / ======= / >>>>>>>)${files.length ? ` in ${files.join(", ")}` : ""}. Fix the marked files, commit the correction, then re-run \`${resumeNext}\`. Nothing was pushed to ${intoBranch}.`,
12278
12520
  "conflict_markers",
12279
12521
  {
12280
12522
  actionRequired: true,
12281
- extra: { workspaceId: id, conflict: true }
12523
+ action: recoveryAction,
12524
+ extra: { workspaceId: id, conflict: true, ...files.length ? { files } : {} }
12282
12525
  }
12283
12526
  );
12284
12527
  }
@@ -12312,6 +12555,7 @@ var landWorkspaceCommand = defineDeepspaceCommand({
12312
12555
  "validation_failed",
12313
12556
  {
12314
12557
  actionRequired: true,
12558
+ action: recoveryAction,
12315
12559
  extra: { workspaceId: id, passed: false, outputTail }
12316
12560
  }
12317
12561
  );
@@ -12323,6 +12567,7 @@ var landWorkspaceCommand = defineDeepspaceCommand({
12323
12567
  "validation_mutated_tree",
12324
12568
  {
12325
12569
  actionRequired: true,
12570
+ action: recoveryAction,
12326
12571
  extra: { workspaceId: id }
12327
12572
  }
12328
12573
  );
@@ -12358,7 +12603,37 @@ var landWorkspaceCommand = defineDeepspaceCommand({
12358
12603
  "push_rejected"
12359
12604
  );
12360
12605
  }
12361
- await api4.landWorkspace(id, { landedOid, intoRef });
12606
+ try {
12607
+ await api4.landWorkspace(id, { landedOid, intoRef });
12608
+ } catch (err) {
12609
+ const code = errorCode(err);
12610
+ if (code === "workspace_not_active") {
12611
+ const status3 = await api4.getWorkspace(id).then((r) => r.view.workspace.status).catch(() => null);
12612
+ if (status3 && status3 !== "active") {
12613
+ spinner2?.stop("Workspace finished.");
12614
+ throw new Refusal(
12615
+ `${intoBranch} took the land push (${landedOid.slice(0, 10)}), and the workspace is already ${status3} \u2014 a concurrent land or drop finished first, so there is nothing left to record. This checkout is a leftover; \`deepspace workspace drop ${id}\` cleans it up.`,
12616
+ "workspace_not_active",
12617
+ {
12618
+ actionRequired: true,
12619
+ action: dropAction,
12620
+ extra: { workspaceId: id, into: intoBranch, landedOid, pushed: true, status: status3 }
12621
+ }
12622
+ );
12623
+ }
12624
+ }
12625
+ spinner2?.stop("Landed; not recorded.");
12626
+ const detail = err instanceof Error ? err.message : String(err);
12627
+ throw new Refusal(
12628
+ `The land push succeeded (${intoBranch} was at ${landedOid.slice(0, 10)}), but recording the land failed (${detail}). Resolve that, then re-run \`${resumeNext}\` \u2014 it re-checks the merge (a no-op when already pushed) and records it.`,
12629
+ "land_unrecorded",
12630
+ {
12631
+ actionRequired: true,
12632
+ action: recoveryAction,
12633
+ extra: { workspaceId: id, into: intoBranch, landedOid, pushed: true }
12634
+ }
12635
+ );
12636
+ }
12362
12637
  spinner2?.stop(`Landed ${id} into ${intoBranch} at ${landedOid.slice(0, 10)}.`);
12363
12638
  const cleanup = keepWorktree ? null : cleanupWorkspaceLocal(appDir, id, intoBranch, { expectedBranchOid: landedOid });
12364
12639
  const inOwnWorktree = !cleanup && inOwnLinkedWorktree(appDir, id);
@@ -12403,6 +12678,22 @@ function isWorkspaceTipPublished(appDir, localTip, publishedOids) {
12403
12678
  );
12404
12679
  }
12405
12680
  function workspaceUnsyncedRefusal(args) {
12681
+ const dropKeepArgv = [
12682
+ "deepspace",
12683
+ "workspace",
12684
+ "drop",
12685
+ args.id,
12686
+ "--app",
12687
+ args.appId,
12688
+ "--keep-worktree"
12689
+ ];
12690
+ if (args.status !== "active") {
12691
+ return new Refusal(
12692
+ `${args.id} is already ${args.status} on the server, and this checkout has committed work that was never published \u2014 nothing can publish it now. Keep it with \`${humanCommand(dropKeepArgv)}\` (retains the branch/worktree for manual disposal), or delete the branch yourself. Nothing was dropped.`,
12693
+ "workspace_unsynced",
12694
+ { extra: { workspaceId: args.id, status: args.status } }
12695
+ );
12696
+ }
12406
12697
  const syncArgv = ["deepspace", "workspace", "sync", "--app", args.appId, "--workspace", args.id];
12407
12698
  const dropArgv = ["deepspace", "workspace", "drop", args.id, "--app", args.appId];
12408
12699
  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)}\`.`;
@@ -12410,11 +12701,25 @@ function workspaceUnsyncedRefusal(args) {
12410
12701
  `${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.`,
12411
12702
  "workspace_unsynced",
12412
12703
  {
12704
+ // Exit 1 with an unblock action, deliberately: drop is destructive and
12705
+ // refusing it is a hard stop, not the "your turn" hand-off exit 2 means.
12413
12706
  ...args.workspaceDir ? { action: { cwd: args.workspaceDir, argv: syncArgv } } : {},
12414
- extra: { workspaceId: args.id }
12707
+ extra: { workspaceId: args.id, status: args.status }
12415
12708
  }
12416
12709
  );
12417
12710
  }
12711
+ async function dropRemoteTolerant(api4, id, statusBefore) {
12712
+ try {
12713
+ const view = (await api4.dropWorkspace(id)).view;
12714
+ return { view, remoteDropped: statusBefore === "active" && view.workspace.status === "dropped" };
12715
+ } catch (err) {
12716
+ const code = errorCode(err);
12717
+ if (code !== "workspace_not_active") throw err;
12718
+ const current = await api4.getWorkspace(id).catch(() => null);
12719
+ if (!current || current.view.workspace.status === "active") throw err;
12720
+ return { view: current.view, remoteDropped: false };
12721
+ }
12722
+ }
12418
12723
  var dropWorkspaceCommand = defineDeepspaceCommand({
12419
12724
  meta: { name: "drop", description: "Abandon a workspace (ref retained briefly, then GC)" },
12420
12725
  args: {
@@ -12439,32 +12744,47 @@ var dropWorkspaceCommand = defineDeepspaceCommand({
12439
12744
  spinner2?.start(`Preparing to drop ${explicitId ?? "workspace"}\u2026`);
12440
12745
  if (!findAppDir() && explicitId) {
12441
12746
  const { api: api5 } = await resolveApiOnly(appArg);
12442
- const { view: view2 } = await api5.dropWorkspace(explicitId);
12747
+ const statusBefore = (await api5.getWorkspace(explicitId)).view.workspace.status;
12748
+ const { view: view2, remoteDropped: remoteDropped2 } = await dropRemoteTolerant(api5, explicitId, statusBefore);
12443
12749
  spinner2?.stop();
12444
12750
  if (!args.json) {
12445
12751
  p31.log.success(
12446
- `Dropped ${explicitId}. Its ref sticks around briefly for undo, then is reaped.`
12752
+ remoteDropped2 ? `Dropped ${explicitId}. Its ref sticks around briefly for undo, then is reaped.` : `Workspace ${explicitId} was already ${view2.workspace.status} on the server \u2014 nothing to drop remotely.`
12447
12753
  );
12448
12754
  }
12449
- return { data: { workspaceId: explicitId, status: view2.workspace.status } };
12755
+ return { data: { workspaceId: explicitId, status: view2.workspace.status, remoteDropped: remoteDropped2 } };
12450
12756
  }
12451
- const { appDir, appId, api: api4 } = await resolveTarget2(appArg);
12757
+ const { appDir, appId, token, api: api4 } = await resolveTarget2(appArg);
12452
12758
  const id = inferWorkspaceId(appDir, explicitId);
12453
12759
  const inspection = inspectWorkspaceCleanup(appDir, id);
12454
12760
  let approvedBranchOid;
12761
+ const { view: before } = await api4.getWorkspace(id);
12455
12762
  if (!keepWorktree && inspection.willDeleteBranch && inspection.branchOid) {
12456
- const { view: before } = await api4.getWorkspace(id);
12457
12763
  const publishedOids = [
12458
12764
  before.tipOid,
12459
12765
  before.workspace.landedOid,
12460
12766
  before.workspace.baseOid
12461
12767
  ].filter((oid) => oid !== null);
12462
- if (!isWorkspaceTipPublished(appDir, inspection.branchOid, publishedOids)) {
12768
+ let published = isWorkspaceTipPublished(appDir, inspection.branchOid, publishedOids);
12769
+ if (!published && before.workspace.status !== "active") {
12770
+ ensureSpaceRemote(appDir, appId);
12771
+ const refs = await api4.getRefs();
12772
+ const trunkRef = refs?.head?.startsWith("refs/heads/") ? refs.head : null;
12773
+ if (trunkRef) {
12774
+ runGitRemote(appDir, token, ["fetch", "--quiet", "--refmap=", SPACE_REMOTE, trunkRef], {
12775
+ allowFail: true
12776
+ });
12777
+ const trunkTip = resolveCommit(appDir, "FETCH_HEAD");
12778
+ published = trunkTip !== null && isWorkspaceTipPublished(appDir, inspection.branchOid, [trunkTip]);
12779
+ }
12780
+ }
12781
+ if (!published) {
12463
12782
  throw workspaceUnsyncedRefusal({
12464
12783
  appId,
12465
12784
  id,
12466
12785
  branch: inspection.branch,
12467
- workspaceDir: inspection.checkout.dir
12786
+ workspaceDir: inspection.checkout.dir,
12787
+ status: before.workspace.status
12468
12788
  });
12469
12789
  }
12470
12790
  approvedBranchOid = inspection.branchOid;
@@ -12479,7 +12799,7 @@ var dropWorkspaceCommand = defineDeepspaceCommand({
12479
12799
  { extra: { workspaceId: id } }
12480
12800
  );
12481
12801
  }
12482
- const { view } = await api4.dropWorkspace(id);
12802
+ const { view, remoteDropped } = await dropRemoteTolerant(api4, id, before.workspace.status);
12483
12803
  spinner2?.message(`Cleaning up ${id} locally\u2026`);
12484
12804
  const cleanup = keepWorktree ? null : cleanupWorkspaceLocal(appDir, id, null, {
12485
12805
  expectedBranchOid: approvedBranchOid,
@@ -12489,12 +12809,15 @@ var dropWorkspaceCommand = defineDeepspaceCommand({
12489
12809
  const data = {
12490
12810
  workspaceId: id,
12491
12811
  status: view.workspace.status,
12812
+ remoteDropped,
12492
12813
  ...retainedWorktree ? { worktree: retainedWorktree } : {},
12493
12814
  ...cleanup ? { cleanup: cleanupJson(cleanup) } : {}
12494
12815
  };
12495
12816
  spinner2?.stop();
12496
12817
  if (!args.json) {
12497
- p31.log.success(`Dropped ${id}. Its ref sticks around briefly for undo, then is reaped.`);
12818
+ p31.log.success(
12819
+ remoteDropped ? `Dropped ${id}. Its ref sticks around briefly for undo, then is reaped.` : `Workspace ${id} was already ${view.workspace.status} on the server \u2014 nothing to drop remotely.`
12820
+ );
12498
12821
  if (cleanup && !cleanup.error) reportCleanupHuman(cleanup);
12499
12822
  else if (retainedWorktree) p31.log.info(`Retained checkout: ${retainedWorktree}`);
12500
12823
  }
@@ -12563,7 +12886,7 @@ function formatEvent2(e, lands, actors) {
12563
12886
  detail = `"${String(s.task ?? "")}" from ${short(s.baseOid)}`;
12564
12887
  break;
12565
12888
  case "workspace.synced":
12566
- detail = `\u2192 ${short(s.headOid)} (${Number(s.files ?? 0)} files vs base)`;
12889
+ detail = `\u2192 ${short(s.headOid)}`;
12567
12890
  break;
12568
12891
  case "workspace.landed":
12569
12892
  detail = `"${String(s.task ?? "")}" \u2192 ${short(s.landedOid)} on ${String(s.into ?? "trunk")}`;
@@ -13061,8 +13384,8 @@ function sameRefs(left, right) {
13061
13384
 
13062
13385
  // src/cli/commands/update.ts
13063
13386
  import * as p34 from "@clack/prompts";
13064
- import { existsSync as existsSync26, readFileSync as readFileSync28, writeFileSync as writeFileSync10 } from "fs";
13065
- import { join as join24, resolve as resolve19 } from "path";
13387
+ import { existsSync as existsSync26, readFileSync as readFileSync29, writeFileSync as writeFileSync10 } from "fs";
13388
+ import { join as join24, resolve as resolve20 } from "path";
13066
13389
  var REGISTRY = "https://registry.npmjs.org/deepspace/latest";
13067
13390
  var DOCS_URL = "https://documentation.deep.space/cli-reference/commands";
13068
13391
  var OLDEST_SUPPORTED_MINOR = 12;
@@ -13078,17 +13401,17 @@ function gapIsTooWide(from, to) {
13078
13401
  function readAppSdkVersion(appDir) {
13079
13402
  const installed = join24(appDir, "node_modules", "deepspace", "package.json");
13080
13403
  if (existsSync26(installed)) {
13081
- const parsed2 = JSON.parse(readFileSync28(installed, "utf8"));
13404
+ const parsed2 = JSON.parse(readFileSync29(installed, "utf8"));
13082
13405
  if (typeof parsed2.version === "string") return parsed2.version;
13083
13406
  }
13084
13407
  const manifest = join24(appDir, "package.json");
13085
13408
  if (!existsSync26(manifest)) return null;
13086
- const parsed = JSON.parse(readFileSync28(manifest, "utf8"));
13409
+ const parsed = JSON.parse(readFileSync29(manifest, "utf8"));
13087
13410
  return parsed.dependencies?.deepspace ?? null;
13088
13411
  }
13089
13412
  function pinSdkVersion(appDir, version) {
13090
13413
  const path = join24(appDir, "package.json");
13091
- const source = readFileSync28(path, "utf8");
13414
+ const source = readFileSync29(path, "utf8");
13092
13415
  const updated = source.replace(
13093
13416
  /("deepspace"\s*:\s*")[^"]+(")/,
13094
13417
  (_match, head, tail) => `${head}^${version}${tail}`
@@ -13153,7 +13476,7 @@ var update_default = defineDeepspaceCommand({
13153
13476
  }
13154
13477
  },
13155
13478
  async run({ args }) {
13156
- const appDir = resolve19(".");
13479
+ const appDir = resolve20(".");
13157
13480
  if (!hasWranglerConfig(appDir)) {
13158
13481
  throw new Refusal(
13159
13482
  "No wrangler.toml found \u2014 run this from inside a DeepSpace app directory.",
@@ -13233,8 +13556,8 @@ var update_default = defineDeepspaceCommand({
13233
13556
  });
13234
13557
 
13235
13558
  // src/cli/cli.ts
13236
- var __dirname = dirname10(fileURLToPath3(import.meta.url));
13237
- var pkg = JSON.parse(readFileSync29(join25(__dirname, "..", "package.json"), "utf-8"));
13559
+ var __dirname = dirname11(fileURLToPath3(import.meta.url));
13560
+ var pkg = JSON.parse(readFileSync30(join25(__dirname, "..", "package.json"), "utf-8"));
13238
13561
  if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync27("/etc/ssl/certs/ca-certificates.crt")) {
13239
13562
  process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
13240
13563
  }
@@ -13361,7 +13684,9 @@ function assertKnownCommandPath(argv) {
13361
13684
  `${unknownCommandMessage(unknown)}
13362
13685
  Run \`${unknown.helpPath.join(" ")} --help\` for the command list.`
13363
13686
  );
13364
- if (unknown.executable) printAction({ cwd: process.cwd(), argv: unknown.suggestion });
13687
+ if (unknown.executable) {
13688
+ printAction(executableAction({ cwd: process.cwd(), argv: unknown.suggestion }));
13689
+ }
13365
13690
  process.exit(1);
13366
13691
  }
13367
13692
  var rawArgs = process.argv.slice(2);
@@ -13383,7 +13708,7 @@ if (rawArgs.includes("--json") && !wantsHelp) {
13383
13708
  ok: false,
13384
13709
  code: "unknown_command",
13385
13710
  error: unknownCommandMessage(unknown),
13386
- ...unknown.executable ? { action: { cwd: process.cwd(), argv: unknown.suggestion } } : {}
13711
+ ...unknown.executable ? { action: executableAction({ cwd: process.cwd(), argv: unknown.suggestion }) } : {}
13387
13712
  })
13388
13713
  );
13389
13714
  process.exit(1);