deepspace 0.16.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
@@ -3970,11 +3997,11 @@ function describeFilesFailure(status3, body) {
3970
3997
  import * as p6 from "@clack/prompts";
3971
3998
  import { execSync as execSync2 } from "child_process";
3972
3999
  import { createHash as createHash2 } from "crypto";
3973
- import { existsSync as existsSync15, readFileSync as readFileSync17, readdirSync as readdirSync4 } from "fs";
3974
- 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";
3975
4002
 
3976
4003
  // src/documentation/deploy.ts
3977
- import { existsSync as existsSync13, readFileSync as readFileSync15 } from "fs";
4004
+ import { existsSync as existsSync13, readFileSync as readFileSync16 } from "fs";
3978
4005
  import { join as join14 } from "path";
3979
4006
  function readDocumentationDeployManifest(appDir, clientDir) {
3980
4007
  if (!existsSync13(join14(appDir, "documentation.json"))) return void 0;
@@ -3984,7 +4011,7 @@ function readDocumentationDeployManifest(appDir, clientDir) {
3984
4011
  "documentation.json exists but the build produced no documentation manifest. Ensure vite.config.ts includes deepSpaceDocumentation() from deepspace/documentation."
3985
4012
  );
3986
4013
  }
3987
- return JSON.parse(readFileSync15(manifestPath, "utf8"));
4014
+ return JSON.parse(readFileSync16(manifestPath, "utf8"));
3988
4015
  }
3989
4016
 
3990
4017
  // src/cli/commands/update/app-migrations.ts
@@ -3996,7 +4023,7 @@ import {
3996
4023
  ftruncateSync,
3997
4024
  lstatSync,
3998
4025
  openSync,
3999
- readFileSync as readFileSync16,
4026
+ readFileSync as readFileSync17,
4000
4027
  writeSync
4001
4028
  } from "fs";
4002
4029
  import { join as join15 } from "path";
@@ -4141,7 +4168,7 @@ function readRegularFile(path) {
4141
4168
  if (!opened.isFile() || opened.dev !== after.dev || opened.ino !== after.ino) {
4142
4169
  throw new Error(`Migration input changed while opening: ${path}`);
4143
4170
  }
4144
- return readFileSync16(fd, "utf-8");
4171
+ return readFileSync17(fd, "utf-8");
4145
4172
  } finally {
4146
4173
  closeSync(fd);
4147
4174
  }
