deepspace 0.8.0 → 0.8.2

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 } from "citty";
5
5
  import { sync as spawnSync8 } from "cross-spawn";
6
- import { readFileSync as readFileSync24, existsSync as existsSync25 } from "fs";
7
- import { dirname as dirname9, join as join25 } from "path";
6
+ import { readFileSync as readFileSync25, existsSync as existsSync24 } from "fs";
7
+ import { dirname as dirname10, join as join23 } from "path";
8
8
  import { fileURLToPath as fileURLToPath3 } from "url";
9
9
 
10
10
  // src/cli/lib/spinner.ts
@@ -836,7 +836,7 @@ ${line}
836
836
  }
837
837
 
838
838
  // src/cli/commands/dev.ts
839
- import { resolve as resolve5, basename as basename2, join as join11 } from "path";
839
+ import { resolve as resolve6, basename as basename2, join as join12 } from "path";
840
840
  import spawn from "cross-spawn";
841
841
 
842
842
  // src/cli/lib/app-context.ts
@@ -874,10 +874,231 @@ function findChildApps(dir) {
874
874
  }
875
875
 
876
876
  // src/cli/lib/launch-config.ts
877
- import { existsSync as existsSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, renameSync, unlinkSync } from "fs";
878
- import { join as join5, resolve as resolve3, dirname as dirname2, basename, isAbsolute as isAbsolute2, sep } from "path";
877
+ import {
878
+ existsSync as existsSync5,
879
+ readFileSync as readFileSync7,
880
+ realpathSync,
881
+ writeFileSync as writeFileSync4,
882
+ mkdirSync as mkdirSync4,
883
+ renameSync,
884
+ unlinkSync
885
+ } from "fs";
886
+ import { join as join6, resolve as resolve4, dirname as dirname3, basename, isAbsolute as isAbsolute2, sep } from "path";
887
+
888
+ // src/cli/lib/git/repository.ts
889
+ import { appendFileSync, mkdirSync as mkdirSync3, readFileSync as readFileSync6 } from "fs";
890
+ import { dirname as dirname2, join as join5, resolve as resolve3 } from "path";
891
+
892
+ // src/cli/lib/git/process.ts
893
+ import { sync as spawnSync } from "cross-spawn";
894
+ var MAX_GIT_BUFFER = 512 * 1024 * 1024;
895
+ var GitError = class extends Error {
896
+ constructor(message, code) {
897
+ super(message);
898
+ this.code = code;
899
+ this.name = "GitError";
900
+ }
901
+ };
902
+ function runGit(cwd, args, opts = {}) {
903
+ const result = spawnSync("git", args, {
904
+ cwd,
905
+ input: opts.input,
906
+ maxBuffer: MAX_GIT_BUFFER,
907
+ stdio: ["pipe", "pipe", "pipe"],
908
+ env: {
909
+ ...process.env,
910
+ GIT_TERMINAL_PROMPT: "0",
911
+ LC_ALL: "C",
912
+ ...opts.env ?? {}
913
+ }
914
+ });
915
+ if (result.error) {
916
+ const code = result.error.code;
917
+ if (code === "ENOENT") {
918
+ throw new GitError(
919
+ "git is not installed or not on PATH \u2014 install git and retry.",
920
+ "git_not_installed"
921
+ );
922
+ }
923
+ throw new GitError(`git ${args[0]} failed: ${result.error.message}`);
924
+ }
925
+ const status3 = result.status ?? 1;
926
+ if (status3 !== 0 && !opts.allowFail) {
927
+ const stderr = result.stderr?.toString("utf-8").trim() ?? "";
928
+ throw new GitError(`git ${args.join(" ")} exited ${status3}${stderr ? `: ${stderr}` : ""}`);
929
+ }
930
+ return {
931
+ stdout: result.stdout ?? Buffer.alloc(0),
932
+ stderr: result.stderr ?? Buffer.alloc(0),
933
+ status: status3
934
+ };
935
+ }
936
+ function gitLine(cwd, args) {
937
+ return runGit(cwd, args).stdout.toString("utf-8").trim();
938
+ }
939
+ function splitNulFields(buffer) {
940
+ const fields = [];
941
+ let start = 0;
942
+ for (let index = 0; index < buffer.length; index++) {
943
+ if (buffer[index] === 0) {
944
+ fields.push(buffer.toString("utf-8", start, index));
945
+ start = index + 1;
946
+ }
947
+ }
948
+ if (start < buffer.length) fields.push(buffer.toString("utf-8", start));
949
+ return fields;
950
+ }
951
+ function parseGitVersion(output) {
952
+ const match = /(\d+)\.(\d+)/.exec(output);
953
+ return match ? [Number(match[1]), Number(match[2])] : null;
954
+ }
955
+ function gitMeetsFloor(version) {
956
+ if (!version) return true;
957
+ const [major, minor] = version;
958
+ return major > 2 || major === 2 && minor >= 29;
959
+ }
960
+
961
+ // src/cli/lib/git/repository.ts
962
+ function isPlausibleBranchName(name) {
963
+ if (!name || name === "HEAD" || name === "@") return false;
964
+ if (name.startsWith("/") || name.startsWith("-") || name.startsWith(".")) return false;
965
+ if (name.endsWith("/") || name.endsWith(".") || name.endsWith(".lock")) return false;
966
+ if (name.includes("/.") || name.includes(".lock/")) return false;
967
+ return !/(\.\.|\/\/|@\{|[\x00-\x20\x7f~^:?*[\\])/.test(name);
968
+ }
969
+ function assertSyncableRepo(cwd) {
970
+ const inside = runGit(cwd, ["rev-parse", "--is-inside-work-tree"], { allowFail: true });
971
+ if (inside.status !== 0 || inside.stdout.toString().trim() !== "true") {
972
+ throw new InputError(
973
+ `${cwd} is not a git repository. DeepSpace apps are scaffolded as git repos \u2014 run \`git init\` first.`,
974
+ "not_git_repo"
975
+ );
976
+ }
977
+ const versionOutput = runGit(cwd, ["--version"], { allowFail: true }).stdout.toString("utf-8").trim();
978
+ if (!gitMeetsFloor(parseGitVersion(versionOutput))) {
979
+ throw gitTooOld(versionOutput);
980
+ }
981
+ const formatResult = runGit(cwd, ["rev-parse", "--show-object-format"], { allowFail: true });
982
+ const format = formatResult.stdout.toString("utf-8").trim();
983
+ if (formatResult.status !== 0 || format.startsWith("--")) throw gitTooOld(versionOutput);
984
+ if (format !== "sha1") {
985
+ throw new InputError(
986
+ `This repo uses object format "${format}" \u2014 DeepSpace version control supports sha1 repos only (git's default) for now.`,
987
+ "unsupported_object_format"
988
+ );
989
+ }
990
+ if (gitLine(cwd, ["rev-parse", "--is-shallow-repository"]) === "true") {
991
+ throw new InputError(
992
+ "This is a shallow clone. Run `git fetch --unshallow` before pushing to DeepSpace.",
993
+ "shallow_repo"
994
+ );
995
+ }
996
+ }
997
+ function gitTooOld(versionOutput) {
998
+ return new InputError(
999
+ `DeepSpace version control needs git \u2265 2.29 \u2014 found ${versionOutput || "an unknown git version"}. Upgrade git and retry.`,
1000
+ "git_too_old"
1001
+ );
1002
+ }
1003
+ function repoToplevel(cwd) {
1004
+ return gitLine(cwd, ["rev-parse", "--show-toplevel"]);
1005
+ }
1006
+ function currentBranch(cwd) {
1007
+ const result = runGit(cwd, ["symbolic-ref", "--short", "-q", "HEAD"], { allowFail: true });
1008
+ const name = result.stdout.toString("utf-8").trim();
1009
+ return result.status === 0 && name ? name : null;
1010
+ }
1011
+ function resolveCommit(cwd, ref) {
1012
+ const result = runGit(cwd, ["rev-parse", "-q", "--verify", `${ref}^{commit}`], {
1013
+ allowFail: true
1014
+ });
1015
+ const oid = result.stdout.toString("utf-8").trim();
1016
+ return result.status === 0 && /^[0-9a-f]{40}$/.test(oid) ? oid : null;
1017
+ }
1018
+ function isWorkTreeClean(cwd) {
1019
+ return gitLine(cwd, ["status", "--porcelain"]) === "";
1020
+ }
1021
+ function updateRef(cwd, ref, oid) {
1022
+ runGit(cwd, ["update-ref", ref, oid]);
1023
+ }
1024
+ function isAncestor(cwd, ancestor, descendant) {
1025
+ return runGit(cwd, ["merge-base", "--is-ancestor", ancestor, descendant], { allowFail: true }).status === 0;
1026
+ }
1027
+ function mergeBase(cwd, left, right) {
1028
+ const result = runGit(cwd, ["merge-base", left, right], { allowFail: true });
1029
+ if (result.status !== 0) return null;
1030
+ const oid = result.stdout.toString("utf-8").trim();
1031
+ return /^[0-9a-f]{40}$/.test(oid) ? oid : null;
1032
+ }
1033
+ function fastForwardCurrentBranch(cwd, oid) {
1034
+ runGit(cwd, ["merge", "--ff-only", oid]);
1035
+ }
1036
+ function checkoutHead(cwd) {
1037
+ runGit(cwd, ["reset", "--hard", "--quiet"]);
1038
+ }
1039
+ function switchToNewBranch(cwd, name, oid) {
1040
+ runGit(cwd, ["switch", "--quiet", "-c", name, oid]);
1041
+ }
1042
+ function diffNameOnly(cwd, base, head) {
1043
+ return splitNulFields(
1044
+ runGit(cwd, [
1045
+ "-c",
1046
+ "core.quotePath=false",
1047
+ "diff",
1048
+ "--name-only",
1049
+ "--no-renames",
1050
+ "-z",
1051
+ base,
1052
+ head
1053
+ ]).stdout
1054
+ );
1055
+ }
1056
+ function revListCount(cwd, exclude, include) {
1057
+ const count = Number(gitLine(cwd, ["rev-list", "--count", `${exclude}..${include}`]));
1058
+ return Number.isFinite(count) ? count : 0;
1059
+ }
1060
+ function addWorktreeNewBranch(cwd, dir, branch, oid) {
1061
+ runGit(cwd, ["worktree", "add", "--quiet", "-b", branch, dir, oid]);
1062
+ }
1063
+ function listWorktrees(cwd) {
1064
+ const result = runGit(cwd, ["worktree", "list", "--porcelain"], { allowFail: true });
1065
+ if (result.status !== 0) return [];
1066
+ const worktrees = [];
1067
+ let current = null;
1068
+ for (const line of result.stdout.toString("utf-8").split("\n")) {
1069
+ if (line.startsWith("worktree ")) {
1070
+ current = { path: line.slice("worktree ".length), branch: null };
1071
+ worktrees.push(current);
1072
+ } else if (line.startsWith("branch ") && current) {
1073
+ const ref = line.slice("branch ".length);
1074
+ current.branch = ref.startsWith("refs/heads/") ? ref.slice("refs/heads/".length) : ref;
1075
+ }
1076
+ }
1077
+ return worktrees;
1078
+ }
1079
+ function ensureLocalExclude(cwd, pattern) {
1080
+ const commonDir = resolve3(cwd, gitLine(cwd, ["rev-parse", "--git-common-dir"]));
1081
+ const excludePath = join5(commonDir, "info", "exclude");
1082
+ let current = "";
1083
+ try {
1084
+ current = readFileSync6(excludePath, "utf-8");
1085
+ } catch {
1086
+ }
1087
+ if (current.split("\n").some((line) => line.trim() === pattern)) return;
1088
+ try {
1089
+ mkdirSync3(dirname2(excludePath), { recursive: true });
1090
+ appendFileSync(
1091
+ excludePath,
1092
+ `${current.endsWith("\n") || current === "" ? "" : "\n"}${pattern}
1093
+ `
1094
+ );
1095
+ } catch {
1096
+ }
1097
+ }
1098
+
1099
+ // src/cli/lib/launch-config.ts
879
1100
  function writeLaunchFile(launchPath, config) {
880
- mkdirSync3(dirname2(launchPath), { recursive: true });
1101
+ mkdirSync4(dirname3(launchPath), { recursive: true });
881
1102
  const tmpPath = `${launchPath}.${process.pid}.tmp`;
882
1103
  writeFileSync4(tmpPath, JSON.stringify(config, null, 2) + "\n");
883
1104
  try {
@@ -893,14 +1114,14 @@ function writeLaunchFile(launchPath, config) {
893
1114
  function readLaunchFile(launchPath) {
894
1115
  const empty = { version: "0.0.1", configurations: [] };
895
1116
  if (!existsSync5(launchPath)) return empty;
896
- const raw = readFileSync6(launchPath, "utf-8").trim();
1117
+ const raw = readFileSync7(launchPath, "utf-8").trim();
897
1118
  if (!raw) return empty;
898
1119
  const parsed = JSON.parse(raw);
899
1120
  if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.configurations)) return null;
900
1121
  return parsed;
901
1122
  }
902
1123
  function writeLaunchConfigIfMissing(appDir, appName, port, opts = {}) {
903
- const launchPath = join5(appDir, ".claude", "launch.json");
1124
+ const launchPath = join6(appDir, ".claude", "launch.json");
904
1125
  try {
905
1126
  const config = readLaunchFile(launchPath);
906
1127
  if (!config) return;
@@ -941,7 +1162,7 @@ function writeLaunchConfigIfMissing(appDir, appName, port, opts = {}) {
941
1162
  }
942
1163
  function resolveAppLaunchPort(appDir) {
943
1164
  try {
944
- const config = readLaunchFile(join5(appDir, ".claude", "launch.json"));
1165
+ const config = readLaunchFile(join6(appDir, ".claude", "launch.json"));
945
1166
  if (!config) return null;
946
1167
  const appName = detectAppName(appDir) ?? basename(appDir);
947
1168
  const entry = config.configurations.find((c) => c?.name === appName) ?? (config.configurations.length === 1 ? config.configurations[0] : void 0);
@@ -952,32 +1173,55 @@ function resolveAppLaunchPort(appDir) {
952
1173
  }
953
1174
  }
954
1175
  function detectClaudeWorktree(appDir) {
955
- const segments = resolve3(appDir).split(sep);
956
- for (let i = segments.length - 3; i >= 1; i--) {
957
- if (segments[i] === ".claude" && segments[i + 1] === "worktrees" && segments[i + 2]) {
958
- return {
959
- mainRepoRoot: segments.slice(0, i).join(sep) || sep,
960
- worktreeName: segments[i + 2]
961
- };
962
- }
1176
+ try {
1177
+ const worktreeRoot = canonicalPath(repoToplevel(appDir));
1178
+ const registered = new Set(
1179
+ listWorktrees(appDir).map((worktree) => canonicalPath(worktree.path))
1180
+ );
1181
+ if (!registered.has(worktreeRoot)) return null;
1182
+ const worktreesDir = dirname3(worktreeRoot);
1183
+ const claudeDir = dirname3(worktreesDir);
1184
+ if (basename(worktreesDir) !== "worktrees" || basename(claudeDir) !== ".claude") return null;
1185
+ const ownerRoot = canonicalPath(dirname3(claudeDir));
1186
+ if (!registered.has(ownerRoot)) return null;
1187
+ return { mainRepoRoot: ownerRoot, worktreeName: basename(worktreeRoot) };
1188
+ } catch {
1189
+ return null;
1190
+ }
1191
+ }
1192
+ function canonicalPath(path) {
1193
+ try {
1194
+ return realpathSync(path);
1195
+ } catch {
1196
+ return resolve4(path);
1197
+ }
1198
+ }
1199
+ function detectLinkedWorktree(appDir) {
1200
+ try {
1201
+ const worktreeRoot = canonicalPath(repoToplevel(appDir));
1202
+ const registered = listWorktrees(appDir).map((worktree) => canonicalPath(worktree.path));
1203
+ const currentIndex = registered.indexOf(worktreeRoot);
1204
+ if (currentIndex <= 0 || !registered[0]) return null;
1205
+ return { worktreeRoot, mainRepoRoot: registered[0] };
1206
+ } catch {
1207
+ return null;
963
1208
  }
964
- return null;
965
1209
  }
966
1210
  var WORKTREE_ENTRY_PREFIX = "wt-";
967
1211
  var WORKTREE_PORT_MIN = 5180;
968
- var WORKTREE_PORT_RANGE = 20;
969
- function deriveWorktreePort(worktreeName) {
1212
+ var WORKTREE_PORT_RANGE = 1e3;
1213
+ function deriveWorktreePort(worktreeKey) {
970
1214
  let hash = 0;
971
- for (const ch of worktreeName) hash = hash * 31 + ch.charCodeAt(0) >>> 0;
1215
+ for (const ch of worktreeKey) hash = hash * 31 + ch.charCodeAt(0) >>> 0;
972
1216
  return WORKTREE_PORT_MIN + hash % WORKTREE_PORT_RANGE;
973
1217
  }
974
1218
  function upsertWorktreeLaunchConfig(mainRepoRoot, worktreeName, appDir, opts) {
975
- const launchPath = join5(mainRepoRoot, ".claude", "launch.json");
1219
+ const launchPath = join6(mainRepoRoot, ".claude", "launch.json");
976
1220
  const entryName = WORKTREE_ENTRY_PREFIX + worktreeName;
977
1221
  try {
978
1222
  const config = readLaunchFile(launchPath);
979
1223
  if (!config) return null;
980
- const worktreesRoot = join5(mainRepoRoot, ".claude", "worktrees") + sep;
1224
+ const worktreesRoot = join6(mainRepoRoot, ".claude", "worktrees") + sep;
981
1225
  config.configurations = config.configurations.filter((c) => {
982
1226
  if (c?.name === entryName) return false;
983
1227
  const isStaleWorktreeEntry = typeof c?.name === "string" && c.name.startsWith(WORKTREE_ENTRY_PREFIX) && typeof c.cwd === "string" && isAbsolute2(c.cwd) && c.cwd.startsWith(worktreesRoot) && !existsSync5(c.cwd);
@@ -1001,7 +1245,7 @@ function upsertWorktreeLaunchConfig(mainRepoRoot, worktreeName, appDir, opts) {
1001
1245
  // the Claude Code desktop launch.json reference alongside `port`) and
1002
1246
  // verified end-to-end: with it set, the preview server builds from the
1003
1247
  // worktree instead of the session's main repo.
1004
- cwd: resolve3(appDir)
1248
+ cwd: resolve4(appDir)
1005
1249
  });
1006
1250
  writeLaunchFile(launchPath, config);
1007
1251
  return { entryName, port };
@@ -1010,22 +1254,25 @@ function upsertWorktreeLaunchConfig(mainRepoRoot, worktreeName, appDir, opts) {
1010
1254
  }
1011
1255
  }
1012
1256
  function resolveWorktreePort(appDir) {
1013
- const worktree = detectClaudeWorktree(appDir);
1014
- if (!worktree) return null;
1015
- try {
1016
- const config = readLaunchFile(join5(worktree.mainRepoRoot, ".claude", "launch.json"));
1017
- const entry = config?.configurations.find(
1018
- (c) => c?.name === WORKTREE_ENTRY_PREFIX + worktree.worktreeName
1019
- );
1020
- const port = Number(entry?.port);
1021
- if (Number.isInteger(port) && port > 0) return port;
1022
- } catch {
1257
+ const linked = detectLinkedWorktree(appDir);
1258
+ if (!linked) return null;
1259
+ const claude = detectClaudeWorktree(appDir);
1260
+ if (claude) {
1261
+ try {
1262
+ const config = readLaunchFile(join6(claude.mainRepoRoot, ".claude", "launch.json"));
1263
+ const entry = config?.configurations.find(
1264
+ (c) => c?.name === WORKTREE_ENTRY_PREFIX + claude.worktreeName
1265
+ );
1266
+ const port = Number(entry?.port);
1267
+ if (Number.isInteger(port) && port > 0) return port;
1268
+ } catch {
1269
+ }
1023
1270
  }
1024
- return deriveWorktreePort(worktree.worktreeName);
1271
+ return deriveWorktreePort(linked.worktreeRoot);
1025
1272
  }
1026
1273
 
1027
1274
  // src/cli/lib/dev-vars.ts
1028
- import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
1275
+ import { existsSync as existsSync7, readFileSync as readFileSync9 } from "fs";
1029
1276
 
1030
1277
  // src/cli/lib/app-tokens.ts
1031
1278
  async function fetchPublicKey(authUrl) {
@@ -1542,8 +1789,8 @@ function writeSecretFileSync(path, content) {
1542
1789
  }
1543
1790
 
1544
1791
  // src/cli/lib/wrangler-env.ts
1545
- import { existsSync as existsSync6, readFileSync as readFileSync7, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
1546
- import { join as join6 } from "path";
1792
+ import { existsSync as existsSync6, readFileSync as readFileSync8, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
1793
+ import { join as join7 } from "path";
1547
1794
  import { parse as parseToml3, stringify as stringifyToml } from "smol-toml";
1548
1795
 
1549
1796
  // src/server/rooms/app-name.ts
@@ -1643,11 +1890,11 @@ function resolveAppNameForEnv(config, envName) {
1643
1890
  return resolveAppName(envBlock.name);
1644
1891
  }
1645
1892
  function devVarsPathFor(appDir, envName, opts = {}) {
1646
- if (opts.sharedDevVarsCache || !envName) return join6(appDir, ".dev.vars");
1647
- return join6(appDir, `.dev.vars.${envName}`);
1893
+ if (opts.sharedDevVarsCache || !envName) return join7(appDir, ".dev.vars");
1894
+ return join7(appDir, `.dev.vars.${envName}`);
1648
1895
  }
1649
1896
  function legacyEnvDevVarsPathFor(appDir, envName) {
1650
- return envName ? join6(appDir, `.dev.vars.${envName}`) : void 0;
1897
+ return envName ? join7(appDir, `.dev.vars.${envName}`) : void 0;
1651
1898
  }
1652
1899
  function warnIfLegacyEnvDevVarsExists(appDir, envName, warn = console.warn) {
1653
1900
  const legacyPath = legacyEnvDevVarsPathFor(appDir, envName);
@@ -1666,7 +1913,7 @@ function prepareWranglerEnvConfig(appDir, envName, opts = {}) {
1666
1913
  const resolved = resolveAppNameForEnv(config, envName);
1667
1914
  if (!resolved.ok) throw new Error(resolved.reason);
1668
1915
  const flattened = flattenWranglerEnvConfig(config, envName);
1669
- const configPath = join6(
1916
+ const configPath = join7(
1670
1917
  appDir,
1671
1918
  `.wrangler.deepspace.${safeGeneratedConfigSegment(envName)}.${process.pid}.${++generatedConfigSeq}.toml`
1672
1919
  );
@@ -1727,8 +1974,8 @@ var WranglerConfigError = class extends Error {
1727
1974
  }
1728
1975
  };
1729
1976
  function readWranglerConfig(appDir) {
1730
- const path = join6(appDir, "wrangler.toml");
1731
- const raw = readFileSync7(path, "utf-8");
1977
+ const path = join7(appDir, "wrangler.toml");
1978
+ const raw = readFileSync8(path, "utf-8");
1732
1979
  try {
1733
1980
  return parseToml3(raw);
1734
1981
  } catch (err) {
@@ -1736,7 +1983,7 @@ function readWranglerConfig(appDir) {
1736
1983
  }
1737
1984
  }
1738
1985
  function hasWranglerConfig(appDir) {
1739
- return existsSync6(join6(appDir, "wrangler.toml"));
1986
+ return existsSync6(join7(appDir, "wrangler.toml"));
1740
1987
  }
1741
1988
 
1742
1989
  // src/cli/lib/dev-vars.ts
@@ -1901,7 +2148,7 @@ async function writeDevVars(appDir, ownerId, callerJwt, wranglerEnv, opts = {})
1901
2148
  const devVarsPath = devVarsPathFor(appDir, wranglerEnv, {
1902
2149
  sharedDevVarsCache: useSharedDevVarsCache
1903
2150
  });
1904
- const existing = existsSync7(devVarsPath) ? readFileSync8(devVarsPath, "utf-8") : "";
2151
+ const existing = existsSync7(devVarsPath) ? readFileSync9(devVarsPath, "utf-8") : "";
1905
2152
  const custom = opts.generatedSecretsCache ?? extractCustomDevVars(stripGeneratedSecretsCache(existing).trimEnd());
1906
2153
  let body;
1907
2154
  if (!custom) {
@@ -1923,22 +2170,22 @@ ${custom}
1923
2170
  }
1924
2171
 
1925
2172
  // src/cli/lib/install-status.ts
1926
- import { existsSync as existsSync9, readFileSync as readFileSync10 } from "fs";
1927
- import { dirname as dirname3, join as join8 } from "path";
2173
+ import { existsSync as existsSync9, readFileSync as readFileSync11 } from "fs";
2174
+ import { dirname as dirname4, join as join9 } from "path";
1928
2175
 
1929
2176
  // src/cli/lib/package-manager.ts
1930
- import { existsSync as existsSync8, readFileSync as readFileSync9 } from "fs";
1931
- import { join as join7 } from "path";
2177
+ import { existsSync as existsSync8, readFileSync as readFileSync10 } from "fs";
2178
+ import { join as join8 } from "path";
1932
2179
  function detectPackageManager(appDir) {
1933
2180
  try {
1934
- const pkg2 = JSON.parse(readFileSync9(join7(appDir, "package.json"), "utf-8"));
2181
+ const pkg2 = JSON.parse(readFileSync10(join8(appDir, "package.json"), "utf-8"));
1935
2182
  const name = typeof pkg2.packageManager === "string" ? pkg2.packageManager.split("@")[0] : "";
1936
2183
  if (name === "npm" || name === "pnpm" || name === "yarn" || name === "bun") return name;
1937
2184
  } catch {
1938
2185
  }
1939
- if (existsSync8(join7(appDir, "pnpm-lock.yaml"))) return "pnpm";
1940
- if (existsSync8(join7(appDir, "yarn.lock"))) return "yarn";
1941
- if (existsSync8(join7(appDir, "bun.lock")) || existsSync8(join7(appDir, "bun.lockb"))) return "bun";
2186
+ if (existsSync8(join8(appDir, "pnpm-lock.yaml"))) return "pnpm";
2187
+ if (existsSync8(join8(appDir, "yarn.lock"))) return "yarn";
2188
+ if (existsSync8(join8(appDir, "bun.lock")) || existsSync8(join8(appDir, "bun.lockb"))) return "bun";
1942
2189
  return "npm";
1943
2190
  }
1944
2191
  function installCommand(appDir) {
@@ -1948,10 +2195,10 @@ function installCommand(appDir) {
1948
2195
  // src/cli/lib/install-status.ts
1949
2196
  function installState(appDir) {
1950
2197
  if (resolvesDeepspace(appDir)) return "ready";
1951
- const sentinelDir = join8(appDir, ".deepspace");
1952
- if (existsSync9(join8(sentinelDir, "install.err"))) return "failed";
1953
- if (existsSync9(join8(sentinelDir, "install.started"))) {
1954
- if (!existsSync9(join8(sentinelDir, "install.done")) && installWorkerAlive(sentinelDir)) {
2198
+ const sentinelDir = join9(appDir, ".deepspace");
2199
+ if (existsSync9(join9(sentinelDir, "install.err"))) return "failed";
2200
+ if (existsSync9(join9(sentinelDir, "install.started"))) {
2201
+ if (!existsSync9(join9(sentinelDir, "install.done")) && installWorkerAlive(sentinelDir)) {
1955
2202
  return "installing";
1956
2203
  }
1957
2204
  return "failed";
@@ -1963,16 +2210,16 @@ function tailHint(logPath, platform = process.platform) {
1963
2210
  }
1964
2211
  function ensureInstallReady(appDir) {
1965
2212
  if (resolvesDeepspace(appDir)) return;
1966
- const sentinelDir = join8(appDir, ".deepspace");
1967
- const errPath = join8(sentinelDir, "install.err");
1968
- const startedPath = join8(sentinelDir, "install.started");
1969
- const donePath = join8(sentinelDir, "install.done");
1970
- const logPath = join8(sentinelDir, "install.log");
2213
+ const sentinelDir = join9(appDir, ".deepspace");
2214
+ const errPath = join9(sentinelDir, "install.err");
2215
+ const startedPath = join9(sentinelDir, "install.started");
2216
+ const donePath = join9(sentinelDir, "install.done");
2217
+ const logPath = join9(sentinelDir, "install.log");
1971
2218
  if (existsSync9(errPath)) {
1972
- const detail = readFileSync10(errPath, "utf-8").trimEnd();
2219
+ const detail = readFileSync11(errPath, "utf-8").trimEnd();
1973
2220
  const log28 = existsSync9(logPath) ? ` Full log: ${logPath}.` : "";
1974
2221
  throw new Refusal(
1975
- `Background install failed: ${detail}.${log28} Run \`npm install\` (or \`bun install\`) manually, then retry.`,
2222
+ `Dependency install failed: ${detail}.${log28} Run \`npm install\` (or \`bun install\`) manually, then retry.`,
1976
2223
  "install_failed",
1977
2224
  { action: { cwd: appDir, argv: [detectPackageManager(appDir), "install"] } }
1978
2225
  );
@@ -1981,14 +2228,14 @@ function ensureInstallReady(appDir) {
1981
2228
  if (!installWorkerAlive(sentinelDir)) {
1982
2229
  const log28 = existsSync9(logPath) ? ` See what happened: ${logPath}.` : "";
1983
2230
  throw new Refusal(
1984
- `The background install is no longer running and never finished.${log28} Run \`npm install\` (or \`bun install\`) manually, then retry.`,
2231
+ `The dependency install is no longer running and never finished.${log28} Run \`npm install\` (or \`bun install\`) manually, then retry.`,
1985
2232
  "install_failed",
1986
2233
  { action: { cwd: appDir, argv: [detectPackageManager(appDir), "install"] } }
1987
2234
  );
1988
2235
  }
1989
2236
  const tail = existsSync9(logPath) ? ` Tail progress: ${tailHint(logPath)}.` : "";
1990
2237
  throw new Refusal(
1991
- `Dependencies are still installing in the background.${tail} Retry once it finishes.`,
2238
+ `Dependencies are still installing.${tail} Retry once it finishes.`,
1992
2239
  "install_in_progress"
1993
2240
  );
1994
2241
  }
@@ -2003,16 +2250,16 @@ function ensureInstallReady(appDir) {
2003
2250
  function resolvesDeepspace(fromDir) {
2004
2251
  let dir = fromDir;
2005
2252
  for (; ; ) {
2006
- if (existsSync9(join8(dir, "node_modules", "deepspace", "package.json"))) return true;
2007
- const parent = dirname3(dir);
2253
+ if (existsSync9(join9(dir, "node_modules", "deepspace", "package.json"))) return true;
2254
+ const parent = dirname4(dir);
2008
2255
  if (parent === dir) return false;
2009
2256
  dir = parent;
2010
2257
  }
2011
2258
  }
2012
2259
  function installWorkerAlive(sentinelDir) {
2013
- const pidPath = join8(sentinelDir, "install.pid");
2260
+ const pidPath = join9(sentinelDir, "install.pid");
2014
2261
  if (!existsSync9(pidPath)) return false;
2015
- const pid = Number(readFileSync10(pidPath, "utf-8").trim());
2262
+ const pid = Number(readFileSync11(pidPath, "utf-8").trim());
2016
2263
  if (!Number.isSafeInteger(pid) || pid <= 0) return false;
2017
2264
  try {
2018
2265
  process.kill(pid, 0);
@@ -2026,7 +2273,7 @@ function installWorkerAlive(sentinelDir) {
2026
2273
  import { execFileSync } from "child_process";
2027
2274
  import { existsSync as existsSync10 } from "fs";
2028
2275
  import * as nodeModule from "module";
2029
- import { resolve as resolve4 } from "path";
2276
+ import { resolve as resolve5 } from "path";
2030
2277
  function preflightNodeVersion(command, hasRegisterHooks = typeof nodeModule.registerHooks === "function") {
2031
2278
  if (hasRegisterHooks) return;
2032
2279
  throw new Refusal(
@@ -2037,7 +2284,7 @@ function preflightNodeVersion(command, hasRegisterHooks = typeof nodeModule.regi
2037
2284
  var DLL_NOT_FOUND = -1073741515;
2038
2285
  function preflightWindowsWorkerd(appDir) {
2039
2286
  if (process.platform !== "win32") return;
2040
- const workerdExe = resolve4(
2287
+ const workerdExe = resolve5(
2041
2288
  appDir,
2042
2289
  "node_modules",
2043
2290
  "@cloudflare",
@@ -2061,7 +2308,7 @@ function preflightWindowsWorkerd(appDir) {
2061
2308
 
2062
2309
  // src/cli/lib/macos-junk.ts
2063
2310
  import { readdirSync as readdirSync2, rmSync as rmSync3 } from "fs";
2064
- import { join as join9 } from "path";
2311
+ import { join as join10 } from "path";
2065
2312
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".wrangler", ".vite"]);
2066
2313
  function removeMacosJunk(dir) {
2067
2314
  let removed = 0;
@@ -2072,7 +2319,7 @@ function removeMacosJunk(dir) {
2072
2319
  return removed;
2073
2320
  }
2074
2321
  for (const entry of entries) {
2075
- const path = join9(dir, entry.name);
2322
+ const path = join10(dir, entry.name);
2076
2323
  if (entry.isDirectory()) {
2077
2324
  if (!SKIP_DIRS.has(entry.name)) removed += removeMacosJunk(path);
2078
2325
  continue;
@@ -2090,7 +2337,7 @@ function removeMacosJunk(dir) {
2090
2337
 
2091
2338
  // src/cli/lib/schema-lint.ts
2092
2339
  import { existsSync as existsSync11 } from "fs";
2093
- import { join as join10 } from "path";
2340
+ import { join as join11 } from "path";
2094
2341
  import { pathToFileURL } from "url";
2095
2342
 
2096
2343
  // src/server/schemas/registry.ts
@@ -2146,12 +2393,12 @@ function lintSchema(schema) {
2146
2393
 
2147
2394
  // src/cli/lib/schema-lint.ts
2148
2395
  async function lintProjectSchemas(appDir) {
2149
- const schemasPath = join10(appDir, "src", "schemas.ts");
2396
+ const schemasPath = join11(appDir, "src", "schemas.ts");
2150
2397
  if (!existsSync11(schemasPath)) return null;
2151
2398
  let schemas;
2152
2399
  try {
2153
2400
  const esbuild = await import("esbuild");
2154
- const outFile = join10(appDir, ".wrangler", "deploy", "schemas.lint.bundle.mjs");
2401
+ const outFile = join11(appDir, ".wrangler", "deploy", "schemas.lint.bundle.mjs");
2155
2402
  await esbuild.build({
2156
2403
  entryPoints: [schemasPath],
2157
2404
  bundle: true,
@@ -2256,7 +2503,7 @@ var dev_default = defineDeepspaceCommand({
2256
2503
  };
2257
2504
  preflightNodeVersion("dev start");
2258
2505
  const wranglerEnv = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
2259
- const start = resolve5(args.dir ?? ".");
2506
+ const start = resolve6(args.dir ?? ".");
2260
2507
  const appDir = findAppDir(start);
2261
2508
  if (!appDir) {
2262
2509
  throw new Refusal(noAppDirMessage(start, findChildApps(start)), "no_app_dir");
@@ -2264,8 +2511,10 @@ var dev_default = defineDeepspaceCommand({
2264
2511
  const junk = removeMacosJunk(appDir);
2265
2512
  if (junk > 0) say(`Removed ${junk} macOS metadata file(s) (._*, .DS_Store)`);
2266
2513
  const worktree = detectClaudeWorktree(appDir);
2514
+ const linkedWorktree = detectLinkedWorktree(appDir);
2267
2515
  const explicitPort = Boolean(args.port);
2268
- let port = worktree && !explicitPort ? deriveWorktreePort(worktree.worktreeName) : resolvePort(args.port);
2516
+ const configuredPort = explicitPort || Boolean(process.env.DEEPSPACE_PORT);
2517
+ let port = linkedWorktree && !configuredPort ? deriveWorktreePort(linkedWorktree.worktreeRoot) : resolvePort(args.port);
2269
2518
  if (worktree) {
2270
2519
  const upserted = upsertWorktreeLaunchConfig(
2271
2520
  worktree.mainRepoRoot,
@@ -2273,7 +2522,7 @@ var dev_default = defineDeepspaceCommand({
2273
2522
  appDir,
2274
2523
  {
2275
2524
  port,
2276
- probePort: !explicitPort,
2525
+ probePort: !configuredPort,
2277
2526
  extraArgs: [...wranglerEnv ? ["--env", wranglerEnv] : []]
2278
2527
  }
2279
2528
  );
@@ -2284,12 +2533,7 @@ var dev_default = defineDeepspaceCommand({
2284
2533
  );
2285
2534
  } else if (!args.json) {
2286
2535
  console.warn(
2287
- `Warning: could not update ${join11(worktree.mainRepoRoot, ".claude", "launch.json")} (malformed or unwritable) \u2014 the Claude preview tool may serve the main repo's code instead of this worktree.`
2288
- );
2289
- }
2290
- if (!explicitPort && process.env.DEEPSPACE_PORT) {
2291
- say(
2292
- `Ignoring DEEPSPACE_PORT=${process.env.DEEPSPACE_PORT} inside a worktree \u2014 using per-worktree port ${port}. Pass --port to override.`
2536
+ `Warning: could not update ${join12(worktree.mainRepoRoot, ".claude", "launch.json")} (malformed or unwritable) \u2014 the Claude preview tool may serve the main repo's code instead of this worktree.`
2293
2537
  );
2294
2538
  }
2295
2539
  }
@@ -2388,15 +2632,15 @@ Free it with \`${killCmd}\`, or start on another port: \`deepspace dev start --p
2388
2632
  });
2389
2633
 
2390
2634
  // src/cli/commands/kill.ts
2391
- import { spawnSync } from "child_process";
2392
- import { readFileSync as readFileSync11, readdirSync as readdirSync3, readlinkSync } from "fs";
2635
+ import { spawnSync as spawnSync2 } from "child_process";
2636
+ import { readFileSync as readFileSync12, readdirSync as readdirSync3, readlinkSync } from "fs";
2393
2637
  import { setTimeout as wait } from "timers/promises";
2394
2638
  var SIGTERM_GRACE_MS = 1500;
2395
2639
  var IS_WIN = process.platform === "win32";
2396
2640
  function pickKillPort(opts) {
2397
2641
  if (opts.explicit != null) return opts.explicit;
2398
- if (opts.worktree != null) return opts.worktree;
2399
2642
  if (opts.env != null) return opts.env;
2643
+ if (opts.worktree != null) return opts.worktree;
2400
2644
  return opts.appLaunch ?? DEFAULT_PORT;
2401
2645
  }
2402
2646
  var kill_default = defineDeepspaceCommand({
@@ -2523,7 +2767,7 @@ function listenerPids(port) {
2523
2767
  }
2524
2768
  function runLsof(port) {
2525
2769
  try {
2526
- const r = spawnSync("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-t"], {
2770
+ const r = spawnSync2("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-t"], {
2527
2771
  encoding: "utf-8"
2528
2772
  });
2529
2773
  if (r.error) return { pids: [], ran: false };
@@ -2540,7 +2784,7 @@ function listenerPidsViaProc(port) {
2540
2784
  for (const f of ["/proc/net/tcp", "/proc/net/tcp6"]) {
2541
2785
  let data;
2542
2786
  try {
2543
- data = readFileSync11(f, "utf-8");
2787
+ data = readFileSync12(f, "utf-8");
2544
2788
  } catch {
2545
2789
  continue;
2546
2790
  }
@@ -2608,16 +2852,19 @@ function sweepByName(names) {
2608
2852
  }
2609
2853
  function runPgrep(pgrepArgs) {
2610
2854
  try {
2611
- const r = spawnSync("pgrep", pgrepArgs, { encoding: "utf-8" });
2855
+ const r = spawnSync2("pgrep", pgrepArgs, { encoding: "utf-8" });
2612
2856
  if (r.error) return { pids: [], ran: false };
2613
- return { pids: r.status === 0 ? parsePidLines(r.stdout) : [], ran: r.status === 0 || r.status === 1 };
2857
+ return {
2858
+ pids: r.status === 0 ? parsePidLines(r.stdout) : [],
2859
+ ran: r.status === 0 || r.status === 1
2860
+ };
2614
2861
  } catch {
2615
2862
  return { pids: [], ran: false };
2616
2863
  }
2617
2864
  }
2618
2865
  function run(cmd) {
2619
2866
  try {
2620
- const r = spawnSync(cmd[0], cmd.slice(1), { encoding: "utf-8" });
2867
+ const r = spawnSync2(cmd[0], cmd.slice(1), { encoding: "utf-8" });
2621
2868
  if (r.status !== 0) return [];
2622
2869
  return parsePidLines(r.stdout);
2623
2870
  } catch {
@@ -2630,7 +2877,7 @@ function runPowershell(script) {
2630
2877
  function runPowershellChecked(script) {
2631
2878
  const exe = pwshOrPowershell();
2632
2879
  try {
2633
- const r = spawnSync(exe, ["-NoProfile", "-NonInteractive", "-Command", script], {
2880
+ const r = spawnSync2(exe, ["-NoProfile", "-NonInteractive", "-Command", script], {
2634
2881
  encoding: "utf-8"
2635
2882
  });
2636
2883
  if (r.error) return { pids: [], ran: false };
@@ -2640,7 +2887,7 @@ function runPowershellChecked(script) {
2640
2887
  }
2641
2888
  }
2642
2889
  function pwshOrPowershell() {
2643
- const probe = spawnSync("pwsh", ["-NoProfile", "-Command", "$PSVersionTable.PSVersion.Major"], {
2890
+ const probe = spawnSync2("pwsh", ["-NoProfile", "-Command", "$PSVersionTable.PSVersion.Major"], {
2644
2891
  encoding: "utf-8"
2645
2892
  });
2646
2893
  if (probe.status === 0) return "pwsh";
@@ -2667,12 +2914,12 @@ function isAlive(pid) {
2667
2914
  }
2668
2915
 
2669
2916
  // src/cli/commands/test.ts
2670
- import { resolve as resolve6 } from "path";
2671
- import { sync as spawnSync3 } from "cross-spawn";
2917
+ import { resolve as resolve7 } from "path";
2918
+ import { sync as spawnSync4 } from "cross-spawn";
2672
2919
 
2673
2920
  // src/cli/lib/playwright.ts
2674
2921
  import { execSync } from "child_process";
2675
- import { sync as spawnSync2 } from "cross-spawn";
2922
+ import { sync as spawnSync3 } from "cross-spawn";
2676
2923
  function ensurePlaywright(appDir) {
2677
2924
  try {
2678
2925
  execSync("npx playwright --version", { cwd: appDir, stdio: "pipe" });
@@ -2683,7 +2930,7 @@ function ensurePlaywright(appDir) {
2683
2930
  const isLinux = process.platform === "linux";
2684
2931
  const isRoot = isLinux && typeof process.getuid === "function" && process.getuid() === 0;
2685
2932
  const args = isRoot ? ["install", "--with-deps", "chromium"] : ["install", "chromium"];
2686
- const result = spawnSync2("npx", ["playwright", ...args], {
2933
+ const result = spawnSync3("npx", ["playwright", ...args], {
2687
2934
  cwd: appDir,
2688
2935
  stdio: "inherit"
2689
2936
  });
@@ -2727,18 +2974,14 @@ var test_default = defineDeepspaceCommand({
2727
2974
  preflightNodeVersion("test run");
2728
2975
  const suite = args.suite ?? "default";
2729
2976
  const wranglerEnv = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
2730
- const start = resolve6(".");
2977
+ const start = resolve7(".");
2731
2978
  const appDir = findAppDir(start);
2732
2979
  if (!appDir) {
2733
2980
  throw new Refusal(noAppDirMessage(start, findChildApps(start)), "no_app_dir");
2734
2981
  }
2735
- const worktreePort = args.port ? null : resolveWorktreePort(appDir);
2982
+ const configuredPort = Boolean(args.port) || Boolean(process.env.DEEPSPACE_PORT);
2983
+ const worktreePort = configuredPort ? null : resolveWorktreePort(appDir);
2736
2984
  const port = worktreePort ?? resolvePort(args.port);
2737
- if (worktreePort && process.env.DEEPSPACE_PORT) {
2738
- say(
2739
- `Ignoring DEEPSPACE_PORT=${process.env.DEEPSPACE_PORT} inside a worktree \u2014 targeting per-worktree port ${port}. Pass --port to override.`
2740
- );
2741
- }
2742
2985
  ensureInstallReady(appDir);
2743
2986
  let token;
2744
2987
  let ownerId;
@@ -2846,7 +3089,7 @@ function runPlaywright(appDir, testFiles, port, wranglerEnv, sharedDevVarsCache
2846
3089
  return 1;
2847
3090
  }
2848
3091
  try {
2849
- const result = spawnSync3(
3092
+ const result = spawnSync4(
2850
3093
  "npx",
2851
3094
  ["playwright", "test", "--config", "tests/playwright.config.ts", ...testFiles],
2852
3095
  {
@@ -2861,7 +3104,7 @@ function runPlaywright(appDir, testFiles, port, wranglerEnv, sharedDevVarsCache
2861
3104
  }
2862
3105
  }
2863
3106
  function runVitest(appDir) {
2864
- const result = spawnSync3("npx", ["vitest", "run", "--passWithNoTests"], {
3107
+ const result = spawnSync4("npx", ["vitest", "run", "--passWithNoTests"], {
2865
3108
  cwd: appDir,
2866
3109
  stdio: "inherit"
2867
3110
  });
@@ -2869,8 +3112,8 @@ function runVitest(appDir) {
2869
3112
  }
2870
3113
 
2871
3114
  // src/cli/commands/screenshot.ts
2872
- import { sync as spawnSync4 } from "cross-spawn";
2873
- import { resolve as resolve7 } from "path";
3115
+ import { sync as spawnSync5 } from "cross-spawn";
3116
+ import { resolve as resolve8 } from "path";
2874
3117
  var screenshot_default = defineDeepspaceCommand({
2875
3118
  meta: {
2876
3119
  name: "screenshot",
@@ -2899,7 +3142,7 @@ var screenshot_default = defineDeepspaceCommand({
2899
3142
  }
2900
3143
  },
2901
3144
  async run({ args }) {
2902
- const appDir = resolve7(".");
3145
+ const appDir = resolve8(".");
2903
3146
  ensurePlaywright(appDir);
2904
3147
  const url = String(args.url);
2905
3148
  const output = String(args.output);
@@ -2908,7 +3151,7 @@ var screenshot_default = defineDeepspaceCommand({
2908
3151
  if (args["wait-for-timeout"]) {
2909
3152
  playwrightArgs.push("--wait-for-timeout", String(args["wait-for-timeout"]));
2910
3153
  }
2911
- const result = spawnSync4("npx", playwrightArgs, {
3154
+ const result = spawnSync5("npx", playwrightArgs, {
2912
3155
  cwd: appDir,
2913
3156
  stdio: "inherit"
2914
3157
  });
@@ -2925,27 +3168,27 @@ var screenshot_default = defineDeepspaceCommand({
2925
3168
  // src/cli/commands/test-accounts.ts
2926
3169
  import { defineCommand as defineCommand2 } from "citty";
2927
3170
  import * as p5 from "@clack/prompts";
2928
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, existsSync as existsSync12, mkdirSync as mkdirSync4 } from "fs";
2929
- import { join as join12 } from "path";
3171
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync7, existsSync as existsSync12, mkdirSync as mkdirSync5 } from "fs";
3172
+ import { join as join13 } from "path";
2930
3173
  import { homedir as homedir3 } from "os";
2931
3174
  var SESSION_COOKIE2 = "__Secure-better-auth.session_token";
2932
3175
  var AUTH_URL4 = process.env.DEEPSPACE_AUTH_URL ?? PLATFORM_URLS.auth;
2933
- var DIR2 = join12(homedir3(), ".deepspace");
2934
- var ACCOUNTS_PATH = join12(DIR2, "test-accounts.json");
3176
+ var DIR2 = join13(homedir3(), ".deepspace");
3177
+ var ACCOUNTS_PATH = join13(DIR2, "test-accounts.json");
2935
3178
  function loadAccounts() {
2936
3179
  if (!existsSync12(ACCOUNTS_PATH)) return [];
2937
3180
  try {
2938
- return JSON.parse(readFileSync12(ACCOUNTS_PATH, "utf-8"));
3181
+ return JSON.parse(readFileSync13(ACCOUNTS_PATH, "utf-8"));
2939
3182
  } catch {
2940
3183
  return [];
2941
3184
  }
2942
3185
  }
2943
3186
  function saveAccounts(accounts) {
2944
- mkdirSync4(DIR2, { recursive: true });
3187
+ mkdirSync5(DIR2, { recursive: true });
2945
3188
  writeFileSync7(ACCOUNTS_PATH, JSON.stringify(accounts, null, 2), { mode: 384 });
2946
3189
  }
2947
3190
  function sessionCookie() {
2948
- const token = readFileSync12(SESSION_PATH, "utf-8").trim();
3191
+ const token = readFileSync13(SESSION_PATH, "utf-8").trim();
2949
3192
  return `${SESSION_COOKIE2}=${encodeURIComponent(token)}`;
2950
3193
  }
2951
3194
  async function fetchRemoteAccounts() {
@@ -3261,8 +3504,8 @@ import { resolve as resolve11 } from "path";
3261
3504
  // src/cli/commands/deploy/build.ts
3262
3505
  import * as p6 from "@clack/prompts";
3263
3506
  import { execSync as execSync2 } from "child_process";
3264
- import { existsSync as existsSync13, readFileSync as readFileSync13, readdirSync as readdirSync4 } from "fs";
3265
- import { dirname as dirname4, join as join13, resolve as resolve8 } from "path";
3507
+ import { existsSync as existsSync13, readFileSync as readFileSync14, readdirSync as readdirSync4 } from "fs";
3508
+ import { dirname as dirname5, join as join14, resolve as resolve9 } from "path";
3266
3509
  var RESERVED_RUN_WORKER_FIRST = /* @__PURE__ */ new Set([
3267
3510
  "/api/*",
3268
3511
  "/ws/*",
@@ -3298,19 +3541,19 @@ async function buildDeployBundle(options) {
3298
3541
  preparedWranglerConfig?.cleanup();
3299
3542
  }
3300
3543
  spinner2.stop("Built");
3301
- const deployConfigPath = join13(appDir, ".wrangler", "deploy", "config.json");
3544
+ const deployConfigPath = join14(appDir, ".wrangler", "deploy", "config.json");
3302
3545
  if (!existsSync13(deployConfigPath)) {
3303
3546
  output.die(
3304
3547
  "Build output config not found at .wrangler/deploy/config.json",
3305
3548
  "build_output_missing"
3306
3549
  );
3307
3550
  }
3308
- const deployConfig = JSON.parse(readFileSync13(deployConfigPath, "utf-8"));
3309
- const outputWranglerPath = resolve8(dirname4(deployConfigPath), deployConfig.configPath);
3551
+ const deployConfig = JSON.parse(readFileSync14(deployConfigPath, "utf-8"));
3552
+ const outputWranglerPath = resolve9(dirname5(deployConfigPath), deployConfig.configPath);
3310
3553
  if (!existsSync13(outputWranglerPath)) {
3311
3554
  output.die(`Output wrangler.json not found at ${outputWranglerPath}`, "build_output_missing");
3312
3555
  }
3313
- const outputConfig = JSON.parse(readFileSync13(outputWranglerPath, "utf-8"));
3556
+ const outputConfig = JSON.parse(readFileSync14(outputWranglerPath, "utf-8"));
3314
3557
  if (outputConfig.name && outputConfig.name !== appName) {
3315
3558
  const envHint = envName ? ` (--env ${envName})` : "";
3316
3559
  output.die(
@@ -3318,9 +3561,9 @@ async function buildDeployBundle(options) {
3318
3561
  "build_output_mismatch"
3319
3562
  );
3320
3563
  }
3321
- const workerDir = dirname4(outputWranglerPath);
3322
- const workerBundlePath = join13(workerDir, outputConfig.main);
3323
- const clientDir = outputConfig.assets?.directory ? resolve8(workerDir, outputConfig.assets.directory) : null;
3564
+ const workerDir = dirname5(outputWranglerPath);
3565
+ const workerBundlePath = join14(workerDir, outputConfig.main);
3566
+ const clientDir = outputConfig.assets?.directory ? resolve9(workerDir, outputConfig.assets.directory) : null;
3324
3567
  if (!existsSync13(workerBundlePath)) {
3325
3568
  output.die(`Worker bundle not found at ${workerBundlePath}`, "build_output_missing");
3326
3569
  }
@@ -3330,7 +3573,7 @@ async function buildDeployBundle(options) {
3330
3573
  spinner2.start("Collecting assets...");
3331
3574
  const assets = collectAssets(clientDir);
3332
3575
  spinner2.stop(`Collected ${assets.length} assets`);
3333
- const workerJs = readFileSync13(workerBundlePath, "utf-8");
3576
+ const workerJs = readFileSync14(workerBundlePath, "utf-8");
3334
3577
  const doBindings = outputConfig.durable_objects?.bindings;
3335
3578
  const sqliteClasses = new Set(
3336
3579
  outputConfig.migrations?.flatMap((migration) => migration.new_sqlite_classes ?? []) ?? []
@@ -3383,14 +3626,14 @@ function collectAssets(dir) {
3383
3626
  const walk = (currentDir, prefix) => {
3384
3627
  for (const entry of readdirSync4(currentDir, { withFileTypes: true })) {
3385
3628
  if (entry.name === ".assetsignore") continue;
3386
- const fullPath = join13(currentDir, entry.name);
3629
+ const fullPath = join14(currentDir, entry.name);
3387
3630
  const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
3388
3631
  if (entry.isDirectory()) {
3389
3632
  walk(fullPath, relativePath);
3390
3633
  } else {
3391
3634
  assets.push({
3392
3635
  path: `/${relativePath}`,
3393
- contentBase64: readFileSync13(fullPath).toString("base64")
3636
+ contentBase64: readFileSync14(fullPath).toString("base64")
3394
3637
  });
3395
3638
  }
3396
3639
  }
@@ -3405,17 +3648,17 @@ function errorMessage(error) {
3405
3648
  // src/cli/commands/deploy/commerce.ts
3406
3649
  import * as p7 from "@clack/prompts";
3407
3650
  import { existsSync as existsSync14 } from "fs";
3408
- import { join as join14 } from "path";
3651
+ import { join as join15 } from "path";
3409
3652
  import { pathToFileURL as pathToFileURL2 } from "url";
3410
3653
  var API_URL2 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
3411
3654
  function errorMessage2(error) {
3412
3655
  return error instanceof Error ? error.message : String(error);
3413
3656
  }
3414
3657
  async function loadDeclaration(appDir, sourceFile, outputFile, exportName) {
3415
- const sourcePath = join14(appDir, "src", sourceFile);
3658
+ const sourcePath = join15(appDir, "src", sourceFile);
3416
3659
  if (!existsSync14(sourcePath)) return void 0;
3417
3660
  const esbuild = await import("esbuild");
3418
- const outputPath = join14(appDir, ".wrangler", "deploy", outputFile);
3661
+ const outputPath = join15(appDir, ".wrangler", "deploy", outputFile);
3419
3662
  await esbuild.build({
3420
3663
  entryPoints: [sourcePath],
3421
3664
  bundle: true,
@@ -3508,7 +3751,7 @@ Stripe will keep charging them until you cancel. Cancel at the end of their curr
3508
3751
  }
3509
3752
  }
3510
3753
  async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
3511
- if (!existsSync14(join14(appDir, "src", "subscriptions.ts"))) return;
3754
+ if (!existsSync14(join15(appDir, "src", "subscriptions.ts"))) return;
3512
3755
  let plans;
3513
3756
  try {
3514
3757
  plans = await loadDeclaration(
@@ -3563,7 +3806,7 @@ async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
3563
3806
  }
3564
3807
  }
3565
3808
  async function syncOneTimeProducts(appDir, appName, token) {
3566
- if (!existsSync14(join14(appDir, "src", "products.ts"))) return;
3809
+ if (!existsSync14(join15(appDir, "src", "products.ts"))) return;
3567
3810
  let products;
3568
3811
  try {
3569
3812
  products = await loadDeclaration(
@@ -3669,217 +3912,6 @@ import { randomUUID as randomUUID2 } from "crypto";
3669
3912
  // src/cli/commands/deploy/repository.ts
3670
3913
  import * as p9 from "@clack/prompts";
3671
3914
 
3672
- // src/cli/lib/git/repository.ts
3673
- import { appendFileSync, mkdirSync as mkdirSync5, readFileSync as readFileSync14 } from "fs";
3674
- import { dirname as dirname5, join as join15, resolve as resolve9 } from "path";
3675
-
3676
- // src/cli/lib/git/process.ts
3677
- import { sync as spawnSync5 } from "cross-spawn";
3678
- var MAX_GIT_BUFFER = 512 * 1024 * 1024;
3679
- var GitError = class extends Error {
3680
- constructor(message, code) {
3681
- super(message);
3682
- this.code = code;
3683
- this.name = "GitError";
3684
- }
3685
- };
3686
- function runGit(cwd, args, opts = {}) {
3687
- const result = spawnSync5("git", args, {
3688
- cwd,
3689
- input: opts.input,
3690
- maxBuffer: MAX_GIT_BUFFER,
3691
- stdio: ["pipe", "pipe", "pipe"],
3692
- env: {
3693
- ...process.env,
3694
- GIT_TERMINAL_PROMPT: "0",
3695
- LC_ALL: "C",
3696
- ...opts.env ?? {}
3697
- }
3698
- });
3699
- if (result.error) {
3700
- const code = result.error.code;
3701
- if (code === "ENOENT") {
3702
- throw new GitError(
3703
- "git is not installed or not on PATH \u2014 install git and retry.",
3704
- "git_not_installed"
3705
- );
3706
- }
3707
- throw new GitError(`git ${args[0]} failed: ${result.error.message}`);
3708
- }
3709
- const status3 = result.status ?? 1;
3710
- if (status3 !== 0 && !opts.allowFail) {
3711
- const stderr = result.stderr?.toString("utf-8").trim() ?? "";
3712
- throw new GitError(`git ${args.join(" ")} exited ${status3}${stderr ? `: ${stderr}` : ""}`);
3713
- }
3714
- return {
3715
- stdout: result.stdout ?? Buffer.alloc(0),
3716
- stderr: result.stderr ?? Buffer.alloc(0),
3717
- status: status3
3718
- };
3719
- }
3720
- function gitLine(cwd, args) {
3721
- return runGit(cwd, args).stdout.toString("utf-8").trim();
3722
- }
3723
- function splitNulFields(buffer) {
3724
- const fields = [];
3725
- let start = 0;
3726
- for (let index = 0; index < buffer.length; index++) {
3727
- if (buffer[index] === 0) {
3728
- fields.push(buffer.toString("utf-8", start, index));
3729
- start = index + 1;
3730
- }
3731
- }
3732
- if (start < buffer.length) fields.push(buffer.toString("utf-8", start));
3733
- return fields;
3734
- }
3735
- function parseGitVersion(output) {
3736
- const match = /(\d+)\.(\d+)/.exec(output);
3737
- return match ? [Number(match[1]), Number(match[2])] : null;
3738
- }
3739
- function gitMeetsFloor(version) {
3740
- if (!version) return true;
3741
- const [major, minor] = version;
3742
- return major > 2 || major === 2 && minor >= 29;
3743
- }
3744
-
3745
- // src/cli/lib/git/repository.ts
3746
- function isPlausibleBranchName(name) {
3747
- if (!name || name === "HEAD" || name === "@") return false;
3748
- if (name.startsWith("/") || name.startsWith("-") || name.startsWith(".")) return false;
3749
- if (name.endsWith("/") || name.endsWith(".") || name.endsWith(".lock")) return false;
3750
- if (name.includes("/.") || name.includes(".lock/")) return false;
3751
- return !/(\.\.|\/\/|@\{|[\x00-\x20\x7f~^:?*[\\])/.test(name);
3752
- }
3753
- function assertSyncableRepo(cwd) {
3754
- const inside = runGit(cwd, ["rev-parse", "--is-inside-work-tree"], { allowFail: true });
3755
- if (inside.status !== 0 || inside.stdout.toString().trim() !== "true") {
3756
- throw new InputError(
3757
- `${cwd} is not a git repository. DeepSpace apps are scaffolded as git repos \u2014 run \`git init\` first.`,
3758
- "not_git_repo"
3759
- );
3760
- }
3761
- const versionOutput = runGit(cwd, ["--version"], { allowFail: true }).stdout.toString("utf-8").trim();
3762
- if (!gitMeetsFloor(parseGitVersion(versionOutput))) {
3763
- throw gitTooOld(versionOutput);
3764
- }
3765
- const formatResult = runGit(cwd, ["rev-parse", "--show-object-format"], { allowFail: true });
3766
- const format = formatResult.stdout.toString("utf-8").trim();
3767
- if (formatResult.status !== 0 || format.startsWith("--")) throw gitTooOld(versionOutput);
3768
- if (format !== "sha1") {
3769
- throw new InputError(
3770
- `This repo uses object format "${format}" \u2014 DeepSpace version control supports sha1 repos only (git's default) for now.`,
3771
- "unsupported_object_format"
3772
- );
3773
- }
3774
- if (gitLine(cwd, ["rev-parse", "--is-shallow-repository"]) === "true") {
3775
- throw new InputError(
3776
- "This is a shallow clone. Run `git fetch --unshallow` before pushing to DeepSpace.",
3777
- "shallow_repo"
3778
- );
3779
- }
3780
- }
3781
- function gitTooOld(versionOutput) {
3782
- return new InputError(
3783
- `DeepSpace version control needs git \u2265 2.29 \u2014 found ${versionOutput || "an unknown git version"}. Upgrade git and retry.`,
3784
- "git_too_old"
3785
- );
3786
- }
3787
- function repoToplevel(cwd) {
3788
- return gitLine(cwd, ["rev-parse", "--show-toplevel"]);
3789
- }
3790
- function currentBranch(cwd) {
3791
- const result = runGit(cwd, ["symbolic-ref", "--short", "-q", "HEAD"], { allowFail: true });
3792
- const name = result.stdout.toString("utf-8").trim();
3793
- return result.status === 0 && name ? name : null;
3794
- }
3795
- function resolveCommit(cwd, ref) {
3796
- const result = runGit(cwd, ["rev-parse", "-q", "--verify", `${ref}^{commit}`], {
3797
- allowFail: true
3798
- });
3799
- const oid = result.stdout.toString("utf-8").trim();
3800
- return result.status === 0 && /^[0-9a-f]{40}$/.test(oid) ? oid : null;
3801
- }
3802
- function isWorkTreeClean(cwd) {
3803
- return gitLine(cwd, ["status", "--porcelain"]) === "";
3804
- }
3805
- function updateRef(cwd, ref, oid) {
3806
- runGit(cwd, ["update-ref", ref, oid]);
3807
- }
3808
- function isAncestor(cwd, ancestor, descendant) {
3809
- return runGit(cwd, ["merge-base", "--is-ancestor", ancestor, descendant], { allowFail: true }).status === 0;
3810
- }
3811
- function mergeBase(cwd, left, right) {
3812
- const result = runGit(cwd, ["merge-base", left, right], { allowFail: true });
3813
- if (result.status !== 0) return null;
3814
- const oid = result.stdout.toString("utf-8").trim();
3815
- return /^[0-9a-f]{40}$/.test(oid) ? oid : null;
3816
- }
3817
- function fastForwardCurrentBranch(cwd, oid) {
3818
- runGit(cwd, ["merge", "--ff-only", oid]);
3819
- }
3820
- function checkoutHead(cwd) {
3821
- runGit(cwd, ["reset", "--hard", "--quiet"]);
3822
- }
3823
- function switchToNewBranch(cwd, name, oid) {
3824
- runGit(cwd, ["switch", "--quiet", "-c", name, oid]);
3825
- }
3826
- function diffNameOnly(cwd, base, head) {
3827
- return splitNulFields(
3828
- runGit(cwd, [
3829
- "-c",
3830
- "core.quotePath=false",
3831
- "diff",
3832
- "--name-only",
3833
- "--no-renames",
3834
- "-z",
3835
- base,
3836
- head
3837
- ]).stdout
3838
- );
3839
- }
3840
- function revListCount(cwd, exclude, include) {
3841
- const count = Number(gitLine(cwd, ["rev-list", "--count", `${exclude}..${include}`]));
3842
- return Number.isFinite(count) ? count : 0;
3843
- }
3844
- function addWorktreeNewBranch(cwd, dir, branch, oid) {
3845
- runGit(cwd, ["worktree", "add", "--quiet", "-b", branch, dir, oid]);
3846
- }
3847
- function listWorktrees(cwd) {
3848
- const result = runGit(cwd, ["worktree", "list", "--porcelain"], { allowFail: true });
3849
- if (result.status !== 0) return [];
3850
- const worktrees = [];
3851
- let current = null;
3852
- for (const line of result.stdout.toString("utf-8").split("\n")) {
3853
- if (line.startsWith("worktree ")) {
3854
- current = { path: line.slice("worktree ".length), branch: null };
3855
- worktrees.push(current);
3856
- } else if (line.startsWith("branch ") && current) {
3857
- const ref = line.slice("branch ".length);
3858
- current.branch = ref.startsWith("refs/heads/") ? ref.slice("refs/heads/".length) : ref;
3859
- }
3860
- }
3861
- return worktrees;
3862
- }
3863
- function ensureLocalExclude(cwd, pattern) {
3864
- const commonDir = resolve9(cwd, gitLine(cwd, ["rev-parse", "--git-common-dir"]));
3865
- const excludePath = join15(commonDir, "info", "exclude");
3866
- let current = "";
3867
- try {
3868
- current = readFileSync14(excludePath, "utf-8");
3869
- } catch {
3870
- }
3871
- if (current.split("\n").some((line) => line.trim() === pattern)) return;
3872
- try {
3873
- mkdirSync5(dirname5(excludePath), { recursive: true });
3874
- appendFileSync(
3875
- excludePath,
3876
- `${current.endsWith("\n") || current === "" ? "" : "\n"}${pattern}
3877
- `
3878
- );
3879
- } catch {
3880
- }
3881
- }
3882
-
3883
3915
  // src/cli/lib/git/safety.ts
3884
3916
  var SECRET_FILE_RE = /(^|\/)(\.dev\.vars(\.[^/]*)?|\.env(\.[^/]*)?|\.npmrc|\.envrc|\.mcp\.json)$/i;
3885
3917
  var SECRET_TEMPLATE_RE = /\.(example|sample|template)$/i;
@@ -4110,7 +4142,15 @@ function installCredentialHelper(cwd, url) {
4110
4142
  const key = `credential.${origin.protocol}//${origin.host}.helper`;
4111
4143
  const helper = credentialHelperCommand();
4112
4144
  const wanted = ["", helper];
4113
- const scope = entryIsTransient(cwd) ? "--local" : "--global";
4145
+ const transient = entryIsTransient(cwd);
4146
+ const scope = transient ? "--worktree" : "--global";
4147
+ if (transient) {
4148
+ const enabled = runGit(cwd, ["config", "--local", "extensions.worktreeConfig", "true"], {
4149
+ allowFail: true
4150
+ });
4151
+ if (enabled.status !== 0) return;
4152
+ runGit(cwd, ["config", "--local", "--unset-all", key], { allowFail: true });
4153
+ }
4114
4154
  const current = runGit(cwd, ["config", scope, "--get-all", key], { allowFail: true });
4115
4155
  if (current.status === 0) {
4116
4156
  const existing = current.stdout.toString("utf-8").split("\n").filter((_, index, lines) => index < lines.length - 1);
@@ -4306,8 +4346,9 @@ async function syncDeployRepository(options) {
4306
4346
  output.die(refusal.error, refusal.code);
4307
4347
  }
4308
4348
  if (!branch) {
4309
- commitOid = resolveCommit(appDir, "HEAD");
4310
- return { commitOid, recoverable, deployKey };
4349
+ const refusal = detachedHeadRefusal();
4350
+ output.die(refusal.error, refusal.code);
4351
+ throw new Error(refusal.error);
4311
4352
  }
4312
4353
  const tip = resolveCommit(appDir, `refs/heads/${branch}`);
4313
4354
  if (!tip) return { commitOid, recoverable, deployKey };
@@ -4453,6 +4494,12 @@ function dirtyWorktreeRefusal(branch) {
4453
4494
  error: "The worktree has uncommitted changes, and a deploy records the commit it ships. " + (workspaceId ? `Commit them to this workspace branch (${branch}) \u2014 WIP commits are fine \u2014 then redeploy. ` : 'Commit them (to a workspace branch if this is work in progress: `deepspace workspace new -t "\u2026"`), then redeploy. ') + "Or pass --no-push to deploy without version-control sync \u2014 the release then records no source lineage."
4454
4495
  };
4455
4496
  }
4497
+ function detachedHeadRefusal() {
4498
+ return {
4499
+ code: "detached_head",
4500
+ error: "HEAD is detached, so DeepSpace cannot publish this commit to a recoverable branch. Create or switch to a branch before deploying, or pass `--no-push` to explicitly deploy without source lineage."
4501
+ };
4502
+ }
4456
4503
 
4457
4504
  // src/cli/commands/deploy/request.ts
4458
4505
  var CLOUDFLARE_DEPLOY_ERROR_HINT = "Deployment reached the DeepSpace deploy worker, but Cloudflare's deployment control-plane API failed. This is often caused by a Cloudflare Dashboard/API incident or Workers for Platforms entitlement outage. Already deployed apps should keep serving; wait for Cloudflare to recover and retry.";
@@ -5694,7 +5741,7 @@ var usage_default = defineDeepspaceCommand({
5694
5741
  });
5695
5742
 
5696
5743
  // src/cli/commands/add.ts
5697
- import { existsSync as existsSync18, readFileSync as readFileSync17, realpathSync } from "fs";
5744
+ import { existsSync as existsSync18, readFileSync as readFileSync17, realpathSync as realpathSync2 } from "fs";
5698
5745
  import { dirname as dirname7, join as join17, resolve as resolve13 } from "path";
5699
5746
  import { pathToFileURL as pathToFileURL3 } from "url";
5700
5747
 
@@ -6435,7 +6482,7 @@ async function targetInstaller(appDir) {
6435
6482
  "feature_installer_missing"
6436
6483
  );
6437
6484
  }
6438
- if (realpathSync(installedRoot) === realpathSync(featurePackageRoot())) {
6485
+ if (realpathSync2(installedRoot) === realpathSync2(featurePackageRoot())) {
6439
6486
  return { installFeature };
6440
6487
  }
6441
6488
  const entry = join17(installedRoot, "dist", "feature-installer.js");
@@ -8588,7 +8635,7 @@ var status_default = defineCommand11({
8588
8635
  const depsInstallCommand = installCommand(appDir);
8589
8636
  lines.push([
8590
8637
  "Deps",
8591
- deps === "ready" ? "installed" : deps === "installing" ? "still installing (scaffold background install)" : `${deps} \u2014 run ${depsInstallCommand}`
8638
+ deps === "ready" ? "installed" : deps === "installing" ? "dependency installation is still running" : `${deps} \u2014 run ${depsInstallCommand}`
8592
8639
  ]);
8593
8640
  json.deps = deps;
8594
8641
  let branch = null;
@@ -8918,7 +8965,7 @@ var push_default = defineDeepspaceCommand({
8918
8965
 
8919
8966
  // src/cli/commands/pull.ts
8920
8967
  import * as p20 from "@clack/prompts";
8921
- import { realpathSync as realpathSync2 } from "fs";
8968
+ import { realpathSync as realpathSync3 } from "fs";
8922
8969
  function targetedVcAction(command, cwd, appId, branch) {
8923
8970
  return {
8924
8971
  cwd,
@@ -8942,7 +8989,7 @@ function workspaceBranchPullRefusal(branch, trunkBranch) {
8942
8989
  function worktreeHoldingBranch(worktrees, branch, selfPath) {
8943
8990
  const real = (pth) => {
8944
8991
  try {
8945
- return realpathSync2(pth);
8992
+ return realpathSync3(pth);
8946
8993
  } catch {
8947
8994
  return pth;
8948
8995
  }
@@ -9506,7 +9553,7 @@ import { defineCommand as defineCommand12 } from "citty";
9506
9553
 
9507
9554
  // src/cli/commands/workspace/new.ts
9508
9555
  import * as p26 from "@clack/prompts";
9509
- import { isAbsolute as isAbsolute6, join as join22, resolve as resolve16 } from "path";
9556
+ import { isAbsolute as isAbsolute6, resolve as resolve16 } from "path";
9510
9557
 
9511
9558
  // src/cli/commands/workspace/analysis.ts
9512
9559
  import * as p24 from "@clack/prompts";
@@ -9631,44 +9678,75 @@ function printOverlaps(overlaps, json) {
9631
9678
 
9632
9679
  // src/cli/commands/workspace/local.ts
9633
9680
  import * as p25 from "@clack/prompts";
9634
- import { existsSync as existsSync23, realpathSync as realpathSync3, statSync, symlinkSync } from "fs";
9635
- import { isAbsolute as isAbsolute5, join as join21, relative as relative2, sep as sep2 } from "path";
9681
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync23, realpathSync as realpathSync4, renameSync as renameSync2, statSync, writeFileSync as writeFileSync9 } from "fs";
9682
+ import { dirname as dirname9, isAbsolute as isAbsolute5, join as join21, relative as relative2, sep as sep2 } from "path";
9683
+ var WORKSPACE_MARKER = "deepspace-workspace.json";
9636
9684
  function excludeWorktreeDir(appDir, dir) {
9637
9685
  try {
9638
9686
  ensureLocalExclude(appDir, ".deepspace/");
9639
- const realDir = realpathSync3(dir);
9640
- const rel = relative2(realpathSync3(repoToplevel(appDir)), realDir);
9687
+ const realDir = realpathSync4(dir);
9688
+ const rel = relative2(realpathSync4(repoToplevel(appDir)), realDir);
9641
9689
  if (!rel || rel === ".." || rel.startsWith(`..${sep2}`) || isAbsolute5(rel)) return;
9642
9690
  const pattern = rel.split(sep2).join("/");
9643
9691
  if (pattern === ".deepspace" || pattern.startsWith(".deepspace/")) return;
9644
- const relApp = relative2(realpathSync3(appDir), realDir).split(sep2).join("/");
9692
+ const relApp = relative2(realpathSync4(appDir), realDir).split(sep2).join("/");
9645
9693
  if (relApp === ".deepspace" || relApp.startsWith(".deepspace/")) return;
9646
9694
  ensureLocalExclude(appDir, `/${pattern}/`);
9647
9695
  } catch {
9648
9696
  }
9649
9697
  }
9650
- function provisionWorktreeDeps(appDir, dir) {
9651
- const linked = [];
9698
+ function worktreeMarkerPath(worktreeRoot) {
9652
9699
  try {
9653
- const src = join21(appDir, "node_modules");
9654
- const dest = join21(dir, "node_modules");
9655
- if (existsSync23(src) && !existsSync23(dest)) {
9656
- symlinkSync(src, dest, process.platform === "win32" ? "junction" : "dir");
9657
- linked.push("node_modules");
9658
- }
9700
+ const result = runGit(worktreeRoot, ["rev-parse", "--absolute-git-dir"], { allowFail: true });
9701
+ if (result.status !== 0) return null;
9702
+ const gitDir = result.stdout.toString("utf-8").trim();
9703
+ return isAbsolute5(gitDir) ? join21(gitDir, WORKSPACE_MARKER) : null;
9704
+ } catch {
9705
+ return null;
9706
+ }
9707
+ }
9708
+ function markManagedWorkspace(worktreeRoot, workspaceId) {
9709
+ const markerPath = worktreeMarkerPath(worktreeRoot);
9710
+ if (!markerPath) return false;
9711
+ const marker = { version: 1, workspaceId, mode: "managed-linked" };
9712
+ const tempPath = `${markerPath}.${process.pid}.tmp`;
9713
+ try {
9714
+ mkdirSync7(dirname9(markerPath), { recursive: true });
9715
+ writeFileSync9(tempPath, JSON.stringify(marker) + "\n");
9716
+ renameSync2(tempPath, markerPath);
9717
+ return true;
9718
+ } catch {
9719
+ return false;
9720
+ }
9721
+ }
9722
+ function isManagedWorkspaceWorktree(worktreeRoot, workspaceId) {
9723
+ const markerPath = worktreeMarkerPath(worktreeRoot);
9724
+ if (!markerPath) return false;
9725
+ try {
9726
+ const marker = JSON.parse(readFileSync23(markerPath, "utf-8"));
9727
+ return marker.version === 1 && marker.workspaceId === workspaceId && marker.mode === "managed-linked";
9659
9728
  } catch {
9729
+ return false;
9660
9730
  }
9661
- return linked;
9662
9731
  }
9663
- function materializeWorkspaceWorktree(appDir, dir, branch, startPoint) {
9664
- addWorktreeNewBranch(appDir, dir, branch, startPoint);
9665
- excludeWorktreeDir(appDir, dir);
9666
- return provisionWorktreeDeps(appDir, dir);
9732
+ function defaultWorkspaceRoot(appDir, workspaceId) {
9733
+ const primary = listWorktrees(appDir)[0];
9734
+ if (!primary) throw new Error("Git did not report a primary worktree");
9735
+ return join21(primary.path, ".deepspace", "ws", workspaceId.slice(3).toLowerCase());
9736
+ }
9737
+ function appDirInWorktree(appDir, worktreeRoot) {
9738
+ const appRelative = relative2(realpathSync4(repoToplevel(appDir)), realpathSync4(appDir));
9739
+ return appRelative ? join21(worktreeRoot, appRelative) : worktreeRoot;
9740
+ }
9741
+ function materializeWorkspaceWorktree(appDir, worktreeRoot, branch, startPoint, workspaceId) {
9742
+ addWorktreeNewBranch(appDir, worktreeRoot, branch, startPoint);
9743
+ excludeWorktreeDir(appDir, worktreeRoot);
9744
+ markManagedWorkspace(worktreeRoot, workspaceId);
9667
9745
  }
9668
9746
  function inOwnLinkedWorktree(appDir, id) {
9669
9747
  if (workspaceIdFromBranch(currentBranch(appDir)) !== id) return false;
9670
9748
  try {
9671
- return statSync(join21(appDir, ".git")).isFile();
9749
+ return statSync(join21(repoToplevel(appDir), ".git")).isFile();
9672
9750
  } catch {
9673
9751
  return false;
9674
9752
  }
@@ -9676,6 +9754,7 @@ function inOwnLinkedWorktree(appDir, id) {
9676
9754
  function cleanupJson(outcome) {
9677
9755
  return {
9678
9756
  worktreeRemoved: outcome.worktreeRemoved,
9757
+ worktreeRetained: outcome.worktreeRetained,
9679
9758
  branchDeleted: outcome.branchDeleted,
9680
9759
  ...outcome.mainDir ? { mainDir: outcome.mainDir } : {},
9681
9760
  ...outcome.error ? { error: outcome.error } : {}
@@ -9703,6 +9782,7 @@ function cleanupWorkspaceLocal(appDir, id, trunkBranch) {
9703
9782
  const branch = workspaceBranchName(id);
9704
9783
  const out = {
9705
9784
  worktreeRemoved: null,
9785
+ worktreeRetained: null,
9706
9786
  branchDeleted: null,
9707
9787
  worktreeDir: null,
9708
9788
  branch
@@ -9713,9 +9793,13 @@ function cleanupWorkspaceLocal(appDir, id, trunkBranch) {
9713
9793
  const linked = worktrees.find((worktree, index) => index > 0 && worktree.branch === branch);
9714
9794
  if (linked) {
9715
9795
  out.worktreeDir = linked.path;
9796
+ if (!isManagedWorkspaceWorktree(linked.path, id)) {
9797
+ out.worktreeRetained = linked.path;
9798
+ return out;
9799
+ }
9716
9800
  let inside = false;
9717
9801
  try {
9718
- inside = realpathSync3(appDir) === realpathSync3(linked.path);
9802
+ inside = realpathSync4(repoToplevel(appDir)) === realpathSync4(linked.path);
9719
9803
  } catch {
9720
9804
  inside = false;
9721
9805
  }
@@ -9769,6 +9853,9 @@ function reportCleanupHuman(outcome) {
9769
9853
  outcome.branchDeleted ? `branch ${outcome.branch}` : null
9770
9854
  ].filter(Boolean);
9771
9855
  if (parts.length) p25.log.success(`Removed ${parts.join(" + ")}.`);
9856
+ if (outcome.worktreeRetained) {
9857
+ p25.log.info(`Retained externally managed worktree: ${outcome.worktreeRetained}`);
9858
+ }
9772
9859
  if (outcome.worktreeRemoved && outcome.mainDir) {
9773
9860
  p25.log.info(`This directory was removed \u2014 run: cd ${shQuote(outcome.mainDir)}`);
9774
9861
  }
@@ -9952,11 +10039,10 @@ var newWorkspaceCommand = defineDeepspaceCommand({
9952
10039
  idempotencyKey: mintIdempotencyKey()
9953
10040
  });
9954
10041
  const dirInput = typeof args.dir === "string" ? args.dir.trim() : "";
9955
- const dirArg = dirInput || join22(".deepspace", "ws", id.slice(3).toLowerCase());
9956
- const dir = isAbsolute6(dirArg) ? dirArg : resolve16(appDir, dirArg);
9957
- let provisioned = [];
10042
+ const worktreeRoot = dirInput ? isAbsolute6(dirInput) ? dirInput : resolve16(appDir, dirInput) : defaultWorkspaceRoot(appDir, id);
10043
+ const dir = appDirInWorktree(appDir, worktreeRoot);
9958
10044
  try {
9959
- provisioned = materializeWorkspaceWorktree(appDir, dir, branch, baseOid);
10045
+ materializeWorkspaceWorktree(appDir, worktreeRoot, branch, baseOid, id);
9960
10046
  } catch (error) {
9961
10047
  spinner2?.stop("Worktree failed.");
9962
10048
  const reason = error instanceof Error ? error.message : String(error);
@@ -9968,13 +10054,12 @@ var newWorkspaceCommand = defineDeepspaceCommand({
9968
10054
  }
9969
10055
  spinner2?.stop(`Workspace ${id} created.`);
9970
10056
  if (!args.json) {
9971
- if (provisioned.length > 0)
9972
- p26.log.info(`Linked from the main checkout: ${provisioned.join(", ")}`);
9973
10057
  p26.log.info(`Work in: cd ${dir}`);
9974
10058
  p26.log.info(
9975
10059
  `Then: commit as usual, \`deepspace workspace sync\` to publish, \`deepspace workspace land\` when done.`
9976
10060
  );
9977
10061
  }
10062
+ const action = { cwd: dir, argv: [detectPackageManager(dir), "install"] };
9978
10063
  return {
9979
10064
  data: {
9980
10065
  workspaceId: id,
@@ -9983,16 +10068,17 @@ var newWorkspaceCommand = defineDeepspaceCommand({
9983
10068
  baseOid,
9984
10069
  branch,
9985
10070
  dir,
9986
- provisioned
9987
- }
10071
+ worktreeRoot
10072
+ },
10073
+ action
9988
10074
  };
9989
10075
  }
9990
10076
  });
9991
10077
 
9992
10078
  // src/cli/commands/workspace/attach.ts
9993
10079
  import * as p27 from "@clack/prompts";
9994
- import { existsSync as existsSync24, readdirSync as readdirSync7, rmSync as rmSync6 } from "fs";
9995
- import { isAbsolute as isAbsolute7, join as join23, resolve as resolve17 } from "path";
10080
+ import { existsSync as existsSync23, readdirSync as readdirSync7, rmSync as rmSync6 } from "fs";
10081
+ import { isAbsolute as isAbsolute7, resolve as resolve17 } from "path";
9996
10082
  function finishedWorkspaceMessage(id, view) {
9997
10083
  const workspace = view.workspace;
9998
10084
  if (workspace.status === "landed") {
@@ -10001,7 +10087,7 @@ function finishedWorkspaceMessage(id, view) {
10001
10087
  return `Workspace ${id} is ${workspace.status} \u2014 its ref is retained briefly (fetch ${workspace.ref} to inspect), but attach only works on active workspaces.`;
10002
10088
  }
10003
10089
  function cleanFailedFreshAttachDir(dir) {
10004
- if (!existsSync24(dir)) return [];
10090
+ if (!existsSync23(dir)) return [];
10005
10091
  let entries;
10006
10092
  try {
10007
10093
  entries = readdirSync7(dir);
@@ -10076,32 +10162,32 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
10076
10162
  );
10077
10163
  }
10078
10164
  const requestedDir = typeof args.dir === "string" ? args.dir.trim() : "";
10079
- const dirArg = requestedDir || join23(".deepspace", "ws", id.slice(3).toLowerCase());
10080
- const dir2 = isAbsolute7(dirArg) ? dirArg : resolve17(appDir, dirArg);
10165
+ const worktreeRoot = requestedDir ? isAbsolute7(requestedDir) ? requestedDir : resolve17(appDir, requestedDir) : defaultWorkspaceRoot(appDir, id);
10166
+ const dir2 = appDirInWorktree(appDir, worktreeRoot);
10081
10167
  if (resolveCommit(appDir, `refs/heads/${branch}`)) {
10082
10168
  throw new Refusal(
10083
10169
  `Branch ${branch} already exists locally (this workspace has a worktree here). Find it with \`git worktree list\` and continue there.`,
10084
10170
  "branch_exists"
10085
10171
  );
10086
10172
  }
10087
- const provisioned = materializeWorkspaceWorktree(appDir, dir2, branch, tip2);
10173
+ materializeWorkspaceWorktree(appDir, worktreeRoot, branch, tip2, id);
10088
10174
  spinner2?.stop(`Attached ${id}.`);
10089
10175
  if (!args.json) {
10090
- if (provisioned.length > 0)
10091
- p27.log.info(`Linked from the main checkout: ${provisioned.join(", ")}`);
10092
10176
  p27.log.info(`Task: ${view2.workspace.task}`);
10093
10177
  p27.log.info(`Work in: cd ${dir2}`);
10094
10178
  }
10179
+ const action2 = { cwd: dir2, argv: [detectPackageManager(dir2), "install"] };
10095
10180
  return {
10096
10181
  data: {
10097
10182
  workspaceId: id,
10098
10183
  mode: "worktree",
10099
10184
  dir: dir2,
10185
+ worktreeRoot,
10100
10186
  branch,
10101
10187
  tipOid: tip2,
10102
- task: view2.workspace.task,
10103
- provisioned
10104
- }
10188
+ task: view2.workspace.task
10189
+ },
10190
+ action: action2
10105
10191
  };
10106
10192
  }
10107
10193
  const token = await ensureToken();
@@ -10119,7 +10205,7 @@ var attachWorkspaceCommand = defineDeepspaceCommand({
10119
10205
  process.cwd(),
10120
10206
  (typeof args.dir === "string" ? args.dir.trim() : "") || `ws-${id.slice(3).toLowerCase()}`
10121
10207
  );
10122
- if (existsSync24(dir)) {
10208
+ if (existsSync23(dir)) {
10123
10209
  spinner2?.stop("Target exists.");
10124
10210
  throw new Refusal(`${dir} already exists \u2014 pass a different directory.`, "dir_exists");
10125
10211
  }
@@ -10436,13 +10522,13 @@ import * as p31 from "@clack/prompts";
10436
10522
 
10437
10523
  // src/cli/lib/validation.ts
10438
10524
  import { sync as spawnSync7 } from "cross-spawn";
10439
- import { readFileSync as readFileSync23 } from "fs";
10440
- import { join as join24 } from "path";
10525
+ import { readFileSync as readFileSync24 } from "fs";
10526
+ import { join as join22 } from "path";
10441
10527
  var MAX_SUMMARY_CHARS = 4e3;
10442
10528
  var MAX_OUTPUT_BUFFER = 64 * 1024 * 1024;
10443
10529
  function configuredCommand(appDir) {
10444
10530
  try {
10445
- const pkg2 = JSON.parse(readFileSync23(join24(appDir, "package.json"), "utf-8"));
10531
+ const pkg2 = JSON.parse(readFileSync24(join22(appDir, "package.json"), "utf-8"));
10446
10532
  if (pkg2.scripts?.validate) return `${detectPackageManager(appDir)} run --silent validate`;
10447
10533
  } catch {
10448
10534
  }
@@ -10706,7 +10792,7 @@ var landWorkspaceCommand = defineDeepspaceCommand({
10706
10792
  spinner2?.stop(`Landed ${id} into ${intoBranch} at ${landedOid.slice(0, 10)}.`);
10707
10793
  const cleanup = keepWorktree ? null : cleanupWorkspaceLocal(appDir, id, intoBranch);
10708
10794
  const inOwnWorktree = !cleanup && inOwnLinkedWorktree(appDir, id);
10709
- const retainedWorktree = keepWorktree || inOwnWorktree ? appDir : null;
10795
+ const retainedWorktree = cleanup?.worktreeRetained ?? (keepWorktree || inOwnWorktree ? appDir : null);
10710
10796
  const data = {
10711
10797
  workspaceId: id,
10712
10798
  into: intoBranch,
@@ -10776,7 +10862,9 @@ var dropWorkspaceCommand = defineDeepspaceCommand({
10776
10862
  }
10777
10863
  const { appDir, api: api4 } = await resolveTarget2(appArg);
10778
10864
  const id = inferWorkspaceId(appDir, explicitId);
10779
- if (!keepWorktree && !isWorkTreeClean(appDir)) {
10865
+ const ownLinkedWorktree = inOwnLinkedWorktree(appDir, id);
10866
+ const cleanupOwnsCheckout = !ownLinkedWorktree || isManagedWorkspaceWorktree(appDir, id);
10867
+ if (!keepWorktree && cleanupOwnsCheckout && !isWorkTreeClean(appDir)) {
10780
10868
  const dropArgv = ["deepspace", "workspace", "drop", ...explicitId ? [explicitId] : []];
10781
10869
  const dropNext = humanCommand(dropArgv);
10782
10870
  throw new Refusal(
@@ -10788,8 +10876,8 @@ var dropWorkspaceCommand = defineDeepspaceCommand({
10788
10876
  const { view } = await api4.dropWorkspace(id);
10789
10877
  spinner2?.message(`Cleaning up ${id} locally\u2026`);
10790
10878
  const cleanup = keepWorktree ? null : cleanupWorkspaceLocal(appDir, id, null);
10791
- const inOwnWorktree = !cleanup && inOwnLinkedWorktree(appDir, id);
10792
- const retainedWorktree = keepWorktree || inOwnWorktree ? appDir : null;
10879
+ const inOwnWorktree = !cleanup && ownLinkedWorktree;
10880
+ const retainedWorktree = cleanup?.worktreeRetained ?? (keepWorktree || inOwnWorktree ? appDir : null);
10793
10881
  const data = {
10794
10882
  workspaceId: id,
10795
10883
  status: view.workspace.status,
@@ -11077,9 +11165,9 @@ var git_credential_default = defineCommand13({
11077
11165
  });
11078
11166
 
11079
11167
  // src/cli/cli.ts
11080
- var __dirname = dirname9(fileURLToPath3(import.meta.url));
11081
- var pkg = JSON.parse(readFileSync24(join25(__dirname, "..", "package.json"), "utf-8"));
11082
- if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync25("/etc/ssl/certs/ca-certificates.crt")) {
11168
+ var __dirname = dirname10(fileURLToPath3(import.meta.url));
11169
+ var pkg = JSON.parse(readFileSync25(join23(__dirname, "..", "package.json"), "utf-8"));
11170
+ if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync24("/etc/ssl/certs/ca-certificates.crt")) {
11083
11171
  process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
11084
11172
  }
11085
11173
  var create2 = defineCommand14({