deepspace 0.25.0 → 0.26.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +7 -6
  3. package/dist/{chunk-2GUGZLAX.js → chunk-YBXIUDZJ.js} +2 -2
  4. package/dist/{chunk-2GUGZLAX.js.map → chunk-YBXIUDZJ.js.map} +1 -1
  5. package/dist/cli.js +1036 -966
  6. package/dist/cli.js.map +1 -1
  7. package/dist/documentation-client-core.js +1 -1
  8. package/dist/documentation-default-renderer.cjs +1 -1
  9. package/dist/documentation-react.js +1 -1
  10. package/dist/documentation-runtime.js +1 -1
  11. package/dist/documentation-server-core.js +1 -1
  12. package/dist/documentation.js +1 -1
  13. package/dist/documentation.js.map +1 -1
  14. package/dist/index.d.ts +26 -434
  15. package/dist/index.js +99 -864
  16. package/dist/index.js.map +1 -1
  17. package/dist/schema.d.ts +4 -41
  18. package/dist/schema.js +2 -242
  19. package/dist/schema.js.map +1 -1
  20. package/dist/server.d.ts +6 -162
  21. package/dist/server.js +13 -281
  22. package/dist/server.js.map +1 -1
  23. package/dist/worker.d.ts +4 -160
  24. package/dist/worker.js +15 -281
  25. package/dist/worker.js.map +1 -1
  26. package/features/messaging/feature.json +3 -43
  27. package/features/messaging/src/ChatPage.tsx +12 -16
  28. package/features/messaging/src/components/chat/ChatHeader.tsx +5 -13
  29. package/features/messaging/src/components/chat/MessageItem.tsx +7 -9
  30. package/features/messaging/src/components/chat/MessageList.tsx +57 -40
  31. package/features/messaging/src/components/chat/ThreadPanel.tsx +25 -11
  32. package/features/messaging/src/components/chat/UserProfilePopover.tsx +10 -29
  33. package/features/messaging/src/hooks/useChatChannel.ts +18 -23
  34. package/features/messaging/src/messaging-schema.ts +0 -2
  35. package/features/search-bar/feature.json +9 -20
  36. package/features/search-bar/src/InlineSearch.tsx +101 -0
  37. package/features/search-bar/src/__tests__/search-model.test.tsx +38 -0
  38. package/features/search-bar/src/search-model.ts +2 -149
  39. package/package.json +1 -1
  40. package/features/messaging/src/ChatMultiPage.tsx +0 -298
  41. package/features/messaging/src/components/chat-multi/AddMemberModal.tsx +0 -188
  42. package/features/messaging/src/components/chat-multi/BrowseGroupsModal.tsx +0 -163
  43. package/features/messaging/src/components/chat-multi/ChannelListItem.tsx +0 -147
  44. package/features/messaging/src/components/chat-multi/ChannelSettingsPanel.tsx +0 -344
  45. package/features/messaging/src/components/chat-multi/ChannelSidebar.tsx +0 -460
  46. package/features/messaging/src/components/chat-multi/CreateGroupModal.tsx +0 -190
  47. package/features/messaging/src/components/chat-multi/InvitationList.tsx +0 -149
  48. package/features/messaging/src/components/chat-multi/NewMessageModal.tsx +0 -159
  49. package/features/messaging/src/hooks/useMultiChannel.ts +0 -142
  50. package/features/search-bar/src/SearchOverlay.tsx +0 -566
  51. package/features/search-bar/src/__tests__/search-model.test.ts +0 -40
  52. package/features/sidebar/feature.json +0 -19
  53. package/features/sidebar/src/AppSidebar.tsx +0 -558
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  runCommand,
8
8
  renderUsage
9
9
  } from "citty";
10
- import { sync as spawnSync8 } from "cross-spawn";
10
+ import { sync as spawnSync9 } from "cross-spawn";
11
11
  import { readFileSync as readFileSync27, existsSync as existsSync30 } from "fs";
12
12
  import { dirname as dirname13, join as join29 } from "path";