@@ -4167,7 +4194,7 @@ function writeRegularFile(path, before, after) {
4167
4194
  if (!opened.isFile() || opened.dev !== currentPath.dev || opened.ino !== currentPath.ino) {
4168
4195
  throw new Error(`Migration input changed while opening: ${path}`);
4169
4196
  }
4170
- if (readFileSync16(fd, "utf-8") !== before) {
4197
+ if (readFileSync17(fd, "utf-8") !== before) {
4171
4198
  throw new Error(`Migration input changed after planning: ${path}`);
4172
4199
  }
4173
4200
  ftruncateSync(fd, 0);
@@ -4290,12 +4317,12 @@ async function buildDeployBundle(options) {
4290
4317
  "build_output_missing"
4291
4318
  );
4292
4319
  }
4293
- const deployConfig = JSON.parse(readFileSync17(deployConfigPath, "utf-8"));
4294
- const outputWranglerPath = resolve10(dirname5(deployConfigPath), deployConfig.configPath);
4320
+ const deployConfig = JSON.parse(readFileSync18(deployConfigPath, "utf-8"));
4321
+ const outputWranglerPath = resolve11(dirname6(deployConfigPath), deployConfig.configPath);
4295
4322
  if (!existsSync15(outputWranglerPath)) {
4296
4323
  output.die(`Output wrangler.json not found at ${outputWranglerPath}`, "build_output_missing");
4297
4324
  }
4298
- const outputConfig = JSON.parse(readFileSync17(outputWranglerPath, "utf-8"));
4325
+ const outputConfig = JSON.parse(readFileSync18(outputWranglerPath, "utf-8"));
4299
4326
  if (outputConfig.name && outputConfig.name !== appName) {
4300
4327
  const envHint = envName ? ` (--env ${envName})` : "";
4301
4328
  output.die(
@@ -4303,9 +4330,9 @@ async function buildDeployBundle(options) {
4303
4330
  "build_output_mismatch"
4304
4331
  );
4305
4332
  }
4306
- const workerDir = dirname5(outputWranglerPath);
4333
+ const workerDir = dirname6(outputWranglerPath);
4307
4334
  const workerBundlePath = join16(workerDir, outputConfig.main);
4308
- const clientDir = outputConfig.assets?.directory ? resolve10(workerDir, outputConfig.assets.directory) : null;
4335
+ const clientDir = outputConfig.assets?.directory ? resolve11(workerDir, outputConfig.assets.directory) : null;
4309
4336
  if (!existsSync15(workerBundlePath)) {
4310
4337
  output.die(`Worker bundle not found at ${workerBundlePath}`, "build_output_missing");
4311
4338
  }
@@ -4322,7 +4349,7 @@ async function buildDeployBundle(options) {
4322
4349
  const assets = collectAssets(clientDir);
4323
4350
  const assetConfig = readDeployAssetConfig(clientDir);
4324
4351
  spinner2.stop(`Collected ${assets.length} assets`);
4325
- const workerJs = readFileSync17(workerBundlePath, "utf-8");
4352
+ const workerJs = readFileSync18(workerBundlePath, "utf-8");
4326
4353
  const doBindings = outputConfig.durable_objects?.bindings;
4327
4354
  const sqliteClasses = new Set(
4328
4355
  outputConfig.migrations?.flatMap((migration) => migration.new_sqlite_classes ?? []) ?? []
@@ -4401,7 +4428,7 @@ function collectAssets(dir) {
4401
4428
  if (entry.isDirectory()) {
4402
4429
  walk(fullPath, relativePath);
4403
4430
  } else {
4404
- const bytes = readFileSync17(fullPath);
4431
+ const bytes = readFileSync18(fullPath);
4405
4432
  assets.push({
4406
4433
  path: `/${relativePath}`,
4407
4434
  hash: createHash2("sha256").update(bytes).digest("hex"),
@@ -4419,7 +4446,7 @@ function readDeployAssetConfig(clientDir) {
4419
4446
  for (const [file, key] of [["_headers", "_headers"], ["_redirects", "_redirects"]]) {
4420
4447
  const filePath = join16(clientDir, file);
4421
4448
  if (!existsSync15(filePath)) continue;
4422
- const content = readFileSync17(filePath, "utf8");
4449
+ const content = readFileSync18(filePath, "utf8");
4423
4450
  if (content.trim()) config[key] = content;
4424
4451
  }
4425
4452
  return config;
@@ -4672,19 +4699,20 @@ function createDeployOutput(json) {
4672
4699
  introShown = true;
4673
4700
  },
4674
4701
  die(message, code, opts = {}) {
4702
+ const action = opts.action ? executableAction(opts.action) : void 0;
4675
4703
  if (json) {
4676
4704
  emitJson({
4677
4705
  ok: false,
4678
4706
  code,
4679
4707
  error: message,
4680
4708
  ...opts.actionRequired ? { actionRequired: true } : {},
4681
- ...opts.action ? { action: opts.action } : {}
4709
+ ...action ? { action } : {}
4682
4710
  });
4683
4711
  } else {
4684
4712
  if (introShown && opts.actionRequired) p8.log.warn(withSlug(message, code));
4685
4713
  else if (introShown) p8.cancel(withSlug(message, code));
4686
4714
  else console.error(withSlug(message, code));
4687
- if (opts.action) printAction(opts.action);
4715
+ if (action) printAction(action);
4688
4716
  }
4689
4717
  process.exit(opts.actionRequired ? 2 : 1);
4690
4718
  }
@@ -4693,7 +4721,7 @@ function createDeployOutput(json) {
4693
4721
 
4694
4722
  // src/cli/commands/deploy/request.ts
4695
4723
  import * as p10 from "@clack/prompts";
4696
- import { readFileSync as readFileSync18 } from "fs";
4724
+ import { readFileSync as readFileSync19 } from "fs";
4697
4725
 
4698
4726
  // src/cli/commands/deploy/repository.ts
4699
4727
  import * as p9 from "@clack/prompts";
@@ -4886,7 +4914,7 @@ function repoApi(deployUrl, token, appId) {
4886
4914
  // src/cli/lib/vc-remote.ts
4887
4915
  import { existsSync as existsSync17 } from "fs";
4888
4916
  import { tmpdir } from "os";
4889
- import { isAbsolute as isAbsolute3, relative, resolve as resolve11 } from "path";
4917
+ import { isAbsolute as isAbsolute3, relative, resolve as resolve12 } from "path";
4890
4918
  function spaceRemoteName(environment = DEEPSPACE_ENV) {
4891
4919
  if (environment === "staging") return "space-staging";
4892
4920
  if (environment === "production") return "space";
@@ -4932,7 +4960,7 @@ function entryIsTransient(cwd) {
4932
4960
  const entry = process.argv[1];
4933
4961
  if (!entry) return true;
4934
4962
  if (/[\\/]_npx[\\/]/.test(entry) || entry.startsWith(tmpdir())) return true;
4935
- const rel = relative(resolve11(cwd), resolve11(entry));
4963
+ const rel = relative(resolve12(cwd), resolve12(entry));
4936
4964
  return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
4937
4965
  }
4938
4966
  function helperEntryUsable(command) {
@@ -5005,6 +5033,25 @@ function ensureSpaceRemote(cwd, appId, remote = SPACE_REMOTE) {
5005
5033
  installCredentialHelper(cwd, url);
5006
5034
  return url;
5007
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
+ }
5008
5055
  function appIdFromRepoUrl(url) {
5009
5056
  const match = /\/api\/repo\/([^/]+)\/?$/.exec(url);
5010
5057
  return match ? decodeURIComponent(match[1]) : null;
@@ -5420,7 +5467,7 @@ async function pushWithTransientRetry(push) {
5420
5467
  const message = errorMessage3(error);
5421
5468
  const transient = /(?:HTTP |error: )(?:429|503)\b/i.test(message);
5422
5469
  if (transient && attempt < backoffMs.length && Date.now() + backoffMs[attempt] < deadline) {
5423
- await new Promise((resolve20) => setTimeout(resolve20, backoffMs[attempt]));
5470
+ await new Promise((resolve21) => setTimeout(resolve21, backoffMs[attempt]));
5424
5471
  continue;
5425
5472
  }
5426
5473
  throw error;
@@ -5460,6 +5507,7 @@ async function deployBuiltBundle(options) {
5460
5507
  appId,
5461
5508
  appName,
5462
5509
  token,
5510
+ claimReleased,
5463
5511
  ignoreStale,
5464
5512
  bundle,
5465
5513
  secrets,
@@ -5477,7 +5525,8 @@ async function deployBuiltBundle(options) {
5477
5525
  await uploadDeployAssets({ deployUrl, appId, token, assets: bundle.assets, output, spinner: spinner2 });
5478
5526
  spinner2.message(`Deploying ${appName}...`);
5479
5527
  let confirmRename = options.rename;
5480
- 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;
5481
5530
  if (stopLabel !== null) spinner2.stop(stopLabel);
5482
5531
  p10.cancel(code ? withSlug(message, code) : message);
5483
5532
  if (action) printAction(action);
@@ -5523,6 +5572,7 @@ async function deployBuiltBundle(options) {
5523
5572
  }
5524
5573
  form.append("name", appName);
5525
5574
  if (confirmRename) form.append("confirmRename", "true");
5575
+ if (claimReleased) form.append("claimReleased", "true");
5526
5576
  if (shouldSendLineage(repository.commitOid, repository.recoverable)) {
5527
5577
  form.append("commitOid", repository.commitOid);
5528
5578
  }
@@ -5696,7 +5746,7 @@ async function uploadDeployAssets(options) {
5696
5746
  // against the plan before it touches storage, and a stream body
5697
5747
  // would be sent chunked with no length at all; a retry also needs a
5698
5748
  // body it can send again.
5699
- body: readFileSync18(asset.sourcePath)
5749
+ body: readFileSync19(asset.sourcePath)
5700
5750
  })
5701
5751
  );
5702
5752
  if (!response.ok) {
@@ -5774,7 +5824,7 @@ async function postWithRetry(url, makeInit, {
5774
5824
  lastError = error;
5775
5825
  }
5776
5826
  if (attempt < attempts) {
5777
- 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))));
5778
5828
  }
5779
5829
  }
5780
5830
  if (lastResponse) return lastResponse;
@@ -5783,7 +5833,7 @@ async function postWithRetry(url, makeInit, {
5783
5833
 
5784
5834
  // src/cli/commands/deploy/secrets.ts
5785
5835
  import * as p11 from "@clack/prompts";
5786
- import { existsSync as existsSync18, readFileSync as readFileSync19 } from "fs";
5836
+ import { existsSync as existsSync18, readFileSync as readFileSync20 } from "fs";
5787
5837
  function classifyDevVarsSecrets(options) {
5788
5838
  const store = new Set(options.storeSecretNames);
5789
5839
  const strayKeys = options.handEditedDevVarKeys.filter((key) => !store.has(key));
@@ -5882,7 +5932,7 @@ function prepareDeploySecrets(options) {
5882
5932
  }
5883
5933
  function readCustomDevVars(path, output) {
5884
5934
  if (!existsSync18(path)) return {};
5885
- const section = extractCustomDevVars(stripGeneratedSecretsCache(readFileSync19(path, "utf-8")));
5935
+ const section = extractCustomDevVars(stripGeneratedSecretsCache(readFileSync20(path, "utf-8")));
5886
5936
  if (!section) return {};
5887
5937
  try {
5888
5938
  return parseDevVars(section);
@@ -5922,6 +5972,11 @@ var deploy_default = defineCommand3({
5922
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.",
5923
5973
  default: false
5924
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
+ },
5925
5980
  "allow-missing-secrets": {
5926
5981
  type: "boolean",
5927
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).",
@@ -5947,7 +6002,7 @@ var deploy_default = defineCommand3({
5947
6002
  async run({ args }) {
5948
6003
  const output = createDeployOutput(args.json === true);
5949
6004
  preflightNodeVersion("deploy");
5950
- const appDir = resolve12(args.dir ?? ".");
6005
+ const appDir = resolve13(args.dir ?? ".");
5951
6006
  const selectorRefusal = blankSelectorRefusal(args);
5952
6007
  if (selectorRefusal) output.die(selectorRefusal.error, selectorRefusal.code);
5953
6008
  const envName = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
@@ -6035,6 +6090,7 @@ var deploy_default = defineCommand3({
6035
6090
  appName,
6036
6091
  token,
6037
6092
  rename: args.rename === true,
6093
+ claimReleased: args["claim-released"] === true,
6038
6094
  ignoreStale: Boolean(args["ignore-stale"]),
6039
6095
  bundle,
6040
6096
  secrets,
@@ -6063,10 +6119,7 @@ var deploy_default = defineCommand3({
6063
6119
  url: body.url,
6064
6120
  releaseId: body.releaseId ?? null,
6065
6121
  bundleRetained: body.bundleRetained ?? null,
6066
- // `serving` says what was actually established; `edgePropagating`
6067
- // stays for callers written against the old shape.
6068
6122
  serving,
6069
- edgePropagating: serving === "unconfirmed",
6070
6123
  recoverable: repository.recoverable,
6071
6124
  ...staleBaseGuardFields(body)
6072
6125
  });
@@ -6085,7 +6138,7 @@ var deploy_default = defineCommand3({
6085
6138
  appName,
6086
6139
  url: body.url ?? null,
6087
6140
  // Always present, so a caller branches on one field instead of
6088
- // inferring success from the absence of `edgePropagating`.
6141
+ // inferring success from an absent one.
6089
6142
  serving,
6090
6143
  releaseId: body.releaseId ?? null,
6091
6144
  bundleRetained: body.bundleRetained ?? null,
@@ -6765,8 +6818,8 @@ var usage_default = defineDeepspaceCommand({
6765
6818
  });
6766
6819
 
6767
6820
  // src/cli/commands/add.ts
6768
- import { existsSync as existsSync20, readFileSync as readFileSync21, realpathSync as realpathSync3 } from "fs";
6769
- 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";
6770
6823
  import { pathToFileURL as pathToFileURL3 } from "url";
6771
6824
 
6772
6825
  // src/cli/commands/feature-installer.ts
@@ -6778,7 +6831,7 @@ import {
6778
6831
  mkdirSync as mkdirSync6,
6779
6832
  mkdtempSync,
6780
6833
  openSync as openSync2,
6781
- readFileSync as readFileSync20,
6834
+ readFileSync as readFileSync21,
6782
6835
  readSync,
6783
6836
  readdirSync as readdirSync5,
6784
6837
  rmSync as rmSync4,
@@ -6786,21 +6839,21 @@ import {
6786
6839
  } from "fs";
6787
6840
  import { spawnSync as spawnSync6 } from "child_process";
6788
6841
  import { tmpdir as tmpdir2 } from "os";
6789
- 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";
6790
6843
  import { fileURLToPath } from "url";
6791
6844
  var NAV_MARKER = "// \u2500\u2500 Features add nav items below this line \u2500\u2500";
6792
6845
  function featurePackageRoot(moduleUrl = import.meta.url) {
6793
- let dir = dirname6(fileURLToPath(moduleUrl));
6846
+ let dir = dirname7(fileURLToPath(moduleUrl));
6794
6847
  for (; ; ) {
6795
6848
  const packagePath = join18(dir, "package.json");
6796
6849
  if (existsSync19(packagePath)) {
6797
6850
  try {
6798
- const pkg2 = JSON.parse(readFileSync20(packagePath, "utf-8"));
6851
+ const pkg2 = JSON.parse(readFileSync21(packagePath, "utf-8"));
6799
6852
  if (pkg2.name === "deepspace") return dir;
6800
6853
  } catch {
6801
6854
  }
6802
6855
  }
6803
- const parent = dirname6(dir);
6856
+ const parent = dirname7(dir);
6804
6857
  if (parent === dir) throw new Error("Could not locate the deepspace package root");
6805
6858
  dir = parent;
6806
6859
  }
@@ -6812,7 +6865,7 @@ function readFeatureConfig(id, packageRoot = featurePackageRoot()) {
6812
6865
  const configPath = join18(packageRoot, "features", id, "feature.json");
6813
6866
  if (!existsSync19(configPath)) return null;
6814
6867
  try {
6815
- const config = JSON.parse(readFileSync20(configPath, "utf-8"));
6868
+ const config = JSON.parse(readFileSync21(configPath, "utf-8"));
6816
6869
  if (typeof config.id !== "string" || typeof config.name !== "string" || !Array.isArray(config.files)) {
6817
6870
  return null;
6818
6871
  }
@@ -6861,7 +6914,7 @@ function suggestFeature(featureId, packageRoot = featurePackageRoot()) {
6861
6914
  return closest !== null && closestDistance <= threshold ? closest : null;
6862
6915
  }
6863
6916
  function resolveTargetDir(raw) {
6864
- return isAbsolute4(raw) ? raw : resolve13(raw);
6917
+ return isAbsolute4(raw) ? raw : resolve14(raw);
6865
6918
  }
6866
6919
  function resolveFeatureDestPath(fileDest, route) {
6867
6920
  const pages = "src/pages/";
@@ -6871,13 +6924,13 @@ function resolveFeatureDestPath(fileDest, route) {
6871
6924
  return route?.protected === false ? `${appPages}${rest}` : `${appPages}(protected)/${rest}`;
6872
6925
  }
6873
6926
  function copyFile(src, dest) {
6874
- mkdirSync6(dirname6(dest), { recursive: true });
6927
+ mkdirSync6(dirname7(dest), { recursive: true });
6875
6928
  copyFileSync(src, dest);
6876
6929
  }
6877
6930
  function copyFileWithImportShift(src, dest, levels) {
6878
- mkdirSync6(dirname6(dest), { recursive: true });
6931
+ mkdirSync6(dirname7(dest), { recursive: true });
6879
6932
  const prefix = "../".repeat(levels);
6880
- const shifted = readFileSync20(src, "utf-8").replace(
6933
+ const shifted = readFileSync21(src, "utf-8").replace(
6881
6934
  /(from\s+|import\s+|import\()(['"])(\.\.\/)/g,
6882
6935
  `$1$2${prefix}$3`
6883
6936
  );
@@ -6936,7 +6989,7 @@ function integrateSchema(config, targetDir, emit) {
6936
6989
  if (!existsSync19(schemasPath)) {
6937
6990
  return [unresolvedSchema(config.schema, "src/schemas.ts was not found.", emit)];
6938
6991
  }
6939
- let content = readFileSync20(schemasPath, "utf-8");
6992
+ let content = readFileSync21(schemasPath, "utf-8");
6940
6993
  const { exportName, importPath, spreadOperator } = config.schema;
6941
6994
  const importPattern = new RegExp(`^import\\s+\\{[^}]*\\b${exportName}\\b`, "m");
6942
6995
  if (importPattern.test(content)) {
@@ -6993,7 +7046,7 @@ function integrateActions(config, targetDir, emit) {
6993
7046
  if (!existsSync19(actionsPath)) {
6994
7047
  return [unresolvedActions(config.actions, "src/actions/index.ts was not found.", emit)];
6995
7048
  }
6996
- let content = readFileSync20(actionsPath, "utf-8");
7049
+ let content = readFileSync21(actionsPath, "utf-8");
6997
7050
  const registryPattern = /export const actions:\s*Record<string,\s*ActionHandler(?:<[^>]*>)?>\s*=\s*\{/;
6998
7051
  if (!registryPattern.test(content)) {
6999
7052
  const reason = "src/actions/index.ts has an unexpected structure.";
@@ -7048,7 +7101,7 @@ function integrateCode(config, targetDir, emit) {
7048
7101
  });
7049
7102
  continue;
7050
7103
  }
7051
- const original = readFileSync20(targetPath, "utf-8");
7104
+ const original = readFileSync21(targetPath, "utf-8");
7052
7105
  if (original.includes(insertion.insert.trim())) {
7053
7106
  emit(` Integration already present: ${insertion.file}`);
7054
7107
  continue;
@@ -7084,7 +7137,7 @@ ${content.slice(importIndex < 0 ? 0 : importIndex)}`;
7084
7137
  function integrateIgnore(config, targetDir) {
7085
7138
  if (!config.ignore?.length) return;
7086
7139
  const targetPath = join18(targetDir, ".gitignore");
7087
- const current = existsSync19(targetPath) ? readFileSync20(targetPath, "utf-8") : "";
7140
+ const current = existsSync19(targetPath) ? readFileSync21(targetPath, "utf-8") : "";
7088
7141
  const present = new Set(current.split(/\r?\n/).map((line) => line.trim()));
7089
7142
  const missing = config.ignore.filter((pattern) => !present.has(pattern));
7090
7143
  if (missing.length === 0) return;
@@ -7104,7 +7157,7 @@ function integrateWrangler(config, targetDir, emit) {
7104
7157
  if (!existsSync19(targetPath)) {
7105
7158
  return [{ area: "wrangler", reason: "wrangler.toml was not found.", steps }];
7106
7159
  }
7107
- let source = readFileSync20(targetPath, "utf-8");
7160
+ let source = readFileSync21(targetPath, "utf-8");
7108
7161
  if (runWorkerFirst && !/^\[assets\]\s*$/m.test(source)) {
7109
7162
  return [{ area: "wrangler", reason: "wrangler.toml has no [assets] section.", steps }];
7110
7163
  }
@@ -7193,7 +7246,7 @@ function integrateCss(packageRoot, config, targetDir, emit) {
7193
7246
  }
7194
7247
  ];
7195
7248
  }
7196
- let styles = readFileSync20(stylesPath, "utf-8");
7249
+ let styles = readFileSync21(stylesPath, "utf-8");
7197
7250
  let integrated = 0;
7198
7251
  const unresolved = [];
7199
7252
  for (const cssFile of config.css) {
@@ -7207,7 +7260,7 @@ function integrateCss(packageRoot, config, targetDir, emit) {
7207
7260
  });
7208
7261
  continue;
7209
7262
  }
7210
- const css = readFileSync20(srcPath, "utf-8");
7263
+ const css = readFileSync21(srcPath, "utf-8");
7211
7264
  const fingerprint = css.match(/\.([\w-]+)\s*\{|@keyframes\s+([\w-]+)/);
7212
7265
  const marker = fingerprint?.[1] ?? fingerprint?.[2];
7213
7266
  if (marker && styles.includes(marker)) {
@@ -7238,7 +7291,7 @@ function integrateRoute(config, targetDir, emit) {
7238
7291
  if (!existsSync19(navPath)) {
7239
7292
  return [unresolvedRoute(config.route, config, "src/nav.ts was not found.", emit)];
7240
7293
  }
7241
- let content = readFileSync20(navPath, "utf-8");
7294
+ let content = readFileSync21(navPath, "utf-8");
7242
7295
  const routePath = config.route.path;
7243
7296
  if (content.includes(`'${routePath}'`)) {
7244
7297
  emit(` Nav already present: ${routePath}`);
@@ -7319,7 +7372,7 @@ function integrateDependencies(config, targetDir, runInstall, outputVisible, emi
7319
7372
  }
7320
7373
  let pkg2;
7321
7374
  try {
7322
- pkg2 = JSON.parse(readFileSync20(packagePath, "utf-8"));
7375
+ pkg2 = JSON.parse(readFileSync21(packagePath, "utf-8"));
7323
7376
  } catch (error) {
7324
7377
  emit(
7325
7378
  ` Could not patch package.json (parse error: ${error instanceof Error ? error.message : String(error)})`
@@ -7379,7 +7432,7 @@ ${child.diagnosticTail}` : "";
7379
7432
  }
7380
7433
  function dependencySnapshot(targetDir) {
7381
7434
  try {
7382
- const pkg2 = JSON.parse(readFileSync20(join18(targetDir, "package.json"), "utf-8"));
7435
+ const pkg2 = JSON.parse(readFileSync21(join18(targetDir, "package.json"), "utf-8"));
7383
7436
  return {
7384
7437
  dependencies: pkg2.dependencies ?? {},
7385
7438
  devDependencies: pkg2.devDependencies ?? {}
@@ -7628,7 +7681,7 @@ function isDeepSpaceApp(dir) {
7628
7681
  const packagePath = join19(dir, "package.json");
7629
7682
  if (!existsSync20(packagePath)) return false;
7630
7683
  try {
7631
- const pkg2 = JSON.parse(readFileSync21(packagePath, "utf-8"));
7684
+ const pkg2 = JSON.parse(readFileSync22(packagePath, "utf-8"));
7632
7685
  return Boolean(pkg2.dependencies?.deepspace || pkg2.devDependencies?.deepspace);
7633
7686
  } catch {
7634
7687
  return false;
@@ -7639,7 +7692,7 @@ function installedPackageRoot(fromDir) {
7639
7692
  for (; ; ) {
7640
7693
  const candidate = join19(dir, "node_modules", "deepspace");
7641
7694
  if (existsSync20(join19(candidate, "package.json"))) return candidate;
7642
- const parent = dirname7(dir);
7695
+ const parent = dirname8(dir);
7643
7696
  if (parent === dir) return null;
7644
7697
  dir = parent;
7645
7698
  }
@@ -7652,7 +7705,7 @@ async function targetInstaller(appDir) {
7652
7705
  "feature_installer_missing"
7653
7706
  );
7654
7707
  }
7655
- if (realpathSync3(installedRoot) === realpathSync3(featurePackageRoot())) {
7708
+ if (realpathSync4(installedRoot) === realpathSync4(featurePackageRoot())) {
7656
7709
  return { installFeature };
7657
7710
  }
7658
7711
  const entry = join19(installedRoot, "dist", "feature-installer.js");
@@ -7749,7 +7802,7 @@ var add_default = defineDeepspaceCommand({
7749
7802
  }
7750
7803
  return { data: { features: featureCatalog(root) } };
7751
7804
  }
7752
- const appDir = resolve14(args.dir ?? ".");
7805
+ const appDir = resolve15(args.dir ?? ".");
7753
7806
  if (!existsSync20(appDir)) {
7754
7807
  throw new Refusal(
7755
7808
  `Target directory not found: ${appDir}
@@ -7831,11 +7884,11 @@ function fmtDate(iso) {
7831
7884
  }
7832
7885
  async function confirm4(message) {
7833
7886
  process.stdout.write(`${message} [y/N] `);
7834
- return new Promise((resolve20) => {
7887
+ return new Promise((resolve21) => {
7835
7888
  process.stdin.setEncoding("utf-8");
7836
7889
  process.stdin.once("data", (data) => {
7837
7890
  const answer = data.toString().trim().toLowerCase();
7838
- resolve20(answer === "y" || answer === "yes");
7891
+ resolve21(answer === "y" || answer === "yes");
7839
7892
  });
7840
7893
  });
7841
7894
  }
@@ -8125,7 +8178,7 @@ var domain_default = defineCommand5({
8125
8178
 
8126
8179
  // src/cli/commands/app-files.ts
8127
8180
  import { defineCommand as defineCommand6 } from "citty";
8128
- import { basename as basename4, resolve as resolve15 } from "path";
8181
+ import { basename as basename4, resolve as resolve16 } from "path";
8129
8182
  import { existsSync as existsSync21, statSync as statSync2 } from "fs";
8130
8183
 
8131
8184
  // src/cli/lib/app-files-api.ts
@@ -8326,7 +8379,7 @@ async function deleteAppFile(baseUrl, token, appId, key) {
8326
8379
  const path = filesPath(appId, `/${encodeKeyPath(key)}`);
8327
8380
  const res = await request(baseUrl, token, path, { method: "DELETE" });
8328
8381
  const body = await readJson(res, path);
8329
- return { existed: body.existed !== false };
8382
+ return { existed: body.existed };
8330
8383
  }
8331
8384
  async function downloadAppFile(baseUrl, token, appId, key, destination) {
8332
8385
  const path = filesPath(appId, `/${encodeKeyPath(key)}`);
@@ -8396,7 +8449,7 @@ var put = defineDeepspaceCommand({
8396
8449
  app: { type: "string", description: "App id or live name (defaults to ./wrangler.toml)" }
8397
8450
  },
8398
8451
  async run({ args }) {
8399
- const localPath = resolve15(String(args.file));
8452
+ const localPath = resolve16(String(args.file));
8400
8453
  if (!existsSync21(localPath) || !statSync2(localPath).isFile()) {
8401
8454
  throw new Refusal(`No such file: ${args.file}`, "file_not_found");
8402
8455
  }
@@ -8497,7 +8550,7 @@ var get = defineDeepspaceCommand({
8497
8550
  },
8498
8551
  async run({ args }) {
8499
8552
  const key = requireKey(String(args.key));
8500
- const destination = resolve15(String(args.out ?? basename4(key)));
8553
+ const destination = resolve16(String(args.out ?? basename4(key)));
8501
8554
  const { token, appId } = await target(args.app);
8502
8555
  const result = await downloadAppFile(PLATFORM_URL, token, appId, key, destination);
8503
8556
  if (!args.json) console.log(`\u2713 ${key} \u2192 ${destination} (${formatBytes(result.bytes)})`);
@@ -8867,7 +8920,7 @@ var transfer_default = defineCommand8({
8867
8920
  import { defineCommand as defineCommand9 } from "citty";
8868
8921
 
8869
8922
  // src/cli/commands/_invoke-impl.ts
8870
- import { readFileSync as readFileSync22 } from "fs";
8923
+ import { readFileSync as readFileSync23 } from "fs";
8871
8924
  import * as p15 from "@clack/prompts";
8872
8925
  var API_URL7 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
8873
8926
  var DEFAULT_TIMEOUT_MS = 12e4;
@@ -8908,7 +8961,7 @@ function resolveBody(opts) {
8908
8961
  if (opts.body != null) {
8909
8962
  raw = opts.body;
8910
8963
  } else if (opts.bodyFile != null) {
8911
- 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");
8912
8965
  } else {
8913
8966
  return "{}";
8914
8967
  }
@@ -9187,8 +9240,8 @@ var integrations_default = defineCommand9({
9187
9240
 
9188
9241
  // src/cli/commands/feedback.ts
9189
9242
  import * as p16 from "@clack/prompts";
9190
- import { existsSync as existsSync22, readFileSync as readFileSync23 } from "fs";
9191
- 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";
9192
9245
  import { fileURLToPath as fileURLToPath2 } from "url";
9193
9246
  import { release } from "os";
9194
9247
  var API_URL8 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
@@ -9209,14 +9262,14 @@ function normalizeType(raw) {
9209
9262
  }
9210
9263
  function readCliVersion() {
9211
9264
  try {
9212
- const here = dirname8(fileURLToPath2(import.meta.url));
9265
+ const here = dirname9(fileURLToPath2(import.meta.url));
9213
9266
  const candidates = [
9214
9267
  join20(here, "..", "package.json"),
9215
9268
  join20(here, "..", "..", "..", "package.json")
9216
9269
  ];
9217
9270
  for (const path of candidates) {
9218
9271
  if (!existsSync22(path)) continue;
9219
- const pkg2 = JSON.parse(readFileSync23(path, "utf-8"));
9272
+ const pkg2 = JSON.parse(readFileSync24(path, "utf-8"));
9220
9273
  if (pkg2.name === "deepspace" && pkg2.version) return pkg2.version;
9221
9274
  }
9222
9275
  } catch {
@@ -9412,7 +9465,7 @@ Context: deepspace ${payload.cliVersion} \xB7 ${payload.nodeVersion} \xB7 ${payl
9412
9465
 
9413
9466
  // src/cli/commands/secrets.ts
9414
9467
  import { defineCommand as defineCommand10 } from "citty";
9415
- import { readFileSync as readFileSync24 } from "fs";
9468
+ import { readFileSync as readFileSync25 } from "fs";
9416
9469
 
9417
9470
  // src/cli/lib/citty-args.ts
9418
9471
  function dedupePositionals(bound, rest) {
@@ -9682,7 +9735,7 @@ var upload = defineCommand10({
9682
9735
  async run({ args }) {
9683
9736
  try {
9684
9737
  const t = await resolveTarget(args);
9685
- 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");
9686
9739
  const secrets = parseSecretsUpload(content);
9687
9740
  if (Object.keys(secrets).length === 0)
9688
9741
  fail(new InputError("No secrets found in the input.", "empty_input"));
@@ -9938,7 +9991,7 @@ var init_default = defineDeepspaceCommand({
9938
9991
  // src/cli/commands/status.ts
9939
9992
  import { defineCommand as defineCommand11 } from "citty";
9940
9993
  import * as p17 from "@clack/prompts";
9941
- import { existsSync as existsSync23, readFileSync as readFileSync25 } from "fs";
9994
+ import { existsSync as existsSync23, readFileSync as readFileSync26 } from "fs";
9942
9995
  function timeAgo(iso) {
9943
9996
  const ms = Date.now() - Date.parse(iso);
9944
9997
  if (!Number.isFinite(ms) || ms < 0) return iso;
@@ -10032,7 +10085,7 @@ var status_default = defineCommand11({
10032
10085
  let who = "logged in";
10033
10086
  try {
10034
10087
  const payload = decodeJwtPayload(
10035
- readFileSync25(TOKEN_PATH, "utf-8").trim()
10088
+ readFileSync26(TOKEN_PATH, "utf-8").trim()
10036
10089
  );
10037
10090
  if (payload.email) who = payload.email;
10038
10091
  userId = payload.sub ?? null;
@@ -10184,21 +10237,24 @@ var status_default = defineCommand11({
10184
10237
  }
10185
10238
  const refs = refsResult.ok ? refsResult.value : void 0;
10186
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;
10187
10241
  if (!facts.workspaceId && branch) {
10242
+ const isTrunk = defaultBranch === null ? null : branch === defaultBranch;
10243
+ const label = isTrunk === false ? "Branch sync" : "Trunk";
10188
10244
  if (githubSource) {
10189
- lines.push(["Trunk", `${branch} is owned through normal Git/GitHub`]);
10190
- json.trunk = { state: "external", provider: "github", branch };
10191
- } 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);
10192
10248
  else {
10193
10249
  const remoteOid = trunkRef?.oid ?? null;
10194
10250
  const localOid = resolveCommit(appDir, `refs/heads/${branch}`);
10195
10251
  const remoteKnownLocally = remoteOid ? resolveCommit(appDir, remoteOid) !== null : false;
10196
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";
10197
10253
  lines.push([
10198
- "Trunk",
10254
+ label,
10199
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)})`
10200
10256
  ]);
10201
- json.trunk = { state: facts.trunkState, localOid, remoteOid };
10257
+ json.trunk = { state: facts.trunkState, localOid, remoteOid, branch, isTrunk };
10202
10258
  }
10203
10259
  }
10204
10260
  if (!releaseResult.ok) reportRemoteFailure2("Live", "live", releaseResult.error);
@@ -10425,7 +10481,7 @@ var push_default = defineDeepspaceCommand({
10425
10481
 
10426
10482
  // src/cli/commands/pull.ts
10427
10483
  import * as p19 from "@clack/prompts";
10428
- import { realpathSync as realpathSync4 } from "fs";
10484
+ import { realpathSync as realpathSync5 } from "fs";
10429
10485
  function targetedVcAction(command, cwd, appId, branch) {
10430
10486
  return {
10431
10487
  cwd,
@@ -10449,7 +10505,7 @@ function workspaceBranchPullRefusal(branch, trunkBranch) {
10449
10505
  function worktreeHoldingBranch(worktrees, branch, selfPath) {
10450
10506
  const real = (pth) => {
10451
10507
  try {
10452
- return realpathSync4(pth);
10508
+ return realpathSync5(pth);
10453
10509
  } catch {
10454
10510
  return pth;
10455
10511
  }
@@ -10707,7 +10763,7 @@ var pull_default = defineDeepspaceCommand({
10707
10763
  // src/cli/commands/clone.ts
10708
10764
  import * as p20 from "@clack/prompts";
10709
10765
  import { existsSync as existsSync24, readdirSync as readdirSync6, rmSync as rmSync5 } from "fs";
10710
- 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";
10711
10767
  function cleanFailedCloneDir(dir) {
10712
10768
  let entries;
10713
10769
  try {
@@ -10761,7 +10817,7 @@ var clone_default = defineDeepspaceCommand({
10761
10817
  async run({ args }) {
10762
10818
  assertAppTargetResolvable(String(args.app));
10763
10819
  const selector = String(args.app).trim();
10764
- const dir = resolve16(String(args.dir ?? selector));
10820
+ const dir = resolve17(String(args.dir ?? selector));
10765
10821
  const dirPreexisted = validateCloneTarget(dir);
10766
10822
  const spinner2 = args.json ? null : createSpinner();
10767
10823
  spinner2?.start(`Resolving ${selector}\u2026`);
@@ -10801,6 +10857,7 @@ var clone_default = defineDeepspaceCommand({
10801
10857
  throw err;
10802
10858
  }
10803
10859
  ensureSpaceRemote(dir, appId);
10860
+ ensureGitIdentity(dir, token);
10804
10861
  const head = resolveCommit(dir, "HEAD");
10805
10862
  const branch = currentBranch(dir);
10806
10863
  spinner2?.stop(
@@ -11023,14 +11080,13 @@ Re-run with --allow-do-deletion to accept the data loss.`,
11023
11080
  spinner2?.message("Waiting for the edge to serve this release...");
11024
11081
  serving = await waitForLiveRelease(result.url, result.releaseStamp, 9e4);
11025
11082
  }
11026
- const edgePropagating = serving === "unconfirmed";
11027
11083
  spinner2?.stop(
11028
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.`
11029
11085
  );
11030
11086
  const doClassGuard = result.doClassGuard === "unverified" ? "unverified" : void 0;
11031
11087
  if (!args.json) {
11032
11088
  if (result.url) {
11033
- 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)`);
11034
11090
  else p22.log.success(`Live at: ${result.url}`);
11035
11091
  }
11036
11092
  if (result.bundleRetained === false) {
@@ -11058,7 +11114,6 @@ Re-run with --allow-do-deletion to accept the data loss.`,
11058
11114
  wranglerEnv: wranglerEnv ?? null,
11059
11115
  // Always present, matching `deploy`: one field to branch on.
11060
11116
  serving,
11061
- ...edgePropagating ? { edgePropagating: true } : {},
11062
11117
  ...doClassGuard ? { doClassGuard } : {}
11063
11118
  }
11064
11119
  };
@@ -11070,7 +11125,7 @@ import { defineCommand as defineCommand12 } from "citty";
11070
11125
 
11071
11126
  // src/cli/commands/workspace/new.ts
11072
11127
  import * as p25 from "@clack/prompts";
11073
- import { isAbsolute as isAbsolute6, resolve as resolve17 } from "path";
11128
+ import { isAbsolute as isAbsolute6, resolve as resolve18 } from "path";
11074
11129
 
11075
11130
  // src/cli/commands/workspace/analysis.ts
11076
11131
  import * as p23 from "@clack/prompts";
@@ -11079,6 +11134,14 @@ var PEER_REF_PREFIX = `${spacePrivateRef("peers")}/`;
11079
11134
  function peerWorkspaceRef(workspaceId) {
11080
11135
  return `${PEER_REF_PREFIX}${workspaceId}`;
11081
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
+ }
11082
11145
  function formatCount(count) {
11083
11146
  if (!count) return "?";
11084
11147
  return count.capped ? `${count.count}+` : String(count.count);
@@ -11195,18 +11258,18 @@ function printOverlaps(overlaps, json) {
11195
11258
 
11196
11259
  // src/cli/commands/workspace/local.ts
11197
11260
  import * as p24 from "@clack/prompts";
11198
- import { mkdirSync as mkdirSync7, readFileSync as readFileSync26, realpathSync as realpathSync5, renameSync as renameSync2, statSync as statSync3, writeFileSync as writeFileSync9 } from "fs";
11199
- 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";
11200
11263
  var WORKSPACE_MARKER = "deepspace-workspace.json";
11201
11264
  function excludeWorktreeDir(appDir, dir) {
11202
11265
  try {
11203
11266
  ensureLocalExclude(appDir, ".deepspace/");
11204
- const realDir = realpathSync5(dir);
11205
- const rel = relative2(realpathSync5(repoToplevel(appDir)), realDir);
11267
+ const realDir = realpathSync6(dir);
11268
+ const rel = relative2(realpathSync6(repoToplevel(appDir)), realDir);
11206
11269
  if (!rel || rel === ".." || rel.startsWith(`..${sep2}`) || isAbsolute5(rel)) return;
11207
11270
  const pattern = rel.split(sep2).join("/");
11208
11271
  if (pattern === ".deepspace" || pattern.startsWith(".deepspace/")) return;
11209
- const relApp = relative2(realpathSync5(appDir), realDir).split(sep2).join("/");
11272
+ const relApp = relative2(realpathSync6(appDir), realDir).split(sep2).join("/");
11210
11273
  if (relApp === ".deepspace" || relApp.startsWith(".deepspace/")) return;
11211
11274
  ensureLocalExclude(appDir, `/${pattern}/`);
11212
11275
  } catch {
@@ -11228,7 +11291,7 @@ function markManagedWorkspace(worktreeRoot, workspaceId) {
11228
11291
  const marker = { version: 1, workspaceId, mode: "managed-linked" };
11229
11292
  const tempPath = `${markerPath}.${process.pid}.tmp`;
11230
11293
  try {
11231
- mkdirSync7(dirname9(markerPath), { recursive: true });
11294
+ mkdirSync7(dirname10(markerPath), { recursive: true });
11232
11295
  writeFileSync9(tempPath, JSON.stringify(marker) + "\n");
11233
11296
  renameSync2(tempPath, markerPath);
11234
11297
  return true;
@@ -11253,7 +11316,7 @@ function isManagedWorkspaceWorktree(worktreeRoot, workspaceId) {
11253
11316
  const markerPath = worktreeMarkerPath(worktreeRoot);
11254
11317
  if (!markerPath) return false;
11255
11318
  try {
11256
- const marker = JSON.parse(readFileSync26(markerPath, "utf-8"));
11319
+ const marker = JSON.parse(readFileSync27(markerPath, "utf-8"));
11257
11320
  return marker.version === 1 && marker.workspaceId === workspaceId && marker.mode === "managed-linked";
11258
11321
  } catch {
11259
11322
  return false;
@@ -11265,7 +11328,7 @@ function defaultWorkspaceRoot(appDir, workspaceId) {
11265
11328
  return join22(primary.path, ".deepspace", "ws", workspaceId.slice(3).toLowerCase());
11266
11329
  }
11267
11330
  function appDirInWorktree(appDir, worktreeRoot) {
11268
- const appRelative = relative2(realpathSync5(repoToplevel(appDir)), realpathSync5(appDir));
11331
+ const appRelative = relative2(realpathSync6(repoToplevel(appDir)), realpathSync6(appDir));
11269
11332
  return appRelative ? join22(worktreeRoot, appRelative) : worktreeRoot;
11270
11333
  }
11271
11334
  function materializeWorkspaceWorktree(appDir, worktreeRoot, branch, startPoint, workspaceId, options = {}) {
@@ -11281,6 +11344,18 @@ function materializeWorkspaceWorktree(appDir, worktreeRoot, branch, startPoint,
11281
11344
  }
11282
11345
  excludeWorktreeDir(appDir, worktreeRoot);
11283
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
+ }
11284
11359
  function inOwnLinkedWorktree(appDir, id) {
11285
11360
  if (workspaceIdFromBranch(currentBranch(appDir)) !== id) return false;
11286
11361
  try {
@@ -11389,7 +11464,7 @@ function cleanupWorkspaceLocal(appDir, id, trunkBranch, options = {}) {
11389
11464
  }
11390
11465
  let inside = false;
11391
11466
  try {
11392
- inside = realpathSync5(repoToplevel(appDir)) === realpathSync5(linked.path);
11467
+ inside = realpathSync6(repoToplevel(appDir)) === realpathSync6(linked.path);
11393
11468
  } catch {
11394
11469
  inside = false;
11395
11470
  }
@@ -11485,7 +11560,7 @@ function inferWorkspaceId(appDir, explicit) {
11485
11560
  const id = explicit?.trim() || workspaceIdFromBranch(currentBranch(appDir));
11486
11561
  if (!id) {
11487
11562
  throw new Refusal(
11488
- '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"`.',
11489
11564
  "not_in_workspace"
11490
11565
  );
11491
11566
  }
@@ -11494,17 +11569,28 @@ function inferWorkspaceId(appDir, explicit) {
11494
11569
  }
11495
11570
  return id;
11496
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
+ }
11497
11582
  function assertSelectedWorkspaceCheckout(appDir, id, resumeArgv) {
11498
11583
  const branch = currentBranch(appDir);
11499
11584
  if (isSelectedWorkspaceCheckout(branch, id)) return;
11500
11585
  const worktree = resolveWorkspaceWorktree(listWorktrees(appDir), id);
11501
- 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] };
11502
11588
  throw new Refusal(
11503
- `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."),
11504
11590
  "workspace_checkout_mismatch",
11505
11591
  {
11506
11592
  action,
11507
- extra: worktree ? { workspaceDir: worktree } : void 0
11593
+ extra: worktreeAppDir ? { workspaceDir: worktreeAppDir } : void 0
11508
11594
  }
11509
11595
  );
11510
11596
  }
@@ -11528,10 +11614,11 @@ function pushWorkspaceRef(appDir, token, ref, headOid) {
11528
11614
  if (push.status === "committed" || push.status === "up_to_date") return;
11529
11615
  if (push.status === "ref_conflict" || push.status === "non_fast_forward") {
11530
11616
  throw new Refusal(
11531
- `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.)`,
11532
11618
  push.status,
11533
11619
  {
11534
- action: { cwd: appDir, argv: ["git", "pull", SPACE_REMOTE, ref] }
11620
+ actionRequired: true,
11621
+ action: { cwd: appDir, argv: ["git", "pull", "--no-rebase", SPACE_REMOTE, ref] }
11535
11622
  }
11536
11623
  );
11537
11624
  }
@@ -11587,6 +11674,7 @@ var newWorkspaceCommand = defineDeepspaceCommand({
11587
11674
  );
11588
11675
  spinner2?.message("Creating workspace\u2026");
11589
11676
  ensureSpaceRemote(appDir, appId);
11677
+ ensureGitIdentity(appDir, token);
11590
11678
  let baseOid = null;
11591
11679
  const refs = await api4.getRefs();
11592
11680
  if (baseArg) {
@@ -11630,7 +11718,7 @@ var newWorkspaceCommand = defineDeepspaceCommand({
11630
11718
  idempotencyKey: mintIdempotencyKey()
11631
11719
  });
11632
11720
  const dirInput = typeof args.dir === "string" ? args.dir.trim() : "";
11633
- 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);
11634
11722
  const dir = appDirInWorktree(appDir, worktreeRoot);
11635
11723
  try {
11636
11724
  materializeWorkspaceWorktree(appDir, worktreeRoot, branch, baseOid, id);
@@ -11668,8 +11756,18 @@ var newWorkspaceCommand = defineDeepspaceCommand({
11668
11756
 
11669
11757
  // src/cli/commands/workspace/attach.ts
11670
11758
  import * as p26 from "@clack/prompts";
11671
- import { existsSync as existsSync25, readdirSync as readdirSync7, rmSync as rmSync6 } from "fs";
11672
- 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
+ }
11673
11771
  function finishedWorkspaceMessage(id, view) {
11674
11772
  const workspace = view.workspace;
11675
11773
  if (workspace.status === "landed") {
@@ -11738,6 +11836,7 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
11738
11836
  });
11739
11837
  }
11740
11838
  ensureSpaceRemote(appDir, appId2);
11839
+ ensureGitIdentity(appDir, token2);
11741
11840
  const localWorkspaceRef = spacePrivateRef(`workspaces/${id}`);
11742
11841
  runGitRemote(appDir, token2, [
11743
11842
  "fetch",
@@ -11754,11 +11853,87 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
11754
11853
  );
11755
11854
  }
11756
11855
  const requestedDir = typeof args.dir === "string" ? args.dir.trim() : "";
11757
- 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);
11758
11857
  const dir2 = appDirInWorktree(appDir, worktreeRoot);
11759
- 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.");
11760
11935
  throw new Refusal(
11761
- `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).`,
11762
11937
  "branch_exists"
11763
11938
  );
11764
11939
  }
@@ -11777,6 +11952,10 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
11777
11952
  worktreeRoot,
11778
11953
  branch,
11779
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",
11780
11959
  task: view2.workspace.task
11781
11960
  },
11782
11961
  action: action2
@@ -11793,7 +11972,7 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
11793
11972
  extra: { status: view.workspace.status }
11794
11973
  });
11795
11974
  }
11796
- const dir = resolve18(
11975
+ const dir = resolve19(
11797
11976
  process.cwd(),
11798
11977
  (typeof args.dir === "string" ? args.dir.trim() : "") || `ws-${id.slice(3).toLowerCase()}`
11799
11978
  );
@@ -11812,6 +11991,7 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
11812
11991
  try {
11813
11992
  runGit(process.cwd(), ["init", "--quiet", dir]);
11814
11993
  ensureSpaceRemote(dir, appId);
11994
+ ensureGitIdentity(dir, token);
11815
11995
  const localWorkspaceRef = spacePrivateRef(`workspaces/${id}`);
11816
11996
  runGitRemote(dir, token, [
11817
11997
  "fetch",
@@ -11848,6 +12028,8 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
11848
12028
  dir,
11849
12029
  branch,
11850
12030
  tipOid: tip,
12031
+ remoteTip: tip,
12032
+ syncRelation: "in_sync",
11851
12033
  task: view.workspace.task
11852
12034
  },
11853
12035
  action
@@ -11880,7 +12062,19 @@ var syncWorkspaceCommand = defineDeepspaceCommand({
11880
12062
  typeof args.app === "string" ? args.app : void 0
11881
12063
  );
11882
12064
  const id = inferWorkspaceId(appDir, workspaceArg);
11883
- 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
+ }
11884
12078
  const headOid = resolveCommit(appDir, "HEAD");
11885
12079
  if (!headOid) {
11886
12080
  throw new Refusal("The workspace has no commits yet \u2014 commit first, then sync.", "no_commits");
@@ -11899,6 +12093,7 @@ var syncWorkspaceCommand = defineDeepspaceCommand({
11899
12093
  );
11900
12094
  }
11901
12095
  ensureSpaceRemote(appDir, appId);
12096
+ ensureGitIdentity(appDir, token);
11902
12097
  pushWorkspaceRef(appDir, token, before.workspace.ref, headOid);
11903
12098
  try {
11904
12099
  fetchTrunk(appDir, token, refs?.head ?? null);
@@ -12039,6 +12234,7 @@ var workspaceStatusCommand = defineDeepspaceCommand({
12039
12234
  const aheadExact = headOid && baseLocal ? revListCount(appDir, workspace.baseOid, headOid) : null;
12040
12235
  const unsynced = headOid !== null && view.tipOid !== headOid;
12041
12236
  const dirty = workspaceDir ? statusFiles(workspaceDir) : [];
12237
+ const relation = !unsynced ? "in_sync" : workspace.status !== "active" || !workspaceDir ? "unknown" : workspaceSyncRelation(workspaceDir, headOid, view.tipOid);
12042
12238
  const lines = await loadWorkspaceLines(appDir, token, api4, trunkOid);
12043
12239
  const myTip = headOid ?? resolveCommit(appDir, peerWorkspaceRef(id)) ?? view.tipOid;
12044
12240
  const line = myTip ? lineChangedPaths(appDir, workspace.baseOid, myTip, trunkOid) : null;
@@ -12058,6 +12254,10 @@ var workspaceStatusCommand = defineDeepspaceCommand({
12058
12254
  ...attached ? {
12059
12255
  headOid,
12060
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,
12061
12261
  uncommitted: dirty,
12062
12262
  trunkOverlap
12063
12263
  } : {},
@@ -12073,10 +12273,29 @@ var workspaceStatusCommand = defineDeepspaceCommand({
12073
12273
  `Not checked out in this clone \u2014 showing the published state (local HEAD/uncommitted unavailable). Attach it with \`deepspace workspace attach ${id} <dir>\`.`
12074
12274
  );
12075
12275
  }
12076
- if (unsynced)
12077
- p29.log.warn(
12078
- `Local HEAD ${headOid?.slice(0, 10)} is not synced (server has ${view.tipOid?.slice(0, 10) ?? "nothing"}) \u2014 run \`deepspace workspace sync\`.`
12079
- );
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
+ }
12080
12299
  if (dirty.length > 0) p29.log.warn(`${dirty.length} uncommitted file(s) in the worktree.`);
12081
12300
  if (trunkOverlap.length > 0) {
12082
12301
  p29.log.warn(
@@ -12094,13 +12313,13 @@ import * as p30 from "@clack/prompts";
12094
12313
 
12095
12314
  // src/cli/lib/validation.ts
12096
12315
  import { sync as spawnSync7 } from "cross-spawn";
12097
- import { readFileSync as readFileSync27 } from "fs";
12316
+ import { readFileSync as readFileSync28 } from "fs";
12098
12317
  import { join as join23 } from "path";
12099
12318
  var MAX_SUMMARY_CHARS = 4e3;
12100
12319
  var MAX_OUTPUT_BUFFER = 64 * 1024 * 1024;
12101
12320
  function configuredCommand(appDir) {
12102
12321
  try {
12103
- const pkg2 = JSON.parse(readFileSync27(join23(appDir, "package.json"), "utf-8"));
12322
+ const pkg2 = JSON.parse(readFileSync28(join23(appDir, "package.json"), "utf-8"));
12104
12323
  if (pkg2.scripts?.validate) return `${detectPackageManager(appDir)} run --silent validate`;
12105
12324
  } catch {
12106
12325
  }
@@ -12130,6 +12349,14 @@ ${res.stderr?.toString("utf-8") ?? ""}`.trim();
12130
12349
  function hasLeftoverConflictMarkers(diffCheckOutput) {
12131
12350
  return /leftover conflict marker/.test(diffCheckOutput);
12132
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
+ }
12133
12360
  function landResumeArgv(args) {
12134
12361
  const argv = ["deepspace", "workspace", "land"];
12135
12362
  const workspace = args.workspace?.trim();
@@ -12195,7 +12422,20 @@ var landWorkspaceCommand = defineDeepspaceCommand({
12195
12422
  });
12196
12423
  const recoveryAction = { cwd: appDir, argv: resumeArgv };
12197
12424
  const resumeNext = humanCommand(resumeArgv);
12198
- 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
+ }
12199
12439
  const headBefore = resolveCommit(appDir, "HEAD");
12200
12440
  if (!headBefore) {
12201
12441
  throw new Refusal("The workspace has no commits \u2014 nothing to land.", "no_commits");
@@ -12211,13 +12451,10 @@ var landWorkspaceCommand = defineDeepspaceCommand({
12211
12451
  const [{ view }, refs] = await Promise.all([api4.getWorkspace(id), api4.getRefs()]);
12212
12452
  if (view.workspace.status !== "active") {
12213
12453
  spinner2?.stop("Workspace finished.");
12214
- throw new Refusal(
12215
- `Workspace ${id} is already ${view.workspace.status}.`,
12216
- "workspace_not_active",
12217
- { extra: { status: view.workspace.status } }
12218
- );
12454
+ throw workspaceNotActiveRefusal(id, view.workspace.status, dropAction);
12219
12455
  }
12220
12456
  ensureSpaceRemote(appDir, appId);
12457
+ ensureGitIdentity(appDir, token);
12221
12458
  const isResumedMerge = resolveCommit(appDir, "HEAD^2") !== null && view.tipOid !== null && resolveCommit(appDir, "HEAD^") === view.tipOid;
12222
12459
  if (!isResumedMerge) {
12223
12460
  pushWorkspaceRef(appDir, token, view.workspace.ref, headBefore);
@@ -12264,6 +12501,7 @@ var landWorkspaceCommand = defineDeepspaceCommand({
12264
12501
  "merge_conflict",
12265
12502
  {
12266
12503
  actionRequired: true,
12504
+ action: recoveryAction,
12267
12505
  extra: { workspaceId: id, conflict: true }
12268
12506
  }
12269
12507
  );
@@ -12273,14 +12511,17 @@ var landWorkspaceCommand = defineDeepspaceCommand({
12273
12511
  const markerCheck = runGit(appDir, ["diff", "--check", "HEAD^", "HEAD"], {
12274
12512
  allowFail: true
12275
12513
  });
12276
- if (hasLeftoverConflictMarkers(markerCheck.stdout.toString("utf-8"))) {
12514
+ const markerOutput = markerCheck.stdout.toString("utf-8");
12515
+ if (hasLeftoverConflictMarkers(markerOutput)) {
12516
+ const files = conflictMarkerFiles(markerOutput);
12277
12517
  spinner2?.stop("Conflict markers in merge.");
12278
12518
  throw new Refusal(
12279
- `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}.`,
12280
12520
  "conflict_markers",
12281
12521
  {
12282
12522
  actionRequired: true,
12283
- extra: { workspaceId: id, conflict: true }
12523
+ action: recoveryAction,
12524
+ extra: { workspaceId: id, conflict: true, ...files.length ? { files } : {} }
12284
12525
  }
12285
12526
  );
12286
12527
  }
@@ -12314,6 +12555,7 @@ var landWorkspaceCommand = defineDeepspaceCommand({
12314
12555
  "validation_failed",
12315
12556
  {
12316
12557
  actionRequired: true,
12558
+ action: recoveryAction,
12317
12559
  extra: { workspaceId: id, passed: false, outputTail }
12318
12560
  }
12319
12561
  );
@@ -12325,6 +12567,7 @@ var landWorkspaceCommand = defineDeepspaceCommand({
12325
12567
  "validation_mutated_tree",
12326
12568
  {
12327
12569
  actionRequired: true,
12570
+ action: recoveryAction,
12328
12571
  extra: { workspaceId: id }
12329
12572
  }
12330
12573
  );
@@ -12360,7 +12603,37 @@ var landWorkspaceCommand = defineDeepspaceCommand({
12360
12603
  "push_rejected"
12361
12604
  );
12362
12605
  }
12363
- 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
+ }
12364
12637
  spinner2?.stop(`Landed ${id} into ${intoBranch} at ${landedOid.slice(0, 10)}.`);
12365
12638
  const cleanup = keepWorktree ? null : cleanupWorkspaceLocal(appDir, id, intoBranch, { expectedBranchOid: landedOid });
12366
12639
  const inOwnWorktree = !cleanup && inOwnLinkedWorktree(appDir, id);
@@ -12405,6 +12678,22 @@ function isWorkspaceTipPublished(appDir, localTip, publishedOids) {
12405
12678
  );
12406
12679
  }
12407
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
+ }
12408
12697
  const syncArgv = ["deepspace", "workspace", "sync", "--app", args.appId, "--workspace", args.id];
12409
12698
  const dropArgv = ["deepspace", "workspace", "drop", args.id, "--app", args.appId];
12410
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)}\`.`;
@@ -12412,11 +12701,25 @@ function workspaceUnsyncedRefusal(args) {
12412
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.`,
12413
12702
  "workspace_unsynced",
12414
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.
12415
12706
  ...args.workspaceDir ? { action: { cwd: args.workspaceDir, argv: syncArgv } } : {},
12416
- extra: { workspaceId: args.id }
12707
+ extra: { workspaceId: args.id, status: args.status }
12417
12708
  }
12418
12709
  );
12419
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
+ }
12420
12723
  var dropWorkspaceCommand = defineDeepspaceCommand({
12421
12724
  meta: { name: "drop", description: "Abandon a workspace (ref retained briefly, then GC)" },
12422
12725
  args: {
@@ -12441,32 +12744,47 @@ var dropWorkspaceCommand = defineDeepspaceCommand({
12441
12744
  spinner2?.start(`Preparing to drop ${explicitId ?? "workspace"}\u2026`);
12442
12745
  if (!findAppDir() && explicitId) {
12443
12746
  const { api: api5 } = await resolveApiOnly(appArg);
12444
- 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);
12445
12749
  spinner2?.stop();
12446
12750
  if (!args.json) {
12447
12751
  p31.log.success(
12448
- `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.`
12449
12753
  );
12450
12754
  }
12451
- return { data: { workspaceId: explicitId, status: view2.workspace.status } };
12755
+ return { data: { workspaceId: explicitId, status: view2.workspace.status, remoteDropped: remoteDropped2 } };
12452
12756
  }
12453
- const { appDir, appId, api: api4 } = await resolveTarget2(appArg);
12757
+ const { appDir, appId, token, api: api4 } = await resolveTarget2(appArg);
12454
12758
  const id = inferWorkspaceId(appDir, explicitId);
12455
12759
  const inspection = inspectWorkspaceCleanup(appDir, id);
12456
12760
  let approvedBranchOid;
12761
+ const { view: before } = await api4.getWorkspace(id);
12457
12762
  if (!keepWorktree && inspection.willDeleteBranch && inspection.branchOid) {
12458
- const { view: before } = await api4.getWorkspace(id);
12459
12763
  const publishedOids = [
12460
12764
  before.tipOid,
12461
12765
  before.workspace.landedOid,
12462
12766
  before.workspace.baseOid
12463
12767
  ].filter((oid) => oid !== null);
12464
- 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) {
12465
12782
  throw workspaceUnsyncedRefusal({
12466
12783
  appId,
12467
12784
  id,
12468
12785
  branch: inspection.branch,
12469
- workspaceDir: inspection.checkout.dir
12786
+ workspaceDir: inspection.checkout.dir,
12787
+ status: before.workspace.status
12470
12788
  });
12471
12789
  }
12472
12790
  approvedBranchOid = inspection.branchOid;
@@ -12481,7 +12799,7 @@ var dropWorkspaceCommand = defineDeepspaceCommand({
12481
12799
  { extra: { workspaceId: id } }
12482
12800
  );
12483
12801
  }
12484
- const { view } = await api4.dropWorkspace(id);
12802
+ const { view, remoteDropped } = await dropRemoteTolerant(api4, id, before.workspace.status);
12485
12803
  spinner2?.message(`Cleaning up ${id} locally\u2026`);
12486
12804
  const cleanup = keepWorktree ? null : cleanupWorkspaceLocal(appDir, id, null, {
12487
12805
  expectedBranchOid: approvedBranchOid,
@@ -12491,12 +12809,15 @@ var dropWorkspaceCommand = defineDeepspaceCommand({
12491
12809
  const data = {
12492
12810
  workspaceId: id,
12493
12811
  status: view.workspace.status,
12812
+ remoteDropped,
12494
12813
  ...retainedWorktree ? { worktree: retainedWorktree } : {},
12495
12814
  ...cleanup ? { cleanup: cleanupJson(cleanup) } : {}
12496
12815
  };
12497
12816
  spinner2?.stop();
12498
12817
  if (!args.json) {
12499
- 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
+ );
12500
12821
  if (cleanup && !cleanup.error) reportCleanupHuman(cleanup);
12501
12822
  else if (retainedWorktree) p31.log.info(`Retained checkout: ${retainedWorktree}`);
12502
12823
  }
@@ -12565,7 +12886,7 @@ function formatEvent2(e, lands, actors) {
12565
12886
  detail = `"${String(s.task ?? "")}" from ${short(s.baseOid)}`;
12566
12887
  break;
12567
12888
  case "workspace.synced":
12568
- detail = `\u2192 ${short(s.headOid)} (${Number(s.files ?? 0)} files vs base)`;
12889
+ detail = `\u2192 ${short(s.headOid)}`;
12569
12890
  break;
12570
12891
  case "workspace.landed":
12571
12892
  detail = `"${String(s.task ?? "")}" \u2192 ${short(s.landedOid)} on ${String(s.into ?? "trunk")}`;
@@ -13063,8 +13384,8 @@ function sameRefs(left, right) {
13063
13384
 
13064
13385
  // src/cli/commands/update.ts
13065
13386
  import * as p34 from "@clack/prompts";
13066
- import { existsSync as existsSync26, readFileSync as readFileSync28, writeFileSync as writeFileSync10 } from "fs";
13067
- 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";
13068
13389
  var REGISTRY = "https://registry.npmjs.org/deepspace/latest";
13069
13390
  var DOCS_URL = "https://documentation.deep.space/cli-reference/commands";
13070
13391
  var OLDEST_SUPPORTED_MINOR = 12;
@@ -13080,17 +13401,17 @@ function gapIsTooWide(from, to) {
13080
13401
  function readAppSdkVersion(appDir) {
13081
13402
  const installed = join24(appDir, "node_modules", "deepspace", "package.json");
13082
13403
  if (existsSync26(installed)) {
13083
- const parsed2 = JSON.parse(readFileSync28(installed, "utf8"));
13404
+ const parsed2 = JSON.parse(readFileSync29(installed, "utf8"));
13084
13405
  if (typeof parsed2.version === "string") return parsed2.version;
13085
13406
  }
13086
13407
  const manifest = join24(appDir, "package.json");
13087
13408
  if (!existsSync26(manifest)) return null;
13088
- const parsed = JSON.parse(readFileSync28(manifest, "utf8"));
13409
+ const parsed = JSON.parse(readFileSync29(manifest, "utf8"));
13089
13410
  return parsed.dependencies?.deepspace ?? null;
13090
13411
  }
13091
13412
  function pinSdkVersion(appDir, version) {
13092
13413
  const path = join24(appDir, "package.json");
13093
- const source = readFileSync28(path, "utf8");
13414
+ const source = readFileSync29(path, "utf8");
13094
13415
  const updated = source.replace(
13095
13416
  /("deepspace"\s*:\s*")[^"]+(")/,
13096
13417
  (_match, head, tail) => `${head}^${version}${tail}`
@@ -13155,7 +13476,7 @@ var update_default = defineDeepspaceCommand({
13155
13476
  }
13156
13477
  },
13157
13478
  async run({ args }) {
13158
- const appDir = resolve19(".");
13479
+ const appDir = resolve20(".");
13159
13480
  if (!hasWranglerConfig(appDir)) {
13160
13481
  throw new Refusal(
13161
13482
  "No wrangler.toml found \u2014 run this from inside a DeepSpace app directory.",
@@ -13235,8 +13556,8 @@ var update_default = defineDeepspaceCommand({
13235
13556
  });
13236
13557
 
13237
13558
  // src/cli/cli.ts
13238
- var __dirname = dirname10(fileURLToPath3(import.meta.url));
13239
- 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"));
13240
13561
  if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync27("/etc/ssl/certs/ca-certificates.crt")) {
13241
13562
  process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
13242
13563
  }
@@ -13363,7 +13684,9 @@ function assertKnownCommandPath(argv) {
13363
13684
  `${unknownCommandMessage(unknown)}
13364
13685
  Run \`${unknown.helpPath.join(" ")} --help\` for the command list.`
13365
13686
  );
13366
- if (unknown.executable) printAction({ cwd: process.cwd(), argv: unknown.suggestion });
13687
+ if (unknown.executable) {
13688
+ printAction(executableAction({ cwd: process.cwd(), argv: unknown.suggestion }));
13689
+ }
13367
13690
  process.exit(1);
13368
13691
  }
13369
13692
  var rawArgs = process.argv.slice(2);
@@ -13385,7 +13708,7 @@ if (rawArgs.includes("--json") && !wantsHelp) {
13385
13708
  ok: false,
13386
13709
  code: "unknown_command",
13387
13710
  error: unknownCommandMessage(unknown),
13388
- ...unknown.executable ? { action: { cwd: process.cwd(), argv: unknown.suggestion } } : {}
13711
+ ...unknown.executable ? { action: executableAction({ cwd: process.cwd(), argv: unknown.suggestion }) } : {}
13389
13712
  })
13390
13713
  );
13391
13714
  process.exit(1);