13
13
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -46,8 +46,12 @@ function displayLines(value) {
46
46
 
47
47
  // src/cli/lib/spinner.ts
48
48
  var activeSpinner = null;
49
+ var plainProgress = false;
50
+ function setPlainProgress(enabled) {
51
+ plainProgress = enabled;
52
+ }
49
53
  function createSpinner() {
50
- const impl = process.stdout.isTTY ? p.spinner() : {
54
+ const impl = !plainProgress && process.stdout.isTTY ? p.spinner() : {
51
55
  start: (msg) => {
52
56
  if (msg) console.log(msg);
53
57
  },
@@ -366,7 +370,9 @@ function errorCode(err) {
366
370
  return void 0;
367
371
  }
368
372
  var API_ERROR_HINTS = {
369
- not_app_owner: "Only the app owner can do this.",
373
+ // Starts where the server sentence ("Only the app owner can do this.")
374
+ // stops, so the two read as one message instead of a stutter.
375
+ not_app_owner: "Collaborators can deploy and manage secrets, but ownership verbs (collaborators, transfer, undeploy) stay with the owner. Ask them to run it, have them transfer the app (`deepspace app transfer offer`), or fork your checkout as your own app (`deepspace app init --new-id`).",
370
376
  app_not_found: "App not found on the service this command asked. Check the app id \u2014 the DEEPSPACE_APP_ID value in wrangler.toml, usually `app_\u2026` (a legacy app's id is its name); list your apps with `deepspace app list`. If you are overriding DEEPSPACE_DEPLOY_URL (staging), note that account/collaborator commands go to the PLATFORM API \u2014 an app registered only on a staging deploy service is unknown there. (`domain` commands take the deployed app *name* instead.)",
371
377
  not_app_owner_or_collaborator: "You must be the app owner or a collaborator to do this.",
372
378
  test_account_cannot_be_collaborator: "Test accounts cannot be added as collaborators. Use a real DeepSpace account.",
@@ -591,13 +597,16 @@ function findUnknownCommand(argv, rootCommands) {
591
597
  const exactAll = commandPaths(table).filter((path) => path.at(-1) === token);
592
598
  const exactHit = exactAll.length === 1 ? exactAll[0] : null;
593
599
  const split = splitTokenPath(token, table);
594
- const relativeSuggestion = exactHit ?? split ?? closestCommandPath(token, table);
595
- if (!relativeSuggestion) return null;
596
- const guessed = relativeSuggestion.at(-1) ?? "";
600
+ const rootExactAll = exactHit || split ? [] : commandPaths(rootCommands).filter((path) => path.at(-1) === token);
601
+ const rootExact = rootExactAll.length === 1 ? rootExactAll[0] : null;
602
+ const relativeSuggestion = exactHit ?? split ?? (rootExact ? null : closestCommandPath(token, table));
603
+ const suggestionPath = relativeSuggestion ? [...accepted, ...relativeSuggestion] : rootExact;
604
+ if (!suggestionPath) return null;
605
+ const guessed = suggestionPath.at(-1) ?? "";
597
606
  return {
598
607
  attemptedPath: ["deepspace", ...accepted, token],
599
608
  helpPath: ["deepspace", ...accepted],
600
- suggestion: ["deepspace", ...accepted, ...relativeSuggestion],
609
+ suggestion: ["deepspace", ...suggestionPath],
601
610
  executable: split !== null && !DESTRUCTIVE_VERBS.has(guessed),
602
611
  // POSITIONALS ONLY. The remainder exists so the corrected verb keeps
603
612
  // the arguments it needs; carrying flags too meant `auth logn --email
@@ -1120,6 +1129,10 @@ var logout_default = defineDeepspaceCommand({
1120
1129
  }
1121
1130
  });
1122
1131
 
1132
+ // src/cli/lib/app-registration.ts
1133
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3, rmSync as rmSync3, statSync } from "fs";
1134
+ import { join as join4 } from "path";
1135
+
1123
1136
  // src/cli/lib/app-identity.ts
1124
1137
  import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
1125
1138
  import { join as join3, resolve as resolve2 } from "path";
@@ -1459,55 +1472,9 @@ ${line}
1459
1472
  writeFileSync3(wranglerPath, src);
1460
1473
  }
1461
1474
 
1462
- // src/cli/commands/dev.ts
1463
- import { resolve as resolve8, basename as basename2, join as join11 } from "path";
1464
- import spawn2 from "cross-spawn";
1465
-
1466
- // src/cli/lib/app-context.ts
1467
- import { existsSync as existsSync5, readdirSync } from "fs";
1468
- import { dirname as dirname3, join as join4, resolve as resolve3 } from "path";
1469
- function detectAppName(cwd = process.cwd()) {
1470
- const appDir = resolve3(cwd);
1471
- if (!existsSync5(join4(appDir, "wrangler.toml"))) return null;
1472
- const { name } = readWranglerConfig(appDir);
1473
- return typeof name === "string" && name.length > 0 ? name : null;
1474
- }
1475
- function findAppDir(start = process.cwd()) {
1476
- let dir = resolve3(start);
1477
- for (; ; ) {
1478
- if (existsSync5(join4(dir, "wrangler.toml"))) return dir;
1479
- const parent = dirname3(dir);
1480
- if (parent === dir) return null;
1481
- dir = parent;
1482
- }
1483
- }
1484
- function findChildApps(dir) {
1485
- try {
1486
- return readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && existsSync5(join4(dir, entry.name, "wrangler.toml"))).map((entry) => entry.name).sort();
1487
- } catch {
1488
- return [];
1489
- }
1490
- }
1491
-
1492
- // src/cli/lib/launch-config.ts
1493
- import {
1494
- existsSync as existsSync8,
1495
- readFileSync as readFileSync7,
1496
- realpathSync as realpathSync2,
1497
- writeFileSync as writeFileSync4,
1498
- mkdirSync as mkdirSync4,
1499
- renameSync,
1500
- unlinkSync
1501
- } from "fs";
1502
- import { join as join6, resolve as resolve5, dirname as dirname5, basename, isAbsolute as isAbsolute2, sep } from "path";
1503
-
1504
- // src/cli/lib/git/repository.ts
1505
- import { appendFileSync, existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync6 } from "fs";
1506
- import { dirname as dirname4, join as join5, resolve as resolve4 } from "path";
1507
-
1508
1475
  // src/cli/lib/git/process.ts
1509
1476
  import { sync as spawnSync } from "cross-spawn";
1510
- import { existsSync as existsSync6 } from "fs";
1477
+ import { existsSync as existsSync5 } from "fs";
1511
1478
  var MAX_GIT_BUFFER = 512 * 1024 * 1024;
1512
1479
  var GIT_TIMEOUT_MS = 3e5;
1513
1480
  var GitError = class extends Error {
@@ -1545,7 +1512,7 @@ function runGit(cwd, args, opts = {}) {
1545
1512
  if (result.error) {
1546
1513
  const code = result.error.code;
1547
1514
  if (code === "ENOENT" || code === "ENOTDIR") {
1548
- if (code === "ENOTDIR" || !existsSync6(cwd)) {
1515
+ if (code === "ENOTDIR" || !existsSync5(cwd)) {
1549
1516
  throw new GitError(
1550
1517
  `The directory this command runs in is not there: ${cwd}. If it was a workspace worktree, prune it (\`git worktree prune\`) and re-attach.`,
1551
1518
  "worktree_missing"
@@ -1594,7 +1561,323 @@ function gitMeetsFloor(version) {
1594
1561
  return major > 2 || major === 2 && minor >= 29;
1595
1562
  }
1596
1563
 
1564
+ // src/cli/lib/vc-remote.ts
1565
+ import { existsSync as existsSync6 } from "fs";
1566
+ import { tmpdir } from "os";
1567
+ import { isAbsolute as isAbsolute2, relative, resolve as resolve3 } from "path";
1568
+ function spaceRemoteName(environment = DEEPSPACE_ENV) {
1569
+ if (environment === "staging") return "space-staging";
1570
+ if (environment === "production") return "space";
1571
+ return "space-invalid";
1572
+ }
1573
+ var SPACE_REMOTE = spaceRemoteName();
1574
+ function spaceTrackingRef(branch, remote = SPACE_REMOTE) {
1575
+ return `refs/remotes/${remote}/${branch}`;
1576
+ }
1577
+ function spacePrivateRef(path, environment = DEEPSPACE_ENV) {
1578
+ const root = environment === "production" ? "refs/deepspace" : `refs/deepspace/${environment}`;
1579
+ return `${root}/${path}`;
1580
+ }
1581
+ function deployBaseUrl() {
1582
+ return process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
1583
+ }
1584
+ function repoUrl(appId, base = deployBaseUrl()) {
1585
+ return `${base.replace(/\/+$/, "")}/api/repo/${encodeURIComponent(appId)}`;
1586
+ }
1587
+ function gitAuthEnv(token, base = deployBaseUrl()) {
1588
+ const existing = Number(process.env.GIT_CONFIG_COUNT ?? "0");
1589
+ const index = Number.isInteger(existing) && existing > 0 ? existing : 0;
1590
+ return {
1591
+ GIT_CONFIG_COUNT: String(index + 1),
1592
+ [`GIT_CONFIG_KEY_${index}`]: `http.${base.replace(/\/+$/, "")}.extraHeader`,
1593
+ [`GIT_CONFIG_VALUE_${index}`]: `Authorization: Bearer ${token}`
1594
+ };
1595
+ }
1596
+ function runGitRemote(cwd, token, args, opts = {}) {
1597
+ return runGit(cwd, args, { ...opts, env: { ...gitAuthEnv(token), ...opts.env ?? {} } });
1598
+ }
1599
+ function entryIsTransient(cwd) {
1600
+ const entry = process.argv[1];
1601
+ if (!entry) return true;
1602
+ if (/[\\/]_npx[\\/]/.test(entry) || entry.startsWith(tmpdir())) return true;
1603
+ const rel = relative(resolve3(cwd), resolve3(entry));
1604
+ return rel === "" || !rel.startsWith("..") && !isAbsolute2(rel);
1605
+ }
1606
+ function helperEntryUsable(command) {
1607
+ const match = /^!(?:DEEPSPACE_ENV=(production|staging) )?'([^']*)' '([^']*)' git-credential$/.exec(command);
1608
+ const expectedEnvironment = DEEPSPACE_ENV === "production" ? void 0 : DEEPSPACE_ENV;
1609
+ if (!match || match[1] !== expectedEnvironment) return false;
1610
+ return existsSync6(match[2]) && existsSync6(match[3]);
1611
+ }
1612
+ function credentialHelperCommand(environment = DEEPSPACE_ENV) {
1613
+ const entry = process.argv[1];
1614
+ if (!entry) throw new Error("CLI entry path is unavailable");
1615
+ const environmentPrefix = environment === "staging" ? "DEEPSPACE_ENV=staging " : "";
1616
+ return `!${environmentPrefix}${shQuote(process.execPath)} ${shQuote(entry)} git-credential`;
1617
+ }
1618
+ function installCredentialHelper(cwd, url) {
1619
+ try {
1620
+ const origin = new URL(url);
1621
+ const key = `credential.${origin.protocol}//${origin.host}.helper`;
1622
+ const helper = credentialHelperCommand();
1623
+ const wanted = ["", helper];
1624
+ const transient = entryIsTransient(cwd);
1625
+ const scope = transient ? "--worktree" : "--global";
1626
+ if (transient) {
1627
+ const enabled = runGit(cwd, ["config", "--local", "extensions.worktreeConfig", "true"], {
1628
+ allowFail: true
1629
+ });
1630
+ if (enabled.status !== 0) return;
1631
+ runGit(cwd, ["config", "--local", "--unset-all", key], { allowFail: true });
1632
+ }
1633
+ const current = runGit(cwd, ["config", scope, "--get-all", key], { allowFail: true });
1634
+ if (current.status === 0) {
1635
+ const existing = current.stdout.toString("utf-8").split("\n").filter((_, index, lines) => index < lines.length - 1);
1636
+ if (existing.length === wanted.length && existing.every((value, i) => value === wanted[i])) {
1637
+ return;
1638
+ }
1639
+ if (existing.length === wanted.length && existing[0] === "" && helperEntryUsable(existing[1])) {
1640
+ return;
1641
+ }
1642
+ runGit(cwd, ["config", scope, "--unset-all", key], { allowFail: true });
1643
+ }
1644
+ runGit(cwd, ["config", scope, "--add", key, ""]);
1645
+ runGit(cwd, ["config", scope, "--add", key, helper]);
1646
+ if (scope === "--global" && !process.argv.includes("--json")) {
1647
+ process.stderr.write(
1648
+ `note: added a git credential helper for ${origin.host} to your global git config so plain \`git clone\`/\`git push\` to DeepSpace authenticate. Undo with: git config --global --unset-all ${key}
1649
+ `
1650
+ );
1651
+ }
1652
+ } catch {
1653
+ }
1654
+ }
1655
+ function ensureSpaceRemote(cwd, appId, remote = SPACE_REMOTE, token) {
1656
+ const url = repoUrl(appId);
1657
+ const current = runGit(cwd, ["remote", "get-url", remote], { allowFail: true });
1658
+ const existing = current.status === 0 ? current.stdout.toString("utf-8").trim() : null;
1659
+ if (appIdFromRepoUrl(existing ?? "") !== appId) {
1660
+ const owned = declaredAppIds(cwd);
1661
+ if (owned.size > 0 && !owned.has(appId)) {
1662
+ throw new InputError(
1663
+ `This checkout declares ${[...owned].join(", ")}, but this command targets ${appId}. Pointing its \`${remote}\` remote at another app would send every later plain \`git push ${remote}\` to the wrong repo. Work on ${appId} from its own checkout (\`deepspace clone ${appId}\` into a new directory), or fork THIS checkout into a brand-new app of your own with \`deepspace app init --new-id\`.`,
1664
+ "app_checkout_mismatch"
1665
+ );
1666
+ }
1667
+ }
1668
+ if (token) ensureGitIdentity(cwd, token);
1669
+ if (existing === null) {
1670
+ runGit(cwd, ["remote", "add", remote, url]);
1671
+ } else if (existing !== url) {
1672
+ const priorApp = appIdFromRepoUrl(existing);
1673
+ if (priorApp && priorApp !== appId && !process.argv.includes("--json")) {
1674
+ process.stderr.write(
1675
+ `warning: remote '${remote}' was pointed at app ${priorApp}; re-aiming it at ${appId}.
1676
+ `
1677
+ );
1678
+ }
1679
+ runGit(cwd, ["remote", "set-url", remote, url]);
1680
+ }
1681
+ installCredentialHelper(cwd, url);
1682
+ return url;
1683
+ }
1684
+ function removeSpaceRemote(cwd, remote = SPACE_REMOTE) {
1685
+ const current = runGit(cwd, ["remote", "get-url", remote], { allowFail: true });
1686
+ if (current.status !== 0) return false;
1687
+ runGit(cwd, ["remote", "remove", remote]);
1688
+ return true;
1689
+ }
1690
+ function ensureGitIdentity(cwd, token) {
1691
+ try {
1692
+ const configured = (key) => runGit(cwd, ["config", "--get", key], { allowFail: true }).stdout.toString("utf-8").trim() !== "";
1693
+ const missingName = !configured("user.name");
1694
+ const missingEmail = !configured("user.email");
1695
+ if (!missingName && !missingEmail) return;
1696
+ if (runGit(cwd, ["config", "--get", "--type=bool", "user.useConfigOnly"], { allowFail: true }).stdout.toString("utf-8").trim() === "true") {
1697
+ return;
1698
+ }
1699
+ const payload = decodeJwtPayload(token);
1700
+ if (!payload.email) return;
1701
+ if (missingName) {
1702
+ runGit(cwd, ["config", "--local", "user.name", payload.name || payload.email], {
1703
+ allowFail: true
1704
+ });
1705
+ }
1706
+ if (missingEmail) {
1707
+ runGit(cwd, ["config", "--local", "user.email", payload.email], { allowFail: true });
1708
+ }
1709
+ const wrote = [...missingEmail ? ["user.email"] : [], ...missingName ? ["user.name"] : []];
1710
+ const dir = /^[A-Za-z0-9_./:@-]+$/.test(cwd) ? cwd : shQuote(cwd);
1711
+ const undo = wrote.map((key) => `git -C ${dir} config --local --unset ${key}`).join(" && ");
1712
+ process.stderr.write(
1713
+ `Git identity for this checkout set from your DeepSpace login (${payload.email}).
1714
+ Undo with: ${undo}
1715
+ `
1716
+ );
1717
+ } catch {
1718
+ }
1719
+ }
1720
+ function appIdFromRepoUrl(url) {
1721
+ const match = /\/api\/repo\/([^/]+)\/?$/.exec(url);
1722
+ return match ? decodeURIComponent(match[1]) : null;
1723
+ }
1724
+
1725
+ // src/cli/lib/app-registration.ts
1726
+ var DEPLOY_URL = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
1727
+ async function mintAppIdentity(appDir, token, opts = {}) {
1728
+ const { appId } = await apiFetch(DEPLOY_URL, token, "/api/apps/mint", {
1729
+ method: "POST"
1730
+ });
1731
+ try {
1732
+ writeAppId(appDir, appId, { wranglerEnv: opts.wranglerEnv, force: Boolean(opts.force) });
1733
+ } catch (error) {
1734
+ throw new Error(
1735
+ `Registered ${appId}, but writing it to wrangler.toml failed: ${error instanceof Error ? error.message : String(error)} \u2014 add DEEPSPACE_APP_ID = "${appId}" ${opts.wranglerEnv ? `to [env.${opts.wranglerEnv}.vars] ` : ""}by hand.`
1736
+ );
1737
+ }
1738
+ const committedScaffold = opts.commitScaffold === true && commitScaffoldIfUnborn(appDir, token);
1739
+ return { appId, committedScaffold };
1740
+ }
1741
+ function canHealAppRegistration(appDir, wranglerEnv) {
1742
+ if (!hasWranglerConfig(appDir)) return false;
1743
+ try {
1744
+ const cfg = readWranglerConfig(appDir);
1745
+ if (wranglerEnv && cfg.env?.[wranglerEnv] === void 0) return false;
1746
+ if (readAppIdVar(cfg, wranglerEnv) === void 0) return false;
1747
+ } catch {
1748
+ return false;
1749
+ }
1750
+ try {
1751
+ const committed = runGit(appDir, ["show", "HEAD:./wrangler.toml"], { allowFail: true });
1752
+ if (committed.status === 0 && committed.stdout.toString("utf-8").includes("__APP_ID__")) {
1753
+ return false;
1754
+ }
1755
+ } catch {
1756
+ }
1757
+ return true;
1758
+ }
1759
+ async function ensureAppRegistered(appDir, token, wranglerEnv, opts = {}) {
1760
+ const existing = readAppId(appDir, wranglerEnv);
1761
+ if (existing) return { appId: existing, minted: false, committedScaffold: false };
1762
+ if (!canHealAppRegistration(appDir, wranglerEnv)) return null;
1763
+ const release2 = acquireMintLock(appDir);
1764
+ if (!release2) {
1765
+ const winner = await waitForMintedId(appDir, wranglerEnv);
1766
+ if (winner) return { appId: winner, minted: false, committedScaffold: false };
1767
+ }
1768
+ try {
1769
+ const raced = readAppId(appDir, wranglerEnv);
1770
+ if (raced) return { appId: raced, minted: false, committedScaffold: false };
1771
+ const { appId, committedScaffold } = await mintAppIdentity(appDir, token, {
1772
+ wranglerEnv,
1773
+ commitScaffold: opts.commitScaffold
1774
+ });
1775
+ process.stderr.write(
1776
+ `Registered ${appId}${wranglerEnv ? ` (env: ${wranglerEnv})` : ""} on ${DEEPSPACE_ENV} to your account \u2014 apps register on first use` + (committedScaffold ? "; initial scaffold commit created" : "") + ".\n"
1777
+ );
1778
+ return { appId, minted: true, committedScaffold };
1779
+ } finally {
1780
+ release2?.();
1781
+ }
1782
+ }
1783
+ function acquireMintLock(appDir) {
1784
+ const lockDir = join4(appDir, ".deepspace", "register.lock");
1785
+ mkdirSync3(join4(appDir, ".deepspace"), { recursive: true });
1786
+ try {
1787
+ mkdirSync3(lockDir);
1788
+ } catch {
1789
+ try {
1790
+ const age = Date.now() - statSync(lockDir).mtimeMs;
1791
+ if (age < 2 * 60 * 1e3) return null;
1792
+ rmSync3(lockDir, { recursive: true, force: true });
1793
+ mkdirSync3(lockDir);
1794
+ } catch {
1795
+ return null;
1796
+ }
1797
+ }
1798
+ return () => rmSync3(lockDir, { recursive: true, force: true });
1799
+ }
1800
+ async function waitForMintedId(appDir, wranglerEnv) {
1801
+ const deadline = Date.now() + 3e4;
1802
+ while (Date.now() < deadline) {
1803
+ const id = readAppId(appDir, wranglerEnv);
1804
+ if (id) return id;
1805
+ if (!existsSync7(join4(appDir, ".deepspace", "register.lock"))) {
1806
+ return readAppId(appDir, wranglerEnv);
1807
+ }
1808
+ await new Promise((resolve23) => setTimeout(resolve23, 250));
1809
+ }
1810
+ return null;
1811
+ }
1812
+ function wranglerConfigUncommitted(appDir) {
1813
+ if (!existsSync7(join4(appDir, ".git"))) return false;
1814
+ const dryRun = runGit(appDir, ["commit", "--dry-run", "-m", "x", "--", "wrangler.toml"], {
1815
+ allowFail: true
1816
+ });
1817
+ return dryRun.status === 0;
1818
+ }
1819
+ function commitScaffoldIfUnborn(appDir, token) {
1820
+ try {
1821
+ if (!existsSync7(join4(appDir, ".git"))) return false;
1822
+ if (runGit(appDir, ["rev-parse", "--verify", "HEAD"], { allowFail: true }).status === 0) {
1823
+ return false;
1824
+ }
1825
+ ensureGitIdentity(appDir, token);
1826
+ if (runGit(appDir, ["add", "-A"], { allowFail: true }).status !== 0) return false;
1827
+ const commit = runGit(appDir, ["commit", "-m", "Initial DeepSpace scaffold", "--no-verify"], {
1828
+ allowFail: true
1829
+ });
1830
+ return commit.status === 0;
1831
+ } catch {
1832
+ return false;
1833
+ }
1834
+ }
1835
+
1836
+ // src/cli/commands/dev.ts
1837
+ import { resolve as resolve9, basename as basename2, join as join12 } from "path";
1838
+ import spawn2 from "cross-spawn";
1839
+
1840
+ // src/cli/lib/app-context.ts
1841
+ import { existsSync as existsSync8, readdirSync } from "fs";
1842
+ import { dirname as dirname3, join as join5, resolve as resolve4 } from "path";
1843
+ function detectAppName(cwd = process.cwd()) {
1844
+ const appDir = resolve4(cwd);
1845
+ if (!existsSync8(join5(appDir, "wrangler.toml"))) return null;
1846
+ const { name } = readWranglerConfig(appDir);
1847
+ return typeof name === "string" && name.length > 0 ? name : null;
1848
+ }
1849
+ function findAppDir(start = process.cwd()) {
1850
+ let dir = resolve4(start);
1851
+ for (; ; ) {
1852
+ if (existsSync8(join5(dir, "wrangler.toml"))) return dir;
1853
+ const parent = dirname3(dir);
1854
+ if (parent === dir) return null;
1855
+ dir = parent;
1856
+ }
1857
+ }
1858
+ function findChildApps(dir) {
1859
+ try {
1860
+ return readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && existsSync8(join5(dir, entry.name, "wrangler.toml"))).map((entry) => entry.name).sort();
1861
+ } catch {
1862
+ return [];
1863
+ }
1864
+ }
1865
+
1866
+ // src/cli/lib/launch-config.ts
1867
+ import {
1868
+ existsSync as existsSync10,
1869
+ readFileSync as readFileSync7,
1870
+ realpathSync as realpathSync2,
1871
+ writeFileSync as writeFileSync4,
1872
+ mkdirSync as mkdirSync5,
1873
+ renameSync,
1874
+ unlinkSync
1875
+ } from "fs";
1876
+ import { join as join7, resolve as resolve6, dirname as dirname5, basename, isAbsolute as isAbsolute3, sep } from "path";
1877
+
1597
1878
  // src/cli/lib/git/repository.ts
1879
+ import { appendFileSync, existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync6 } from "fs";
1880
+ import { dirname as dirname4, join as join6, resolve as resolve5 } from "path";
1598
1881
  function isPlausibleBranchName(name) {
1599
1882
  if (!name || name === "HEAD" || name === "@") return false;
1600
1883
  if (name.startsWith("/") || name.startsWith("-") || name.startsWith(".")) return false;
@@ -1644,7 +1927,7 @@ function assertNoOperationInProgress(cwd) {
1644
1927
  ["REVERT_HEAD", "revert", "git revert --continue", "git revert --abort"],
1645
1928
  ["rebase-merge", "rebase", "git rebase --continue", "git rebase --abort"],
1646
1929
  ["rebase-apply", "rebase", "git rebase --continue", "git rebase --abort"]
1647
- ].find(([marker]) => existsSync7(join5(resolve4(cwd, gitDir), marker)));
1930
+ ].find(([marker]) => existsSync9(join6(resolve5(cwd, gitDir), marker)));
1648
1931
  if (!inProgress) return;
1649
1932
  const [, kind, finish, abort] = inProgress;
1650
1933
  throw new InputError(
@@ -1658,13 +1941,13 @@ function repoToplevel(cwd) {
1658
1941
  function interruptedGitOperation(cwd) {
1659
1942
  const gitDir = runGit(cwd, ["rev-parse", "--git-path", "HEAD"], { allowFail: true });
1660
1943
  if (gitDir.status !== 0) return null;
1661
- const dir = dirname4(resolve4(cwd, gitDir.stdout.toString("utf-8").trim()));
1662
- if (existsSync7(join5(dir, "rebase-apply", "applying"))) return "am";
1663
- if (existsSync7(join5(dir, "rebase-merge")) || existsSync7(join5(dir, "rebase-apply"))) return "rebase";
1664
- if (existsSync7(join5(dir, "MERGE_HEAD"))) return "merge";
1665
- if (existsSync7(join5(dir, "CHERRY_PICK_HEAD"))) return "cherry-pick";
1666
- if (existsSync7(join5(dir, "REVERT_HEAD"))) return "revert";
1667
- if (existsSync7(join5(dir, "BISECT_LOG"))) return "bisect";
1944
+ const dir = dirname4(resolve5(cwd, gitDir.stdout.toString("utf-8").trim()));
1945
+ if (existsSync9(join6(dir, "rebase-apply", "applying"))) return "am";
1946
+ if (existsSync9(join6(dir, "rebase-merge")) || existsSync9(join6(dir, "rebase-apply"))) return "rebase";
1947
+ if (existsSync9(join6(dir, "MERGE_HEAD"))) return "merge";
1948
+ if (existsSync9(join6(dir, "CHERRY_PICK_HEAD"))) return "cherry-pick";
1949
+ if (existsSync9(join6(dir, "REVERT_HEAD"))) return "revert";
1950
+ if (existsSync9(join6(dir, "BISECT_LOG"))) return "bisect";
1668
1951
  return null;
1669
1952
  }
1670
1953
  function currentBranch(cwd) {
@@ -1741,11 +2024,11 @@ function listWorktrees(cwd) {
1741
2024
  current.branch = ref.startsWith("refs/heads/") ? ref.slice("refs/heads/".length) : ref;
1742
2025
  }
1743
2026
  }
1744
- return worktrees.filter((worktree) => existsSync7(worktree.path));
2027
+ return worktrees.filter((worktree) => existsSync9(worktree.path));
1745
2028
  }
1746
2029
  function ensureLocalExclude(cwd, pattern) {
1747
- const commonDir = resolve4(cwd, gitLine(cwd, ["rev-parse", "--git-common-dir"]));
1748
- const excludePath = join5(commonDir, "info", "exclude");
2030
+ const commonDir = resolve5(cwd, gitLine(cwd, ["rev-parse", "--git-common-dir"]));
2031
+ const excludePath = join6(commonDir, "info", "exclude");
1749
2032
  let current = "";
1750
2033
  try {
1751
2034
  current = readFileSync6(excludePath, "utf-8");
@@ -1753,7 +2036,7 @@ function ensureLocalExclude(cwd, pattern) {
1753
2036
  }
1754
2037
  if (current.split("\n").some((line) => line.trim() === pattern)) return;
1755
2038
  try {
1756
- mkdirSync3(dirname4(excludePath), { recursive: true });
2039
+ mkdirSync4(dirname4(excludePath), { recursive: true });
1757
2040
  appendFileSync(
1758
2041
  excludePath,
1759
2042
  `${current.endsWith("\n") || current === "" ? "" : "\n"}${pattern}
@@ -1765,7 +2048,7 @@ function ensureLocalExclude(cwd, pattern) {
1765
2048
 
1766
2049
  // src/cli/lib/launch-config.ts
1767
2050
  function writeLaunchFile(launchPath, config) {
1768
- mkdirSync4(dirname5(launchPath), { recursive: true });
2051
+ mkdirSync5(dirname5(launchPath), { recursive: true });
1769
2052
  const tmpPath = `${launchPath}.${process.pid}.tmp`;
1770
2053
  writeFileSync4(tmpPath, JSON.stringify(config, null, 2) + "\n");
1771
2054
  try {
@@ -1780,7 +2063,7 @@ function writeLaunchFile(launchPath, config) {
1780
2063
  }
1781
2064
  function readLaunchFile(launchPath) {
1782
2065
  const empty = { version: "0.0.1", configurations: [] };
1783
- if (!existsSync8(launchPath)) return empty;
2066
+ if (!existsSync10(launchPath)) return empty;
1784
2067
  const raw = readFileSync7(launchPath, "utf-8").trim();
1785
2068
  if (!raw) return empty;
1786
2069
  const parsed = JSON.parse(raw);
@@ -1788,7 +2071,7 @@ function readLaunchFile(launchPath) {
1788
2071
  return parsed;
1789
2072
  }
1790
2073
  function writeLaunchConfigIfMissing(appDir, appName, port, opts = {}) {
1791
- const launchPath = join6(appDir, ".claude", "launch.json");
2074
+ const launchPath = join7(appDir, ".claude", "launch.json");
1792
2075
  try {
1793
2076
  const config = readLaunchFile(launchPath);
1794
2077
  if (!config) return;
@@ -1829,7 +2112,7 @@ function writeLaunchConfigIfMissing(appDir, appName, port, opts = {}) {
1829
2112
  }
1830
2113
  function resolveAppLaunchPort(appDir) {
1831
2114
  try {
1832
- const config = readLaunchFile(join6(appDir, ".claude", "launch.json"));
2115
+ const config = readLaunchFile(join7(appDir, ".claude", "launch.json"));
1833
2116
  if (!config) return null;
1834
2117
  const appName = detectAppName(appDir) ?? basename(appDir);
1835
2118
  const entry = config.configurations.find((c) => c?.name === appName) ?? (config.configurations.length === 1 ? config.configurations[0] : void 0);
@@ -1860,7 +2143,7 @@ function canonicalPath(path) {
1860
2143
  try {
1861
2144
  return realpathSync2(path);
1862
2145
  } catch {
1863
- return resolve5(path);
2146
+ return resolve6(path);
1864
2147
  }
1865
2148
  }
1866
2149
  function detectLinkedWorktree(appDir) {
@@ -1883,21 +2166,21 @@ function deriveWorktreePort(worktreeKey) {
1883
2166
  return WORKTREE_PORT_MIN + hash % WORKTREE_PORT_RANGE;
1884
2167
  }
1885
2168
  function upsertWorktreeLaunchConfig(mainRepoRoot, worktreeName, appDir, opts) {
1886
- const launchPath = join6(mainRepoRoot, ".claude", "launch.json");
2169
+ const launchPath = join7(mainRepoRoot, ".claude", "launch.json");
1887
2170
  const entryName = WORKTREE_ENTRY_PREFIX + worktreeName;
1888
2171
  try {
1889
2172
  const config = readLaunchFile(launchPath);
1890
2173
  if (!config) return null;
1891
- const worktreesRoot = join6(mainRepoRoot, ".claude", "worktrees") + sep;
2174
+ const worktreesRoot = join7(mainRepoRoot, ".claude", "worktrees") + sep;
1892
2175
  config.configurations = config.configurations.filter((c) => {
1893
2176
  if (c?.name === entryName) return false;
1894
- const isStaleWorktreeEntry = typeof c?.name === "string" && c.name.startsWith(WORKTREE_ENTRY_PREFIX) && typeof c.cwd === "string" && isAbsolute2(c.cwd) && c.cwd.startsWith(worktreesRoot) && !existsSync8(c.cwd);
2177
+ const isStaleWorktreeEntry = typeof c?.name === "string" && c.name.startsWith(WORKTREE_ENTRY_PREFIX) && typeof c.cwd === "string" && isAbsolute3(c.cwd) && c.cwd.startsWith(worktreesRoot) && !existsSync10(c.cwd);
1895
2178
  return !isStaleWorktreeEntry;
1896
2179
  });
1897
2180
  let port = opts.port;
1898
2181
  if (opts.probePort) {
1899
2182
  const taken = new Set(
1900
- config.configurations.map((c) => Number(c?.port)).filter((p36) => Number.isInteger(p36) && p36 > 0)
2183
+ config.configurations.map((c) => Number(c?.port)).filter((p35) => Number.isInteger(p35) && p35 > 0)
1901
2184
  );
1902
2185
  for (let i = 0; i < WORKTREE_PORT_RANGE && taken.has(port); i++) {
1903
2186
  port = WORKTREE_PORT_MIN + (port - WORKTREE_PORT_MIN + 1) % WORKTREE_PORT_RANGE;
@@ -1912,7 +2195,7 @@ function upsertWorktreeLaunchConfig(mainRepoRoot, worktreeName, appDir, opts) {
1912
2195
  // the Claude Code desktop launch.json reference alongside `port`) and
1913
2196
  // verified end-to-end: with it set, the preview server builds from the
1914
2197
  // worktree instead of the session's main repo.
1915
- cwd: resolve5(appDir)
2198
+ cwd: resolve6(appDir)
1916
2199
  });
1917
2200
  writeLaunchFile(launchPath, config);
1918
2201
  return { entryName, port };
@@ -1926,7 +2209,7 @@ function resolveWorktreePort(appDir) {
1926
2209
  const claude = detectClaudeWorktree(appDir);
1927
2210
  if (claude) {
1928
2211
  try {
1929
- const config = readLaunchFile(join6(claude.mainRepoRoot, ".claude", "launch.json"));
2212
+ const config = readLaunchFile(join7(claude.mainRepoRoot, ".claude", "launch.json"));
1930
2213
  const entry = config?.configurations.find(
1931
2214
  (c) => c?.name === WORKTREE_ENTRY_PREFIX + claude.worktreeName
1932
2215
  );
@@ -1987,14 +2270,6 @@ async function getAppSource(deployUrl, token, appId) {
1987
2270
  throw error;
1988
2271
  }
1989
2272
  }
1990
- function setAppSource(deployUrl, token, appId, input) {
1991
- return apiFetch(
1992
- deployUrl,
1993
- token,
1994
- `/api/apps/${encodeURIComponent(appId)}/source`,
1995
- { method: "POST", body: JSON.stringify(input) }
1996
- );
1997
- }
1998
2273
 
1999
2274
  // src/cli/lib/repo-api.ts
2000
2275
  function isWorkspaceRef(ref) {
@@ -2002,10 +2277,15 @@ function isWorkspaceRef(ref) {
2002
2277
  return ref.startsWith(prefix) && isWorkspaceId(ref.slice(prefix.length));
2003
2278
  }
2004
2279
  function releaseSourceLabel(release2) {
2005
- if (release2.commitOid) return `commit ${release2.commitOid.slice(0, 10)}`;
2006
- if (release2.source?.provider === "github") return `GitHub \xB7 ${release2.source.repository}`;
2007
- if (release2.source?.provider === "deepspace") return "DeepSpace source, no commit recorded";
2008
- return "no source recorded";
2280
+ const dirty = release2.sourceDirty === true ? " (dirty worktree)" : "";
2281
+ if (release2.commitOid) return `commit ${release2.commitOid.slice(0, 10)}${dirty}`;
2282
+ if (release2.source?.provider === "github") {
2283
+ return `GitHub \xB7 ${release2.source.repository}${dirty ? ", dirty worktree" : ""}`;
2284
+ }
2285
+ if (release2.source?.provider === "deepspace") {
2286
+ return `DeepSpace source, no commit recorded${dirty}`;
2287
+ }
2288
+ return `no source recorded${dirty}`;
2009
2289
  }
2010
2290
  function mintIdempotencyKey() {
2011
2291
  return randomUUID();
@@ -2186,6 +2466,11 @@ async function resolveAppTarget(deployUrl, token, explicit, options = {}) {
2186
2466
  if (app2 !== void 0) return resolveAppSelector(deployUrl, token, app2);
2187
2467
  const { wranglerEnv } = parseWranglerEnvArg(options.wranglerEnv);
2188
2468
  const appDir = findAppDir();
2469
+ if (options.register === true) {
2470
+ const ensured = appDir ? await ensureAppRegistered(appDir, token, wranglerEnv) : null;
2471
+ if (!ensured) throw appTargetMissingError(appDir, wranglerEnv);
2472
+ return ensured.appId;
2473
+ }
2189
2474
  const id = appDir ? readAppId(appDir, wranglerEnv) : null;
2190
2475
  if (!id) throw appTargetMissingError(appDir, wranglerEnv);
2191
2476
  return id;
@@ -2255,7 +2540,9 @@ function assertAppTargetResolvable(appArg, options = {}) {
2255
2540
  }
2256
2541
  if (app2 !== void 0) return;
2257
2542
  const appDir = findAppDir();
2258
- if (!appDir || !readAppId(appDir, wranglerEnv)) throw appTargetMissingError(appDir, wranglerEnv);
2543
+ if (appDir && readAppId(appDir, wranglerEnv)) return;
2544
+ if (options.register === true && appDir && canHealAppRegistration(appDir, wranglerEnv)) return;
2545
+ throw appTargetMissingError(appDir, wranglerEnv);
2259
2546
  }
2260
2547
  async function warnIfPhantomApp(deployUrl, token, appId, explicit) {
2261
2548
  if (!explicit || !STRICT_APP_ID_RE.test(explicit.trim())) return;
@@ -2357,22 +2644,34 @@ async function writeDevVars(appDir, ownerId, callerJwt, wranglerEnv, opts = {})
2357
2644
  }
2358
2645
 
2359
2646
  // src/cli/lib/install-status.ts
2360
- import { existsSync as existsSync10, readFileSync as readFileSync9, realpathSync as realpathSync3 } from "fs";
2361
- import { dirname as dirname6, join as join8, resolve as resolve6 } from "path";
2647
+ import {
2648
+ appendFileSync as appendFileSync2,
2649
+ closeSync,
2650
+ existsSync as existsSync12,
2651
+ mkdirSync as mkdirSync6,
2652
+ openSync,
2653
+ readFileSync as readFileSync9,
2654
+ realpathSync as realpathSync3,
2655
+ rmSync as rmSync4,
2656
+ statSync as statSync2,
2657
+ writeFileSync as writeFileSync5
2658
+ } from "fs";
2659
+ import { dirname as dirname6, join as join9, resolve as resolve7 } from "path";
2660
+ import { sync as spawnSync2 } from "cross-spawn";
2362
2661
 
2363
2662
  // src/cli/lib/package-manager.ts
2364
- import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
2365
- import { join as join7 } from "path";
2663
+ import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
2664
+ import { join as join8 } from "path";
2366
2665
  function detectPackageManager(appDir) {
2367
2666
  try {
2368
- const pkg2 = JSON.parse(readFileSync8(join7(appDir, "package.json"), "utf-8"));
2667
+ const pkg2 = JSON.parse(readFileSync8(join8(appDir, "package.json"), "utf-8"));
2369
2668
  const name = typeof pkg2.packageManager === "string" ? pkg2.packageManager.split("@")[0] : "";
2370
2669
  if (name === "npm" || name === "pnpm" || name === "yarn" || name === "bun") return name;
2371
2670
  } catch {
2372
2671
  }
2373
- if (existsSync9(join7(appDir, "pnpm-lock.yaml"))) return "pnpm";
2374
- if (existsSync9(join7(appDir, "yarn.lock"))) return "yarn";
2375
- if (existsSync9(join7(appDir, "bun.lock")) || existsSync9(join7(appDir, "bun.lockb"))) return "bun";
2672
+ if (existsSync11(join8(appDir, "pnpm-lock.yaml"))) return "pnpm";
2673
+ if (existsSync11(join8(appDir, "yarn.lock"))) return "yarn";
2674
+ if (existsSync11(join8(appDir, "bun.lock")) || existsSync11(join8(appDir, "bun.lockb"))) return "bun";
2376
2675
  return "npm";
2377
2676
  }
2378
2677
  function installCommand(appDir) {
@@ -2382,10 +2681,10 @@ function installCommand(appDir) {
2382
2681
  // src/cli/lib/install-status.ts
2383
2682
  function installState(appDir) {
2384
2683
  if (resolvesDeepspace(appDir)) return "ready";
2385
- const sentinelDir = join8(appDir, ".deepspace");
2386
- if (existsSync10(join8(sentinelDir, "install.err"))) return "failed";
2387
- if (existsSync10(join8(sentinelDir, "install.started"))) {
2388
- if (!existsSync10(join8(sentinelDir, "install.done")) && installWorkerAlive(sentinelDir)) {
2684
+ const sentinelDir = join9(appDir, ".deepspace");
2685
+ if (existsSync12(join9(sentinelDir, "install.err"))) return "failed";
2686
+ if (existsSync12(join9(sentinelDir, "install.started"))) {
2687
+ if (!existsSync12(join9(sentinelDir, "install.done")) && installWorkerAlive(sentinelDir)) {
2389
2688
  return "installing";
2390
2689
  }
2391
2690
  return "failed";
@@ -2397,42 +2696,108 @@ function tailHint(logPath, platform = process.platform) {
2397
2696
  }
2398
2697
  function ensureInstallReady(appDir) {
2399
2698
  if (resolvesDeepspace(appDir)) return;
2400
- const sentinelDir = join8(appDir, ".deepspace");
2401
- const errPath = join8(sentinelDir, "install.err");
2402
- const startedPath = join8(sentinelDir, "install.started");
2403
- const donePath = join8(sentinelDir, "install.done");
2404
- const logPath = join8(sentinelDir, "install.log");
2405
- if (existsSync10(errPath)) {
2406
- const detail = readFileSync9(errPath, "utf-8").trimEnd();
2407
- const log29 = existsSync10(logPath) ? ` Full log: ${logPath}.` : "";
2699
+ const sentinelDir = join9(appDir, ".deepspace");
2700
+ const startedPath = join9(sentinelDir, "install.started");
2701
+ const donePath = join9(sentinelDir, "install.done");
2702
+ const logPath = join9(sentinelDir, "install.log");
2703
+ if (process.env.DEEPSPACE_NO_INSTALL) {
2408
2704
  throw new Refusal(
2409
- `Dependency install failed: ${detail}.${log29} Run \`npm install\` (or \`bun install\`) manually, then retry.`,
2410
- "install_failed",
2705
+ "Dependencies not installed, and DEEPSPACE_NO_INSTALL is set \u2014 run the install yourself, then retry.",
2706
+ "deps_missing",
2411
2707
  { action: { cwd: appDir, argv: [detectPackageManager(appDir), "install"] } }
2412
2708
  );
2413
2709
  }
2414
- if (existsSync10(startedPath) && !existsSync10(donePath)) {
2415
- if (!installWorkerAlive(sentinelDir)) {
2416
- const log29 = existsSync10(logPath) ? ` See what happened: ${logPath}.` : "";
2417
- throw new Refusal(
2418
- `The dependency install is no longer running and never finished.${log29} Run \`npm install\` (or \`bun install\`) manually, then retry.`,
2419
- "install_failed",
2420
- { action: { cwd: appDir, argv: [detectPackageManager(appDir), "install"] } }
2421
- );
2422
- }
2423
- const tail = existsSync10(logPath) ? ` Tail progress: ${tailHint(logPath)}.` : "";
2710
+ if (existsSync12(startedPath) && !existsSync12(donePath) && installWorkerAlive(sentinelDir) && sentinelAgeMs(startedPath) < 30 * 60 * 1e3) {
2711
+ const tail = existsSync12(logPath) ? ` Tail progress: ${tailHint(logPath)}.` : "";
2424
2712
  throw new Refusal(
2425
- `Dependencies are still installing.${tail} Retry once it finishes.`,
2713
+ `Dependencies are still installing.${tail} Retry once it finishes (or remove ${sentinelDir}/install.started if no install is actually running).`,
2426
2714
  "install_in_progress"
2427
2715
  );
2428
2716
  }
2429
- throw new Refusal(
2430
- "Dependencies not installed. Run `npm install` (or `bun install`) first.",
2431
- "deps_missing",
2432
- {
2433
- action: { cwd: appDir, argv: [detectPackageManager(appDir), "install"] }
2434
- }
2435
- );
2717
+ healMissingInstall(appDir);
2718
+ }
2719
+ function healMissingInstall(appDir) {
2720
+ const manager = detectPackageManager(appDir);
2721
+ const sentinelDir = join9(appDir, ".deepspace");
2722
+ const logPath = join9(sentinelDir, "install.log");
2723
+ const errPath = join9(sentinelDir, "install.err");
2724
+ const previousFailure = existsSync12(errPath) ? readFileSync9(errPath, "utf-8").trimEnd() : null;
2725
+ process.stderr.write(
2726
+ `Installing dependencies (${manager} install) \u2014 first use installs them. ${previousFailure ? `Retrying after: ${previousFailure}. ` : ""}Watch: ${tailHint(logPath)}
2727
+ `
2728
+ );
2729
+ if (process.platform === "win32") {
2730
+ for (const ext of [".cmd", ".bat", ".exe", ".com", ".ps1"]) {
2731
+ if (existsSync12(join9(appDir, `${manager}${ext}`))) {
2732
+ throw new Refusal(
2733
+ `Refusing to install: ${appDir} contains ${manager}${ext}, which Windows would execute instead of the real ${manager}. Remove it, or run the install yourself from a shell you trust.`,
2734
+ "install_failed"
2735
+ );
2736
+ }
2737
+ }
2738
+ }
2739
+ mkdirSync6(sentinelDir, { recursive: true });
2740
+ ensureSentinelsIgnored(appDir);
2741
+ rmSync4(errPath, { force: true });
2742
+ rmSync4(join9(sentinelDir, "install.done"), { force: true });
2743
+ writeFileSync5(join9(sentinelDir, "install.started"), (/* @__PURE__ */ new Date()).toISOString());
2744
+ writeFileSync5(join9(sentinelDir, "install.pid"), String(process.pid));
2745
+ const logFd = openSync(logPath, "w");
2746
+ let result;
2747
+ try {
2748
+ result = spawnSync2(manager, ["install"], {
2749
+ cwd: appDir,
2750
+ stdio: ["ignore", logFd, logFd],
2751
+ // Bounded (repo rule: no task over 5 minutes): a hung registry must
2752
+ // surface as a fast failure, never a stall. The log names the cure.
2753
+ timeout: 5 * 60 * 1e3
2754
+ });
2755
+ } finally {
2756
+ closeSync(logFd);
2757
+ }
2758
+ if (result.status === 0 && !result.error && resolvesDeepspace(appDir)) {
2759
+ writeFileSync5(join9(sentinelDir, "install.done"), (/* @__PURE__ */ new Date()).toISOString());
2760
+ return;
2761
+ }
2762
+ const detail = result.error ? result.error.code === "ETIMEDOUT" ? `${manager} install timed out after 5 minutes` : result.error.message : result.status === 0 ? `${manager} install completed but the \`deepspace\` package still does not resolve \u2014 is this directory the app (its package.json must depend on deepspace)?` : result.status === null ? `${manager} install was killed (${result.signal ?? "signal"})` : `${manager} install exited ${result.status}`;
2763
+ writeFileSync5(errPath, detail);
2764
+ let logTail = "";
2765
+ try {
2766
+ const log28 = readFileSync9(logPath, "utf-8");
2767
+ const tail = log28.slice(-1500).trimEnd();
2768
+ if (tail) logTail = `
2769
+ --- install log (tail) ---
2770
+ ${tail}`;
2771
+ } catch {
2772
+ }
2773
+ throw new Refusal(
2774
+ `Dependency install failed: ${detail}. Full log: ${logPath}. It will be retried on the next command, or run \`${manager} install\` yourself.${logTail}`,
2775
+ "install_failed",
2776
+ { action: { cwd: appDir, argv: [manager, "install"] } }
2777
+ );
2778
+ }
2779
+ function sentinelAgeMs(path) {
2780
+ try {
2781
+ return Date.now() - statSync2(path).mtimeMs;
2782
+ } catch {
2783
+ return Number.POSITIVE_INFINITY;
2784
+ }
2785
+ }
2786
+ function ensureSentinelsIgnored(appDir) {
2787
+ try {
2788
+ const check = spawnSync2("git", ["-C", appDir, "check-ignore", "-q", ".deepspace"], {
2789
+ stdio: "ignore"
2790
+ });
2791
+ if (check.status === 0 || check.error) return;
2792
+ const excludePath = spawnSync2("git", ["-C", appDir, "rev-parse", "--git-path", "info/exclude"], {
2793
+ encoding: "utf-8"
2794
+ });
2795
+ if (excludePath.status !== 0) return;
2796
+ const target2 = resolve7(appDir, excludePath.stdout.trim());
2797
+ mkdirSync6(dirname6(target2), { recursive: true });
2798
+ appendFileSync2(target2, "\n# deepspace CLI state (added by first-use install)\n.deepspace/\n");
2799
+ } catch {
2800
+ }
2436
2801
  }
2437
2802
  function resolvesDeepspace(fromDir) {
2438
2803
  let dir = canonicalPath2(fromDir);
@@ -2442,7 +2807,7 @@ function resolvesDeepspace(fromDir) {
2442
2807
  } catch {
2443
2808
  }
2444
2809
  for (; ; ) {
2445
- if (existsSync10(join8(dir, "node_modules", "deepspace", "package.json"))) return true;
2810
+ if (existsSync12(join9(dir, "node_modules", "deepspace", "package.json"))) return true;
2446
2811
  if (dir === checkoutRoot) return false;
2447
2812
  const parent = dirname6(dir);
2448
2813
  if (parent === dir) return false;
@@ -2453,13 +2818,15 @@ function canonicalPath2(path) {
2453
2818
  try {
2454
2819
  return realpathSync3(path);
2455
2820
  } catch {
2456
- return resolve6(path);
2821
+ return resolve7(path);
2457
2822
  }
2458
2823
  }
2459
2824
  function installWorkerAlive(sentinelDir) {
2460
- const pidPath = join8(sentinelDir, "install.pid");
2461
- if (!existsSync10(pidPath)) return false;
2462
- return processAlive(Number(readFileSync9(pidPath, "utf-8").trim()));
2825
+ const pidPath = join9(sentinelDir, "install.pid");
2826
+ if (!existsSync12(pidPath)) return false;
2827
+ const pid = Number(readFileSync9(pidPath, "utf-8").trim());
2828
+ if (pid === process.pid) return false;
2829
+ return processAlive(pid);
2463
2830
  }
2464
2831
  function processAlive(pid) {
2465
2832
  if (!Number.isSafeInteger(pid) || pid <= 0) return false;
@@ -2473,9 +2840,9 @@ function processAlive(pid) {
2473
2840
 
2474
2841
  // src/cli/lib/preflight.ts
2475
2842
  import { execFileSync } from "child_process";
2476
- import { existsSync as existsSync11 } from "fs";
2843
+ import { existsSync as existsSync13 } from "fs";
2477
2844
  import * as nodeModule from "module";
2478
- import { resolve as resolve7 } from "path";
2845
+ import { resolve as resolve8 } from "path";
2479
2846
  function preflightNodeVersion(command, hasRegisterHooks = typeof nodeModule.registerHooks === "function", version = process.versions.node) {
2480
2847
  const [major, minor] = version.split(".").map(Number);
2481
2848
  const supportedLine = major === 22 && minor >= 15 || major === 24 || major === 26;
@@ -2488,7 +2855,7 @@ function preflightNodeVersion(command, hasRegisterHooks = typeof nodeModule.regi
2488
2855
  var DLL_NOT_FOUND = -1073741515;
2489
2856
  function preflightWindowsWorkerd(appDir) {
2490
2857
  if (process.platform !== "win32") return;
2491
- const workerdExe = resolve7(
2858
+ const workerdExe = resolve8(
2492
2859
  appDir,
2493
2860
  "node_modules",
2494
2861
  "@cloudflare",
@@ -2496,7 +2863,7 @@ function preflightWindowsWorkerd(appDir) {
2496
2863
  "bin",
2497
2864
  "workerd.exe"
2498
2865
  );
2499
- if (!existsSync11(workerdExe)) return;
2866
+ if (!existsSync13(workerdExe)) return;
2500
2867
  try {
2501
2868
  execFileSync(workerdExe, ["--help"], { stdio: "pipe", timeout: 1e4 });
2502
2869
  return;
@@ -2511,8 +2878,8 @@ function preflightWindowsWorkerd(appDir) {
2511
2878
  }
2512
2879
 
2513
2880
  // src/cli/lib/macos-junk.ts
2514
- import { readdirSync as readdirSync2, rmSync as rmSync3 } from "fs";
2515
- import { join as join9 } from "path";
2881
+ import { readdirSync as readdirSync2, rmSync as rmSync5 } from "fs";
2882
+ import { join as join10 } from "path";
2516
2883
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".wrangler", ".vite"]);
2517
2884
  function removeMacosJunk(dir) {
2518
2885
  let removed = 0;
@@ -2523,14 +2890,14 @@ function removeMacosJunk(dir) {
2523
2890
  return removed;
2524
2891
  }
2525
2892
  for (const entry of entries) {
2526
- const path = join9(dir, entry.name);
2893
+ const path = join10(dir, entry.name);
2527
2894
  if (entry.isDirectory()) {
2528
2895
  if (!SKIP_DIRS.has(entry.name)) removed += removeMacosJunk(path);
2529
2896
  continue;
2530
2897
  }
2531
2898
  if (entry.name === ".DS_Store" || entry.name.startsWith("._")) {
2532
2899
  try {
2533
- rmSync3(path, { force: true });
2900
+ rmSync5(path, { force: true });
2534
2901
  removed++;
2535
2902
  } catch {
2536
2903
  }
@@ -3105,8 +3472,8 @@ async function refreshSecretsCache(deployUrl, token, appId, wranglerEnv, selecte
3105
3472
  }
3106
3473
 
3107
3474
  // src/cli/lib/schema-lint.ts
3108
- import { existsSync as existsSync12 } from "fs";
3109
- import { join as join10 } from "path";
3475
+ import { existsSync as existsSync14 } from "fs";
3476
+ import { join as join11 } from "path";
3110
3477
  import { pathToFileURL } from "url";
3111
3478
 
3112
3479
  // src/server/schemas/registry.ts
@@ -3139,7 +3506,7 @@ function lintSchema(schema) {
3139
3506
  const offendingRoles = Object.entries(schema.permissions).filter(([, perms]) => perms.read === true).map(([role]) => role);
3140
3507
  if (offendingRoles.length > 0) {
3141
3508
  const usesAware = Object.values(schema.permissions).some(
3142
- (p36) => typeof p36.read === "string" && VISIBILITY_AWARE.includes(p36.read)
3509
+ (p35) => typeof p35.read === "string" && VISIBILITY_AWARE.includes(p35.read)
3143
3510
  );
3144
3511
  if (!usesAware) {
3145
3512
  warnings.push(
@@ -3175,7 +3542,7 @@ function lintSchemaSet(schemas) {
3175
3542
  const warnings = [];
3176
3543
  if (!schemas.some((schema) => schema.name === "team_members")) {
3177
3544
  for (const schema of schemas) {
3178
- const teamRoles = Object.entries(schema.permissions).filter(([, p36]) => [p36.read, p36.update, p36.delete].includes("team")).map(([role]) => role);
3545
+ const teamRoles = Object.entries(schema.permissions).filter(([, p35]) => [p35.read, p35.update, p35.delete].includes("team")).map(([role]) => role);
3179
3546
  if (teamRoles.length > 0) {
3180
3547
  warnings.push(
3181
3548
  `[${schema.name}] role(s) ${teamRoles.join(", ")} use the 'team' permission level, but no 'team_members' collection is registered. 'team' resolves membership from team_members (teamId, userId, status), so without it those roles are denied everything \u2014 silently. Register a team_members schema or use another level.`
@@ -3188,12 +3555,12 @@ function lintSchemaSet(schemas) {
3188
3555
 
3189
3556
  // src/cli/lib/schema-lint.ts
3190
3557
  async function lintProjectSchemas(appDir) {
3191
- const schemasPath = join10(appDir, "src", "schemas.ts");
3192
- if (!existsSync12(schemasPath)) return null;
3558
+ const schemasPath = join11(appDir, "src", "schemas.ts");
3559
+ if (!existsSync14(schemasPath)) return null;
3193
3560
  let schemas;
3194
3561
  try {
3195
3562
  const esbuild = await import("esbuild");
3196
- const outFile = join10(appDir, ".wrangler", "deploy", "schemas.lint.bundle.mjs");
3563
+ const outFile = join11(appDir, ".wrangler", "deploy", "schemas.lint.bundle.mjs");
3197
3564
  await esbuild.build({
3198
3565
  entryPoints: [schemasPath],
3199
3566
  bundle: true,
@@ -3242,8 +3609,33 @@ function formatSchemaLintFindings(findings) {
3242
3609
  }
3243
3610
 
3244
3611
  // src/cli/lib/port.ts
3245
- import { createServer, Socket } from "net";
3612
+ import { Socket } from "net";
3246
3613
  var DEFAULT_PORT = 5173;
3614
+ async function ensurePortFree(port, host, waitMs = 15e3) {
3615
+ if (!await isPortListening(port, host)) return;
3616
+ if (!await isHttpResponding(port)) {
3617
+ const deadline = Date.now() + waitMs;
3618
+ while (Date.now() < deadline) {
3619
+ await new Promise((resolve23) => setTimeout(resolve23, 500));
3620
+ if (!await isPortListening(port, host)) return;
3621
+ }
3622
+ }
3623
+ const killArgv = port === DEFAULT_PORT ? ["deepspace", "dev", "kill"] : ["deepspace", "dev", "kill", "--port", String(port)];
3624
+ throw new Refusal(
3625
+ `Port ${port} is already in use.
3626
+ Free it with \`${killArgv.join(" ")}\`, or use another port: \`--port <other>\`.`,
3627
+ "port_in_use",
3628
+ { extra: { port } }
3629
+ );
3630
+ }
3631
+ async function isHttpResponding(port, timeoutMs = 2e3) {
3632
+ try {
3633
+ await fetch(`http://127.0.0.1:${port}/`, { signal: AbortSignal.timeout(timeoutMs) });
3634
+ return true;
3635
+ } catch {
3636
+ return false;
3637
+ }
3638
+ }
3247
3639
  function isPortListening(port, host, timeoutMs = 500) {
3248
3640
  const target2 = host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host;
3249
3641
  return new Promise((resolve23) => {
@@ -3280,7 +3672,7 @@ function resolvePort(arg) {
3280
3672
  // src/cli/commands/dev.ts
3281
3673
  var DEFAULT_DEV_HOST = "0.0.0.0";
3282
3674
  var READINESS_TIMEOUT_MS = 12e4;
3283
- var DEPLOY_URL = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
3675
+ var DEPLOY_URL2 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
3284
3676
  function noAppDirRefusal(start) {
3285
3677
  const children = findChildApps(start);
3286
3678
  const lines = [`No wrangler.toml found at or above ${start}.`];
@@ -3330,7 +3722,7 @@ var dev_default = defineDeepspaceCommand({
3330
3722
  preflightNodeVersion("dev start");
3331
3723
  const wranglerEnv = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
3332
3724
  const host = typeof args.host === "string" && args.host.trim() ? args.host.trim() : DEFAULT_DEV_HOST;
3333
- const start = resolve8(args.dir ?? ".");
3725
+ const start = resolve9(args.dir ?? ".");
3334
3726
  const appDir = findAppDir(start);
3335
3727
  if (!appDir) {
3336
3728
  throw noAppDirRefusal(start);
@@ -3360,18 +3752,13 @@ var dev_default = defineDeepspaceCommand({
3360
3752
  );
3361
3753
  } else if (!args.json) {
3362
3754
  console.warn(
3363
- `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.`
3755
+ `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.`
3364
3756
  );
3365
3757
  }
3366
3758
  }
3367
3759
  writeLaunchConfigIfMissing(appDir, detectAppName(appDir) ?? basename2(appDir), port, {
3368
3760
  updatePort: explicitPort
3369
3761
  });
3370
- ensureInstallReady(appDir);
3371
- const lintFindings = await lintProjectSchemas(appDir);
3372
- if (lintFindings && lintFindings.length > 0 && !args.json) {
3373
- for (const line of formatSchemaLintFindings(lintFindings)) console.warn(line);
3374
- }
3375
3762
  let token;
3376
3763
  try {
3377
3764
  token = await ensureToken();
@@ -3394,11 +3781,24 @@ var dev_default = defineDeepspaceCommand({
3394
3781
  if (wranglerEnv) say(`Wrangler env: ${wranglerEnv}`);
3395
3782
  say(`Port: ${port}`);
3396
3783
  if (host !== DEFAULT_DEV_HOST) say(`Host: ${host}`);
3784
+ ensureInstallReady(appDir);
3785
+ const lintFindings = await lintProjectSchemas(appDir);
3786
+ if (lintFindings && lintFindings.length > 0 && !args.json) {
3787
+ for (const line of formatSchemaLintFindings(lintFindings)) console.warn(line);
3788
+ }
3397
3789
  let generatedSecretsCache;
3398
- const appIdForSecrets = readAppId(appDir, wranglerEnv);
3790
+ let appIdForSecrets = null;
3791
+ try {
3792
+ appIdForSecrets = (await ensureAppRegistered(appDir, token, wranglerEnv))?.appId ?? null;
3793
+ } catch (registrationError) {
3794
+ process.stderr.write(
3795
+ `warning: could not register the app (${registrationError instanceof Error ? registrationError.message.split("\n")[0] : String(registrationError)}) \u2014 continuing without app secrets.
3796
+ `
3797
+ );
3798
+ }
3399
3799
  if (appIdForSecrets) {
3400
3800
  try {
3401
- const refreshed = await refreshSecretsCache(DEPLOY_URL, token, appIdForSecrets, wranglerEnv);
3801
+ const refreshed = await refreshSecretsCache(DEPLOY_URL2, token, appIdForSecrets, wranglerEnv);
3402
3802
  generatedSecretsCache = refreshed.rendered;
3403
3803
  if (refreshed.summary) say(refreshed.summary);
3404
3804
  } catch (err) {
@@ -3413,16 +3813,11 @@ var dev_default = defineDeepspaceCommand({
3413
3813
  });
3414
3814
  const wranglerConfig = prepareWranglerEnvConfig(appDir, wranglerEnv);
3415
3815
  preflightWindowsWorkerd(appDir);
3416
- if (await isPortListening(port, host)) {
3417
- const killArgv = port === DEFAULT_PORT ? ["deepspace", "dev", "kill"] : ["deepspace", "dev", "kill", "--port", String(port)];
3418
- const killCmd = killArgv.join(" ");
3816
+ try {
3817
+ await ensurePortFree(port, host);
3818
+ } catch (err) {
3419
3819
  wranglerConfig.cleanup();
3420
- throw new Refusal(
3421
- `Port ${port} is already in use.
3422
- Free it with \`${killCmd}\`, or start on another port: \`deepspace dev start --port <other>\`.`,
3423
- "port_in_use",
3424
- { extra: { port } }
3425
- );
3820
+ throw err;
3426
3821
  }
3427
3822
  const url = `http://${host === DEFAULT_DEV_HOST ? "localhost" : host}:${port}`;
3428
3823
  say("Starting dev server...\n");
@@ -3481,7 +3876,7 @@ Free it with \`${killCmd}\`, or start on another port: \`deepspace dev start --p
3481
3876
  });
3482
3877
 
3483
3878
  // src/cli/commands/kill.ts
3484
- import { spawnSync as spawnSync2 } from "child_process";
3879
+ import { spawnSync as spawnSync3 } from "child_process";
3485
3880
  import { readFileSync as readFileSync10, readdirSync as readdirSync3, readlinkSync } from "fs";
3486
3881
  import { setTimeout as wait } from "timers/promises";
3487
3882
  var SIGTERM_GRACE_MS = 1500;
@@ -3616,7 +4011,7 @@ function listenerPids(port) {
3616
4011
  }
3617
4012
  function runLsof(port) {
3618
4013
  try {
3619
- const r = spawnSync2("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-t"], {
4014
+ const r = spawnSync3("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-t"], {
3620
4015
  encoding: "utf-8"
3621
4016
  });
3622
4017
  if (r.error) return { pids: [], ran: false };
@@ -3701,7 +4096,7 @@ function sweepByName(names) {
3701
4096
  }
3702
4097
  function runPgrep(pgrepArgs) {
3703
4098
  try {
3704
- const r = spawnSync2("pgrep", pgrepArgs, { encoding: "utf-8" });
4099
+ const r = spawnSync3("pgrep", pgrepArgs, { encoding: "utf-8" });
3705
4100
  if (r.error) return { pids: [], ran: false };
3706
4101
  return {
3707
4102
  pids: r.status === 0 ? parsePidLines(r.stdout) : [],
@@ -3713,7 +4108,7 @@ function runPgrep(pgrepArgs) {
3713
4108
  }
3714
4109
  function run(cmd) {
3715
4110
  try {
3716
- const r = spawnSync2(cmd[0], cmd.slice(1), { encoding: "utf-8" });
4111
+ const r = spawnSync3(cmd[0], cmd.slice(1), { encoding: "utf-8" });
3717
4112
  if (r.status !== 0) return [];
3718
4113
  return parsePidLines(r.stdout);
3719
4114
  } catch {
@@ -3734,7 +4129,7 @@ function readPowershellPids(stdout) {
3734
4129
  function runPowershellChecked(script) {
3735
4130
  const exe = pwshOrPowershell();
3736
4131
  try {
3737
- const r = spawnSync2(exe, ["-NoProfile", "-NonInteractive", "-Command", withSentinel(script)], {
4132
+ const r = spawnSync3(exe, ["-NoProfile", "-NonInteractive", "-Command", withSentinel(script)], {
3738
4133
  encoding: "utf-8"
3739
4134
  });
3740
4135
  if (r.error) return { pids: [], ran: false };
@@ -3744,7 +4139,7 @@ function runPowershellChecked(script) {
3744
4139
  }
3745
4140
  }
3746
4141
  function pwshOrPowershell() {
3747
- const probe = spawnSync2("pwsh", ["-NoProfile", "-Command", "$PSVersionTable.PSVersion.Major"], {
4142
+ const probe = spawnSync3("pwsh", ["-NoProfile", "-Command", "$PSVersionTable.PSVersion.Major"], {
3748
4143
  encoding: "utf-8"
3749
4144
  });
3750
4145
  if (probe.status === 0) return "pwsh";
@@ -3772,12 +4167,12 @@ function isAlive(pid) {
3772
4167
 
3773
4168
  // src/cli/commands/test.ts
3774
4169
  import { readdirSync as readdirSync4 } from "fs";
3775
- import { join as join13, relative, resolve as resolve9, sep as sep2 } from "path";
3776
- import { sync as spawnSync4 } from "cross-spawn";
4170
+ import { join as join14, relative as relative2, resolve as resolve10, sep as sep2 } from "path";
4171
+ import { sync as spawnSync5 } from "cross-spawn";
3777
4172
 
3778
4173
  // src/cli/lib/playwright.ts
3779
4174
  import { execSync } from "child_process";
3780
- import { sync as spawnSync3 } from "cross-spawn";
4175
+ import { sync as spawnSync4 } from "cross-spawn";
3781
4176
  var childStdoutToStderr = false;
3782
4177
  function routeChildStdoutToStderr(enabled) {
3783
4178
  childStdoutToStderr = enabled;
@@ -3800,7 +4195,7 @@ function ensurePlaywright(appDir) {
3800
4195
  "Installing Chromium and its system libraries \u2014 this runs `apt-get` as root and can take several minutes on a cold machine."
3801
4196
  );
3802
4197
  }
3803
- const result = spawnSync3("npx", ["playwright", ...args], {
4198
+ const result = spawnSync4("npx", ["playwright", ...args], {
3804
4199
  cwd: appDir,
3805
4200
  stdio: childStdio()
3806
4201
  });
@@ -3816,10 +4211,10 @@ function ensurePlaywright(appDir) {
3816
4211
  import { readFileSync as readFileSync12 } from "fs";
3817
4212
 
3818
4213
  // src/testing/accounts.ts
3819
- import { existsSync as existsSync13, mkdirSync as mkdirSync5, readFileSync as readFileSync11 } from "fs";
4214
+ import { existsSync as existsSync15, mkdirSync as mkdirSync7, readFileSync as readFileSync11 } from "fs";
3820
4215
  import { homedir as homedir2 } from "os";
3821
- import { join as join12 } from "path";
3822
- var TEST_ACCOUNTS_PATH = join12(homedir2(), ".deepspace", "test-accounts.json");
4216
+ import { join as join13 } from "path";
4217
+ var TEST_ACCOUNTS_PATH = join13(homedir2(), ".deepspace", "test-accounts.json");
3823
4218
  var PRODUCTION_AUTH_ORIGIN = "https://auth.deep.space";
3824
4219
  var STAGING_AUTH_ORIGIN = "https://auth.deepspacesites.com";
3825
4220
  function currentTestAccountScope(env = process.env) {
@@ -3884,7 +4279,7 @@ function removeTestAccounts(ids, emails = [], scope = currentTestAccountScope())
3884
4279
  });
3885
4280
  }
3886
4281
  function readTestAccountStore() {
3887
- if (!existsSync13(TEST_ACCOUNTS_PATH)) return { version: 2, scopes: {} };
4282
+ if (!existsSync15(TEST_ACCOUNTS_PATH)) return { version: 2, scopes: {} };
3888
4283
  try {
3889
4284
  const raw = JSON.parse(readFileSync11(TEST_ACCOUNTS_PATH, "utf-8"));
3890
4285
  if (Array.isArray(raw)) return { version: 2, scopes: {} };
@@ -3909,7 +4304,7 @@ function validTestAccounts(value) {
3909
4304
  );
3910
4305
  }
3911
4306
  function saveTestAccountStore(store) {
3912
- mkdirSync5(join12(homedir2(), ".deepspace"), { recursive: true });
4307
+ mkdirSync7(join13(homedir2(), ".deepspace"), { recursive: true });
3913
4308
  writeSecretFileSync(TEST_ACCOUNTS_PATH, JSON.stringify(store, null, 2));
3914
4309
  }
3915
4310
 
@@ -3987,7 +4382,7 @@ async function syncTestAccountStore() {
3987
4382
  }
3988
4383
 
3989
4384
  // src/cli/commands/test.ts
3990
- var DEPLOY_URL2 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
4385
+ var DEPLOY_URL3 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
3991
4386
  var PLAYWRIGHT_OUTPUT_DIR = ".deepspace/test-results";
3992
4387
  var DEFAULT_SUITE_SPECS = ["tests/smoke.spec.ts", "tests/api.spec.ts"];
3993
4388
  function specsSkippedByDefaultSuite(appDir) {
@@ -4001,14 +4396,14 @@ function specsSkippedByDefaultSuite(appDir) {
4001
4396
  }
4002
4397
  for (const entry of entries) {
4003
4398
  if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
4004
- const full = join13(dir, entry.name);
4399
+ const full = join14(dir, entry.name);
4005
4400
  if (entry.isDirectory()) walk(full);
4006
4401
  else if (entry.name.endsWith(".spec.ts")) {
4007
- found.push(relative(appDir, full).split(sep2).join("/"));
4402
+ found.push(relative2(appDir, full).split(sep2).join("/"));
4008
4403
  }
4009
4404
  }
4010
4405
  };
4011
- walk(join13(appDir, "tests"));
4406
+ walk(join14(appDir, "tests"));
4012
4407
  return found.filter((spec) => !DEFAULT_SUITE_SPECS.includes(spec)).sort();
4013
4408
  }
4014
4409
  function playwrightTestArgs(testFiles, flags = {}) {
@@ -4077,7 +4472,7 @@ var test_default = defineDeepspaceCommand({
4077
4472
  project: typeof args.project === "string" && args.project ? args.project : void 0,
4078
4473
  headed: Boolean(args.headed)
4079
4474
  };
4080
- const start = resolve9(".");
4475
+ const start = resolve10(".");
4081
4476
  const appDir = findAppDir(start);
4082
4477
  if (!appDir) {
4083
4478
  throw noAppDirRefusal(start);
@@ -4085,7 +4480,7 @@ var test_default = defineDeepspaceCommand({
4085
4480
  const configuredPort = Boolean(args.port) || Boolean(process.env.DEEPSPACE_PORT);
4086
4481
  const worktreePort = configuredPort ? null : resolveWorktreePort(appDir);
4087
4482
  const port = worktreePort ?? resolvePort(args.port);
4088
- ensureInstallReady(appDir);
4483
+ if (suite !== "unit") await ensurePortFree(port, "0.0.0.0");
4089
4484
  let token;
4090
4485
  let ownerId;
4091
4486
  try {
@@ -4099,11 +4494,20 @@ var test_default = defineDeepspaceCommand({
4099
4494
  { action: cliAction("deepspace", "auth", "login") }
4100
4495
  );
4101
4496
  }
4497
+ ensureInstallReady(appDir);
4102
4498
  let generatedSecretsCache;
4103
- const appIdForSecrets = readAppId(appDir, wranglerEnv);
4499
+ let appIdForSecrets = null;
4500
+ try {
4501
+ appIdForSecrets = (await ensureAppRegistered(appDir, token, wranglerEnv))?.appId ?? null;
4502
+ } catch (registrationError) {
4503
+ process.stderr.write(
4504
+ `warning: could not register the app (${registrationError instanceof Error ? registrationError.message.split("\n")[0] : String(registrationError)}) \u2014 continuing without app secrets.
4505
+ `
4506
+ );
4507
+ }
4104
4508
  if (appIdForSecrets) {
4105
4509
  try {
4106
- const refreshed = await refreshSecretsCache(DEPLOY_URL2, token, appIdForSecrets, wranglerEnv);
4510
+ const refreshed = await refreshSecretsCache(DEPLOY_URL3, token, appIdForSecrets, wranglerEnv);
4107
4511
  generatedSecretsCache = refreshed.rendered;
4108
4512
  if (refreshed.summary) say(refreshed.summary);
4109
4513
  } catch (err) {
@@ -4144,7 +4548,9 @@ var test_default = defineDeepspaceCommand({
4144
4548
  break;
4145
4549
  case "unit":
4146
4550
  if (forwarded.grep || forwarded.project || forwarded.headed) {
4147
- console.error("note: --grep/--project/--headed apply to Playwright suites and are ignored by `unit`");
4551
+ console.error(
4552
+ "note: --grep/--project/--headed apply to Playwright suites and are ignored by `unit`"
4553
+ );
4148
4554
  }
4149
4555
  exitCode = runVitest(appDir, wranglerEnv);
4150
4556
  break;
@@ -4189,7 +4595,7 @@ function runSuite(appDir, argv, wranglerEnv, extraEnv = {}) {
4189
4595
  return 1;
4190
4596
  }
4191
4597
  try {
4192
- const result = spawnSync4("npx", argv, {
4598
+ const result = spawnSync5("npx", argv, {
4193
4599
  cwd: appDir,
4194
4600
  // Under --json the child's stdout is routed to our stderr: the live
4195
4601
  // suite output stays visible and stdout stays a single JSON line.
@@ -4212,8 +4618,20 @@ function runVitest(appDir, wranglerEnv) {
4212
4618
  }
4213
4619
 
4214
4620
  // src/cli/commands/screenshot.ts
4215
- import { sync as spawnSync5 } from "cross-spawn";
4216
- import { resolve as resolve10 } from "path";
4621
+ import { sync as spawnSync6 } from "cross-spawn";
4622
+ import { resolve as resolve11 } from "path";
4623
+ var DEFAULT_SCREENSHOT_WAIT_MS = 1e3;
4624
+ function normalizeWaitTimeout(value) {
4625
+ if (value === void 0) return DEFAULT_SCREENSHOT_WAIT_MS;
4626
+ const text2 = String(value).trim();
4627
+ if (!/^\d+$/.test(text2) || !Number.isSafeInteger(Number(text2))) {
4628
+ throw new Refusal(
4629
+ "Screenshot wait timeout must be a non-negative whole number of milliseconds.",
4630
+ "invalid_wait_timeout"
4631
+ );
4632
+ }
4633
+ return Number(text2);
4634
+ }
4217
4635
  function normalizeViewportSize(value) {
4218
4636
  if (value === void 0) return void 0;
4219
4637
  const match = /^(\d+)\s*[x,]\s*(\d+)$/i.exec(String(value).trim());
@@ -4255,25 +4673,32 @@ var screenshot_default = defineDeepspaceCommand({
4255
4673
  },
4256
4674
  "wait-for-timeout": {
4257
4675
  type: "string",
4258
- description: "Milliseconds to wait before capture",
4676
+ description: `Milliseconds to settle before capture (default ${DEFAULT_SCREENSHOT_WAIT_MS}, 0 disables)`,
4677
+ required: false
4678
+ },
4679
+ "wait-for-selector": {
4680
+ type: "string",
4681
+ description: "Visible selector to await before capture (off by default)",
4259
4682
  required: false
4260
4683
  }
4261
4684
  },
4262
4685
  async run({ args }) {
4686
+ routeChildStdoutToStderr(args.json === true);
4263
4687
  const viewport = normalizeViewportSize(args.viewport);
4264
- const appDir = resolve10(".");
4688
+ const waitForTimeout = normalizeWaitTimeout(args["wait-for-timeout"]);
4689
+ const appDir = resolve11(".");
4265
4690
  ensurePlaywright(appDir);
4266
4691
  const url = String(args.url);
4267
4692
  const output = String(args.output);
4268
4693
  const playwrightArgs = ["playwright", "screenshot", url, output];
4694
+ const waitForSelector = args["wait-for-selector"] === void 0 ? "" : String(args["wait-for-selector"]);
4269
4695
  if (args["full-page"]) playwrightArgs.push("--full-page");
4270
4696
  if (viewport) playwrightArgs.push("--viewport-size", viewport);
4271
- if (args["wait-for-timeout"] !== void 0) {
4272
- playwrightArgs.push("--wait-for-timeout", String(args["wait-for-timeout"]));
4273
- }
4274
- const result = spawnSync5("npx", playwrightArgs, {
4697
+ if (waitForSelector) playwrightArgs.push("--wait-for-selector", waitForSelector);
4698
+ if (waitForTimeout !== 0) playwrightArgs.push("--wait-for-timeout", String(waitForTimeout));
4699
+ const result = spawnSync6("npx", playwrightArgs, {
4275
4700
  cwd: appDir,
4276
- stdio: "inherit"
4701
+ stdio: childStdio()
4277
4702
  });
4278
4703
  if (result.status !== 0) {
4279
4704
  throw new Refusal(
@@ -4282,7 +4707,14 @@ var screenshot_default = defineDeepspaceCommand({
4282
4707
  );
4283
4708
  }
4284
4709
  return {
4285
- data: { url, output, fullPage: Boolean(args["full-page"]), viewport: viewport ?? null }
4710
+ data: {
4711
+ url,
4712
+ output,
4713
+ fullPage: Boolean(args["full-page"]),
4714
+ viewport: viewport ?? null,
4715
+ waitForSelector: waitForSelector || null,
4716
+ waitForTimeout
4717
+ }
4286
4718
  };
4287
4719
  }
4288
4720
  });
@@ -4303,8 +4735,13 @@ var create = defineDeepspaceCommand({
4303
4735
  },
4304
4736
  password: {
4305
4737
  type: "string",
4306
- description: "Password (min 8 characters)",
4307
- required: true
4738
+ description: "Password (min 8 characters). Prefer --password-stdin \u2014 argv is visible in `ps` and shell history",
4739
+ required: false
4740
+ },
4741
+ "password-stdin": {
4742
+ type: "boolean",
4743
+ description: "Read the password from stdin (one trailing newline stripped)",
4744
+ default: false
4308
4745
  },
4309
4746
  name: {
4310
4747
  type: "string",
@@ -4320,7 +4757,26 @@ var create = defineDeepspaceCommand({
4320
4757
  async run({ args }) {
4321
4758
  await ensureToken();
4322
4759
  const email = args.email;
4323
- const password = args.password;
4760
+ if (args["password-stdin"] && args.password !== void 0) {
4761
+ throw new Refusal("Pass either --password or --password-stdin, not both.", "invalid_flags");
4762
+ }
4763
+ let password;
4764
+ if (args["password-stdin"]) {
4765
+ if (process.stdin.isTTY) {
4766
+ throw new Refusal(
4767
+ '--password-stdin expects the password piped on stdin, e.g. `printf %s "$PW" | deepspace test accounts create --email bot@deepspace.test --password-stdin`.',
4768
+ "password_stdin_needs_pipe"
4769
+ );
4770
+ }
4771
+ password = (await readStreamText(process.stdin, MAX_STDIN_BYTES)).replace(/\r?\n$/, "");
4772
+ } else if (typeof args.password === "string") {
4773
+ password = args.password;
4774
+ } else {
4775
+ throw new Refusal(
4776
+ "A password is required: pipe it with --password-stdin (preferred), or pass --password.",
4777
+ "missing_password"
4778
+ );
4779
+ }
4324
4780
  const name = args.name ?? email.split("@")[0];
4325
4781
  const label = args.label;
4326
4782
  let account;
@@ -4688,7 +5144,7 @@ var test_accounts_default = defineCommand2({
4688
5144
  // src/cli/commands/deploy.ts
4689
5145
  import * as p12 from "@clack/prompts";
4690
5146
  import { defineCommand as defineCommand3 } from "citty";
4691
- import { join as join20, resolve as resolve15 } from "path";
5147
+ import { join as join21, resolve as resolve15 } from "path";
4692
5148
 
4693
5149
  // src/cli/lib/edge-propagation.ts
4694
5150
  import { request as httpRequest } from "http";
@@ -4852,11 +5308,11 @@ function describeFilesFailure(status3, body) {
4852
5308
  import * as p6 from "@clack/prompts";
4853
5309
  import { execSync as execSync2 } from "child_process";
4854
5310
  import { createHash as createHash2 } from "crypto";
4855
- import { existsSync as existsSync17, readFileSync as readFileSync15, readdirSync as readdirSync6 } from "fs";
4856
- import { dirname as dirname7, isAbsolute as isAbsolute3, join as join17, relative as relative2, resolve as resolve13, sep as sep3 } from "path";
5311
+ import { existsSync as existsSync19, readFileSync as readFileSync15, readdirSync as readdirSync6 } from "fs";
5312
+ import { dirname as dirname7, isAbsolute as isAbsolute4, join as join18, relative as relative3, resolve as resolve14, sep as sep3 } from "path";
4857
5313
 
4858
5314
  // src/build/app-id.ts
4859
- import { resolve as resolve11 } from "path";
5315
+ import { resolve as resolve12 } from "path";
4860
5316
  var APP_ID_ADOPTION_EDITS = {
4861
5317
  "vite.config.ts": `import { deepspaceBuild } from 'deepspace/build' and add
4862
5318
  deepspaceBuild({ appDir: fileURLToPath(new URL('.', import.meta.url)) }) to plugins`,
@@ -4870,11 +5326,11 @@ var APP_ID_ADOPTION_STEPS = `Point the client at the build-injected id \u2014 th
4870
5326
  `).join("") + APP_ID_ADOPTION_TOGETHER;
4871
5327
 
4872
5328
  // src/build/plugin.ts
4873
- import { existsSync as existsSync14, lstatSync, readdirSync as readdirSync5, unlinkSync as unlinkSync2 } from "fs";
4874
- import { join as join14, resolve as resolve12 } from "path";
5329
+ import { existsSync as existsSync16, lstatSync, readdirSync as readdirSync5, unlinkSync as unlinkSync2 } from "fs";
5330
+ import { join as join15, resolve as resolve13 } from "path";
4875
5331
  function removeBuildDevVars(workerDir) {
4876
- const path = join14(workerDir, ".dev.vars");
4877
- if (!existsSync14(path)) return false;
5332
+ const path = join15(workerDir, ".dev.vars");
5333
+ if (!existsSync16(path)) return false;
4878
5334
  if (!lstatSync(path).isFile()) {
4879
5335
  throw new Error(`Refusing unsafe build secret path: ${path}`);
4880
5336
  }
@@ -4883,12 +5339,12 @@ function removeBuildDevVars(workerDir) {
4883
5339
  }
4884
5340
 
4885
5341
  // src/documentation/deploy.ts
4886
- import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
4887
- import { join as join15 } from "path";
5342
+ import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
5343
+ import { join as join16 } from "path";
4888
5344
  function readDocumentationDeployManifest(appDir, clientDir) {
4889
- if (!existsSync15(join15(appDir, "documentation.json"))) return void 0;
4890
- const manifestPath = join15(clientDir, "_documentation", "manifest.json");
4891
- if (!existsSync15(manifestPath)) {
5345
+ if (!existsSync17(join16(appDir, "documentation.json"))) return void 0;
5346
+ const manifestPath = join16(clientDir, "_documentation", "manifest.json");
5347
+ if (!existsSync17(manifestPath)) {
4892
5348
  throw new Error(
4893
5349
  "documentation.json exists but the build produced no documentation manifest. Ensure vite.config.ts includes deepSpaceDocumentation() from deepspace/documentation."
4894
5350
  );
@@ -4897,8 +5353,8 @@ function readDocumentationDeployManifest(appDir, clientDir) {
4897
5353
  }
4898
5354
 
4899
5355
  // src/cli/commands/update/app-migrations.ts
4900
- import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
4901
- import { join as join16 } from "path";
5356
+ import { existsSync as existsSync18, readFileSync as readFileSync14 } from "fs";
5357
+ import { join as join17 } from "path";
4902
5358
 
4903
5359
  // src/shared/protocol/app-migrations.ts
4904
5360
  var APP_MIGRATION_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/;
@@ -4972,8 +5428,8 @@ var APP_MIGRATION_DEFINITIONS = APP_MIGRATION_GUIDANCE.map(({ id, description })
4972
5428
  description
4973
5429
  }));
4974
5430
  function readAppliedAppMigrations(appDir) {
4975
- const path = join16(appDir, APP_MIGRATIONS_MANIFEST);
4976
- if (!existsSync16(path)) return [];
5431
+ const path = join17(appDir, APP_MIGRATIONS_MANIFEST);
5432
+ if (!existsSync18(path)) return [];
4977
5433
  let parsed;
4978
5434
  try {
4979
5435
  parsed = JSON.parse(readFileSync14(path, "utf8"));
@@ -5035,16 +5491,16 @@ async function buildDeployBundle(options) {
5035
5491
  preparedWranglerConfig?.cleanup();
5036
5492
  }
5037
5493
  spinner2.stop(`Built (${Math.round((Date.now() - buildStarted) / 1e3)}s)`);
5038
- const deployConfigPath = join17(appDir, ".wrangler", "deploy", "config.json");
5039
- if (!existsSync17(deployConfigPath)) {
5494
+ const deployConfigPath = join18(appDir, ".wrangler", "deploy", "config.json");
5495
+ if (!existsSync19(deployConfigPath)) {
5040
5496
  output.die(
5041
5497
  "Build output config not found at .wrangler/deploy/config.json",
5042
5498
  "build_output_missing"
5043
5499
  );
5044
5500
  }
5045
5501
  const deployConfig = JSON.parse(readFileSync15(deployConfigPath, "utf-8"));
5046
- const outputWranglerPath = resolve13(dirname7(deployConfigPath), deployConfig.configPath);
5047
- if (!existsSync17(outputWranglerPath)) {
5502
+ const outputWranglerPath = resolve14(dirname7(deployConfigPath), deployConfig.configPath);
5503
+ if (!existsSync19(outputWranglerPath)) {
5048
5504
  output.die(`Output wrangler.json not found at ${outputWranglerPath}`, "build_output_missing");
5049
5505
  }
5050
5506
  const outputConfig = JSON.parse(readFileSync15(outputWranglerPath, "utf-8"));
@@ -5061,12 +5517,12 @@ async function buildDeployBundle(options) {
5061
5517
  } catch (error) {
5062
5518
  output.die(errorMessage(error), "build_output_unsafe");
5063
5519
  }
5064
- const workerBundlePath = join17(workerDir, outputConfig.main);
5065
- const clientDir = outputConfig.assets?.directory ? resolve13(workerDir, outputConfig.assets.directory) : null;
5066
- if (!existsSync17(workerBundlePath)) {
5520
+ const workerBundlePath = join18(workerDir, outputConfig.main);
5521
+ const clientDir = outputConfig.assets?.directory ? resolve14(workerDir, outputConfig.assets.directory) : null;
5522
+ if (!existsSync19(workerBundlePath)) {
5067
5523
  output.die(`Worker bundle not found at ${workerBundlePath}`, "build_output_missing");
5068
5524
  }
5069
- if (!clientDir || !existsSync17(clientDir)) {
5525
+ if (!clientDir || !existsSync19(clientDir)) {
5070
5526
  return output.die(`Client assets not found at ${clientDir}`, "build_output_missing");
5071
5527
  }
5072
5528
  try {
@@ -5134,16 +5590,16 @@ async function buildDeployBundle(options) {
5134
5590
  }
5135
5591
  var WORKER_ES_MODULE = /\.(?:js|mjs)$/i;
5136
5592
  function collectWorkerBundle(workerDir, mainPath, clientDir) {
5137
- const root = resolve13(workerDir);
5138
- const mainAbsolute = resolve13(mainPath);
5139
- const excludedClient = clientDir ? resolve13(clientDir) : null;
5593
+ const root = resolve14(workerDir);
5594
+ const mainAbsolute = resolve14(mainPath);
5595
+ const excludedClient = clientDir ? resolve14(clientDir) : null;
5140
5596
  const main2 = moduleName(root, mainAbsolute);
5141
5597
  const modules = [];
5142
5598
  const walk = (directory) => {
5143
5599
  for (const entry of readdirSync6(directory, { withFileTypes: true }).sort(
5144
5600
  (a, b) => a.name.localeCompare(b.name)
5145
5601
  )) {
5146
- const fullPath = join17(directory, entry.name);
5602
+ const fullPath = join18(directory, entry.name);
5147
5603
  if (excludedClient && isWithin(excludedClient, fullPath)) continue;
5148
5604
  if (entry.isDirectory()) {
5149
5605
  walk(fullPath);
@@ -5159,15 +5615,15 @@ function collectWorkerBundle(workerDir, mainPath, clientDir) {
5159
5615
  return { main: main2, modules: [mainModule, ...modules] };
5160
5616
  }
5161
5617
  function moduleName(root, path) {
5162
- const name = relative2(root, path);
5163
- if (!name || isAbsolute3(name) || name === ".." || name.startsWith(`..${sep3}`)) {
5618
+ const name = relative3(root, path);
5619
+ if (!name || isAbsolute4(name) || name === ".." || name.startsWith(`..${sep3}`)) {
5164
5620
  throw new Error(`Worker module is outside the generated output directory: ${path}`);
5165
5621
  }
5166
5622
  return name.split(sep3).join("/");
5167
5623
  }
5168
5624
  function isWithin(root, path) {
5169
- const name = relative2(root, path);
5170
- return name === "" || !isAbsolute3(name) && name !== ".." && !name.startsWith(`..${sep3}`);
5625
+ const name = relative3(root, path);
5626
+ return name === "" || !isAbsolute4(name) && name !== ".." && !name.startsWith(`..${sep3}`);
5171
5627
  }
5172
5628
  function extractRunWorkerFirst(config) {
5173
5629
  const raw = Array.isArray(config.assets?.run_worker_first) ? config.assets.run_worker_first : [];
@@ -5229,7 +5685,7 @@ function collectAssets(dir) {
5229
5685
  const walk = (currentDir, prefix) => {
5230
5686
  for (const entry of readdirSync6(currentDir, { withFileTypes: true })) {
5231
5687
  if (isDeployAssetControlFile(entry.name)) continue;
5232
- const fullPath = join17(currentDir, entry.name);
5688
+ const fullPath = join18(currentDir, entry.name);
5233
5689
  const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
5234
5690
  if (entry.isDirectory()) {
5235
5691
  walk(fullPath, relativePath);
@@ -5253,8 +5709,8 @@ function readDeployAssetConfig(clientDir) {
5253
5709
  ["_headers", "_headers"],
5254
5710
  ["_redirects", "_redirects"]
5255
5711
  ]) {
5256
- const filePath = join17(clientDir, file);
5257
- if (!existsSync17(filePath)) continue;
5712
+ const filePath = join18(clientDir, file);
5713
+ if (!existsSync19(filePath)) continue;
5258
5714
  const content = readFileSync15(filePath, "utf8");
5259
5715
  if (content.trim()) config[key] = content;
5260
5716
  }
@@ -5269,8 +5725,8 @@ function errorMessage(error) {
5269
5725
 
5270
5726
  // src/cli/commands/deploy/commerce.ts
5271
5727
  import * as p7 from "@clack/prompts";
5272
- import { existsSync as existsSync18 } from "fs";
5273
- import { join as join18 } from "path";
5728
+ import { existsSync as existsSync20 } from "fs";
5729
+ import { join as join19 } from "path";
5274
5730
  import { pathToFileURL as pathToFileURL2 } from "url";
5275
5731
 
5276
5732
  // src/shared/api-error.ts
@@ -5322,10 +5778,10 @@ function errorMessage2(error) {
5322
5778
  return error instanceof Error ? error.message : String(error);
5323
5779
  }
5324
5780
  async function loadDeclaration(appDir, sourceFile, outputFile, exportName) {
5325
- const sourcePath = join18(appDir, "src", sourceFile);
5326
- if (!existsSync18(sourcePath)) return void 0;
5781
+ const sourcePath = join19(appDir, "src", sourceFile);
5782
+ if (!existsSync20(sourcePath)) return void 0;
5327
5783
  const esbuild = await import("esbuild");
5328
- const outputPath = join18(appDir, ".wrangler", "deploy", outputFile);
5784
+ const outputPath = join19(appDir, ".wrangler", "deploy", outputFile);
5329
5785
  await esbuild.build({
5330
5786
  entryPoints: [sourcePath],
5331
5787
  bundle: true,
@@ -5418,7 +5874,7 @@ Stripe will keep charging them until you cancel. Cancel at the end of their curr
5418
5874
  }
5419
5875
  }
5420
5876
  async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
5421
- if (!existsSync18(join18(appDir, "src", "subscriptions.ts"))) return;
5877
+ if (!existsSync20(join19(appDir, "src", "subscriptions.ts"))) return;
5422
5878
  let plans;
5423
5879
  try {
5424
5880
  plans = await loadDeclaration(
@@ -5473,7 +5929,7 @@ async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
5473
5929
  }
5474
5930
  }
5475
5931
  async function syncOneTimeProducts(appDir, appName, token) {
5476
- if (!existsSync18(join18(appDir, "src", "products.ts"))) return;
5932
+ if (!existsSync20(join19(appDir, "src", "products.ts"))) return;
5477
5933
  let products;
5478
5934
  try {
5479
5935
  products = await loadDeclaration(
@@ -5578,184 +6034,13 @@ function createDeployOutput(json) {
5578
6034
  };
5579
6035
  }
5580
6036
 
5581
- // src/cli/commands/deploy/request.ts
5582
- import * as p10 from "@clack/prompts";
5583
- import { readFileSync as readFileSync16 } from "fs";
5584
-
5585
- // src/cli/commands/deploy/repository.ts
5586
- import * as p9 from "@clack/prompts";
5587
-
5588
- // src/cli/lib/vc-remote.ts
5589
- import { existsSync as existsSync19 } from "fs";
5590
- import { tmpdir } from "os";
5591
- import { isAbsolute as isAbsolute4, relative as relative3, resolve as resolve14 } from "path";
5592
- function spaceRemoteName(environment = DEEPSPACE_ENV) {
5593
- if (environment === "staging") return "space-staging";
5594
- if (environment === "production") return "space";
5595
- return "space-invalid";
5596
- }
5597
- var SPACE_REMOTE = spaceRemoteName();
5598
- function spaceTrackingRef(branch, remote = SPACE_REMOTE) {
5599
- return `refs/remotes/${remote}/${branch}`;
5600
- }
5601
- function spacePrivateRef(path, environment = DEEPSPACE_ENV) {
5602
- const root = environment === "production" ? "refs/deepspace" : `refs/deepspace/${environment}`;
5603
- return `${root}/${path}`;
5604
- }
5605
- function deployBaseUrl() {
5606
- return process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
5607
- }
5608
- function repoUrl(appId, base = deployBaseUrl()) {
5609
- return `${base.replace(/\/+$/, "")}/api/repo/${encodeURIComponent(appId)}`;
5610
- }
5611
- function gitAuthEnv(token, base = deployBaseUrl()) {
5612
- const existing = Number(process.env.GIT_CONFIG_COUNT ?? "0");
5613
- const index = Number.isInteger(existing) && existing > 0 ? existing : 0;
5614
- return {
5615
- GIT_CONFIG_COUNT: String(index + 1),
5616
- [`GIT_CONFIG_KEY_${index}`]: `http.${base.replace(/\/+$/, "")}.extraHeader`,
5617
- [`GIT_CONFIG_VALUE_${index}`]: `Authorization: Bearer ${token}`
5618
- };
5619
- }
5620
- function gitSourceImportEnv(token, revision, base = deployBaseUrl()) {
5621
- const env = gitAuthEnv(token, base);
5622
- const index = Number(env.GIT_CONFIG_COUNT);
5623
- return {
5624
- ...env,
5625
- GIT_CONFIG_COUNT: String(index + 1),
5626
- [`GIT_CONFIG_KEY_${index}`]: `http.${base.replace(/\/+$/, "")}.extraHeader`,
5627
- [`GIT_CONFIG_VALUE_${index}`]: `X-DeepSpace-Source-Revision: ${revision}`
5628
- };
5629
- }
5630
- function runGitRemote(cwd, token, args, opts = {}) {
5631
- return runGit(cwd, args, { ...opts, env: { ...gitAuthEnv(token), ...opts.env ?? {} } });
5632
- }
5633
- function entryIsTransient(cwd) {
5634
- const entry = process.argv[1];
5635
- if (!entry) return true;
5636
- if (/[\\/]_npx[\\/]/.test(entry) || entry.startsWith(tmpdir())) return true;
5637
- const rel = relative3(resolve14(cwd), resolve14(entry));
5638
- return rel === "" || !rel.startsWith("..") && !isAbsolute4(rel);
5639
- }
5640
- function helperEntryUsable(command) {
5641
- const match = /^!(?:DEEPSPACE_ENV=(production|staging) )?'([^']*)' '([^']*)' git-credential$/.exec(command);
5642
- const expectedEnvironment = DEEPSPACE_ENV === "production" ? void 0 : DEEPSPACE_ENV;
5643
- if (!match || match[1] !== expectedEnvironment) return false;
5644
- return existsSync19(match[2]) && existsSync19(match[3]);
5645
- }
5646
- function credentialHelperCommand(environment = DEEPSPACE_ENV) {
5647
- const entry = process.argv[1];
5648
- if (!entry) throw new Error("CLI entry path is unavailable");
5649
- const environmentPrefix = environment === "staging" ? "DEEPSPACE_ENV=staging " : "";
5650
- return `!${environmentPrefix}${shQuote(process.execPath)} ${shQuote(entry)} git-credential`;
5651
- }
5652
- function installCredentialHelper(cwd, url) {
5653
- try {
5654
- const origin = new URL(url);
5655
- const key = `credential.${origin.protocol}//${origin.host}.helper`;
5656
- const helper = credentialHelperCommand();
5657
- const wanted = ["", helper];
5658
- const transient = entryIsTransient(cwd);
5659
- const scope = transient ? "--worktree" : "--global";
5660
- if (transient) {
5661
- const enabled = runGit(cwd, ["config", "--local", "extensions.worktreeConfig", "true"], {
5662
- allowFail: true
5663
- });
5664
- if (enabled.status !== 0) return;
5665
- runGit(cwd, ["config", "--local", "--unset-all", key], { allowFail: true });
5666
- }
5667
- const current = runGit(cwd, ["config", scope, "--get-all", key], { allowFail: true });
5668
- if (current.status === 0) {
5669
- const existing = current.stdout.toString("utf-8").split("\n").filter((_, index, lines) => index < lines.length - 1);
5670
- if (existing.length === wanted.length && existing.every((value, i) => value === wanted[i])) {
5671
- return;
5672
- }
5673
- if (existing.length === wanted.length && existing[0] === "" && helperEntryUsable(existing[1])) {
5674
- return;
5675
- }
5676
- runGit(cwd, ["config", scope, "--unset-all", key], { allowFail: true });
5677
- }
5678
- runGit(cwd, ["config", scope, "--add", key, ""]);
5679
- runGit(cwd, ["config", scope, "--add", key, helper]);
5680
- if (scope === "--global" && !process.argv.includes("--json")) {
5681
- process.stderr.write(
5682
- `note: added a git credential helper for ${origin.host} to your global git config so plain \`git clone\`/\`git push\` to DeepSpace authenticate. Undo with: git config --global --unset-all ${key}
5683
- `
5684
- );
5685
- }
5686
- } catch {
5687
- }
5688
- }
5689
- function ensureSpaceRemote(cwd, appId, remote = SPACE_REMOTE, token) {
5690
- const url = repoUrl(appId);
5691
- const current = runGit(cwd, ["remote", "get-url", remote], { allowFail: true });
5692
- const existing = current.status === 0 ? current.stdout.toString("utf-8").trim() : null;
5693
- if (appIdFromRepoUrl(existing ?? "") !== appId) {
5694
- const owned = declaredAppIds(cwd);
5695
- if (owned.size > 0 && !owned.has(appId)) {
5696
- throw new InputError(
5697
- `This checkout declares ${[...owned].join(", ")}, but this command targets ${appId}. Pointing its \`${remote}\` remote at another app would send every later plain \`git push ${remote}\` to the wrong repo. Work on ${appId} from its own checkout (\`deepspace clone ${appId}\` into a new directory), or fork THIS checkout into a brand-new app of your own with \`deepspace app init --new-id\`.`,
5698
- "app_checkout_mismatch"
5699
- );
5700
- }
5701
- }
5702
- if (token) ensureGitIdentity(cwd, token);
5703
- if (existing === null) {
5704
- runGit(cwd, ["remote", "add", remote, url]);
5705
- } else if (existing !== url) {
5706
- const priorApp = appIdFromRepoUrl(existing);
5707
- if (priorApp && priorApp !== appId && !process.argv.includes("--json")) {
5708
- process.stderr.write(
5709
- `warning: remote '${remote}' was pointed at app ${priorApp}; re-aiming it at ${appId}.
5710
- `
5711
- );
5712
- }
5713
- runGit(cwd, ["remote", "set-url", remote, url]);
5714
- }
5715
- installCredentialHelper(cwd, url);
5716
- return url;
5717
- }
5718
- function removeSpaceRemote(cwd, remote = SPACE_REMOTE) {
5719
- const current = runGit(cwd, ["remote", "get-url", remote], { allowFail: true });
5720
- if (current.status !== 0) return false;
5721
- runGit(cwd, ["remote", "remove", remote]);
5722
- return true;
5723
- }
5724
- function ensureGitIdentity(cwd, token) {
5725
- try {
5726
- const configured = (key) => runGit(cwd, ["config", "--get", key], { allowFail: true }).stdout.toString("utf-8").trim() !== "";
5727
- const missingName = !configured("user.name");
5728
- const missingEmail = !configured("user.email");
5729
- if (!missingName && !missingEmail) return;
5730
- if (runGit(cwd, ["config", "--get", "--type=bool", "user.useConfigOnly"], { allowFail: true }).stdout.toString("utf-8").trim() === "true") {
5731
- return;
5732
- }
5733
- const payload = decodeJwtPayload(token);
5734
- if (!payload.email) return;
5735
- if (missingName) {
5736
- runGit(cwd, ["config", "--local", "user.name", payload.name || payload.email], {
5737
- allowFail: true
5738
- });
5739
- }
5740
- if (missingEmail) {
5741
- runGit(cwd, ["config", "--local", "user.email", payload.email], { allowFail: true });
5742
- }
5743
- const wrote = [...missingEmail ? ["user.email"] : [], ...missingName ? ["user.name"] : []];
5744
- const dir = /^[A-Za-z0-9_./:@-]+$/.test(cwd) ? cwd : shQuote(cwd);
5745
- const undo = wrote.map((key) => `git -C ${dir} config --local --unset ${key}`).join(" && ");
5746
- process.stderr.write(
5747
- `Git identity for this checkout set from your DeepSpace login (${payload.email}).
5748
- Undo with: ${undo}
5749
- `
5750
- );
5751
- } catch {
5752
- }
5753
- }
5754
- function appIdFromRepoUrl(url) {
5755
- const match = /\/api\/repo\/([^/]+)\/?$/.exec(url);
5756
- return match ? decodeURIComponent(match[1]) : null;
5757
- }
5758
-
6037
+ // src/cli/commands/deploy/request.ts
6038
+ import * as p10 from "@clack/prompts";
6039
+ import { readFileSync as readFileSync16 } from "fs";
6040
+
6041
+ // src/cli/commands/deploy/repository.ts
6042
+ import * as p9 from "@clack/prompts";
6043
+
5759
6044
  // src/cli/lib/git/safety.ts
5760
6045
  var SECRET_FILE_RE = /(^|\/)(\.dev\.vars(\.[^/]*)?|\.env(\.[^/]*)?|\.npmrc|\.envrc|\.mcp\.json)$/i;
5761
6046
  var SECRET_TEMPLATE_RE = /\.(example|sample|template)$/i;
@@ -6196,41 +6481,6 @@ function selectGitHubRemote(cwd, options = {}) {
6196
6481
  const matching = repository ? remotes.filter((remote) => remote.repository === repository) : remotes;
6197
6482
  return matching.find((remote) => remote.name === "origin") ?? (matching.length === 1 ? matching[0] : null);
6198
6483
  }
6199
- function remotePublicRefs(cwd, remote) {
6200
- let output;
6201
- try {
6202
- output = runGit(cwd, ["ls-remote", "--refs", remote]).stdout.toString("utf-8");
6203
- } catch (error) {
6204
- if (!isGitCredentialFailure(error)) throw error;
6205
- throw new GitError(
6206
- "Git could not authenticate the remote. In a container, forward an SSH agent or use an ephemeral Git credential helper, then retry. Do not put tokens in remote URLs.",
6207
- "github_credentials_required"
6208
- );
6209
- }
6210
- return output.split("\n").map((line) => line.trim()).filter(Boolean).map((line) => {
6211
- const [oid, name] = line.split(/\s+/, 2);
6212
- return { oid, name };
6213
- }).filter(
6214
- (ref) => /^[0-9a-f]{40}$/.test(ref.oid) && (ref.name.startsWith("refs/heads/") || ref.name.startsWith("refs/tags/"))
6215
- );
6216
- }
6217
- function isGitCredentialFailure(error) {
6218
- if (!(error instanceof Error)) return false;
6219
- return /authentication failed|could not read username|permission denied \(publickey\)|repository not found|terminal prompts disabled/i.test(
6220
- error.message
6221
- );
6222
- }
6223
- function localTransferRefs(cwd, prefix, target2) {
6224
- const output = runGit(cwd, [
6225
- "for-each-ref",
6226
- "--format=%(refname) %(objectname)",
6227
- prefix
6228
- ]).stdout.toString("utf-8");
6229
- return output.split("\n").map((line) => line.trim()).filter(Boolean).map((line) => {
6230
- const [name, oid] = line.split(/\s+/, 2);
6231
- return { name: `${target2}/${name.slice(`${prefix}/`.length)}`, oid };
6232
- }).filter((ref) => /^[0-9a-f]{40}$/.test(ref.oid));
6233
- }
6234
6484
 
6235
6485
  // src/cli/commands/deploy/repository.ts
6236
6486
  function describeWorktree(appDir) {
@@ -6240,17 +6490,27 @@ function describeWorktree(appDir) {
6240
6490
  return { branch: null, dirty: null };
6241
6491
  }
6242
6492
  }
6493
+ function externalGitSource(appDir, source) {
6494
+ if (source?.provider === "github") return true;
6495
+ if (source !== null) return false;
6496
+ try {
6497
+ return listGitHubRemotes(appDir).length > 0;
6498
+ } catch {
6499
+ return false;
6500
+ }
6501
+ }
6502
+ function observedGitHubRepository(appDir) {
6503
+ try {
6504
+ return selectGitHubRemote(appDir)?.repository ?? listGitHubRemotes(appDir)[0]?.repository ?? null;
6505
+ } catch {
6506
+ return null;
6507
+ }
6508
+ }
6243
6509
  function preflightDeployRepository(options) {
6244
6510
  const { appDir, push, source } = options;
6245
- if (!push || source?.provider === "github") return null;
6511
+ if (!push || externalGitSource(appDir, source)) return null;
6246
6512
  try {
6247
6513
  assertSyncableRepo(appDir);
6248
- if (!source && listGitHubRemotes(appDir).length > 0) {
6249
- return {
6250
- code: "source_unclaimed",
6251
- error: "This app has a GitHub remote but no claimed source. Choose once with `deepspace app source github` (manual GitHub ownership) or `deepspace app source deepspace` (packaged DeepSpace source), then deploy again."
6252
- };
6253
- }
6254
6514
  assertNoOperationInProgress(appDir);
6255
6515
  const branch = currentBranch(appDir);
6256
6516
  if (!isWorkTreeClean(appDir)) return dirtyWorktreeRefusal(branch, appDir);
@@ -6267,9 +6527,18 @@ async function syncDeployRepository(options) {
6267
6527
  const deployKey = mintIdempotencyKey();
6268
6528
  let source = sourceState.source;
6269
6529
  let sourceRevision = sourceState.revision;
6270
- const baseReleaseId = null;
6271
6530
  const worktree = describeWorktree(appDir);
6272
- if (source?.provider === "github") {
6531
+ const external = externalGitSource(appDir, source);
6532
+ if (external) {
6533
+ if (sourceState.source?.provider === "github") {
6534
+ try {
6535
+ if (removeSpaceRemote(appDir)) {
6536
+ p9.log.info(`Removed the stale \`space\` git remote \u2014 GitHub owns this app's source.`);
6537
+ }
6538
+ } catch {
6539
+ }
6540
+ }
6541
+ const observedRepository = source === null ? observedGitHubRepository(appDir) : null;
6273
6542
  const where = `GitHub source: shipping the working tree of ${worktree.branch ?? "(detached HEAD)"}`;
6274
6543
  if (worktree.dirty) {
6275
6544
  p9.log.warn(
@@ -6278,7 +6547,7 @@ async function syncDeployRepository(options) {
6278
6547
  } else {
6279
6548
  p9.log.info(`${where} (clean). GitHub-source releases record no commit lineage.`);
6280
6549
  }
6281
- return { commitOid, recoverable, deployKey, source, sourceRevision, baseReleaseId, ...worktree };
6550
+ return { commitOid, recoverable, deployKey, source, sourceRevision, observedRepository, ...worktree };
6282
6551
  }
6283
6552
  if (!push) {
6284
6553
  try {
@@ -6286,7 +6555,12 @@ async function syncDeployRepository(options) {
6286
6555
  commitOid = resolveCommit(appDir, "HEAD");
6287
6556
  } catch {
6288
6557
  }
6289
- return { commitOid, recoverable, deployKey, source, sourceRevision, baseReleaseId, ...worktree };
6558
+ if (worktree.dirty) {
6559
+ p9.log.warn(
6560
+ `--no-push: shipping the working tree WITH uncommitted changes \u2014 the recorded commit ${commitOid ? commitOid.slice(0, 10) : ""} is not exactly what went live.`
6561
+ );
6562
+ }
6563
+ return { commitOid, recoverable, deployKey, source, sourceRevision, observedRepository: null, ...worktree };
6290
6564
  }
6291
6565
  try {
6292
6566
  const preflight = preflightDeployRepository({ appDir, push, source });
@@ -6302,7 +6576,7 @@ async function syncDeployRepository(options) {
6302
6576
  }
6303
6577
  const tip = resolveCommit(appDir, `refs/heads/${branch}`);
6304
6578
  if (!tip) {
6305
- return { commitOid, recoverable, deployKey, source, sourceRevision, baseReleaseId, ...worktree };
6579
+ return { commitOid, recoverable, deployKey, source, sourceRevision, observedRepository: null, ...worktree };
6306
6580
  }
6307
6581
  const secretFiles = workspaceBranchId ? [] : trackedSecretFiles(appDir, tip);
6308
6582
  if (workspaceBranchId) {
@@ -6331,6 +6605,11 @@ async function syncDeployRepository(options) {
6331
6605
  `Skipping cloud-repo push \u2014 the branch tracks secret file(s): ${secretFiles.join(", ")}. ` + secretRecoverySentence(secretFiles, "`deepspace push`") + ` Without the push this deploy carries no source lineage: if the live release has one, the server refuses it as stale (--ignore-stale replaces it anyway), and this release's source isn't recoverable until the push works.`
6332
6606
  );
6333
6607
  } else {
6608
+ if (sourceState.source === null) {
6609
+ process.stderr.write(
6610
+ "This app's source is unclaimed \u2014 this deploy's push claims DeepSpace source for it, permanently.\n"
6611
+ );
6612
+ }
6334
6613
  const pushResult = await pushWithTransientRetry(
6335
6614
  () => pushToSpace(appDir, token, `refs/heads/${branch}:refs/heads/${branch}`, {
6336
6615
  remote: sourceRemote
@@ -6391,7 +6670,7 @@ async function syncDeployRepository(options) {
6391
6670
  const failure = deployRepositoryFailure(error, appDir);
6392
6671
  output.die(failure.error, failure.code);
6393
6672
  }
6394
- return { commitOid, recoverable, deployKey, source, sourceRevision, baseReleaseId, ...worktree };
6673
+ return { commitOid, recoverable, deployKey, source, sourceRevision, observedRepository: null, ...worktree };
6395
6674
  }
6396
6675
  function classifyRemoteState(appDir, token, branch, localTip, remote) {
6397
6676
  let remoteTip = null;
@@ -6617,7 +6896,10 @@ async function deployBuiltBundle(options) {
6617
6896
  form.append("sourceRepository", repository.source.repository);
6618
6897
  }
6619
6898
  }
6620
- if (repository.baseReleaseId) form.append("baseReleaseId", repository.baseReleaseId);
6899
+ if (repository.dirty !== null) form.append("sourceDirty", repository.dirty ? "true" : "false");
6900
+ if (repository.observedRepository) {
6901
+ form.append("observedRepository", repository.observedRepository);
6902
+ }
6621
6903
  if (ignoreStale) form.append("ignoreStale", "true");
6622
6904
  form.append("deployKey", repository.deployKey);
6623
6905
  return form;
@@ -6951,21 +7233,21 @@ function errorMessage5(error) {
6951
7233
 
6952
7234
  // src/cli/commands/deploy/lock.ts
6953
7235
  import { randomUUID as randomUUID2 } from "crypto";
6954
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync17, rmSync as rmSync4, writeFileSync as writeFileSync5 } from "fs";
6955
- import { dirname as dirname8, join as join19 } from "path";
7236
+ import { mkdirSync as mkdirSync8, readFileSync as readFileSync17, rmSync as rmSync6, writeFileSync as writeFileSync6 } from "fs";
7237
+ import { dirname as dirname8, join as join20 } from "path";
6956
7238
  function deployLockPath(appDir) {
6957
- return join19(appDir, ".deepspace", "deploy.lock");
7239
+ return join20(appDir, ".deepspace", "deploy.lock");
6958
7240
  }
6959
7241
  function acquireDeployLock(appDir) {
6960
7242
  const path = deployLockPath(appDir);
6961
- mkdirSync6(dirname8(path), { recursive: true });
7243
+ mkdirSync8(dirname8(path), { recursive: true });
6962
7244
  const record = {
6963
7245
  pid: process.pid,
6964
7246
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
6965
7247
  token: randomUUID2()
6966
7248
  };
6967
7249
  try {
6968
- writeFileSync5(path, JSON.stringify(record), { flag: "wx" });
7250
+ writeFileSync6(path, JSON.stringify(record), { flag: "wx" });
6969
7251
  } catch (err) {
6970
7252
  if (err.code !== "EEXIST") throw err;
6971
7253
  const holder = readLock(path);
@@ -6981,7 +7263,7 @@ function acquireDeployLock(appDir) {
6981
7263
  );
6982
7264
  }
6983
7265
  return () => {
6984
- if (readLock(path)?.token === record.token) rmSync4(path, { force: true });
7266
+ if (readLock(path)?.token === record.token) rmSync6(path, { force: true });
6985
7267
  };
6986
7268
  }
6987
7269
  function readLock(path) {
@@ -6994,7 +7276,7 @@ function readLock(path) {
6994
7276
  }
6995
7277
 
6996
7278
  // src/cli/commands/deploy.ts
6997
- var DEPLOY_URL3 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
7279
+ var DEPLOY_URL4 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
6998
7280
  var DEPLOY_TOKEN_MIN_VALIDITY_MS = 10 * 60 * 1e3;
6999
7281
  var POST_DEPLOY_TOKEN_MIN_VALIDITY_MS = 2 * 60 * 1e3;
7000
7282
  var deploy_default = defineCommand3({
@@ -7047,6 +7329,7 @@ var deploy_default = defineCommand3({
7047
7329
  },
7048
7330
  async run({ args }) {
7049
7331
  const output = createDeployOutput(args.json === true);
7332
+ setPlainProgress(output.json);
7050
7333
  try {
7051
7334
  await runDeploy(args, output);
7052
7335
  } catch (error) {
@@ -7067,15 +7350,7 @@ async function runDeploy(args, output) {
7067
7350
  if (selectorRefusal) output.die(selectorRefusal.error, selectorRefusal.code);
7068
7351
  const envName = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
7069
7352
  if (!hasWranglerConfig(appDir)) {
7070
- output.die(noWranglerConfigMessage(join20(appDir, "wrangler.toml")), "not_in_app_repo");
7071
- }
7072
- try {
7073
- ensureInstallReady(appDir);
7074
- } catch (error) {
7075
- const refusal = error;
7076
- output.die(refusal.message ?? String(error), refusal.code ?? "deps_missing", {
7077
- action: refusal.action
7078
- });
7353
+ output.die(noWranglerConfigMessage(join21(appDir, "wrangler.toml")), "not_in_app_repo");
7079
7354
  }
7080
7355
  const releaseLock = acquireDeployLock(appDir);
7081
7356
  try {
@@ -7099,24 +7374,30 @@ async function runLockedDeploy(args, output, appDir, envName) {
7099
7374
  );
7100
7375
  }
7101
7376
  const appName = nameResult.name;
7102
- const appId = readAppId(appDir, envName);
7377
+ let appId = readAppId(appDir, envName);
7103
7378
  p12.log.info(envName ? `App: ${appName} (env: ${envName})` : `App: ${appName}`);
7104
7379
  const { token, ownerId } = await authenticate(appDir, output);
7380
+ try {
7381
+ ensureInstallReady(appDir);
7382
+ } catch (error) {
7383
+ const refusal = error;
7384
+ return output.die(refusal.message ?? String(error), refusal.code ?? "install_failed", {
7385
+ action: refusal.action
7386
+ });
7387
+ }
7105
7388
  if (!appId) {
7106
- return output.die(
7107
- `No app id in wrangler.toml${envName ? ` for [env.${envName}]` : ""}. Run \`deepspace app init${envName ? ` --env ${envName}` : ""}\` to register this app, then re-deploy.`,
7108
- "app_not_initialized",
7109
- {
7110
- action: {
7111
- cwd: appDir,
7112
- argv: ["deepspace", "app", "init", ...envName ? ["--env", envName] : []]
7113
- },
7114
- actionRequired: true
7115
- }
7116
- );
7389
+ const ensured = await ensureAppRegistered(appDir, token, envName, { commitScaffold: true });
7390
+ if (!ensured) {
7391
+ return output.die(
7392
+ envName ? `No app id for [env.${envName}] \u2014 declare the [env.${envName}] block with its own DEEPSPACE_APP_ID entry (the \`__APP_ID__\` placeholder is enough), or run \`deepspace app init --env ${envName}\`.` : "wrangler.toml declares no DEEPSPACE_APP_ID \u2014 this does not look like a DeepSpace app. Run `deepspace app init` to register it as one.",
7393
+ envName ? "no_app_id_for_env" : "app_not_initialized"
7394
+ );
7395
+ }
7396
+ appId = ensured.appId;
7117
7397
  }
7398
+ commitScaffoldIfUnborn(appDir, token);
7118
7399
  p12.log.info(`Id: ${appId}`);
7119
- const sourceState = await getAppSource(DEPLOY_URL3, token, appId).catch((error) => {
7400
+ const sourceState = await getAppSource(DEPLOY_URL4, token, appId).catch((error) => {
7120
7401
  if (errorCode(error) === "forbidden") {
7121
7402
  output.die(forbiddenDeployMessage(appId, token), "forbidden");
7122
7403
  }
@@ -7129,7 +7410,7 @@ async function runLockedDeploy(args, output, appDir, envName) {
7129
7410
  );
7130
7411
  }
7131
7412
  const secretsCache = await loadDeploySecrets({
7132
- deployUrl: DEPLOY_URL3,
7413
+ deployUrl: DEPLOY_URL4,
7133
7414
  appDir,
7134
7415
  appId,
7135
7416
  envName,
@@ -7173,7 +7454,7 @@ async function runLockedDeploy(args, output, appDir, envName) {
7173
7454
  }
7174
7455
  const { token: deployToken } = await authenticate(appDir, output, DEPLOY_TOKEN_MIN_VALIDITY_MS);
7175
7456
  const repository = await syncDeployRepository({
7176
- deployUrl: DEPLOY_URL3,
7457
+ deployUrl: DEPLOY_URL4,
7177
7458
  appDir,
7178
7459
  appId,
7179
7460
  token: deployToken,
@@ -7189,7 +7470,7 @@ async function runLockedDeploy(args, output, appDir, envName) {
7189
7470
  output
7190
7471
  });
7191
7472
  const body = await deployBuiltBundle({
7192
- deployUrl: DEPLOY_URL3,
7473
+ deployUrl: DEPLOY_URL4,
7193
7474
  appDir,
7194
7475
  appId,
7195
7476
  appName,
@@ -7342,7 +7623,7 @@ async function authenticate(appDir, output, minimumValidityMs) {
7342
7623
 
7343
7624
  // src/cli/commands/undeploy.ts
7344
7625
  import * as p13 from "@clack/prompts";
7345
- var DEPLOY_URL4 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
7626
+ var DEPLOY_URL5 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
7346
7627
  var undeploy_default = defineDeepspaceCommand({
7347
7628
  meta: {
7348
7629
  name: "undeploy",
@@ -7361,7 +7642,8 @@ var undeploy_default = defineDeepspaceCommand({
7361
7642
  },
7362
7643
  yes: {
7363
7644
  type: "boolean",
7364
- description: "Skip the interactive confirmation. (Scripts and --json callers are never prompted; the command itself is their consent.)",
7645
+ alias: "y",
7646
+ description: "Skip the confirmation (required for --json / non-interactive)",
7365
7647
  default: false
7366
7648
  }
7367
7649
  },
@@ -7379,7 +7661,7 @@ var undeploy_default = defineDeepspaceCommand({
7379
7661
  let label;
7380
7662
  const positional = typeof args.name === "string" && args.name.trim() ? args.name.trim() : void 0;
7381
7663
  if (positional) {
7382
- appId = await resolveAppSelector(DEPLOY_URL4, token, positional);
7664
+ appId = await resolveAppSelector(DEPLOY_URL5, token, positional);
7383
7665
  label = positional;
7384
7666
  } else if (hasWranglerConfig(process.cwd())) {
7385
7667
  appId = readAppId(process.cwd(), envName) ?? void 0;
@@ -7393,6 +7675,13 @@ var undeploy_default = defineDeepspaceCommand({
7393
7675
  );
7394
7676
  }
7395
7677
  const target2 = label && label !== appId ? `${label} (${appId})` : appId;
7678
+ if (args.yes !== true && (args.json === true || !process.stdin.isTTY)) {
7679
+ throw new Refusal(
7680
+ `Undeploying ${target2} takes its URL offline immediately and destroys its live data \u2014 records, messages, canvas state, cron history \u2014 with the worker (secrets and the registration stay). Re-run with --yes to confirm.`,
7681
+ "confirmation_required",
7682
+ { extra: { appId } }
7683
+ );
7684
+ }
7396
7685
  if (!args.json && !args.yes && process.stdin.isTTY) {
7397
7686
  p13.intro(`Undeploying ${target2}`);
7398
7687
  const confirmed = await p13.confirm({
@@ -7407,7 +7696,7 @@ var undeploy_default = defineDeepspaceCommand({
7407
7696
  }
7408
7697
  const s = args.json ? null : createSpinner();
7409
7698
  s?.start("Removing...");
7410
- const res = await fetch(`${DEPLOY_URL4}/api/deploy/${appId}`, {
7699
+ const res = await fetch(`${DEPLOY_URL5}/api/deploy/${appId}`, {
7411
7700
  method: "DELETE",
7412
7701
  headers: { Authorization: `Bearer ${token}` }
7413
7702
  });
@@ -7492,7 +7781,7 @@ var whoami_default = defineDeepspaceCommand({
7492
7781
  });
7493
7782
 
7494
7783
  // src/cli/commands/apps.ts
7495
- var DEPLOY_URL5 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
7784
+ var DEPLOY_URL6 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
7496
7785
  var apps_default = defineDeepspaceCommand({
7497
7786
  meta: {
7498
7787
  name: "apps",
@@ -7500,7 +7789,7 @@ var apps_default = defineDeepspaceCommand({
7500
7789
  },
7501
7790
  async run({ args }) {
7502
7791
  const token = await ensureToken();
7503
- const { apps, pendingTransfers } = await listAppsPage(DEPLOY_URL5, token);
7792
+ const { apps, pendingTransfers } = await listAppsPage(DEPLOY_URL6, token);
7504
7793
  if (!args.json) {
7505
7794
  if (!apps.length && !pendingTransfers.length) {
7506
7795
  console.log("No apps yet. Create one with `npx create-deepspace <name>` and `deepspace deploy`.");
@@ -7558,7 +7847,7 @@ import { setTimeout as delay } from "timers/promises";
7558
7847
  var LOG_LEVELS = ["debug", "log", "info", "warn", "error"];
7559
7848
 
7560
7849
  // src/cli/commands/logs.ts
7561
- var DEPLOY_URL6 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
7850
+ var DEPLOY_URL7 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
7562
7851
  var RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
7563
7852
  var DEFAULT_WINDOW_MS = 15 * 60 * 1e3;
7564
7853
  var MIN_POLL_MS = 2500;
@@ -7719,7 +8008,7 @@ var logs_default = defineCommand4({
7719
8008
  const envArg = args.env;
7720
8009
  assertAppTargetResolvable(args.app, { wranglerEnv: envArg });
7721
8010
  const token = await ensureToken();
7722
- const appId = await resolveAppTarget(DEPLOY_URL6, token, args.app, { wranglerEnv: envArg });
8011
+ const appId = await resolveAppTarget(DEPLOY_URL7, token, args.app, { wranglerEnv: envArg });
7723
8012
  if (args.level && !LOG_LEVELS.includes(args.level)) {
7724
8013
  throw new InputError(
7725
8014
  `Invalid --level "${args.level}". Use: ${LOG_LEVELS.join(", ")}`,
@@ -7738,7 +8027,7 @@ var logs_default = defineCommand4({
7738
8027
  }
7739
8028
  const initialSince = args.since ? parseSince(args.since) : Date.now() - DEFAULT_WINDOW_MS;
7740
8029
  const windowLabel = args.since?.trim() || "15m";
7741
- const registryRow = (await listApps(DEPLOY_URL6, token)).find((app2) => app2.appId === appId);
8030
+ const registryRow = (await listApps(DEPLOY_URL7, token)).find((app2) => app2.appId === appId);
7742
8031
  if (registryRow && registryRow.deployedAt === null) {
7743
8032
  const appDir = args.app === void 0 ? findAppDir() : null;
7744
8033
  throw new Refusal(
@@ -7760,7 +8049,7 @@ var logs_default = defineCommand4({
7760
8049
  if (args.level) params.set("level", args.level);
7761
8050
  if (args.search) params.set("search", args.search);
7762
8051
  const path = `/api/apps/${appId}/logs?${params}`;
7763
- return signal ? apiFetch(DEPLOY_URL6, token2, path, { signal }) : apiFetchReadWithRetry(DEPLOY_URL6, token2, path);
8052
+ return signal ? apiFetch(DEPLOY_URL7, token2, path, { signal }) : apiFetchReadWithRetry(DEPLOY_URL7, token2, path);
7764
8053
  };
7765
8054
  const print = (events) => {
7766
8055
  for (const e of events) {
@@ -7911,6 +8200,8 @@ function formatDate(iso) {
7911
8200
  function renderSummary(summary) {
7912
8201
  const { credits, usageByIntegration } = summary;
7913
8202
  const lines = [];
8203
+ lines.push("Account usage (all apps)");
8204
+ lines.push("");
7914
8205
  const renews = credits.subscription.renewsAt ? ` \xB7 renews ${formatDate(credits.subscription.renewsAt)}` : "";
7915
8206
  lines.push(`Plan: ${credits.subscription.tier}${renews}`);
7916
8207
  lines.push(
@@ -7939,7 +8230,9 @@ function renderSummary(summary) {
7939
8230
  const nameWidth = Math.max(11, ...usageByIntegration.map((u) => u.name.length));
7940
8231
  lines.push(`${"INTEGRATION".padEnd(nameWidth)} CALLS COST`);
7941
8232
  for (const u of usageByIntegration) {
7942
- lines.push(`${u.name.padEnd(nameWidth)} ${String(u.count).padEnd(5)} ${formatUsd(u.totalCost)}`);
8233
+ lines.push(
8234
+ `${u.name.padEnd(nameWidth)} ${String(u.count).padEnd(5)} ${formatUsd(u.totalCost)}`
8235
+ );
7943
8236
  }
7944
8237
  }
7945
8238
  lines.push("");
@@ -7949,46 +8242,46 @@ function renderSummary(summary) {
7949
8242
  var usage_default = defineDeepspaceCommand({
7950
8243
  meta: {
7951
8244
  name: "usage",
7952
- description: "Show credit balance, quota headroom, and per-integration spend"
8245
+ description: "Show account-wide credits and integration spend (all apps)"
7953
8246
  },
7954
8247
  async run({ args }) {
7955
8248
  const token = await ensureToken();
7956
8249
  const summary = await apiFetch(API_URL3, token, "/api/usage/summary");
7957
8250
  if (!args.json) console.log(renderSummary(summary));
7958
- return { data: { ...summary } };
8251
+ return { data: { scope: "account", ...summary } };
7959
8252
  }
7960
8253
  });
7961
8254
 
7962
8255
  // src/cli/commands/add.ts
7963
- import { existsSync as existsSync21, readFileSync as readFileSync19, realpathSync as realpathSync4 } from "fs";
7964
- import { dirname as dirname10, join as join22, resolve as resolve17 } from "path";
8256
+ import { existsSync as existsSync22, readFileSync as readFileSync19, realpathSync as realpathSync4 } from "fs";
8257
+ import { dirname as dirname10, join as join23, resolve as resolve17 } from "path";
7965
8258
  import { pathToFileURL as pathToFileURL3 } from "url";
7966
8259
 
7967
8260
  // src/cli/commands/feature-installer.ts
7968
8261
  import {
7969
- closeSync,
8262
+ closeSync as closeSync2,
7970
8263
  copyFileSync,
7971
- existsSync as existsSync20,
8264
+ existsSync as existsSync21,
7972
8265
  fstatSync,
7973
- mkdirSync as mkdirSync7,
8266
+ mkdirSync as mkdirSync9,
7974
8267
  mkdtempSync,
7975
- openSync,
8268
+ openSync as openSync2,
7976
8269
  readFileSync as readFileSync18,
7977
8270
  readSync,
7978
8271
  readdirSync as readdirSync7,
7979
- rmSync as rmSync5,
7980
- writeFileSync as writeFileSync6
8272
+ rmSync as rmSync7,
8273
+ writeFileSync as writeFileSync7
7981
8274
  } from "fs";
7982
- import { spawnSync as spawnSync6 } from "child_process";
8275
+ import { spawnSync as spawnSync7 } from "child_process";
7983
8276
  import { tmpdir as tmpdir2 } from "os";
7984
- import { dirname as dirname9, isAbsolute as isAbsolute5, join as join21, resolve as resolve16 } from "path";
8277
+ import { dirname as dirname9, isAbsolute as isAbsolute5, join as join22, resolve as resolve16 } from "path";
7985
8278
  import { fileURLToPath } from "url";
7986
8279
  var NAV_MARKER = "// \u2500\u2500 Features add nav items below this line \u2500\u2500";
7987
8280
  function featurePackageRoot(moduleUrl = import.meta.url) {
7988
8281
  let dir = dirname9(fileURLToPath(moduleUrl));
7989
8282
  for (; ; ) {
7990
- const packagePath = join21(dir, "package.json");
7991
- if (existsSync20(packagePath)) {
8283
+ const packagePath = join22(dir, "package.json");
8284
+ if (existsSync21(packagePath)) {
7992
8285
  try {
7993
8286
  const pkg2 = JSON.parse(readFileSync18(packagePath, "utf-8"));
7994
8287
  if (pkg2.name === "deepspace") return dir;
@@ -8001,11 +8294,11 @@ function featurePackageRoot(moduleUrl = import.meta.url) {
8001
8294
  }
8002
8295
  }
8003
8296
  function hasFeatureCatalog(packageRoot = featurePackageRoot()) {
8004
- return existsSync20(join21(packageRoot, "features"));
8297
+ return existsSync21(join22(packageRoot, "features"));
8005
8298
  }
8006
8299
  function readFeatureConfig(id, packageRoot = featurePackageRoot()) {
8007
- const configPath = join21(packageRoot, "features", id, "feature.json");
8008
- if (!existsSync20(configPath)) return null;
8300
+ const configPath = join22(packageRoot, "features", id, "feature.json");
8301
+ if (!existsSync21(configPath)) return null;
8009
8302
  try {
8010
8303
  const config = JSON.parse(readFileSync18(configPath, "utf-8"));
8011
8304
  if (typeof config.id !== "string" || typeof config.name !== "string" || !Array.isArray(config.files)) {
@@ -8017,8 +8310,8 @@ function readFeatureConfig(id, packageRoot = featurePackageRoot()) {
8017
8310
  }
8018
8311
  }
8019
8312
  function listFeatureConfigs(packageRoot = featurePackageRoot()) {
8020
- const featuresDir = join21(packageRoot, "features");
8021
- if (!existsSync20(featuresDir)) return [];
8313
+ const featuresDir = join22(packageRoot, "features");
8314
+ if (!existsSync21(featuresDir)) return [];
8022
8315
  return readdirSync7(featuresDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => readFeatureConfig(entry.name, packageRoot)).filter((config) => config !== null && !config.internal).sort((a, b) => a.id.localeCompare(b.id));
8023
8316
  }
8024
8317
  function featureCatalog(packageRoot = featurePackageRoot()) {
@@ -8066,17 +8359,17 @@ function resolveFeatureDestPath(fileDest, route) {
8066
8359
  return route?.protected === false ? `${appPages}${rest}` : `${appPages}(protected)/${rest}`;
8067
8360
  }
8068
8361
  function copyFile(src, dest) {
8069
- mkdirSync7(dirname9(dest), { recursive: true });
8362
+ mkdirSync9(dirname9(dest), { recursive: true });
8070
8363
  copyFileSync(src, dest);
8071
8364
  }
8072
8365
  function copyFileWithImportShift(src, dest, levels) {
8073
- mkdirSync7(dirname9(dest), { recursive: true });
8366
+ mkdirSync9(dirname9(dest), { recursive: true });
8074
8367
  const prefix = "../".repeat(levels);
8075
8368
  const shifted = readFileSync18(src, "utf-8").replace(
8076
8369
  /(from\s+|import\s+|import\()(['"])(\.\.\/)/g,
8077
8370
  `$1$2${prefix}$3`
8078
8371
  );
8079
- writeFileSync6(dest, shifted);
8372
+ writeFileSync7(dest, shifted);
8080
8373
  }
8081
8374
  function installFiles(packageRoot, config, targetDir, emit) {
8082
8375
  let copied = 0;
@@ -8085,12 +8378,12 @@ function installFiles(packageRoot, config, targetDir, emit) {
8085
8378
  const preexisting = [];
8086
8379
  const missing = [];
8087
8380
  for (const file of config.files) {
8088
- const srcPath = join21(packageRoot, "features", config.id, file.src);
8381
+ const srcPath = join22(packageRoot, "features", config.id, file.src);
8089
8382
  const resolvedDest = resolveFeatureDestPath(file.dest, config.route);
8090
- const destPath = join21(targetDir, resolvedDest);
8091
- const existed = existsSync20(destPath);
8383
+ const destPath = join22(targetDir, resolvedDest);
8384
+ const existed = existsSync21(destPath);
8092
8385
  if (existed) preexisting.push(resolvedDest);
8093
- if (!existsSync20(srcPath)) {
8386
+ if (!existsSync21(srcPath)) {
8094
8387
  emit(` Warning: source not found: ${file.src}`, "stderr");
8095
8388
  if (!existed) missing.push(resolvedDest);
8096
8389
  continue;
@@ -8127,8 +8420,8 @@ function unresolvedSchema(schema, reason, emit) {
8127
8420
  }
8128
8421
  function integrateSchema(config, targetDir, emit) {
8129
8422
  if (!config.schema) return [];
8130
- const schemasPath = join21(targetDir, "src", "schemas.ts");
8131
- if (!existsSync20(schemasPath)) {
8423
+ const schemasPath = join22(targetDir, "src", "schemas.ts");
8424
+ if (!existsSync21(schemasPath)) {
8132
8425
  return [unresolvedSchema(config.schema, "src/schemas.ts was not found.", emit)];
8133
8426
  }
8134
8427
  let content = readFileSync18(schemasPath, "utf-8");
@@ -8162,7 +8455,7 @@ export const schemas`
8162
8455
  return [unresolvedSchema(config.schema, reason, emit)];
8163
8456
  }
8164
8457
  content = withEntry;
8165
- writeFileSync6(schemasPath, content);
8458
+ writeFileSync7(schemasPath, content);
8166
8459
  emit(` Schema integrated: ${exportName} -> schemas.ts`);
8167
8460
  return [];
8168
8461
  }
@@ -8184,8 +8477,8 @@ function unresolvedActions(actions, reason, emit) {
8184
8477
  }
8185
8478
  function integrateActions(config, targetDir, emit) {
8186
8479
  if (!config.actions?.length) return [];
8187
- const actionsPath = join21(targetDir, "src", "actions", "index.ts");
8188
- if (!existsSync20(actionsPath)) {
8480
+ const actionsPath = join22(targetDir, "src", "actions", "index.ts");
8481
+ if (!existsSync21(actionsPath)) {
8189
8482
  return [unresolvedActions(config.actions, "src/actions/index.ts was not found.", emit)];
8190
8483
  }
8191
8484
  let content = readFileSync18(actionsPath, "utf-8");
@@ -8228,14 +8521,14 @@ ${registryEntry}`);
8228
8521
  integrated++;
8229
8522
  emit(` Action integrated: ${action.registerAs} (${action.exportName})`);
8230
8523
  }
8231
- if (integrated > 0) writeFileSync6(actionsPath, content);
8524
+ if (integrated > 0) writeFileSync7(actionsPath, content);
8232
8525
  return unresolved;
8233
8526
  }
8234
8527
  function integrateCode(config, targetDir, emit) {
8235
8528
  const unresolved = [];
8236
8529
  for (const insertion of config.code ?? []) {
8237
- const targetPath = join21(targetDir, insertion.file);
8238
- if (!existsSync20(targetPath)) {
8530
+ const targetPath = join22(targetDir, insertion.file);
8531
+ if (!existsSync21(targetPath)) {
8239
8532
  unresolved.push({
8240
8533
  area: "code",
8241
8534
  reason: `${insertion.file} was not found.`,
@@ -8273,32 +8566,32 @@ ${content.slice(
8273
8566
  const prefix = insertion.placement === "before" ? "" : "\n";
8274
8567
  const suffix = insertion.placement === "before" ? "\n" : "";
8275
8568
  content = `${content.slice(0, insertAt)}${prefix}${insertion.insert}${suffix}${content.slice(insertAt)}`;
8276
- writeFileSync6(targetPath, content);
8569
+ writeFileSync7(targetPath, content);
8277
8570
  emit(` Integrated: ${insertion.file}`);
8278
8571
  }
8279
8572
  return unresolved;
8280
8573
  }
8281
8574
  function integrateIgnore(config, targetDir) {
8282
8575
  if (!config.ignore?.length) return;
8283
- const targetPath = join21(targetDir, ".gitignore");
8284
- const current = existsSync20(targetPath) ? readFileSync18(targetPath, "utf-8") : "";
8576
+ const targetPath = join22(targetDir, ".gitignore");
8577
+ const current = existsSync21(targetPath) ? readFileSync18(targetPath, "utf-8") : "";
8285
8578
  const present = new Set(current.split(/\r?\n/).map((line) => line.trim()));
8286
8579
  const missing = config.ignore.filter((pattern) => !present.has(pattern));
8287
8580
  if (missing.length === 0) return;
8288
8581
  const separator = current === "" || current.endsWith("\n") ? "" : "\n";
8289
- writeFileSync6(targetPath, `${current}${separator}${missing.join("\n")}
8582
+ writeFileSync7(targetPath, `${current}${separator}${missing.join("\n")}
8290
8583
  `);
8291
8584
  }
8292
8585
  function integrateWrangler(config, targetDir, emit) {
8293
8586
  const runWorkerFirst = config.wrangler?.runWorkerFirst === true;
8294
8587
  const rateLimits = config.wrangler?.rateLimits ?? [];
8295
8588
  if (!runWorkerFirst && rateLimits.length === 0) return [];
8296
- const targetPath = join21(targetDir, "wrangler.toml");
8589
+ const targetPath = join22(targetDir, "wrangler.toml");
8297
8590
  const steps = [
8298
8591
  ...runWorkerFirst ? ["Set assets.run_worker_first = true in wrangler.toml."] : [],
8299
8592
  ...rateLimits.map((binding) => `Add the ${binding.name} rate-limit binding to wrangler.toml.`)
8300
8593
  ];
8301
- if (!existsSync20(targetPath)) {
8594
+ if (!existsSync21(targetPath)) {
8302
8595
  return [{ area: "wrangler", reason: "wrangler.toml was not found.", steps }];
8303
8596
  }
8304
8597
  let source = readFileSync18(targetPath, "utf-8");
@@ -8323,7 +8616,7 @@ ${rateLimitBindingBlock(scope, binding)}
8323
8616
  }
8324
8617
  }
8325
8618
  if (changed) {
8326
- writeFileSync6(targetPath, source);
8619
+ writeFileSync7(targetPath, source);
8327
8620
  emit(" Integrated: wrangler.toml");
8328
8621
  } else emit(" Integration already present: wrangler.toml");
8329
8622
  return [];
@@ -8377,8 +8670,8 @@ function rateLimitBindingBlock(scope, binding) {
8377
8670
  }
8378
8671
  function integrateCss(packageRoot, config, targetDir, emit) {
8379
8672
  if (!config.css?.length) return [];
8380
- const stylesPath = join21(targetDir, "src", "styles.css");
8381
- if (!existsSync20(stylesPath)) {
8673
+ const stylesPath = join22(targetDir, "src", "styles.css");
8674
+ if (!existsSync21(stylesPath)) {
8382
8675
  emit(" Warning: Cannot integrate CSS \u2014 src/styles.css not found");
8383
8676
  return [
8384
8677
  {
@@ -8394,8 +8687,8 @@ function integrateCss(packageRoot, config, targetDir, emit) {
8394
8687
  let integrated = 0;
8395
8688
  const unresolved = [];
8396
8689
  for (const cssFile of config.css) {
8397
- const srcPath = join21(packageRoot, "features", config.id, cssFile);
8398
- if (!existsSync20(srcPath)) {
8690
+ const srcPath = join22(packageRoot, "features", config.id, cssFile);
8691
+ if (!existsSync21(srcPath)) {
8399
8692
  emit(` Warning: CSS source not found: ${cssFile}`, "stderr");
8400
8693
  unresolved.push({
8401
8694
  area: "css",
@@ -8416,7 +8709,7 @@ ${css}`;
8416
8709
  integrated++;
8417
8710
  emit(` CSS integrated: ${cssFile} -> styles.css`);
8418
8711
  }
8419
- if (integrated > 0) writeFileSync6(stylesPath, styles);
8712
+ if (integrated > 0) writeFileSync7(stylesPath, styles);
8420
8713
  return unresolved;
8421
8714
  }
8422
8715
  function unresolvedRoute(route, config, reason, emit) {
@@ -8438,8 +8731,8 @@ function routeRolesSuffix(route) {
8438
8731
  }
8439
8732
  function integrateRoute(config, targetDir, emit) {
8440
8733
  if (!config.route) return [];
8441
- const navPath = join21(targetDir, "src", "nav.ts");
8442
- if (!existsSync20(navPath)) {
8734
+ const navPath = join22(targetDir, "src", "nav.ts");
8735
+ if (!existsSync21(navPath)) {
8443
8736
  return [unresolvedRoute(config.route, config, "src/nav.ts was not found.", emit)];
8444
8737
  }
8445
8738
  let content = readFileSync18(navPath, "utf-8");
@@ -8463,7 +8756,7 @@ function integrateRoute(config, targetDir, emit) {
8463
8756
  const entry = ` { path: '${routePath}', label: '${config.name}'${roles} },`;
8464
8757
  content = content.replace(NAV_MARKER, `${NAV_MARKER}
8465
8758
  ${entry}`);
8466
- writeFileSync6(navPath, content);
8759
+ writeFileSync7(navPath, content);
8467
8760
  emit(` Nav wired: ${routePath} (${config.name})`);
8468
8761
  emit(" Route: automatic (file-based routing via src/pages/)");
8469
8762
  return [];
@@ -8486,14 +8779,14 @@ function runPackageInstall(packageManager, targetDir, outputVisible) {
8486
8779
  shell: process.platform === "win32"
8487
8780
  };
8488
8781
  if (outputVisible) {
8489
- const child = spawnSync6(packageManager, ["install"], { ...options, stdio: "inherit" });
8782
+ const child = spawnSync7(packageManager, ["install"], { ...options, stdio: "inherit" });
8490
8783
  return { error: child.error, status: child.status };
8491
8784
  }
8492
- const captureDir = mkdtempSync(join21(tmpdir2(), "deepspace-add-install-"));
8785
+ const captureDir = mkdtempSync(join22(tmpdir2(), "deepspace-add-install-"));
8493
8786
  let outputFd;
8494
8787
  try {
8495
- outputFd = openSync(join21(captureDir, "output.log"), "w+");
8496
- const child = spawnSync6(packageManager, ["install"], {
8788
+ outputFd = openSync2(join22(captureDir, "output.log"), "w+");
8789
+ const child = spawnSync7(packageManager, ["install"], {
8497
8790
  ...options,
8498
8791
  stdio: ["ignore", outputFd, outputFd]
8499
8792
  });
@@ -8503,8 +8796,8 @@ function runPackageInstall(packageManager, targetDir, outputVisible) {
8503
8796
  diagnosticTail: readDiagnosticTail(outputFd)
8504
8797
  };
8505
8798
  } finally {
8506
- if (outputFd !== void 0) closeSync(outputFd);
8507
- rmSync5(captureDir, { recursive: true, force: true });
8799
+ if (outputFd !== void 0) closeSync2(outputFd);
8800
+ rmSync7(captureDir, { recursive: true, force: true });
8508
8801
  }
8509
8802
  }
8510
8803
  function integrateDependencies(config, targetDir, runInstall, outputVisible, emit) {
@@ -8516,8 +8809,8 @@ function integrateDependencies(config, targetDir, runInstall, outputVisible, emi
8516
8809
  if (declarations.every(({ values }) => !values || Object.keys(values).length === 0)) {
8517
8810
  return { added: [], installRan: false, packageManager };
8518
8811
  }
8519
- const packagePath = join21(targetDir, "package.json");
8520
- if (!existsSync20(packagePath)) {
8812
+ const packagePath = join22(targetDir, "package.json");
8813
+ if (!existsSync21(packagePath)) {
8521
8814
  emit(" Could not patch package.json (file not found)");
8522
8815
  return { added: [], installRan: false, packageManager };
8523
8816
  }
@@ -8547,7 +8840,7 @@ function integrateDependencies(config, targetDir, runInstall, outputVisible, emi
8547
8840
  if (added.length === 0) {
8548
8841
  emit(" Dependencies already present");
8549
8842
  } else {
8550
- writeFileSync6(packagePath, `${JSON.stringify(pkg2, null, 2)}
8843
+ writeFileSync7(packagePath, `${JSON.stringify(pkg2, null, 2)}
8551
8844
  `, "utf-8");
8552
8845
  emit(` Added to package.json: ${added.map(({ name }) => name).join(", ")}`);
8553
8846
  if (skipped.length) emit(` Already present (kept your version): ${skipped.join(", ")}`);
@@ -8583,7 +8876,7 @@ ${child.diagnosticTail}` : "";
8583
8876
  }
8584
8877
  function dependencySnapshot(targetDir) {
8585
8878
  try {
8586
- const pkg2 = JSON.parse(readFileSync18(join21(targetDir, "package.json"), "utf-8"));
8879
+ const pkg2 = JSON.parse(readFileSync18(join22(targetDir, "package.json"), "utf-8"));
8587
8880
  return {
8588
8881
  dependencies: pkg2.dependencies ?? {},
8589
8882
  devDependencies: pkg2.devDependencies ?? {}
@@ -8595,7 +8888,7 @@ function dependencySnapshot(targetDir) {
8595
8888
  function installedNavFeatures(packageRoot, targetDir) {
8596
8889
  return listFeatureConfigs(packageRoot).filter((feature) => feature.category === "nav" && feature.files.length > 0).filter((feature) => {
8597
8890
  const signature = resolveFeatureDestPath(feature.files[0].dest, feature.route);
8598
- return existsSync20(join21(targetDir, signature));
8891
+ return existsSync21(join22(targetDir, signature));
8599
8892
  }).map((feature) => feature.id);
8600
8893
  }
8601
8894
  function warnNavConflicts(packageRoot, config, targetDir, emit) {
@@ -8828,9 +9121,9 @@ function renderFeatureInfo(config) {
8828
9121
  return lines.join("\n");
8829
9122
  }
8830
9123
  function isDeepSpaceApp(dir) {
8831
- if (existsSync21(join22(dir, "wrangler.toml"))) return true;
8832
- const packagePath = join22(dir, "package.json");
8833
- if (!existsSync21(packagePath)) return false;
9124
+ if (existsSync22(join23(dir, "wrangler.toml"))) return true;
9125
+ const packagePath = join23(dir, "package.json");
9126
+ if (!existsSync22(packagePath)) return false;
8834
9127
  try {
8835
9128
  const pkg2 = JSON.parse(readFileSync19(packagePath, "utf-8"));
8836
9129
  return Boolean(pkg2.dependencies?.deepspace || pkg2.devDependencies?.deepspace);
@@ -8841,8 +9134,8 @@ function isDeepSpaceApp(dir) {
8841
9134
  function installedPackageRoot(fromDir) {
8842
9135
  let dir = fromDir;
8843
9136
  for (; ; ) {
8844
- const candidate = join22(dir, "node_modules", "deepspace");
8845
- if (existsSync21(join22(candidate, "package.json"))) return candidate;
9137
+ const candidate = join23(dir, "node_modules", "deepspace");
9138
+ if (existsSync22(join23(candidate, "package.json"))) return candidate;
8846
9139
  const parent = dirname10(dir);
8847
9140
  if (parent === dir) return null;
8848
9141
  dir = parent;
@@ -8859,9 +9152,9 @@ async function targetInstaller(appDir) {
8859
9152
  if (realpathSync4(installedRoot) === realpathSync4(featurePackageRoot())) {
8860
9153
  return { installFeature };
8861
9154
  }
8862
- const entry = join22(installedRoot, "dist", "feature-installer.js");
8863
- const catalog = join22(installedRoot, "features");
8864
- if (!existsSync21(entry) || !existsSync21(catalog)) {
9155
+ const entry = join23(installedRoot, "dist", "feature-installer.js");
9156
+ const catalog = join23(installedRoot, "features");
9157
+ if (!existsSync22(entry) || !existsSync22(catalog)) {
8865
9158
  throw new Refusal(
8866
9159
  `The app's installed deepspace package does not contain the typed feature installer.
8867
9160
  Expected: ${entry}
@@ -8954,7 +9247,7 @@ var add_default = defineDeepspaceCommand({
8954
9247
  return { data: { features: featureCatalog(root) } };
8955
9248
  }
8956
9249
  const appDir = resolve17(args.dir ?? ".");
8957
- if (!existsSync21(appDir)) {
9250
+ if (!existsSync22(appDir)) {
8958
9251
  throw new Refusal(
8959
9252
  `Target directory not found: ${appDir}
8960
9253
  Create a new app first: deepspace app create <name>`,
@@ -9012,7 +9305,7 @@ Create one with: deepspace app create <name>`,
9012
9305
  // src/cli/commands/domain.ts
9013
9306
  import { defineCommand as defineCommand5 } from "citty";
9014
9307
  var API_URL4 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
9015
- var DEPLOY_URL7 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
9308
+ var DEPLOY_URL8 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
9016
9309
  var api = (token, path, init) => apiFetch(API_URL4, token, path, init);
9017
9310
  function scrubInternal(value) {
9018
9311
  if (Array.isArray(value)) return value.map(scrubInternal);
@@ -9093,7 +9386,7 @@ var buy = defineDeepspaceCommand({
9093
9386
  async run({ args }) {
9094
9387
  const domain = String(args.domain);
9095
9388
  const token = await ensureToken();
9096
- const appId = await resolveAppTarget(DEPLOY_URL7, token, args.app);
9389
+ const appId = await resolveAppTarget(DEPLOY_URL8, token, args.app);
9097
9390
  const priceCheck = await api(token, "/api/domains/check", {
9098
9391
  method: "POST",
9099
9392
  body: JSON.stringify({ domains: [domain] })
@@ -9246,7 +9539,7 @@ var attach = defineDeepspaceCommand({
9246
9539
  const domain = String(args.domain);
9247
9540
  const token = await ensureToken();
9248
9541
  const targetAppId = await resolveAppTarget(
9249
- DEPLOY_URL7,
9542
+ DEPLOY_URL8,
9250
9543
  token,
9251
9544
  args.app
9252
9545
  );
@@ -9330,10 +9623,10 @@ var domain_default = defineCommand5({
9330
9623
  // src/cli/commands/app-files.ts
9331
9624
  import { defineCommand as defineCommand6 } from "citty";
9332
9625
  import { basename as basename4, resolve as resolve18 } from "path";
9333
- import { existsSync as existsSync22, statSync as statSync2 } from "fs";
9626
+ import { existsSync as existsSync23, statSync as statSync4 } from "fs";
9334
9627
 
9335
9628
  // src/cli/lib/app-files-api.ts
9336
- import { createWriteStream, openAsBlob, statSync } from "fs";
9629
+ import { createWriteStream, openAsBlob, statSync as statSync3 } from "fs";
9337
9630
  import { basename as basename3, extname } from "path";
9338
9631
  import { Readable } from "stream";
9339
9632
  import { pipeline } from "stream/promises";
@@ -9443,7 +9736,7 @@ Content-Type: ${contentType}\r
9443
9736
  }
9444
9737
  async function uploadAppFile(baseUrl, token, appId, localPath, key, onProgress) {
9445
9738
  const path = filesPath(appId, `/upload?key=${encodeKeyPath(key)}`);
9446
- const size = statSync(localPath).size;
9739
+ const size = statSync3(localPath).size;
9447
9740
  if (size > MAX_APP_FILE_BYTES) {
9448
9741
  throw new ApiError(oversizeMessage(size), 0, "too_large", path);
9449
9742
  }
@@ -9561,11 +9854,11 @@ async function downloadAppFile(baseUrl, token, appId, key, destination) {
9561
9854
  }
9562
9855
 
9563
9856
  // src/cli/commands/app-files.ts
9564
- var DEPLOY_URL8 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
9857
+ var DEPLOY_URL9 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
9565
9858
  var PLATFORM_URL = process.env.DEEPSPACE_PLATFORM_URL ?? PLATFORM_URLS.platform;
9566
9859
  async function target(app2) {
9567
9860
  const token = await ensureToken();
9568
- const appId = await resolveAppTarget(DEPLOY_URL8, token, app2);
9861
+ const appId = await resolveAppTarget(DEPLOY_URL9, token, app2);
9569
9862
  return { token, appId };
9570
9863
  }
9571
9864
  var MAX_LIST_LIMIT = 1e3;
@@ -9609,7 +9902,7 @@ var put = defineDeepspaceCommand({
9609
9902
  },
9610
9903
  async run({ args }) {
9611
9904
  const localPath = resolve18(String(args.file));
9612
- if (!existsSync22(localPath) || !statSync2(localPath).isFile()) {
9905
+ if (!existsSync23(localPath) || !statSync4(localPath).isFile()) {
9613
9906
  throw new Refusal(`No such file: ${args.file}`, "file_not_found");
9614
9907
  }
9615
9908
  const key = requireKey(args.key ?? basename4(localPath));
@@ -9752,7 +10045,7 @@ function isTestAccountEmail(email) {
9752
10045
 
9753
10046
  // src/cli/commands/collaborators.ts
9754
10047
  var API_URL5 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
9755
- var DEPLOY_URL9 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
10048
+ var DEPLOY_URL10 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
9756
10049
  var COLLABORATOR_GRANTS = ["deploy", "secrets:read", "secrets:write"];
9757
10050
  var COLLABORATOR_GRANT_SENTENCE = `grants ${COLLABORATOR_GRANTS.join(", ")} \u2014 they can deploy the app AND read and change every app secret in plaintext; they cannot undeploy or transfer it`;
9758
10051
  function api2(token, path, init) {
@@ -9765,7 +10058,7 @@ var list4 = defineDeepspaceCommand({
9765
10058
  },
9766
10059
  async run({ args }) {
9767
10060
  const token = await ensureToken();
9768
- const app2 = await resolveAppTarget(DEPLOY_URL9, token, args.app);
10061
+ const app2 = await resolveAppTarget(DEPLOY_URL10, token, args.app);
9769
10062
  const { collaborators, pending = [] } = await api2(token, `/api/app-collaborators/${encodeURIComponent(app2)}`);
9770
10063
  if (!collaborators.length && !pending.length) {
9771
10064
  if (!args.json) {
@@ -9782,8 +10075,8 @@ var list4 = defineDeepspaceCommand({
9782
10075
  }
9783
10076
  if (pending.length) {
9784
10077
  console.log(`PENDING INVITES ON ${app2}`);
9785
- for (const p36 of pending) {
9786
- console.log(` ${p36.email} (expires ${new Date(p36.expiresAt).toLocaleDateString()})`);
10078
+ for (const p35 of pending) {
10079
+ console.log(` ${p35.email} (expires ${new Date(p35.expiresAt).toLocaleDateString()})`);
9787
10080
  }
9788
10081
  }
9789
10082
  }
@@ -9806,7 +10099,7 @@ var add = defineDeepspaceCommand({
9806
10099
  async run({ args }) {
9807
10100
  const email = String(args.email);
9808
10101
  const token = await ensureToken();
9809
- const app2 = await resolveAppTarget(DEPLOY_URL9, token, args.app);
10102
+ const app2 = await resolveAppTarget(DEPLOY_URL10, token, args.app);
9810
10103
  if (isTestAccountEmail(email)) {
9811
10104
  throw new Refusal(
9812
10105
  "Test accounts (@deepspace.test) cannot be added as collaborators. Use a real DeepSpace account.",
@@ -9853,7 +10146,7 @@ var remove = defineDeepspaceCommand({
9853
10146
  async run({ args }) {
9854
10147
  const email = String(args.email);
9855
10148
  const token = await ensureToken();
9856
- const app2 = await resolveAppTarget(DEPLOY_URL9, token, args.app);
10149
+ const app2 = await resolveAppTarget(DEPLOY_URL10, token, args.app);
9857
10150
  const { collaborators } = await api2(
9858
10151
  token,
9859
10152
  `/api/app-collaborators/${encodeURIComponent(app2)}`
@@ -9883,13 +10176,13 @@ var cancel3 = defineDeepspaceCommand({
9883
10176
  async run({ args }) {
9884
10177
  const email = String(args.email);
9885
10178
  const token = await ensureToken();
9886
- const app2 = await resolveAppTarget(DEPLOY_URL9, token, args.app);
10179
+ const app2 = await resolveAppTarget(DEPLOY_URL10, token, args.app);
9887
10180
  const { pending = [] } = await api2(
9888
10181
  token,
9889
10182
  `/api/app-collaborators/${encodeURIComponent(app2)}`
9890
10183
  );
9891
10184
  const target2 = email.trim().toLowerCase();
9892
- const found = pending.find((p36) => p36.email.toLowerCase() === target2);
10185
+ const found = pending.find((p35) => p35.email.toLowerCase() === target2);
9893
10186
  if (!found) {
9894
10187
  throw new Refusal(`${email} has no pending invite on ${app2}`, "no_pending_invite", {
9895
10188
  action: cliAction("deepspace", "app", "collaborators", "list", "--app", app2)
@@ -9963,7 +10256,7 @@ var collaborators_default = defineCommand7({
9963
10256
  import { defineCommand as defineCommand8 } from "citty";
9964
10257
  import * as p14 from "@clack/prompts";
9965
10258
  var API_URL6 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
9966
- var DEPLOY_URL10 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
10259
+ var DEPLOY_URL11 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
9967
10260
  var offer = defineDeepspaceCommand({
9968
10261
  meta: { name: "offer", description: "Offer this app to another DeepSpace user" },
9969
10262
  args: {
@@ -9977,7 +10270,7 @@ var offer = defineDeepspaceCommand({
9977
10270
  async run({ args }) {
9978
10271
  const email = String(args.email);
9979
10272
  const token = await ensureToken();
9980
- const app2 = await resolveAppTarget(DEPLOY_URL10, token, args.app);
10273
+ const app2 = await resolveAppTarget(DEPLOY_URL11, token, args.app);
9981
10274
  const pending = await apiFetch(
9982
10275
  API_URL6,
9983
10276
  token,
@@ -10033,7 +10326,7 @@ var status2 = defineDeepspaceCommand({
10033
10326
  },
10034
10327
  async run({ args }) {
10035
10328
  const token = await ensureToken();
10036
- const app2 = await resolveAppTarget(DEPLOY_URL10, token, args.app);
10329
+ const app2 = await resolveAppTarget(DEPLOY_URL11, token, args.app);
10037
10330
  const { transfer } = await apiFetch(API_URL6, token, `/api/app-transfers/${encodeURIComponent(app2)}`);
10038
10331
  if (!transfer) {
10039
10332
  if (!args.json) console.log(`No pending transfer for ${app2}.`);
@@ -10062,7 +10355,7 @@ var accept2 = defineDeepspaceCommand({
10062
10355
  const msg = err instanceof Error ? err.message : String(err);
10063
10356
  throw new Refusal(msg, msg.startsWith("No app id") ? "not_in_app_repo" : "invalid_app");
10064
10357
  }
10065
- const { replayed, acceptedAt, inheritedCollaborators } = await apiFetch(DEPLOY_URL10, token, `/api/apps/${encodeURIComponent(app2)}/transfer/accept`, {
10358
+ const { replayed, acceptedAt, inheritedCollaborators } = await apiFetch(DEPLOY_URL11, token, `/api/apps/${encodeURIComponent(app2)}/transfer/accept`, {
10066
10359
  method: "POST"
10067
10360
  });
10068
10361
  if (!args.json) {
@@ -10097,7 +10390,7 @@ var cancel4 = defineDeepspaceCommand({
10097
10390
  },
10098
10391
  async run({ args }) {
10099
10392
  const token = await ensureToken();
10100
- const app2 = await resolveAppTarget(DEPLOY_URL10, token, args.app);
10393
+ const app2 = await resolveAppTarget(DEPLOY_URL11, token, args.app);
10101
10394
  await apiFetch(API_URL6, token, `/api/app-transfers/${encodeURIComponent(app2)}`, { method: "DELETE" });
10102
10395
  if (!args.json) console.log(`\u2713 Transfer offer for ${app2} cancelled.`);
10103
10396
  return { data: { app: app2, cancelled: true } };
@@ -10491,8 +10784,8 @@ var integrations_default = defineCommand9({
10491
10784
 
10492
10785
  // src/cli/commands/feedback.ts
10493
10786
  import * as p16 from "@clack/prompts";
10494
- import { existsSync as existsSync23, readFileSync as readFileSync21 } from "fs";
10495
- import { dirname as dirname11, join as join23 } from "path";
10787
+ import { existsSync as existsSync24, readFileSync as readFileSync21 } from "fs";
10788
+ import { dirname as dirname11, join as join24 } from "path";
10496
10789
  import { fileURLToPath as fileURLToPath2 } from "url";
10497
10790
  import { release } from "os";
10498
10791
  var API_URL8 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
@@ -10515,11 +10808,11 @@ function readCliVersion() {
10515
10808
  try {
10516
10809
  const here = dirname11(fileURLToPath2(import.meta.url));
10517
10810
  const candidates = [
10518
- join23(here, "..", "package.json"),
10519
- join23(here, "..", "..", "..", "package.json")
10811
+ join24(here, "..", "package.json"),
10812
+ join24(here, "..", "..", "..", "package.json")
10520
10813
  ];
10521
10814
  for (const path of candidates) {
10522
- if (!existsSync23(path)) continue;
10815
+ if (!existsSync24(path)) continue;
10523
10816
  const pkg2 = JSON.parse(readFileSync21(path, "utf-8"));
10524
10817
  if (pkg2.name === "deepspace" && pkg2.version) return pkg2.version;
10525
10818
  }
@@ -10750,7 +11043,7 @@ function parseCursorArg(raw) {
10750
11043
  }
10751
11044
 
10752
11045
  // src/cli/commands/secrets.ts
10753
- var DEPLOY_URL11 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
11046
+ var DEPLOY_URL12 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
10754
11047
  var COMMON_ARGS = {
10755
11048
  app: {
10756
11049
  type: "string",
@@ -10776,12 +11069,13 @@ var COMMON_ARGS = {
10776
11069
  default: false
10777
11070
  }
10778
11071
  };
10779
- async function resolveTarget(args) {
10780
- assertAppTargetResolvable(args.app, { wranglerEnv: args.env });
11072
+ async function resolveTarget(args, opts = {}) {
11073
+ const register = opts.register === true;
11074
+ assertAppTargetResolvable(args.app, { wranglerEnv: args.env, register });
10781
11075
  const { wranglerEnv } = parseWranglerEnvArg(args.env);
10782
11076
  const configName = validateConfigName(args.config?.trim() || defaultConfigNameForEnv(wranglerEnv));
10783
11077
  const token = await ensureToken();
10784
- const appId = await resolveAppTarget(DEPLOY_URL11, token, args.app, { wranglerEnv });
11078
+ const appId = await resolveAppTarget(DEPLOY_URL12, token, args.app, { wranglerEnv, register });
10785
11079
  return { appId, configName, token };
10786
11080
  }
10787
11081
  function ok(json, data, human) {
@@ -10810,7 +11104,7 @@ var list6 = defineCommand10({
10810
11104
  },
10811
11105
  async run({ args }) {
10812
11106
  const t = await resolveTarget(args);
10813
- const { secrets } = await listSecrets(DEPLOY_URL11, t.token, t.appId, t.configName);
11107
+ const { secrets } = await listSecrets(DEPLOY_URL12, t.token, t.appId, t.configName);
10814
11108
  if (args.json) {
10815
11109
  console.log(JSON.stringify({ ok: true, appId: t.appId, config: t.configName, secrets }));
10816
11110
  return;
@@ -10831,7 +11125,7 @@ var set = defineCommand10({
10831
11125
  secret: { type: "positional", description: "KEY=value (repeatable)", required: true }
10832
11126
  },
10833
11127
  async run({ args }) {
10834
- const t = await resolveTarget(args);
11128
+ const t = await resolveTarget(args, { register: true });
10835
11129
  const pairs = dedupePositionals(args.secret, args._);
10836
11130
  const secrets = {};
10837
11131
  const dupes = [];
@@ -10851,9 +11145,9 @@ var set = defineCommand10({
10851
11145
  }
10852
11146
  if (Object.keys(secrets).length === 1) {
10853
11147
  const [[k, v]] = Object.entries(secrets);
10854
- await setSecret(DEPLOY_URL11, t.token, t.appId, t.configName, k, v);
11148
+ await setSecret(DEPLOY_URL12, t.token, t.appId, t.configName, k, v);
10855
11149
  } else {
10856
- await uploadSecrets(DEPLOY_URL11, t.token, t.appId, t.configName, secrets, false);
11150
+ await uploadSecrets(DEPLOY_URL12, t.token, t.appId, t.configName, secrets, false);
10857
11151
  }
10858
11152
  const n = Object.keys(secrets).length;
10859
11153
  ok(
@@ -10886,11 +11180,11 @@ var get2 = defineCommand10({
10886
11180
  const t = await resolveTarget(args);
10887
11181
  const key = validateSecretName(args.key);
10888
11182
  if (args.plain) {
10889
- const { value } = await getSecretPlain(DEPLOY_URL11, t.token, t.appId, t.configName, key);
11183
+ const { value } = await getSecretPlain(DEPLOY_URL12, t.token, t.appId, t.configName, key);
10890
11184
  process.stdout.write(process.stdout.isTTY ? value + "\n" : value);
10891
11185
  return;
10892
11186
  }
10893
- const { secrets } = await listSecrets(DEPLOY_URL11, t.token, t.appId, t.configName);
11187
+ const { secrets } = await listSecrets(DEPLOY_URL12, t.token, t.appId, t.configName);
10894
11188
  const item = secrets.find((s) => s.key === key);
10895
11189
  if (!item)
10896
11190
  throw new InputError(`Secret "${key}" not found in ${t.configName}`, "secret_not_found");
@@ -10921,7 +11215,7 @@ var del2 = defineCommand10({
10921
11215
  for (const key of keys) {
10922
11216
  const name = validateSecretName(key);
10923
11217
  try {
10924
- const result = await deleteSecret(DEPLOY_URL11, t.token, t.appId, t.configName, name);
11218
+ const result = await deleteSecret(DEPLOY_URL12, t.token, t.appId, t.configName, name);
10925
11219
  if (result.deleted === false) absent++;
10926
11220
  else deleted++;
10927
11221
  } catch (err) {
@@ -10959,11 +11253,11 @@ var upload = defineCommand10({
10959
11253
  }
10960
11254
  },
10961
11255
  async run({ args }) {
10962
- const t = await resolveTarget(args);
11256
+ const t = await resolveTarget(args, { register: true });
10963
11257
  const secrets = parseSecretsUpload(await readUploadSource(String(args.file)));
10964
11258
  if (Object.keys(secrets).length === 0)
10965
11259
  throw new InputError("No secrets found in the input.", "empty_input");
10966
- await uploadSecrets(DEPLOY_URL11, t.token, t.appId, t.configName, secrets, args.replace);
11260
+ await uploadSecrets(DEPLOY_URL12, t.token, t.appId, t.configName, secrets, args.replace);
10967
11261
  const uploaded = Object.keys(secrets);
10968
11262
  ok(
10969
11263
  args.json === true,
@@ -11038,7 +11332,7 @@ var download = defineCommand10({
11038
11332
  "invalid_format"
11039
11333
  );
11040
11334
  }
11041
- const { secrets } = await fetchSecretsValues(DEPLOY_URL11, t.token, t.appId, t.configName);
11335
+ const { secrets } = await fetchSecretsValues(DEPLOY_URL12, t.token, t.appId, t.configName);
11042
11336
  process.stdout.write(formatSecretsDownload(secrets, format));
11043
11337
  }
11044
11338
  });
@@ -11053,10 +11347,10 @@ var pull = defineCommand10({
11053
11347
  "Run from a DeepSpace app directory (one containing wrangler.toml).",
11054
11348
  "not_in_app_repo"
11055
11349
  );
11056
- const t = await resolveTarget(args);
11350
+ const t = await resolveTarget(args, { register: true });
11057
11351
  const ownerId = decodeJwtPayload(t.token).sub;
11058
11352
  const refreshed = await refreshSecretsCache(
11059
- DEPLOY_URL11,
11353
+ DEPLOY_URL12,
11060
11354
  t.token,
11061
11355
  t.appId,
11062
11356
  wranglerEnv,
@@ -11081,7 +11375,7 @@ var configsList = defineCommand10({
11081
11375
  args: { ...COMMON_ARGS },
11082
11376
  async run({ args }) {
11083
11377
  const t = await resolveTarget(args);
11084
- const { configs: configs2 } = await listConfigs(DEPLOY_URL11, t.token, t.appId);
11378
+ const { configs: configs2 } = await listConfigs(DEPLOY_URL12, t.token, t.appId);
11085
11379
  ok(args.json === true, { appId: t.appId, configs: configs2 }, () => {
11086
11380
  if (configs2.length === 0) {
11087
11381
  console.log("No configs yet \u2014 the first `secrets set` creates one.");
@@ -11101,10 +11395,10 @@ var configsCreate = defineCommand10({
11101
11395
  "copy-from": { type: "string", description: "Copy secrets from this config", required: false }
11102
11396
  },
11103
11397
  async run({ args }) {
11104
- const t = await resolveTarget(args);
11398
+ const t = await resolveTarget(args, { register: true });
11105
11399
  const name = validateConfigName(args.name);
11106
11400
  const copyFrom = args["copy-from"] || void 0;
11107
- const { configs: configs2 } = await listConfigs(DEPLOY_URL11, t.token, t.appId);
11401
+ const { configs: configs2 } = await listConfigs(DEPLOY_URL12, t.token, t.appId);
11108
11402
  if (configs2.some((c) => c.name === name)) {
11109
11403
  if (copyFrom) {
11110
11404
  throw new InputError(
@@ -11119,7 +11413,7 @@ var configsCreate = defineCommand10({
11119
11413
  );
11120
11414
  return;
11121
11415
  }
11122
- await createConfig(DEPLOY_URL11, t.token, t.appId, name, copyFrom);
11416
+ await createConfig(DEPLOY_URL12, t.token, t.appId, name, copyFrom);
11123
11417
  ok(
11124
11418
  args.json === true,
11125
11419
  { appId: t.appId, config: name, created: true, ...copyFrom ? { copyFrom } : {} },
@@ -11151,7 +11445,7 @@ var configsDelete = defineCommand10({
11151
11445
  let secrets = [];
11152
11446
  let counted = true;
11153
11447
  try {
11154
- secrets = (await listSecrets(DEPLOY_URL11, t.token, t.appId, name)).secrets;
11448
+ secrets = (await listSecrets(DEPLOY_URL12, t.token, t.appId, name)).secrets;
11155
11449
  } catch (error) {
11156
11450
  if (args.yes !== true) throw error;
11157
11451
  counted = false;
@@ -11167,7 +11461,7 @@ var configsDelete = defineCommand10({
11167
11461
  throw new InputError("Cancelled.", "confirmation_required");
11168
11462
  }
11169
11463
  }
11170
- await deleteConfig(DEPLOY_URL11, t.token, t.appId, name);
11464
+ await deleteConfig(DEPLOY_URL12, t.token, t.appId, name);
11171
11465
  ok(
11172
11466
  args.json === true,
11173
11467
  { appId: t.appId, config: name, deleted: true, secretsDeleted: counted ? count : null },
@@ -11196,32 +11490,7 @@ var secrets_default = defineCommand10({
11196
11490
  });
11197
11491
 
11198
11492
  // src/cli/commands/init.ts
11199
- import { existsSync as existsSync24 } from "fs";
11200
- import { join as join24 } from "path";
11201
- var DEPLOY_URL12 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
11202
- function wranglerConfigUncommitted(appDir) {
11203
- if (!existsSync24(join24(appDir, ".git"))) return false;
11204
- const dryRun = runGit(appDir, ["commit", "--dry-run", "-m", "x", "--", "wrangler.toml"], {
11205
- allowFail: true
11206
- });
11207
- return dryRun.status === 0;
11208
- }
11209
- function commitScaffoldIfUnborn(appDir, token) {
11210
- try {
11211
- if (!existsSync24(join24(appDir, ".git"))) return false;
11212
- if (runGit(appDir, ["rev-parse", "--verify", "HEAD"], { allowFail: true }).status === 0) {
11213
- return false;
11214
- }
11215
- ensureGitIdentity(appDir, token);
11216
- if (runGit(appDir, ["add", "-A"], { allowFail: true }).status !== 0) return false;
11217
- const commit = runGit(appDir, ["commit", "-m", "Initial DeepSpace scaffold", "--no-verify"], {
11218
- allowFail: true
11219
- });
11220
- return commit.status === 0;
11221
- } catch {
11222
- return false;
11223
- }
11224
- }
11493
+ var DEPLOY_URL13 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
11225
11494
  var init_default = defineDeepspaceCommand({
11226
11495
  meta: {
11227
11496
  name: "init",
@@ -11262,7 +11531,7 @@ var init_default = defineDeepspaceCommand({
11262
11531
  const token2 = await ensureToken();
11263
11532
  let state;
11264
11533
  try {
11265
- state = await getAppSource(DEPLOY_URL12, token2, existing);
11534
+ state = await getAppSource(DEPLOY_URL13, token2, existing);
11266
11535
  } catch (error) {
11267
11536
  if (error instanceof ApiError && error.code === "forbidden") {
11268
11537
  throw new Refusal(
@@ -11287,15 +11556,17 @@ var init_default = defineDeepspaceCommand({
11287
11556
  }
11288
11557
  );
11289
11558
  }
11559
+ const healedScaffold = commitScaffoldIfUnborn(appDir, token2);
11290
11560
  if (!args.json) {
11291
11561
  console.log(
11292
- `Already initialized: ${existing}${envName ? ` (env: ${envName})` : ""} \u2014 registered on ${DEEPSPACE_ENV}`
11562
+ `Already initialized: ${existing}${envName ? ` (env: ${envName})` : ""} \u2014 registered on ${DEEPSPACE_ENV}` + (healedScaffold ? " \u2014 initial scaffold commit created." : "")
11293
11563
  );
11294
11564
  console.log(`App dir: ${appDir}`);
11295
11565
  }
11296
11566
  return {
11297
11567
  data: {
11298
11568
  status: "already_initialized",
11569
+ ...healedScaffold ? { committedScaffold: true } : {},
11299
11570
  appId: existing,
11300
11571
  appDir,
11301
11572
  env: DEEPSPACE_ENV,
@@ -11304,11 +11575,11 @@ var init_default = defineDeepspaceCommand({
11304
11575
  };
11305
11576
  }
11306
11577
  const token = await ensureToken();
11307
- const { appId } = await apiFetch(DEPLOY_URL12, token, "/api/apps/mint", {
11308
- method: "POST"
11578
+ const { appId, committedScaffold } = await mintAppIdentity(appDir, token, {
11579
+ wranglerEnv: envName,
11580
+ force: Boolean(args["new-id"]),
11581
+ commitScaffold: true
11309
11582
  });
11310
- writeAppId(appDir, appId, { wranglerEnv: envName, force: Boolean(args["new-id"]) });
11311
- const committedScaffold = commitScaffoldIfUnborn(appDir, token);
11312
11583
  if (!args.json) {
11313
11584
  const envSuffix = envName ? ` (env: ${envName})` : "";
11314
11585
  const commitNote = committedScaffold ? " \u2014 initial scaffold commit created." : " \u2014 commit wrangler.toml.";
@@ -11484,7 +11755,7 @@ var status_default = defineCommand11({
11484
11755
  const { appName, appId, appIdError } = resolveStatusApp(appDir, wranglerEnv);
11485
11756
  lines.push([
11486
11757
  "App",
11487
- `${appName}${wranglerEnv ? ` [env.${wranglerEnv}]` : ""}${appId ? ` (${appId})` : appIdError ? ` \u2014 ${appIdError}` : " \u2014 no DEEPSPACE_APP_ID; run `deepspace app init`"}`
11758
+ `${appName}${wranglerEnv ? ` [env.${wranglerEnv}]` : ""}${appId ? ` (${appId})` : appIdError ? ` \u2014 ${appIdError}` : " \u2014 no DEEPSPACE_APP_ID yet; it registers on first use (deploy, secrets, dev\u2026)"}`
11488
11759
  ]);
11489
11760
  json.inApp = true;
11490
11761
  json.appName = appName;
@@ -11495,7 +11766,7 @@ var status_default = defineCommand11({
11495
11766
  const depsInstallCommand = installCommand(appDir);
11496
11767
  lines.push([
11497
11768
  "Deps",
11498
- deps === "ready" ? "installed" : deps === "installing" ? "dependency installation is still running" : `${deps} \u2014 run ${depsInstallCommand}`
11769
+ deps === "ready" ? "installed" : deps === "installing" ? "dependency installation is still running" : deps === "missing" ? "missing \u2014 any command that needs them installs on first use" : `failed \u2014 retried automatically by the next dev/test/deploy (or run ${depsInstallCommand})`
11499
11770
  ]);
11500
11771
  json.deps = deps;
11501
11772
  let branch = null;
@@ -11573,7 +11844,10 @@ var status_default = defineCommand11({
11573
11844
  createdAt: rel.createdAt,
11574
11845
  byYou,
11575
11846
  source: rel.source,
11576
- sourceRevision: rel.sourceRevision
11847
+ sourceRevision: rel.sourceRevision,
11848
+ // The human line can say "dirty worktree"; the machine mirror
11849
+ // must be able to say the same.
11850
+ sourceDirty: rel.sourceDirty ?? null
11577
11851
  };
11578
11852
  if (!entry) {
11579
11853
  lines.push(["Live", `${release2} \xB7 serving state not visible from your account`]);
@@ -11604,8 +11878,13 @@ var status_default = defineCommand11({
11604
11878
  ]);
11605
11879
  json.source = { ...source, revision: sourceResult.value.revision };
11606
11880
  } else {
11607
- lines.push(["Source", "unclaimed \xB7 next normal deploy defaults to DeepSpace"]);
11881
+ const github = externalGitSource(appDir, null);
11882
+ lines.push([
11883
+ "Source",
11884
+ `unclaimed \xB7 ${github ? "next deploy infers GitHub from this checkout\u2019s remote" : "next deploy syncs to DeepSpace and claims it, permanently"}`
11885
+ ]);
11608
11886
  json.source = null;
11887
+ json.sourceInference = github ? "github" : "deepspace";
11609
11888
  }
11610
11889
  const [refsResult, wsResult, releaseResult, appsResult] = await Promise.all([
11611
11890
  githubSource ? Promise.resolve({ ok: true, value: null }) : observeRemote(api4.getRefs()),
@@ -11775,9 +12054,9 @@ var push_default = defineDeepspaceCommand({
11775
12054
  const spinner2 = args.json ? null : createSpinner();
11776
12055
  spinner2?.start(`Checking ${branch} before push\u2026`);
11777
12056
  const resolveTarget3 = async () => {
11778
- assertAppTargetResolvable(appArg);
12057
+ assertAppTargetResolvable(appArg, { register: true });
11779
12058
  const token2 = await ensureToken();
11780
- const appId2 = await resolveAppTarget(deployBaseUrl(), token2, appArg);
12059
+ const appId2 = await resolveAppTarget(deployBaseUrl(), token2, appArg, { register: true });
11781
12060
  await warnIfPhantomApp(deployBaseUrl(), token2, appId2, appArg?.trim() || void 0);
11782
12061
  return { token: token2, appId: appId2 };
11783
12062
  };
@@ -11850,6 +12129,12 @@ var push_default = defineDeepspaceCommand({
11850
12129
  if (sourceState.source?.provider === "github") {
11851
12130
  throw githubSourceRefusal(appId, sourceState.source.repository);
11852
12131
  }
12132
+ if (sourceState.source === null) {
12133
+ process.stderr.write(
12134
+ `This app's source is unclaimed \u2014 this push claims DeepSpace source for it, permanently (a checkout that deploys from GitHub instead should not run \`deepspace push\`).
12135
+ `
12136
+ );
12137
+ }
11853
12138
  const pullRecoveryCwd = selectedBranchCheckout(appDir, branch) ?? appDir;
11854
12139
  ensureSpaceRemote(appDir, appId, void 0, token);
11855
12140
  if (force) {
@@ -11939,7 +12224,21 @@ var push_default = defineDeepspaceCommand({
11939
12224
  extra: { status: result.status, appId, branch, oid: tipOid }
11940
12225
  });
11941
12226
  }
11942
- return { data: { status: result.status, appId, branch, oid: tipOid } };
12227
+ const uncommitted = appDir ? statusFiles(appDir) : [];
12228
+ if (!args.json && uncommitted.length > 0) {
12229
+ p19.log.warn(
12230
+ `${uncommitted.length} uncommitted file${uncommitted.length === 1 ? "" : "s"} NOT pushed \u2014 push publishes committed history only. Commit and push again to publish them.`
12231
+ );
12232
+ }
12233
+ return {
12234
+ data: {
12235
+ status: result.status,
12236
+ appId,
12237
+ branch,
12238
+ oid: tipOid,
12239
+ ...uncommitted.length > 0 ? { uncommitted } : {}
12240
+ }
12241
+ };
11943
12242
  } catch (err) {
11944
12243
  if (err instanceof Refusal) throw err;
11945
12244
  const transportFailure = classifyPushTransportFailure(err, appDir ?? void 0);
@@ -12236,7 +12535,7 @@ var pull_default = defineDeepspaceCommand({
12236
12535
 
12237
12536
  // src/cli/commands/clone.ts
12238
12537
  import * as p21 from "@clack/prompts";
12239
- import { existsSync as existsSync26, readdirSync as readdirSync8, rmSync as rmSync6 } from "fs";
12538
+ import { existsSync as existsSync26, readdirSync as readdirSync8, rmSync as rmSync8 } from "fs";
12240
12539
  import { resolve as resolve19, basename as basename5, join as join25 } from "path";
12241
12540
  function cleanFailedCloneDir(dir) {
12242
12541
  let entries;
@@ -12247,7 +12546,7 @@ function cleanFailedCloneDir(dir) {
12247
12546
  }
12248
12547
  if (entries.length === 1 && entries[0] === ".git") {
12249
12548
  try {
12250
- rmSync6(join25(dir, ".git"), { recursive: true, force: true });
12549
+ rmSync8(join25(dir, ".git"), { recursive: true, force: true });
12251
12550
  return [];
12252
12551
  } catch {
12253
12552
  return entries;
@@ -12378,7 +12677,7 @@ async function actorLabels(token, appId) {
12378
12677
  }
12379
12678
 
12380
12679
  // src/cli/commands/releases.ts
12381
- var DEPLOY_URL13 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
12680
+ var DEPLOY_URL14 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
12382
12681
  var releases_default = defineDeepspaceCommand({
12383
12682
  meta: {
12384
12683
  name: "releases",
@@ -12413,8 +12712,8 @@ var releases_default = defineDeepspaceCommand({
12413
12712
  const spinner2 = args.json ? null : createSpinner();
12414
12713
  spinner2?.start("Loading release history\u2026");
12415
12714
  const token = await ensureToken();
12416
- const appId = await resolveAppTarget(DEPLOY_URL13, token, appArg, { wranglerEnv: envArg });
12417
- const { releases } = await repoApi(DEPLOY_URL13, token, appId).listReleases(limit);
12715
+ const appId = await resolveAppTarget(DEPLOY_URL14, token, appArg, { wranglerEnv: envArg });
12716
+ const { releases } = await repoApi(DEPLOY_URL14, token, appId).listReleases(limit);
12418
12717
  spinner2?.stop(`Loaded ${releases.length} ${releases.length === 1 ? "release" : "releases"}.`);
12419
12718
  if (!args.json) {
12420
12719
  if (releases.length === 0) {
@@ -12422,7 +12721,7 @@ var releases_default = defineDeepspaceCommand({
12422
12721
  } else {
12423
12722
  const actors = await actorLabels(token, appId);
12424
12723
  for (const r of releases) {
12425
- const source = releaseSourceLabel(r);
12724
+ const source = displayText(releaseSourceLabel(r));
12426
12725
  const rollback = r.rollbackAvailable ? "rollback available" : "rollback unavailable";
12427
12726
  console.log(
12428
12727
  `#${r.seq} ${r.id} ${r.kind.padEnd(8)} ${r.createdAt} ${actors.get(r.actor) ?? r.actor} ${source} ${rollback}`
@@ -12447,7 +12746,7 @@ Retry in a moment, or re-run with --allow-do-deletion to accept the risk.`,
12447
12746
  "do_guard_unavailable"
12448
12747
  );
12449
12748
  }
12450
- var DEPLOY_URL14 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
12749
+ var DEPLOY_URL15 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
12451
12750
  function pickPreviousRelease(releases) {
12452
12751
  if (releases.length === 0) {
12453
12752
  return { error: "No releases recorded yet \u2014 nothing to roll back to.", code: "no_releases" };
@@ -12509,8 +12808,8 @@ var rollback_default = defineDeepspaceCommand({
12509
12808
  const spinner2 = args.json ? null : createSpinner();
12510
12809
  spinner2?.start("Preparing rollback\u2026");
12511
12810
  const token = await ensureToken();
12512
- const appId = await resolveAppTarget(DEPLOY_URL14, token, appArg, { wranglerEnv: envArg });
12513
- const api4 = repoApi(DEPLOY_URL14, token, appId);
12811
+ const appId = await resolveAppTarget(DEPLOY_URL15, token, appArg, { wranglerEnv: envArg });
12812
+ const api4 = repoApi(DEPLOY_URL15, token, appId);
12514
12813
  let releaseId = args.release ? String(args.release).trim() : "";
12515
12814
  if (!releaseId) {
12516
12815
  const { releases } = await api4.listReleases(2);
@@ -12521,7 +12820,7 @@ var rollback_default = defineDeepspaceCommand({
12521
12820
  spinner2?.message(`Rolling back to ${releaseId}\u2026`);
12522
12821
  let result;
12523
12822
  try {
12524
- result = await apiFetch(DEPLOY_URL14, token, `/api/deploy/${appId}/rollback`, {
12823
+ result = await apiFetch(DEPLOY_URL15, token, `/api/deploy/${appId}/rollback`, {
12525
12824
  method: "POST",
12526
12825
  body: JSON.stringify({
12527
12826
  releaseId,
@@ -12733,12 +13032,12 @@ function printOverlaps(overlaps, json) {
12733
13032
  import * as p25 from "@clack/prompts";
12734
13033
  import {
12735
13034
  existsSync as existsSync27,
12736
- mkdirSync as mkdirSync8,
13035
+ mkdirSync as mkdirSync10,
12737
13036
  readFileSync as readFileSync24,
12738
13037
  realpathSync as realpathSync6,
12739
13038
  renameSync as renameSync2,
12740
- statSync as statSync3,
12741
- writeFileSync as writeFileSync7
13039
+ statSync as statSync5,
13040
+ writeFileSync as writeFileSync8
12742
13041
  } from "fs";
12743
13042
  import { dirname as dirname12, isAbsolute as isAbsolute6, join as join26, relative as relative4, sep as sep4 } from "path";
12744
13043
  var WORKSPACE_MARKER = "deepspace-workspace.json";
@@ -12772,8 +13071,8 @@ function markManagedWorkspace(worktreeRoot, workspaceId) {
12772
13071
  const marker = { version: 1, workspaceId, mode: "managed-linked" };
12773
13072
  const tempPath = `${markerPath}.${process.pid}.tmp`;
12774
13073
  try {
12775
- mkdirSync8(dirname12(markerPath), { recursive: true });
12776
- writeFileSync7(tempPath, JSON.stringify(marker) + "\n");
13074
+ mkdirSync10(dirname12(markerPath), { recursive: true });
13075
+ writeFileSync8(tempPath, JSON.stringify(marker) + "\n");
12777
13076
  renameSync2(tempPath, markerPath);
12778
13077
  return true;
12779
13078
  } catch {
@@ -12840,7 +13139,7 @@ function rematerializeWorkspaceWorktree(appDir, worktreeRoot, branch, workspaceI
12840
13139
  function inOwnLinkedWorktree(appDir, id) {
12841
13140
  if (workspaceIdFromBranch(currentBranch(appDir)) !== id) return false;
12842
13141
  try {
12843
- return statSync3(join26(repoToplevel(appDir), ".git")).isFile();
13142
+ return statSync5(join26(repoToplevel(appDir), ".git")).isFile();
12844
13143
  } catch {
12845
13144
  return false;
12846
13145
  }
@@ -13286,14 +13585,14 @@ var newWorkspaceCommand = defineDeepspaceCommand({
13286
13585
 
13287
13586
  // src/cli/commands/workspace/attach.ts
13288
13587
  import * as p27 from "@clack/prompts";
13289
- import { existsSync as existsSync28, readdirSync as readdirSync9, realpathSync as realpathSync7, rmSync as rmSync7 } from "fs";
13588
+ import { existsSync as existsSync28, readdirSync as readdirSync9, realpathSync as realpathSync7, rmSync as rmSync9 } from "fs";
13290
13589
  import { isAbsolute as isAbsolute8, resolve as resolve21 } from "path";
13291
13590
  function samePath(a, b) {
13292
- const real = (p36) => {
13591
+ const real = (p35) => {
13293
13592
  try {
13294
- return realpathSync7(p36);
13593
+ return realpathSync7(p35);
13295
13594
  } catch {
13296
- return resolve21(p36);
13595
+ return resolve21(p35);
13297
13596
  }
13298
13597
  };
13299
13598
  return real(a) === real(b);
@@ -13317,7 +13616,7 @@ function cleanFailedFreshAttachDir(dir) {
13317
13616
  return entries;
13318
13617
  }
13319
13618
  try {
13320
- rmSync7(dir, { recursive: true, force: true });
13619
+ rmSync9(dir, { recursive: true, force: true });
13321
13620
  return [];
13322
13621
  } catch {
13323
13622
  return entries.length > 0 ? entries : ["<cleanup-failed>"];
@@ -13851,7 +14150,7 @@ var workspaceStatusCommand = defineDeepspaceCommand({
13851
14150
  import * as p31 from "@clack/prompts";
13852
14151
 
13853
14152
  // src/cli/lib/validation.ts
13854
- import { sync as spawnSync7 } from "cross-spawn";
14153
+ import { sync as spawnSync8 } from "cross-spawn";
13855
14154
  import { readFileSync as readFileSync25 } from "fs";
13856
14155
  import { join as join27 } from "path";
13857
14156
  var MAX_SUMMARY_CHARS = 4e3;
@@ -13869,7 +14168,7 @@ function resolveValidationCommand(appDir, explicit) {
13869
14168
  }
13870
14169
  function runValidationCommand(appDir, command) {
13871
14170
  const started = Date.now();
13872
- const res = spawnSync7(command, [], {
14171
+ const res = spawnSync8(command, [], {
13873
14172
  cwd: appDir,
13874
14173
  shell: true,
13875
14174
  stdio: ["ignore", "pipe", "pipe"],
@@ -14537,7 +14836,8 @@ function formatEvent2(e, lands, actors) {
14537
14836
  case "release.rollback":
14538
14837
  detail = `#${Number(s.seq ?? 0)} ${releaseSourceLabel({
14539
14838
  commitOid: typeof s.commitOid === "string" ? s.commitOid : null,
14540
- source: s.sourceProvider === "github" && typeof s.sourceRepository === "string" ? { provider: "github", repository: s.sourceRepository } : s.sourceProvider === "deepspace" ? { provider: "deepspace" } : null
14839
+ source: s.sourceProvider === "github" && typeof s.sourceRepository === "string" ? { provider: "github", repository: s.sourceRepository } : s.sourceProvider === "deepspace" ? { provider: "deepspace" } : null,
14840
+ sourceDirty: typeof s.sourceDirty === "boolean" ? s.sourceDirty : null
14541
14841
  })}`;
14542
14842
  break;
14543
14843
  // Unknown kinds still print their kind + actor — never crash the feed.
@@ -14731,17 +15031,15 @@ var git_credential_default = defineCommand13({
14731
15031
  });
14732
15032
 
14733
15033
  // src/cli/commands/source.ts
14734
- import * as p34 from "@clack/prompts";
14735
- var TRANSFER_PREFIX = spacePrivateRef("source-transfer");
14736
15034
  var source_default = defineDeepspaceCommand({
14737
15035
  meta: {
14738
15036
  name: "source",
14739
- description: "Show or change the app\u2019s one authoritative Git repository"
15037
+ description: "Show the app\u2019s source authority (inferred from use, never declared)"
14740
15038
  },
14741
15039
  args: {
14742
15040
  provider: {
14743
15041
  type: "positional",
14744
- description: "Source authority: deepspace is platform-hosted and commit-first; github deploys the local working tree without Git sync (omit to show)",
15042
+ description: "No longer accepted \u2014 source is inferred, not declared",
14745
15043
  required: false
14746
15044
  },
14747
15045
  app: {
@@ -14749,71 +15047,41 @@ var source_default = defineDeepspaceCommand({
14749
15047
  alias: "a",
14750
15048
  description: "App id or subdomain name (default: current app)",
14751
15049
  required: false
14752
- },
14753
- remote: {
14754
- type: "string",
14755
- description: "GitHub remote name when it cannot be inferred (default: origin)",
14756
- required: false
14757
15050
  }
14758
15051
  },
14759
15052
  async run({ args }) {
14760
- const requested = typeof args.provider === "string" ? args.provider.trim().toLowerCase() : "";
14761
- if (requested && requested !== "github" && requested !== "deepspace") {
14762
- throw new Refusal("Source must be `github` or `deepspace`.", "invalid_source");
14763
- }
14764
- const token = await ensureToken();
14765
- const deployUrl = deployBaseUrl();
14766
- const appId = await resolveAppTarget(
14767
- deployUrl,
14768
- token,
14769
- typeof args.app === "string" ? args.app : void 0
14770
- );
14771
- const state = await getAppSource(deployUrl, token, appId);
14772
- if (!requested) {
14773
- reportSource(appId, state, args.json);
14774
- return { data: sourceData(state) };
14775
- }
14776
- const appDir = findAppDir();
14777
- if (!appDir) {
15053
+ if (typeof args.provider === "string" && args.provider.trim()) {
14778
15054
  throw new Refusal(
14779
- "Changing source requires a local app checkout with wrangler.toml and Git history.",
14780
- "not_in_app_repo"
15055
+ "Source is no longer declared. A checkout with a GitHub remote deploys as GitHub automatically, and an app becomes DeepSpace-source by publishing to it (`deepspace push`) \u2014 permanently. This command only reports.",
15056
+ "source_inferred"
14781
15057
  );
14782
15058
  }
14783
- const remoteArg = typeof args.remote === "string" ? args.remote.trim() : void 0;
14784
- const result = requested === "github" ? await moveToGitHub({ appDir, appId, token, deployUrl, state, remoteArg }) : await moveToDeepSpace({ appDir, appId, token, deployUrl, state, remoteArg });
14785
- const reconciliation = reconcileSpaceRemote(appDir, appId, result.source);
14786
- const spaceRemote = reconciliation.spaceRemote;
14787
- if (!args.json) {
14788
- p34.log.success(
14789
- result.source.provider === "github" ? `Source: GitHub \xB7 ${result.source.repository} (revision ${result.revision})` : `Source: DeepSpace (revision ${result.revision})`
14790
- );
14791
- p34.log.info(
14792
- spaceRemote === "removed" ? `Removed the \`${SPACE_REMOTE}\` git remote \u2014 DeepSpace no longer owns this repo.` : spaceRemote === "absent" ? `No \`${SPACE_REMOTE}\` git remote to remove \u2014 source operations go through GitHub.` : spaceRemote === "present" ? `The \`${SPACE_REMOTE}\` git remote points at this app's cloud repo.` : `The local \`${SPACE_REMOTE}\` git remote still needs reconciliation.`
14793
- );
14794
- if (reconciliation.localReconciliation) {
14795
- p34.log.warn(reconciliation.localReconciliation.message);
15059
+ const appArg = typeof args.app === "string" ? args.app : void 0;
15060
+ if (appArg === void 0) {
15061
+ const appDir = findAppDir();
15062
+ if (appDir && !readAppId(appDir)) {
15063
+ if (!args.json) {
15064
+ console.log("App: not registered yet \u2014 it registers on first use (deploy, secrets, dev\u2026)");
15065
+ console.log("Source: unclaimed \u2014 a checkout with a GitHub remote deploys as GitHub; the first `deepspace push` claims DeepSpace source permanently.");
15066
+ }
15067
+ return { data: { appId: null, source: null, revision: 0, registered: false } };
14796
15068
  }
14797
15069
  }
15070
+ const token = await ensureToken();
15071
+ const deployUrl = deployBaseUrl();
15072
+ const appId = await resolveAppTarget(deployUrl, token, appArg);
15073
+ const state = await getAppSource(deployUrl, token, appId);
15074
+ reportSource(appId, state, args.json);
14798
15075
  return {
14799
15076
  data: {
14800
- appId,
14801
- source: result.source,
14802
- revision: result.revision,
14803
- spaceRemote,
14804
- ...reconciliation.localReconciliation ? { localReconciliation: reconciliation.localReconciliation } : {}
15077
+ appId: state.appId,
15078
+ source: state.source,
15079
+ revision: state.revision,
15080
+ registered: state.registered
14805
15081
  }
14806
15082
  };
14807
15083
  }
14808
15084
  });
14809
- function sourceData(state) {
14810
- return {
14811
- appId: state.appId,
14812
- source: state.source,
14813
- revision: state.revision,
14814
- registered: state.registered
14815
- };
14816
- }
14817
15085
  function reportSource(appId, state, json) {
14818
15086
  if (json) return;
14819
15087
  console.log(`App: ${appId}`);
@@ -14822,220 +15090,22 @@ function reportSource(appId, state, json) {
14822
15090
  } else if (state.source?.provider === "deepspace") {
14823
15091
  console.log("Source: DeepSpace");
14824
15092
  } else {
14825
- console.log("Source: unclaimed (the first normal deploy defaults to DeepSpace)");
14826
- }
14827
- console.log(`Revision: ${state.revision}`);
14828
- }
14829
- async function moveToGitHub(input) {
14830
- const { appDir, appId, token, deployUrl, state, remoteArg } = input;
14831
- const selected = requireGitHubRemote(appDir, remoteArg);
14832
- const target2 = { provider: "github", repository: selected.repository };
14833
- if (sameSource(state.source, target2)) return { source: target2, revision: state.revision };
14834
- if (state.source?.provider === "deepspace") {
14835
- assertSyncableRepo(appDir);
14836
- const api4 = repoApi(deployUrl, token, appId);
14837
- const { views } = await api4.listWorkspaces();
14838
- if (views.length > 0) {
14839
- throw new Refusal(
14840
- "Land or drop every active DeepSpace workspace before moving source to GitHub.",
14841
- "active_workspaces"
14842
- );
14843
- }
14844
- ensureSpaceRemote(appDir, appId);
14845
- runGitRemote(appDir, token, [
14846
- "fetch",
14847
- "--quiet",
14848
- "--prune",
14849
- SPACE_REMOTE,
14850
- `+refs/heads/*:${TRANSFER_PREFIX}/heads/*`,
14851
- `+refs/tags/*:${TRANSFER_PREFIX}/tags/*`
14852
- ]);
14853
- const { refs } = await api4.getRefs().then((value) => {
14854
- if (!value)
14855
- throw new Refusal("The DeepSpace source repository is unavailable.", "source_unavailable");
14856
- return value;
14857
- });
14858
- const wanted = refs.filter(isPublicRef).map(({ name, oid }) => ({ name, oid }));
14859
- const fetched = transferRefs(appDir);
14860
- if (!sameRefs(fetched, wanted)) {
14861
- throw new Refusal(
14862
- "DeepSpace branches or tags changed while they were being fetched. Retry the transfer from fresh refs.",
14863
- "source_changed"
14864
- );
14865
- }
14866
- const latest = await api4.latestRelease().then((value) => value.release);
14867
- requireReleaseReachable(appDir, latest, wanted);
14868
- const remote = new Map(
14869
- remotePublicRefs(appDir, selected.name).map((ref) => [ref.name, ref.oid])
14870
- );
14871
- const missing = wanted.filter((ref) => remote.get(ref.name) !== ref.oid);
14872
- if (missing.length > 0) {
14873
- const refspecs = missing.map((ref) => transferRefspec(ref.name));
14874
- throw new Refusal(
14875
- `GitHub ${selected.repository} is missing ${missing.length} authoritative branch/tag ref(s). Push them manually, then rerun this command.`,
14876
- "github_push_required",
14877
- {
14878
- actionRequired: true,
14879
- action: { cwd: appDir, argv: ["git", "push", "--atomic", selected.name, ...refspecs] },
14880
- extra: {
14881
- appId,
14882
- repository: selected.repository,
14883
- missing: missing.map((ref) => ref.name)
14884
- }
14885
- }
14886
- );
14887
- }
14888
- return setAppSource(deployUrl, token, appId, {
14889
- source: target2,
14890
- expectedRevision: state.revision,
14891
- refs: wanted,
14892
- ...releaseExpectation(latest)
14893
- });
14894
- }
14895
- remotePublicRefs(appDir, selected.name);
14896
- return setAppSource(deployUrl, token, appId, {
14897
- source: target2,
14898
- expectedRevision: state.revision
14899
- });
14900
- }
14901
- async function moveToDeepSpace(input) {
14902
- const { appDir, appId, token, deployUrl, state, remoteArg } = input;
14903
- const target2 = { provider: "deepspace" };
14904
- if (sameSource(state.source, target2)) return { source: target2, revision: state.revision };
14905
- assertSyncableRepo(appDir);
14906
- if (state.source?.provider !== "github") {
14907
- return setAppSource(deployUrl, token, appId, {
14908
- source: target2,
14909
- expectedRevision: state.revision
14910
- });
14911
- }
14912
- const selected = requireGitHubRemote(appDir, remoteArg, state.source.repository);
14913
- fetchGitHubTransferRefs(appDir, selected.name);
14914
- const refs = transferRefs(appDir);
14915
- if (refs.length === 0) {
14916
- throw new Refusal("GitHub has no branches or tags to import.", "source_empty");
14917
- }
14918
- const latest = await repoApi(deployUrl, token, appId).latestRelease().then((value) => value.release);
14919
- requireReleaseReachable(appDir, latest, refs);
14920
- const pushed = runGit(
14921
- appDir,
14922
- [
14923
- "push",
14924
- "--force",
14925
- "--atomic",
14926
- "--prune",
14927
- repoUrl(appId, deployUrl),
14928
- `${TRANSFER_PREFIX}/heads/*:refs/heads/*`,
14929
- `${TRANSFER_PREFIX}/tags/*:refs/tags/*`
14930
- ],
14931
- { allowFail: true, env: gitSourceImportEnv(token, state.revision, deployUrl) }
14932
- );
14933
- if (pushed.status !== 0) {
14934
- throw new Refusal(
14935
- `DeepSpace source import failed: ${pushed.stderr.toString("utf-8").trim() || "git push failed"}`,
14936
- "source_import_failed"
14937
- );
14938
- }
14939
- const latestGitHubRefs = remotePublicRefs(appDir, selected.name);
14940
- if (!sameRefs(latestGitHubRefs, refs)) {
14941
- throw new Refusal(
14942
- "GitHub branches or tags changed while they were being imported. Retry the transfer from fresh refs.",
14943
- "source_changed"
14944
- );
14945
- }
14946
- return setAppSource(deployUrl, token, appId, {
14947
- source: target2,
14948
- expectedRevision: state.revision,
14949
- refs,
14950
- ...releaseExpectation(latest)
14951
- });
14952
- }
14953
- function reconcileSpaceRemote(appDir, appId, source) {
14954
- try {
14955
- if (source.provider === "github") {
14956
- return { spaceRemote: removeSpaceRemote(appDir) ? "removed" : "absent" };
14957
- }
14958
- ensureSpaceRemote(appDir, appId);
14959
- return { spaceRemote: "present" };
14960
- } catch (error) {
14961
- const detail = error instanceof Error ? error.message : String(error);
14962
- return {
14963
- spaceRemote: "repair_required",
14964
- localReconciliation: {
14965
- required: true,
14966
- code: "git_remote_reconciliation_failed",
14967
- message: `Source authority is ${source.provider}, but the local \`${SPACE_REMOTE}\` remote could not be reconciled: ${detail}`
14968
- }
14969
- };
14970
- }
14971
- }
14972
- function requireGitHubRemote(appDir, remoteArg, repository) {
14973
- const selected = selectGitHubRemote(appDir, { name: remoteArg, repository });
14974
- if (selected) return selected;
14975
- throw new Refusal(
14976
- remoteArg ? `Remote "${remoteArg}" is not a GitHub repository${repository ? ` for ${repository}` : ""}.` : repository ? `No local GitHub remote points at ${repository}. Add one or pass --remote <name>.` : "No unambiguous GitHub remote was found. Add `origin` or pass --remote <name>.",
14977
- "github_remote_required"
14978
- );
14979
- }
14980
- function fetchGitHubTransferRefs(appDir, remote) {
14981
- runGit(appDir, [
14982
- "fetch",
14983
- "--quiet",
14984
- "--prune",
14985
- remote,
14986
- `+refs/heads/*:${TRANSFER_PREFIX}/heads/*`,
14987
- `+refs/tags/*:${TRANSFER_PREFIX}/tags/*`
14988
- ]);
14989
- }
14990
- function transferRefs(appDir) {
14991
- return localTransferRefs(appDir, `${TRANSFER_PREFIX}/heads`, "refs/heads").concat(
14992
- localTransferRefs(appDir, `${TRANSFER_PREFIX}/tags`, "refs/tags")
14993
- );
14994
- }
14995
- function transferRefspec(ref) {
14996
- if (ref.startsWith("refs/heads/")) {
14997
- return `${TRANSFER_PREFIX}/heads/${ref.slice("refs/heads/".length)}:${ref}`;
14998
- }
14999
- return `${TRANSFER_PREFIX}/tags/${ref.slice("refs/tags/".length)}:${ref}`;
15000
- }
15001
- function isPublicRef(ref) {
15002
- return ref.name.startsWith("refs/heads/") || ref.name.startsWith("refs/tags/");
15003
- }
15004
- function requireReleaseReachable(appDir, release2, refs) {
15005
- if (!release2?.commitOid) return;
15006
- const known = resolveCommit(appDir, release2.commitOid) !== null;
15007
- const reachable = known && refs.some((ref) => isAncestor(appDir, release2.commitOid, ref.oid));
15008
- if (!reachable) {
15009
- throw new Refusal(
15010
- `Live release #${release2.seq} (${release2.commitOid.slice(0, 10)}) is not reachable from a public branch or tag. Redeploy a public branch before changing source.`,
15011
- "release_not_public"
15093
+ console.log(
15094
+ "Source: unclaimed \u2014 a checkout with a GitHub remote deploys as GitHub; the first `deepspace push` claims DeepSpace source permanently."
15012
15095
  );
15013
15096
  }
15014
- }
15015
- function releaseExpectation(release2) {
15016
- return {
15017
- expectedReleaseId: release2?.id ?? null,
15018
- expectedReleaseCommitOid: release2?.commitOid ?? null
15019
- };
15020
- }
15021
- function sameSource(left, right) {
15022
- return left?.provider === right.provider && (left.provider !== "github" || right.provider === "github" && left.repository === right.repository);
15023
- }
15024
- function sameRefs(left, right) {
15025
- if (left.length !== right.length) return false;
15026
- const expected = new Map(right.map((ref) => [ref.name, ref.oid]));
15027
- return left.every((ref) => expected.get(ref.name) === ref.oid);
15097
+ console.log(`Revision: ${state.revision}`);
15028
15098
  }
15029
15099
 
15030
15100
  // src/cli/commands/update.ts
15031
- import * as p35 from "@clack/prompts";
15101
+ import * as p34 from "@clack/prompts";
15032
15102
  import { existsSync as existsSync29, readFileSync as readFileSync26 } from "fs";
15033
15103
  import { join as join28, resolve as resolve22 } from "path";
15034
15104
 
15035
15105
  // package.json
15036
15106
  var package_default = {
15037
15107
  name: "deepspace",
15038
- version: "0.25.0",
15108
+ version: "0.26.1",
15039
15109
  engines: {
15040
15110
  node: ">=22.15.0 <23 || >=24 <25 || >=26 <27"
15041
15111
  },
@@ -15290,7 +15360,7 @@ var update_default = defineDeepspaceCommand({
15290
15360
  } : null;
15291
15361
  if (versionGuidance) {
15292
15362
  const steps2 = [versionGuidance.step];
15293
- if (!args.json) p35.log.warn(versionGuidance.step);
15363
+ if (!args.json) p34.log.warn(versionGuidance.step);
15294
15364
  return {
15295
15365
  data: {
15296
15366
  ready: false,
@@ -15326,9 +15396,9 @@ var update_default = defineDeepspaceCommand({
15326
15396
  ];
15327
15397
  const status3 = unverifiedSdk ? "dependency_unverified" : steps.length === 0 ? "aligned" : "guidance_available";
15328
15398
  if (!args.json) {
15329
- p35.log.info(`App dependency: ${currentSpec}; guidance owner: deepspace CLI ${targetVersion}`);
15330
- for (const step of steps) p35.log.message(`\u2022 ${step}`);
15331
- if (steps.length === 0) p35.log.success("This app is already aligned with this CLI version.");
15399
+ p34.log.info(`App dependency: ${currentSpec}; guidance owner: deepspace CLI ${targetVersion}`);
15400
+ for (const step of steps) p34.log.message(`\u2022 ${step}`);
15401
+ if (steps.length === 0) p34.log.success("This app is already aligned with this CLI version.");
15332
15402
  }
15333
15403
  const data = {
15334
15404
  ready: status3 === "aligned",
@@ -15394,7 +15464,7 @@ var create2 = defineCommand14({
15394
15464
  }
15395
15465
  const scaffolder = `create-deepspace@${pkg.version}`;
15396
15466
  console.log(`Running: npx ${scaffolder} ${rawArgs2.join(" ")}`.trimEnd() + "\n");
15397
- const res = spawnSync8("npx", ["-y", scaffolder, ...rawArgs2], { stdio: "inherit" });
15467
+ const res = spawnSync9("npx", ["-y", scaffolder, ...rawArgs2], { stdio: "inherit" });
15398
15468
  if (res.error) {
15399
15469
  const err = res.error;
15400
15470
  console.error(