deepspace 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -3,14 +3,14 @@
3
3
  // src/cli/cli.ts
4
4
  import { defineCommand as defineCommand19, runMain } from "citty";
5
5
  import { execSync as execSync5 } from "child_process";
6
- import { readFileSync as readFileSync11, existsSync as existsSync14 } from "fs";
7
- import { dirname as dirname2, join as join16 } from "path";
6
+ import { readFileSync as readFileSync11, existsSync as existsSync12 } from "fs";
7
+ import { dirname as dirname3, join as join14 } from "path";
8
8
  import { fileURLToPath } from "url";
9
9
 
10
10
  // src/cli/commands/login.ts
11
11
  import { defineCommand } from "citty";
12
12
  import { writeFileSync as writeFileSync2, mkdirSync } from "fs";
13
- import { join as join2 } from "path";
13
+ import { join as join3 } from "path";
14
14
  import { homedir } from "os";
15
15
  import { exec } from "child_process";
16
16
  import { platform } from "os";
@@ -35,7 +35,11 @@ function createSpinner() {
35
35
  }
36
36
 
37
37
  // src/cli/env.ts
38
- import { existsSync, readFileSync, writeFileSync } from "fs";
38
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
39
+ import { join as join2 } from "path";
40
+
41
+ // src/cli/lib/wrangler-env.ts
42
+ import { existsSync, readFileSync } from "fs";
39
43
  import { join } from "path";
40
44
  import { parse as parseToml } from "smol-toml";
41
45
 
@@ -92,6 +96,57 @@ function sanitizeAppName(raw) {
92
96
  return raw.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "");
93
97
  }
94
98
 
99
+ // src/cli/lib/wrangler-env.ts
100
+ function resolveAppNameForEnv(config, envName) {
101
+ if (!envName) {
102
+ return resolveAppName(config.name);
103
+ }
104
+ const envBlock = config.env?.[envName];
105
+ if (!envBlock) {
106
+ return {
107
+ ok: false,
108
+ reason: `wrangler.toml: no [env.${envName}] block found. Add one with a distinct \`name\` to deploy this environment.`
109
+ };
110
+ }
111
+ if (typeof envBlock.name !== "string" || envBlock.name.trim() === "") {
112
+ return {
113
+ ok: false,
114
+ reason: `wrangler.toml: [env.${envName}].name is missing. Set a distinct app name (e.g. "${typeof config.name === "string" ? config.name : "myapp"}-${envName}") so this environment doesn't overwrite the default.`
115
+ };
116
+ }
117
+ if (envBlock.name === config.name) {
118
+ return {
119
+ ok: false,
120
+ reason: `wrangler.toml: [env.${envName}].name "${envBlock.name}" is the same as the top-level name. Pick a distinct name (e.g. "${config.name}-${envName}") so deploys don't collide.`
121
+ };
122
+ }
123
+ return resolveAppName(envBlock.name);
124
+ }
125
+ function devVarsPathFor(appDir, envName) {
126
+ return envName ? join(appDir, `.dev.vars.${envName}`) : join(appDir, ".dev.vars");
127
+ }
128
+ var WranglerConfigError = class extends Error {
129
+ constructor(path, cause) {
130
+ const detail = cause instanceof Error ? cause.message : String(cause);
131
+ super(`wrangler.toml: malformed TOML at ${path}: ${detail}`);
132
+ this.path = path;
133
+ this.cause = cause;
134
+ this.name = "WranglerConfigError";
135
+ }
136
+ };
137
+ function readWranglerConfig(appDir) {
138
+ const path = join(appDir, "wrangler.toml");
139
+ const raw = readFileSync(path, "utf-8");
140
+ try {
141
+ return parseToml(raw);
142
+ } catch (err) {
143
+ throw new WranglerConfigError(path, err);
144
+ }
145
+ }
146
+ function hasWranglerConfig(appDir) {
147
+ return existsSync(join(appDir, "wrangler.toml"));
148
+ }
149
+
95
150
  // src/cli/env.ts
96
151
  var ENV_URLS = {
97
152
  auth: "https://auth.deep.space",
@@ -115,19 +170,18 @@ async function fetchPublicKey(authUrl) {
115
170
  }
116
171
  return data.publicKey;
117
172
  }
118
- function readAppName(appDir) {
119
- const wranglerPath = join(appDir, "wrangler.toml");
120
- if (!existsSync(wranglerPath)) {
173
+ function readAppName(appDir, wranglerEnv) {
174
+ if (!hasWranglerConfig(appDir)) {
121
175
  throw new Error("No wrangler.toml found. Are you in a DeepSpace app directory?");
122
176
  }
123
- const parsed = parseToml(readFileSync(wranglerPath, "utf-8"));
124
- const resolved = resolveAppName(parsed.name);
125
- if (!resolved.ok) {
126
- throw new Error(`wrangler.toml: ${resolved.reason}`);
127
- }
128
- if (parsed.name !== resolved.name) {
177
+ const config = readWranglerConfig(appDir);
178
+ const resolved = resolveAppNameForEnv(config, wranglerEnv);
179
+ if (!resolved.ok) throw new Error(resolved.reason);
180
+ const rawName = wranglerEnv ? config.env?.[wranglerEnv]?.name : config.name;
181
+ if (rawName !== resolved.name) {
182
+ const where = wranglerEnv ? `[env.${wranglerEnv}].name` : "`name`";
129
183
  throw new Error(
130
- `wrangler.toml: name "${parsed.name}" is not in canonical form. Update \`name\` to "${resolved.name}" and re-run.`
184
+ `wrangler.toml: ${where} "${rawName}" is not in canonical form. Update it to "${resolved.name}" and re-run.`
131
185
  );
132
186
  }
133
187
  return resolved.name;
@@ -187,10 +241,12 @@ var DEV_VARS_DIVIDER = "# --- not managed by the SDK; preserved across dev/test
187
241
  function extractCustomDevVars(content) {
188
242
  const lines = content.split("\n");
189
243
  const out = [];
244
+ let belowDivider = false;
190
245
  let i = 0;
191
246
  while (i < lines.length) {
192
247
  const line = lines[i];
193
248
  if (line.trim() === DEV_VARS_DIVIDER.trim()) {
249
+ belowDivider = true;
194
250
  i++;
195
251
  continue;
196
252
  }
@@ -215,7 +271,7 @@ function extractCustomDevVars(content) {
215
271
  } else {
216
272
  i++;
217
273
  }
218
- if (!SDK_MANAGED_KEYS.has(key)) {
274
+ if (belowDivider || !SDK_MANAGED_KEYS.has(key)) {
219
275
  out.push(...block);
220
276
  }
221
277
  }
@@ -287,10 +343,10 @@ function parseDevVars(content) {
287
343
  }
288
344
  return result;
289
345
  }
290
- async function writeDevVars(appDir, env, ownerId, callerJwt) {
346
+ async function writeDevVars(appDir, env, ownerId, callerJwt, wranglerEnv) {
291
347
  const urls = ENVS[env];
292
348
  const publicKey = await fetchPublicKey(urls.auth);
293
- const appName = readAppName(appDir);
349
+ const appName = readAppName(appDir, wranglerEnv);
294
350
  const appOwnerJwt = await mintAppOwnerJwt(urls.auth, callerJwt, appName);
295
351
  const appIdentityToken = await fetchAppIdentityToken(urls.deploy, callerJwt, appName);
296
352
  const sdkVars = [
@@ -306,8 +362,8 @@ async function writeDevVars(appDir, env, ownerId, callerJwt) {
306
362
  // Production secrets never set this, so deployed apps 404 the route.
307
363
  `ALLOW_DEBUG_ROUTES=true`
308
364
  ].join("\n");
309
- const devVarsPath = join(appDir, ".dev.vars");
310
- const existing = existsSync(devVarsPath) ? readFileSync(devVarsPath, "utf-8") : "";
365
+ const devVarsPath = join2(appDir, wranglerEnv ? `.dev.vars.${wranglerEnv}` : ".dev.vars");
366
+ const existing = existsSync2(devVarsPath) ? readFileSync2(devVarsPath, "utf-8") : "";
311
367
  const custom = extractCustomDevVars(existing);
312
368
  const body = custom ? `${sdkVars}
313
369
 
@@ -431,10 +487,10 @@ async function pollForCompletion(sessionId, codeVerifier) {
431
487
  return null;
432
488
  }
433
489
  function storeCredentials(sessionToken, jwt) {
434
- const dir = join2(homedir(), ".deepspace");
490
+ const dir = join3(homedir(), ".deepspace");
435
491
  mkdirSync(dir, { recursive: true, mode: 448 });
436
- writeFileSync2(join2(dir, "session"), sessionToken, { mode: 384 });
437
- writeFileSync2(join2(dir, "token"), jwt, { mode: 384 });
492
+ writeFileSync2(join3(dir, "session"), sessionToken, { mode: 384 });
493
+ writeFileSync2(join3(dir, "token"), jwt, { mode: 384 });
438
494
  }
439
495
  function openBrowser(url) {
440
496
  const cmd = platform() === "darwin" ? `open "${url}"` : platform() === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`;
@@ -473,20 +529,20 @@ async function doEmailLogin(email, password) {
473
529
 
474
530
  // src/cli/commands/logout.ts
475
531
  import { defineCommand as defineCommand2 } from "citty";
476
- import { existsSync as existsSync2, readFileSync as readFileSync2, rmSync } from "fs";
477
- import { join as join3 } from "path";
532
+ import { existsSync as existsSync3, readFileSync as readFileSync3, rmSync } from "fs";
533
+ import { join as join4 } from "path";
478
534
  import { homedir as homedir2 } from "os";
479
535
  var AUTH_URL2 = process.env.DEEPSPACE_AUTH_URL ?? ENVS.prod.auth;
480
- var DIR = join3(homedir2(), ".deepspace");
481
- var SESSION_PATH = join3(DIR, "session");
482
- var TOKEN_PATH = join3(DIR, "token");
536
+ var DIR = join4(homedir2(), ".deepspace");
537
+ var SESSION_PATH = join4(DIR, "session");
538
+ var TOKEN_PATH = join4(DIR, "token");
483
539
  var logout_default = defineCommand2({
484
540
  meta: {
485
541
  name: "logout",
486
542
  description: "Sign out and remove cached credentials"
487
543
  },
488
544
  async run() {
489
- const sessionToken = existsSync2(SESSION_PATH) ? readFileSync2(SESSION_PATH, "utf-8").trim() : null;
545
+ const sessionToken = existsSync3(SESSION_PATH) ? readFileSync3(SESSION_PATH, "utf-8").trim() : null;
490
546
  if (sessionToken) {
491
547
  try {
492
548
  await fetch(`${AUTH_URL2}/api/auth/sign-out`, {
@@ -501,7 +557,7 @@ var logout_default = defineCommand2({
501
557
  }
502
558
  let removed = 0;
503
559
  for (const path of [SESSION_PATH, TOKEN_PATH]) {
504
- if (existsSync2(path)) {
560
+ if (existsSync3(path)) {
505
561
  rmSync(path);
506
562
  removed++;
507
563
  }
@@ -516,13 +572,12 @@ var logout_default = defineCommand2({
516
572
 
517
573
  // src/cli/commands/dev.ts
518
574
  import { defineCommand as defineCommand3 } from "citty";
519
- import { existsSync as existsSync6 } from "fs";
520
- import { join as join6, resolve as resolve2 } from "path";
575
+ import { resolve as resolve3, basename } from "path";
521
576
  import spawn from "cross-spawn";
522
577
 
523
578
  // src/cli/auth.ts
524
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
525
- import { join as join4 } from "path";
579
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
580
+ import { join as join5 } from "path";
526
581
  import { homedir as homedir3 } from "os";
527
582
 
528
583
  // src/cli/jwt.ts
@@ -536,20 +591,20 @@ function decodeJwtPayload(token2) {
536
591
 
537
592
  // src/cli/auth.ts
538
593
  var AUTH_URL3 = process.env.DEEPSPACE_AUTH_URL ?? ENVS.prod.auth;
539
- var DIR2 = join4(homedir3(), ".deepspace");
540
- var SESSION_PATH2 = join4(DIR2, "session");
541
- var TOKEN_PATH2 = join4(DIR2, "token");
594
+ var DIR2 = join5(homedir3(), ".deepspace");
595
+ var SESSION_PATH2 = join5(DIR2, "session");
596
+ var TOKEN_PATH2 = join5(DIR2, "token");
542
597
  async function ensureToken() {
543
- if (existsSync3(TOKEN_PATH2)) {
544
- const existing = readFileSync3(TOKEN_PATH2, "utf-8").trim();
598
+ if (existsSync4(TOKEN_PATH2)) {
599
+ const existing = readFileSync4(TOKEN_PATH2, "utf-8").trim();
545
600
  if (isTokenValid(existing)) {
546
601
  return existing;
547
602
  }
548
603
  }
549
- if (!existsSync3(SESSION_PATH2)) {
604
+ if (!existsSync4(SESSION_PATH2)) {
550
605
  throw new Error("Not logged in. Run `deepspace login` first.");
551
606
  }
552
- const sessionToken = readFileSync3(SESSION_PATH2, "utf-8").trim();
607
+ const sessionToken = readFileSync4(SESSION_PATH2, "utf-8").trim();
553
608
  const token2 = await exchangeSession(AUTH_URL3, sessionToken);
554
609
  if (!token2) {
555
610
  throw new Error("Session expired. Run `deepspace login` to re-authenticate.");
@@ -567,27 +622,86 @@ function isTokenValid(jwt) {
567
622
  }
568
623
  }
569
624
 
625
+ // src/cli/lib/app-context.ts
626
+ import { existsSync as existsSync5, readFileSync as readFileSync5, readdirSync, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3 } from "fs";
627
+ import { join as join6, resolve, dirname } from "path";
628
+ import { parse as parseToml2 } from "smol-toml";
629
+ function detectAppName(cwd = process.cwd()) {
630
+ const wranglerPath = join6(resolve(cwd), "wrangler.toml");
631
+ if (!existsSync5(wranglerPath)) return null;
632
+ try {
633
+ const cfg = parseToml2(readFileSync5(wranglerPath, "utf-8"));
634
+ return typeof cfg.name === "string" && cfg.name.length > 0 ? cfg.name : null;
635
+ } catch {
636
+ return null;
637
+ }
638
+ }
639
+ function requireAppName(explicit, cwd) {
640
+ if (explicit) return explicit;
641
+ const detected = detectAppName(cwd);
642
+ if (detected) return detected;
643
+ throw new Error(
644
+ "No app specified. Pass --app <name>, or run from an app directory with a wrangler.toml."
645
+ );
646
+ }
647
+ function findAppDir(start = process.cwd()) {
648
+ let dir = resolve(start);
649
+ for (; ; ) {
650
+ if (existsSync5(join6(dir, "wrangler.toml"))) return dir;
651
+ const parent = dirname(dir);
652
+ if (parent === dir) return null;
653
+ dir = parent;
654
+ }
655
+ }
656
+ function findChildApps(dir) {
657
+ try {
658
+ return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync5(join6(dir, e.name, "wrangler.toml"))).map((e) => e.name).sort();
659
+ } catch {
660
+ return [];
661
+ }
662
+ }
663
+ function writeLaunchConfigIfMissing(appDir, appName, port) {
664
+ const launchPath = join6(appDir, ".claude", "launch.json");
665
+ if (existsSync5(launchPath)) return;
666
+ const config = {
667
+ version: "0.0.1",
668
+ configurations: [
669
+ {
670
+ name: appName,
671
+ runtimeExecutable: "npx",
672
+ runtimeArgs: ["deepspace", "dev", "--port", String(port)],
673
+ port
674
+ }
675
+ ]
676
+ };
677
+ try {
678
+ mkdirSync3(join6(appDir, ".claude"), { recursive: true });
679
+ writeFileSync4(launchPath, JSON.stringify(config, null, 2) + "\n");
680
+ } catch {
681
+ }
682
+ }
683
+
570
684
  // src/cli/lib/install-status.ts
571
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
572
- import { join as join5 } from "path";
685
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
686
+ import { join as join7 } from "path";
573
687
  function ensureInstallReady(appDir) {
574
- if (existsSync4(join5(appDir, "node_modules", "deepspace", "package.json"))) return;
575
- const sentinelDir = join5(appDir, ".deepspace");
576
- const errPath = join5(sentinelDir, "install.err");
577
- const startedPath = join5(sentinelDir, "install.started");
578
- const donePath = join5(sentinelDir, "install.done");
579
- const logPath = join5(sentinelDir, "install.log");
580
- if (existsSync4(errPath)) {
688
+ if (existsSync6(join7(appDir, "node_modules", "deepspace", "package.json"))) return;
689
+ const sentinelDir = join7(appDir, ".deepspace");
690
+ const errPath = join7(sentinelDir, "install.err");
691
+ const startedPath = join7(sentinelDir, "install.started");
692
+ const donePath = join7(sentinelDir, "install.done");
693
+ const logPath = join7(sentinelDir, "install.log");
694
+ if (existsSync6(errPath)) {
581
695
  console.error("Background install failed:");
582
- console.error(readFileSync4(errPath, "utf-8").trimEnd());
583
- if (existsSync4(logPath)) console.error(`
696
+ console.error(readFileSync6(errPath, "utf-8").trimEnd());
697
+ if (existsSync6(logPath)) console.error(`
584
698
  Full log: ${logPath}`);
585
699
  console.error("\nRun `npm install` (or `bun install`) manually, then retry.");
586
700
  process.exit(1);
587
701
  }
588
- if (existsSync4(startedPath) && !existsSync4(donePath)) {
702
+ if (existsSync6(startedPath) && !existsSync6(donePath)) {
589
703
  console.error("Dependencies are still installing in the background.");
590
- if (existsSync4(logPath)) console.error(`Tail progress: tail -f ${logPath}`);
704
+ if (existsSync6(logPath)) console.error(`Tail progress: tail -f ${logPath}`);
591
705
  console.error("Retry once it finishes.");
592
706
  process.exit(1);
593
707
  }
@@ -597,12 +711,12 @@ Full log: ${logPath}`);
597
711
 
598
712
  // src/cli/lib/preflight.ts
599
713
  import { execFileSync } from "child_process";
600
- import { existsSync as existsSync5 } from "fs";
601
- import { resolve } from "path";
714
+ import { existsSync as existsSync7 } from "fs";
715
+ import { resolve as resolve2 } from "path";
602
716
  var DLL_NOT_FOUND = -1073741515;
603
717
  function preflightWindowsWorkerd(appDir) {
604
718
  if (process.platform !== "win32") return;
605
- const workerdExe = resolve(
719
+ const workerdExe = resolve2(
606
720
  appDir,
607
721
  "node_modules",
608
722
  "@cloudflare",
@@ -610,7 +724,7 @@ function preflightWindowsWorkerd(appDir) {
610
724
  "bin",
611
725
  "workerd.exe"
612
726
  );
613
- if (!existsSync5(workerdExe)) return;
727
+ if (!existsSync7(workerdExe)) return;
614
728
  try {
615
729
  execFileSync(workerdExe, ["--help"], { stdio: "pipe", timeout: 1e4 });
616
730
  return;
@@ -650,6 +764,11 @@ var dev_default = defineCommand3({
650
764
  description: "Use production workers (real auth, real API calls)",
651
765
  default: false
652
766
  },
767
+ env: {
768
+ type: "string",
769
+ description: "wrangler.toml [env.<name>] block to run (e.g. --env staging). Reads .dev.vars.<name> and applies the env's overrides at build time.",
770
+ required: false
771
+ },
653
772
  port: {
654
773
  type: "string",
655
774
  description: `Port to bind (default ${DEFAULT_PORT}, or $DEEPSPACE_PORT)`,
@@ -657,13 +776,23 @@ var dev_default = defineCommand3({
657
776
  }
658
777
  },
659
778
  async run({ args }) {
660
- const appDir = resolve2(args.dir ?? ".");
661
779
  const env = args.prod ? "prod" : "dev";
780
+ const wranglerEnv = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
662
781
  const port = resolvePort(args.port);
663
- if (!existsSync6(join6(appDir, "wrangler.toml"))) {
664
- console.error("No wrangler.toml found. Are you in a DeepSpace app directory?");
782
+ const start = resolve3(args.dir ?? ".");
783
+ const appDir = findAppDir(start);
784
+ if (!appDir) {
785
+ console.error(`No wrangler.toml found at or above ${start}.`);
786
+ const children = findChildApps(start);
787
+ if (children.length > 0) {
788
+ console.error("Did you mean to run inside one of these app directories?");
789
+ for (const c of children) console.error(` cd ${c}`);
790
+ } else {
791
+ console.error("Run from a DeepSpace app directory (one containing wrangler.toml).");
792
+ }
665
793
  process.exit(1);
666
794
  }
795
+ writeLaunchConfigIfMissing(appDir, detectAppName(appDir) ?? basename(appDir), port);
667
796
  ensureInstallReady(appDir);
668
797
  let token2;
669
798
  try {
@@ -680,9 +809,10 @@ var dev_default = defineCommand3({
680
809
  process.exit(1);
681
810
  }
682
811
  console.log(`Logged in as ${payload.name ?? payload.email}`);
683
- console.log(`Environment: ${env}`);
812
+ console.log(`Platform: ${env}`);
813
+ if (wranglerEnv) console.log(`Wrangler env: ${wranglerEnv}`);
684
814
  console.log(`Port: ${port}`);
685
- await writeDevVars(appDir, env, payload.sub, token2);
815
+ await writeDevVars(appDir, env, payload.sub, token2, wranglerEnv);
686
816
  preflightWindowsWorkerd(appDir);
687
817
  console.log("Starting dev server...\n");
688
818
  const vite = spawn(
@@ -691,7 +821,15 @@ var dev_default = defineCommand3({
691
821
  {
692
822
  cwd: appDir,
693
823
  stdio: "inherit",
694
- env: { ...process.env, DEEPSPACE_PORT: String(port) }
824
+ // CLOUDFLARE_ENV is what the Cloudflare Vite plugin keys off — it
825
+ // applies `[env.<name>]` overrides and loads `.dev.vars.<name>`
826
+ // instead of `.dev.vars`. Set only when --env was passed so the
827
+ // default invocation behaves exactly as before.
828
+ env: {
829
+ ...process.env,
830
+ DEEPSPACE_PORT: String(port),
831
+ ...wranglerEnv ? { CLOUDFLARE_ENV: wranglerEnv } : {}
832
+ }
695
833
  }
696
834
  );
697
835
  process.on("SIGINT", () => vite.kill());
@@ -873,8 +1011,7 @@ function isAlive(pid) {
873
1011
 
874
1012
  // src/cli/commands/test.ts
875
1013
  import { defineCommand as defineCommand5 } from "citty";
876
- import { existsSync as existsSync7 } from "fs";
877
- import { join as join7, resolve as resolve3 } from "path";
1014
+ import { resolve as resolve4 } from "path";
878
1015
  import { sync as spawnSync3 } from "cross-spawn";
879
1016
 
880
1017
  // src/cli/lib/playwright.ts
@@ -921,11 +1058,19 @@ var test_default = defineCommand5({
921
1058
  }
922
1059
  },
923
1060
  async run({ args }) {
924
- const appDir = resolve3(".");
925
1061
  const suite = args.suite ?? "default";
926
1062
  const port = resolvePort3(args.port);
927
- if (!existsSync7(join7(appDir, "wrangler.toml"))) {
928
- console.error("No wrangler.toml found. Are you in a DeepSpace app directory?");
1063
+ const start = resolve4(".");
1064
+ const appDir = findAppDir(start);
1065
+ if (!appDir) {
1066
+ console.error(`No wrangler.toml found at or above ${start}.`);
1067
+ const children = findChildApps(start);
1068
+ if (children.length > 0) {
1069
+ console.error("Did you mean to run inside one of these app directories?");
1070
+ for (const c of children) console.error(` cd ${c}`);
1071
+ } else {
1072
+ console.error("Run from a DeepSpace app directory (one containing wrangler.toml).");
1073
+ }
929
1074
  process.exit(1);
930
1075
  }
931
1076
  ensureInstallReady(appDir);
@@ -1012,7 +1157,7 @@ function resolvePort3(arg) {
1012
1157
  // src/cli/commands/screenshot.ts
1013
1158
  import { defineCommand as defineCommand6 } from "citty";
1014
1159
  import { sync as spawnSync4 } from "cross-spawn";
1015
- import { resolve as resolve4 } from "path";
1160
+ import { resolve as resolve5 } from "path";
1016
1161
  var screenshot_default = defineCommand6({
1017
1162
  meta: {
1018
1163
  name: "screenshot",
@@ -1041,7 +1186,7 @@ var screenshot_default = defineCommand6({
1041
1186
  }
1042
1187
  },
1043
1188
  run({ args }) {
1044
- const appDir = resolve4(".");
1189
+ const appDir = resolve5(".");
1045
1190
  ensurePlaywright(appDir);
1046
1191
  const playwrightArgs = ["playwright", "screenshot", args.url, args.output];
1047
1192
  if (args["full-page"]) playwrightArgs.push("--full-page");
@@ -1059,7 +1204,7 @@ var screenshot_default = defineCommand6({
1059
1204
  // src/cli/commands/test-accounts.ts
1060
1205
  import { defineCommand as defineCommand7 } from "citty";
1061
1206
  import * as p3 from "@clack/prompts";
1062
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
1207
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
1063
1208
  import { join as join8 } from "path";
1064
1209
  import { homedir as homedir4 } from "os";
1065
1210
  var SESSION_COOKIE2 = "__Secure-better-auth.session_token";
@@ -1069,17 +1214,17 @@ var ACCOUNTS_PATH = join8(DIR3, "test-accounts.json");
1069
1214
  function loadAccounts() {
1070
1215
  if (!existsSync8(ACCOUNTS_PATH)) return [];
1071
1216
  try {
1072
- return JSON.parse(readFileSync5(ACCOUNTS_PATH, "utf-8"));
1217
+ return JSON.parse(readFileSync7(ACCOUNTS_PATH, "utf-8"));
1073
1218
  } catch {
1074
1219
  return [];
1075
1220
  }
1076
1221
  }
1077
1222
  function saveAccounts(accounts) {
1078
- mkdirSync3(DIR3, { recursive: true });
1079
- writeFileSync4(ACCOUNTS_PATH, JSON.stringify(accounts, null, 2), { mode: 384 });
1223
+ mkdirSync4(DIR3, { recursive: true });
1224
+ writeFileSync5(ACCOUNTS_PATH, JSON.stringify(accounts, null, 2), { mode: 384 });
1080
1225
  }
1081
1226
  function sessionCookie() {
1082
- const token2 = readFileSync5(SESSION_PATH2, "utf-8").trim();
1227
+ const token2 = readFileSync7(SESSION_PATH2, "utf-8").trim();
1083
1228
  return `${SESSION_COOKIE2}=${encodeURIComponent(token2)}`;
1084
1229
  }
1085
1230
  async function fetchRemoteAccounts() {
@@ -1346,9 +1491,9 @@ var test_accounts_default = defineCommand7({
1346
1491
  // src/cli/commands/deploy.ts
1347
1492
  import { defineCommand as defineCommand8 } from "citty";
1348
1493
  import { execSync as execSync2 } from "child_process";
1349
- import { readFileSync as readFileSync6, readdirSync, existsSync as existsSync9 } from "fs";
1350
- import { parse as parseToml2 } from "smol-toml";
1351
- import { join as join9, resolve as resolve5, dirname } from "path";
1494
+ import { randomUUID } from "crypto";
1495
+ import { readFileSync as readFileSync8, readdirSync as readdirSync2, existsSync as existsSync9 } from "fs";
1496
+ import { join as join9, resolve as resolve6, dirname as dirname2 } from "path";
1352
1497
  import { pathToFileURL } from "url";
1353
1498
  import * as p4 from "@clack/prompts";
1354
1499
 
@@ -1599,6 +1744,59 @@ function formatDeployWorkerError(status2, error) {
1599
1744
 
1600
1745
  Underlying error: ${detail}`;
1601
1746
  }
1747
+ var MAX_GROUP_BYTES = 40 * 1024 * 1024;
1748
+ var GROUP_BYTES = (() => {
1749
+ const raw = Number(process.env.DEEPSPACE_DEPLOY_GROUP_BYTES);
1750
+ const v = Number.isInteger(raw) && raw > 0 ? raw : 3 * 1024 * 1024;
1751
+ return Math.min(v, MAX_GROUP_BYTES);
1752
+ })();
1753
+ function packAssetGroups(assets, maxBytes) {
1754
+ const groups = [];
1755
+ let current = [];
1756
+ let currentBytes = 2;
1757
+ for (const a of assets) {
1758
+ const entryBytes = Buffer.byteLength(JSON.stringify(a), "utf-8") + 1;
1759
+ if (current.length > 0 && currentBytes + entryBytes > maxBytes) {
1760
+ groups.push(current);
1761
+ current = [];
1762
+ currentBytes = 2;
1763
+ }
1764
+ current.push(a);
1765
+ currentBytes += entryBytes;
1766
+ }
1767
+ if (current.length > 0) groups.push(current);
1768
+ return groups;
1769
+ }
1770
+ async function postWithRetry(url, makeInit, { attempts = 4, retryServerErrors = true } = {}) {
1771
+ let lastRes;
1772
+ let lastErr;
1773
+ for (let attempt = 1; attempt <= attempts; attempt++) {
1774
+ try {
1775
+ const res = await fetch(url, makeInit());
1776
+ const isTransient = retryServerErrors && (res.status >= 500 || res.status === 408 || res.status === 429);
1777
+ if (!isTransient) {
1778
+ return res;
1779
+ }
1780
+ lastRes = res;
1781
+ } catch (err) {
1782
+ lastErr = err;
1783
+ }
1784
+ if (attempt < attempts) {
1785
+ await new Promise((r) => setTimeout(r, Math.min(8e3, 500 * 2 ** (attempt - 1))));
1786
+ }
1787
+ }
1788
+ if (lastRes) return lastRes;
1789
+ throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
1790
+ }
1791
+ async function abortStagedUpload(appName, uploadId, totalGroups, token2) {
1792
+ try {
1793
+ await fetch(
1794
+ `${DEPLOY_URL}/api/deploy/${appName}/assets?uploadId=${uploadId}&totalGroups=${totalGroups}`,
1795
+ { method: "DELETE", headers: { Authorization: `Bearer ${token2}` } }
1796
+ );
1797
+ } catch {
1798
+ }
1799
+ }
1602
1800
  var deploy_default = defineCommand8({
1603
1801
  meta: {
1604
1802
  name: "deploy",
@@ -1609,31 +1807,38 @@ var deploy_default = defineCommand8({
1609
1807
  type: "positional",
1610
1808
  description: "App directory (default: current directory)",
1611
1809
  required: false
1810
+ },
1811
+ env: {
1812
+ type: "string",
1813
+ description: "wrangler.toml [env.<name>] block to deploy (e.g. --env staging). Omit to deploy the top-level config.",
1814
+ required: false
1612
1815
  }
1613
1816
  },
1614
1817
  async run({ args }) {
1615
- const appDir = resolve5(args.dir ?? ".");
1616
- const wranglerPath = join9(appDir, "wrangler.toml");
1617
- if (!existsSync9(wranglerPath)) {
1818
+ const appDir = resolve6(args.dir ?? ".");
1819
+ const envName = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
1820
+ if (!hasWranglerConfig(appDir)) {
1618
1821
  console.error("No wrangler.toml found. Are you in a DeepSpace app directory?");
1619
1822
  process.exit(1);
1620
1823
  }
1621
1824
  ensureInstallReady(appDir);
1622
1825
  p4.intro("Deploying DeepSpace app");
1623
- const wranglerConfig = parseToml2(readFileSync6(wranglerPath, "utf-8"));
1624
- const nameRes = resolveAppName(wranglerConfig.name);
1826
+ const wranglerConfig = readWranglerConfig(appDir);
1827
+ const nameRes = resolveAppNameForEnv(wranglerConfig, envName);
1625
1828
  if (!nameRes.ok) {
1626
- p4.cancel(`wrangler.toml: ${nameRes.reason}`);
1829
+ p4.cancel(nameRes.reason);
1627
1830
  process.exit(1);
1628
1831
  }
1629
- if (wranglerConfig.name !== nameRes.name) {
1832
+ const declaredName = envName ? wranglerConfig.env?.[envName]?.name : wranglerConfig.name;
1833
+ if (declaredName !== nameRes.name) {
1834
+ const where = envName ? `[env.${envName}].name` : "`name`";
1630
1835
  p4.cancel(
1631
- `wrangler.toml: name "${wranglerConfig.name}" is not in canonical form. Update \`name\` to "${nameRes.name}" and re-run.`
1836
+ `wrangler.toml: ${where} "${declaredName}" is not in canonical form. Update it to "${nameRes.name}" and re-run.`
1632
1837
  );
1633
1838
  process.exit(1);
1634
1839
  }
1635
1840
  const appName = nameRes.name;
1636
- p4.log.info(`App: ${appName}`);
1841
+ p4.log.info(envName ? `App: ${appName} (env: ${envName})` : `App: ${appName}`);
1637
1842
  let token2;
1638
1843
  try {
1639
1844
  token2 = await ensureToken();
@@ -1644,10 +1849,15 @@ var deploy_default = defineCommand8({
1644
1849
  const s = createSpinner();
1645
1850
  s.start("Building...");
1646
1851
  try {
1647
- execSync2("npx vite build", { cwd: appDir, stdio: "pipe" });
1852
+ execSync2("npx vite build", {
1853
+ cwd: appDir,
1854
+ stdio: "pipe",
1855
+ env: envName ? { ...process.env, CLOUDFLARE_ENV: envName } : process.env
1856
+ });
1648
1857
  } catch (err) {
1649
1858
  s.stop("Build failed");
1650
- console.error(err.stderr?.toString() ?? err.message);
1859
+ const detail = [err.stdout?.toString(), err.stderr?.toString()].filter(Boolean).join("\n").trim();
1860
+ console.error(detail || err.message);
1651
1861
  process.exit(1);
1652
1862
  }
1653
1863
  s.stop("Built");
@@ -1656,16 +1866,23 @@ var deploy_default = defineCommand8({
1656
1866
  p4.cancel("Build output config not found at .wrangler/deploy/config.json");
1657
1867
  process.exit(1);
1658
1868
  }
1659
- const deployConfig = JSON.parse(readFileSync6(deployConfigPath, "utf-8"));
1660
- const outputWranglerPath = resolve5(dirname(deployConfigPath), deployConfig.configPath);
1869
+ const deployConfig = JSON.parse(readFileSync8(deployConfigPath, "utf-8"));
1870
+ const outputWranglerPath = resolve6(dirname2(deployConfigPath), deployConfig.configPath);
1661
1871
  if (!existsSync9(outputWranglerPath)) {
1662
1872
  p4.cancel(`Output wrangler.json not found at ${outputWranglerPath}`);
1663
1873
  process.exit(1);
1664
1874
  }
1665
- const outputConfig = JSON.parse(readFileSync6(outputWranglerPath, "utf-8"));
1666
- const workerDir = dirname(outputWranglerPath);
1875
+ const outputConfig = JSON.parse(readFileSync8(outputWranglerPath, "utf-8"));
1876
+ if (outputConfig.name && outputConfig.name !== appName) {
1877
+ const envHint = envName ? ` (--env ${envName})` : "";
1878
+ p4.cancel(
1879
+ `Build output mismatch: expected name "${appName}"${envHint}, but the built wrangler.json declares "${outputConfig.name}". This usually means the Cloudflare Vite plugin didn't apply the requested env block. Check the plugin version and re-run.`
1880
+ );
1881
+ process.exit(1);
1882
+ }
1883
+ const workerDir = dirname2(outputWranglerPath);
1667
1884
  const workerBundlePath = join9(workerDir, outputConfig.main);
1668
- const clientDir = outputConfig.assets?.directory ? resolve5(workerDir, outputConfig.assets.directory) : null;
1885
+ const clientDir = outputConfig.assets?.directory ? resolve6(workerDir, outputConfig.assets.directory) : null;
1669
1886
  if (!existsSync9(workerBundlePath)) {
1670
1887
  p4.cancel(`Worker bundle not found at ${workerBundlePath}`);
1671
1888
  process.exit(1);
@@ -1677,7 +1894,7 @@ var deploy_default = defineCommand8({
1677
1894
  s.start("Collecting assets...");
1678
1895
  const assets = collectAssets(clientDir);
1679
1896
  s.stop(`Collected ${assets.length} assets`);
1680
- const workerJs = readFileSync6(workerBundlePath, "utf-8");
1897
+ const workerJs = readFileSync8(workerBundlePath, "utf-8");
1681
1898
  const doBindings = outputConfig.durable_objects?.bindings;
1682
1899
  const sqliteClasses = new Set(
1683
1900
  outputConfig.migrations?.flatMap((m) => m.new_sqlite_classes ?? []) ?? []
@@ -1704,10 +1921,10 @@ var deploy_default = defineCommand8({
1704
1921
  `Custom bindings: ${customBindings.map((b) => `${b.name} (${b.type})`).join(", ")}`
1705
1922
  );
1706
1923
  }
1707
- const devVarsPath = join9(appDir, ".dev.vars");
1924
+ const devVarsPath = devVarsPathFor(appDir, envName);
1708
1925
  let userSecrets = {};
1709
1926
  if (existsSync9(devVarsPath)) {
1710
- const customSection = extractCustomDevVars(readFileSync6(devVarsPath, "utf-8"));
1927
+ const customSection = extractCustomDevVars(readFileSync8(devVarsPath, "utf-8"));
1711
1928
  if (customSection) {
1712
1929
  try {
1713
1930
  userSecrets = parseDevVars(customSection);
@@ -1740,30 +1957,98 @@ var deploy_default = defineCommand8({
1740
1957
  p4.log.info(`App secrets (from .dev.vars): ${userSecretNames.join(", ")}`);
1741
1958
  }
1742
1959
  s.start(`Deploying to ${appName}.app.space...`);
1743
- const form = new FormData();
1744
- form.append("worker", new Blob([workerJs], { type: "application/javascript" }), "worker.js");
1745
- form.append("assets", JSON.stringify(assets));
1746
- if (doManifest) {
1747
- form.append("doManifest", JSON.stringify(doManifest));
1748
- }
1749
- if (customBindings.length) {
1750
- form.append("bindingManifest", JSON.stringify(customBindings));
1751
- }
1752
- if (userSecretNames.length) {
1753
- form.append("userSecrets", JSON.stringify(userSecrets));
1754
- }
1755
1960
  const extraRoutes = extractRunWorkerFirst(wranglerConfig);
1756
1961
  if (extraRoutes.length) {
1757
- form.append("extraRunWorkerFirst", JSON.stringify(extraRoutes));
1758
1962
  p4.log.info(`Custom worker-first routes: ${extraRoutes.join(", ")}`);
1759
1963
  }
1760
- const res = await fetch(`${DEPLOY_URL}/api/deploy/${appName}`, {
1761
- method: "POST",
1762
- headers: { Authorization: `Bearer ${token2}` },
1763
- body: form
1764
- });
1964
+ const assetGroups = packAssetGroups(assets, GROUP_BYTES);
1965
+ let inlineAssets = null;
1966
+ let uploadId = null;
1967
+ let totalGroups = 0;
1968
+ if (assetGroups.length <= 1) {
1969
+ inlineAssets = JSON.stringify(assets);
1970
+ } else {
1971
+ uploadId = randomUUID();
1972
+ totalGroups = assetGroups.length;
1973
+ try {
1974
+ for (let i = 0; i < totalGroups; i++) {
1975
+ const groupJson = JSON.stringify(assetGroups[i]);
1976
+ s.message(`Uploading assets \u2014 group ${i + 1}/${totalGroups}...`);
1977
+ const groupRes = await postWithRetry(
1978
+ `${DEPLOY_URL}/api/deploy/${appName}/assets?uploadId=${uploadId}&groupIndex=${i}&totalGroups=${totalGroups}`,
1979
+ () => ({
1980
+ method: "POST",
1981
+ headers: {
1982
+ Authorization: `Bearer ${token2}`,
1983
+ "Content-Type": "application/json"
1984
+ },
1985
+ body: groupJson
1986
+ })
1987
+ );
1988
+ if (!groupRes.ok) {
1989
+ const errBody = await groupRes.json().catch(() => ({}));
1990
+ await abortStagedUpload(appName, uploadId, totalGroups, token2);
1991
+ s.stop("Deploy failed");
1992
+ p4.cancel(
1993
+ `Asset upload failed on group ${i + 1}/${totalGroups}: ${errBody.error ?? `HTTP ${groupRes.status}`}`
1994
+ );
1995
+ process.exit(1);
1996
+ }
1997
+ }
1998
+ } catch (err) {
1999
+ await abortStagedUpload(appName, uploadId, totalGroups, token2);
2000
+ s.stop("Deploy failed");
2001
+ p4.cancel(`Asset upload failed (network): ${err?.message ?? err}`);
2002
+ process.exit(1);
2003
+ }
2004
+ s.message(`Deploying to ${appName}.app.space...`);
2005
+ }
2006
+ const makeForm = () => {
2007
+ const form = new FormData();
2008
+ form.append("worker", new Blob([workerJs], { type: "application/javascript" }), "worker.js");
2009
+ if (inlineAssets !== null) {
2010
+ form.append("assets", inlineAssets);
2011
+ } else {
2012
+ form.append("uploadId", uploadId);
2013
+ form.append("totalGroups", String(totalGroups));
2014
+ }
2015
+ if (doManifest) {
2016
+ form.append("doManifest", JSON.stringify(doManifest));
2017
+ }
2018
+ if (customBindings.length) {
2019
+ form.append("bindingManifest", JSON.stringify(customBindings));
2020
+ }
2021
+ if (userSecretNames.length) {
2022
+ form.append("userSecrets", JSON.stringify(userSecrets));
2023
+ }
2024
+ if (extraRoutes.length) {
2025
+ form.append("extraRunWorkerFirst", JSON.stringify(extraRoutes));
2026
+ }
2027
+ return form;
2028
+ };
2029
+ let res;
2030
+ try {
2031
+ res = await postWithRetry(
2032
+ `${DEPLOY_URL}/api/deploy/${appName}`,
2033
+ () => ({
2034
+ method: "POST",
2035
+ headers: { Authorization: `Bearer ${token2}` },
2036
+ body: makeForm()
2037
+ }),
2038
+ // Commit runs a real deploy — don't retry its 5xx (could double-deploy);
2039
+ // only a thrown fetch (dropped connection) is retried, and the staged
2040
+ // groups survive on the worker until this request succeeds.
2041
+ { retryServerErrors: false }
2042
+ );
2043
+ } catch (err) {
2044
+ if (uploadId) await abortStagedUpload(appName, uploadId, totalGroups, token2);
2045
+ s.stop("Deploy failed");
2046
+ p4.cancel(`Deploy request failed: ${err?.message ?? err}`);
2047
+ process.exit(1);
2048
+ }
1765
2049
  const body = await res.json().catch(() => ({}));
1766
2050
  if (!res.ok || !body.success) {
2051
+ if (uploadId) await abortStagedUpload(appName, uploadId, totalGroups, token2);
1767
2052
  s.stop("Deploy failed");
1768
2053
  p4.cancel(formatDeployWorkerError(res.status, body.error));
1769
2054
  process.exit(1);
@@ -2018,7 +2303,7 @@ async function waitForAssetsReady(url, timeoutMs) {
2018
2303
  function collectAssets(dir) {
2019
2304
  const assets = [];
2020
2305
  function walk(d, prefix) {
2021
- for (const entry of readdirSync(d, { withFileTypes: true })) {
2306
+ for (const entry of readdirSync2(d, { withFileTypes: true })) {
2022
2307
  if (entry.name === ".assetsignore") continue;
2023
2308
  const full = join9(d, entry.name);
2024
2309
  const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
@@ -2027,7 +2312,7 @@ function collectAssets(dir) {
2027
2312
  } else {
2028
2313
  assets.push({
2029
2314
  path: "/" + rel,
2030
- contentBase64: readFileSync6(full).toString("base64")
2315
+ contentBase64: readFileSync8(full).toString("base64")
2031
2316
  });
2032
2317
  }
2033
2318
  }
@@ -2038,9 +2323,6 @@ function collectAssets(dir) {
2038
2323
 
2039
2324
  // src/cli/commands/undeploy.ts
2040
2325
  import { defineCommand as defineCommand9 } from "citty";
2041
- import { readFileSync as readFileSync7, existsSync as existsSync10 } from "fs";
2042
- import { join as join10 } from "path";
2043
- import { parse as parseToml3 } from "smol-toml";
2044
2326
  import * as p5 from "@clack/prompts";
2045
2327
  var DEPLOY_URL2 = process.env.DEEPSPACE_DEPLOY_URL ?? ENVS.prod.deploy;
2046
2328
  var undeploy_default = defineCommand9({
@@ -2053,15 +2335,25 @@ var undeploy_default = defineCommand9({
2053
2335
  type: "positional",
2054
2336
  description: "App name to undeploy (reads from wrangler.toml if omitted)",
2055
2337
  required: false
2338
+ },
2339
+ env: {
2340
+ type: "string",
2341
+ description: "wrangler.toml [env.<name>] block whose deployed app to remove (e.g. --env staging). Ignored if a positional name is given.",
2342
+ required: false
2056
2343
  }
2057
2344
  },
2058
2345
  async run({ args }) {
2059
2346
  let appName = args.name;
2347
+ const envName = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
2060
2348
  if (!appName) {
2061
- const wranglerPath = join10(process.cwd(), "wrangler.toml");
2062
- if (existsSync10(wranglerPath)) {
2063
- const config = parseToml3(readFileSync7(wranglerPath, "utf-8"));
2064
- if (config.name) appName = config.name;
2349
+ if (hasWranglerConfig(process.cwd())) {
2350
+ const config = readWranglerConfig(process.cwd());
2351
+ const nameRes = resolveAppNameForEnv(config, envName);
2352
+ if (!nameRes.ok) {
2353
+ p5.cancel(nameRes.reason);
2354
+ process.exit(1);
2355
+ }
2356
+ appName = nameRes.name;
2065
2357
  }
2066
2358
  }
2067
2359
  if (!appName) {
@@ -2151,14 +2443,14 @@ var whoami_default = defineCommand10({
2151
2443
 
2152
2444
  // src/cli/commands/add.ts
2153
2445
  import { defineCommand as defineCommand11 } from "citty";
2154
- import { existsSync as existsSync11 } from "fs";
2155
- import { resolve as resolve6, join as join11 } from "path";
2446
+ import { existsSync as existsSync10 } from "fs";
2447
+ import { resolve as resolve7, join as join10 } from "path";
2156
2448
  import { execSync as execSync3 } from "child_process";
2157
2449
  function findScript(from) {
2158
- const inNodeModules = join11(from, "node_modules", "deepspace", "scripts", "add-feature.cjs");
2159
- if (existsSync11(inNodeModules)) return inNodeModules;
2160
- const inDotDeepspace = join11(from, ".deepspace", "scripts", "add-feature.cjs");
2161
- if (existsSync11(inDotDeepspace)) return inDotDeepspace;
2450
+ const inNodeModules = join10(from, "node_modules", "deepspace", "scripts", "add-feature.cjs");
2451
+ if (existsSync10(inNodeModules)) return inNodeModules;
2452
+ const inDotDeepspace = join10(from, ".deepspace", "scripts", "add-feature.cjs");
2453
+ if (existsSync10(inDotDeepspace)) return inDotDeepspace;
2162
2454
  return null;
2163
2455
  }
2164
2456
  var add_default = defineCommand11({
@@ -2190,14 +2482,14 @@ var add_default = defineCommand11({
2190
2482
  }
2191
2483
  },
2192
2484
  run({ args }) {
2193
- const appDir = resolve6(args.dir ?? ".");
2485
+ const appDir = resolve7(args.dir ?? ".");
2194
2486
  ensureInstallReady(appDir);
2195
2487
  const script = findScript(appDir);
2196
2488
  if (!script) {
2197
2489
  console.error("Could not find the deepspace feature installer.");
2198
2490
  console.error("Looked in:");
2199
- console.error(` ${join11(appDir, "node_modules", "deepspace", "scripts", "add-feature.cjs")}`);
2200
- console.error(` ${join11(appDir, ".deepspace", "scripts", "add-feature.cjs")}`);
2491
+ console.error(` ${join10(appDir, "node_modules", "deepspace", "scripts", "add-feature.cjs")}`);
2492
+ console.error(` ${join10(appDir, ".deepspace", "scripts", "add-feature.cjs")}`);
2201
2493
  console.error("Are you in a DeepSpace app directory? Try `npm install` first.");
2202
2494
  process.exit(1);
2203
2495
  }
@@ -2221,8 +2513,8 @@ var add_default = defineCommand11({
2221
2513
  // src/cli/commands/skills.ts
2222
2514
  import { defineCommand as defineCommand12 } from "citty";
2223
2515
  import spawn2 from "cross-spawn";
2224
- import { mkdirSync as mkdirSync4 } from "fs";
2225
- import { join as join12 } from "path";
2516
+ import { mkdirSync as mkdirSync5 } from "fs";
2517
+ import { join as join11 } from "path";
2226
2518
  import { homedir as homedir5 } from "os";
2227
2519
  var SKILL_REPO = "deepdotspace/deepspace-skill";
2228
2520
  function runSkillsAdd(extraArgs) {
@@ -2241,7 +2533,7 @@ function runSkillsAdd(extraArgs) {
2241
2533
  });
2242
2534
  }
2243
2535
  function precreateClaudeDir(baseDir) {
2244
- mkdirSync4(join12(baseDir, ".claude"), { recursive: true });
2536
+ mkdirSync5(join11(baseDir, ".claude"), { recursive: true });
2245
2537
  }
2246
2538
  var add = defineCommand12({
2247
2539
  meta: {
@@ -2298,31 +2590,6 @@ var skills_default = defineCommand12({
2298
2590
  // src/cli/commands/domain.ts
2299
2591
  import { defineCommand as defineCommand13 } from "citty";
2300
2592
  import { execSync as execSync4 } from "child_process";
2301
-
2302
- // src/cli/lib/app-context.ts
2303
- import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
2304
- import { join as join13, resolve as resolve7 } from "path";
2305
- import { parse as parseToml4 } from "smol-toml";
2306
- function detectAppName(cwd = process.cwd()) {
2307
- const wranglerPath = join13(resolve7(cwd), "wrangler.toml");
2308
- if (!existsSync12(wranglerPath)) return null;
2309
- try {
2310
- const cfg = parseToml4(readFileSync8(wranglerPath, "utf-8"));
2311
- return typeof cfg.name === "string" && cfg.name.length > 0 ? cfg.name : null;
2312
- } catch {
2313
- return null;
2314
- }
2315
- }
2316
- function requireAppName(explicit, cwd) {
2317
- if (explicit) return explicit;
2318
- const detected = detectAppName(cwd);
2319
- if (detected) return detected;
2320
- throw new Error(
2321
- "No app specified. Pass --app <name>, or run from an app directory with a wrangler.toml."
2322
- );
2323
- }
2324
-
2325
- // src/cli/commands/domain.ts
2326
2593
  var API_URL2 = process.env.DEEPSPACE_API_URL ?? ENVS.prod.api;
2327
2594
  async function api(token2, path, init) {
2328
2595
  const res = await fetch(`${API_URL2}${path}`, {
@@ -2690,8 +2957,9 @@ function flushAndExit(code) {
2690
2957
  process.stdout.write("", () => process.exit(code));
2691
2958
  });
2692
2959
  }
2693
- async function fetchCatalog() {
2694
- const res = await fetch(`${API_URL3}/api/integrations`);
2960
+ async function fetchCatalog(opts = {}) {
2961
+ const url = opts.summary ? `${API_URL3}/api/integrations?summary=1` : `${API_URL3}/api/integrations`;
2962
+ const res = await fetch(url);
2695
2963
  if (!res.ok) {
2696
2964
  throw new Error(`Failed to fetch integration catalog (${res.status})`);
2697
2965
  }
@@ -2707,7 +2975,7 @@ function formatCurrency(value, currency) {
2707
2975
  return `${value} ${currency}`;
2708
2976
  }
2709
2977
  async function runList(args) {
2710
- const catalog = await fetchCatalog();
2978
+ const catalog = await fetchCatalog({ summary: true });
2711
2979
  if (args.json) {
2712
2980
  process.stdout.write(JSON.stringify(catalog) + "\n");
2713
2981
  await flushAndExit(0);
@@ -3254,9 +3522,9 @@ var managed_repos_default = defineCommand16({
3254
3522
 
3255
3523
  // src/cli/commands/library.ts
3256
3524
  import { defineCommand as defineCommand17 } from "citty";
3257
- import { existsSync as existsSync13, readFileSync as readFileSync10 } from "fs";
3258
- import { join as join14, resolve as resolve8 } from "path";
3259
- import { parse as parseToml5 } from "smol-toml";
3525
+ import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
3526
+ import { join as join12, resolve as resolve8 } from "path";
3527
+ import { parse as parseToml3 } from "smol-toml";
3260
3528
  import * as p6 from "@clack/prompts";
3261
3529
  var DEFAULT_LIBRARY_APP = "deepdotspace-site";
3262
3530
  var publish = defineCommand17({
@@ -3333,11 +3601,11 @@ var publish = defineCommand17({
3333
3601
  let appName = typeof args.app === "string" && args.app.trim() ? args.app.trim() : void 0;
3334
3602
  if (!appName) {
3335
3603
  const appDir = resolve8(typeof args.dir === "string" && args.dir ? args.dir : ".");
3336
- const wranglerPath = join14(appDir, "wrangler.toml");
3337
- if (!existsSync13(wranglerPath)) {
3604
+ const wranglerPath = join12(appDir, "wrangler.toml");
3605
+ if (!existsSync11(wranglerPath)) {
3338
3606
  return die("No --app provided and no wrangler.toml found. Either pass --app <name> or run from your app's directory.");
3339
3607
  }
3340
- const cfg = parseToml5(readFileSync10(wranglerPath, "utf-8"));
3608
+ const cfg = parseToml3(readFileSync10(wranglerPath, "utf-8"));
3341
3609
  const nameRes = resolveAppName(cfg.name);
3342
3610
  if (!nameRes.ok) return die(`wrangler.toml: ${nameRes.reason}`);
3343
3611
  appName = nameRes.name;
@@ -3470,9 +3738,9 @@ var library_default = defineCommand17({
3470
3738
 
3471
3739
  // src/cli/commands/admin.ts
3472
3740
  import { defineCommand as defineCommand18 } from "citty";
3473
- import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
3741
+ import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
3474
3742
  import { homedir as homedir6 } from "os";
3475
- import { join as join15 } from "path";
3743
+ import { join as join13 } from "path";
3476
3744
  var AUTH_URL5 = process.env.DEEPSPACE_AUTH_URL ?? ENVS.prod.auth;
3477
3745
  var impersonate = defineCommand18({
3478
3746
  meta: {
@@ -3522,9 +3790,9 @@ var impersonate = defineCommand18({
3522
3790
  console.error(`Failed: ${data.error ?? `HTTP ${res.status}`}`);
3523
3791
  process.exit(1);
3524
3792
  }
3525
- const dir = join15(homedir6(), ".deepspace");
3526
- mkdirSync5(dir, { recursive: true });
3527
- writeFileSync5(TOKEN_PATH2, data.token, { mode: 384 });
3793
+ const dir = join13(homedir6(), ".deepspace");
3794
+ mkdirSync6(dir, { recursive: true });
3795
+ writeFileSync6(TOKEN_PATH2, data.token, { mode: 384 });
3528
3796
  console.log(`Impersonating ${data.user.name ?? data.user.email ?? data.user.id}`);
3529
3797
  console.log(`Email: ${data.user.email ?? "(none)"}`);
3530
3798
  console.log(`UserID: ${data.user.id}`);
@@ -3541,9 +3809,9 @@ var admin_default = defineCommand18({
3541
3809
  });
3542
3810
 
3543
3811
  // src/cli/cli.ts
3544
- var __dirname = dirname2(fileURLToPath(import.meta.url));
3545
- var pkg = JSON.parse(readFileSync11(join16(__dirname, "..", "package.json"), "utf-8"));
3546
- if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync14("/etc/ssl/certs/ca-certificates.crt")) {
3812
+ var __dirname = dirname3(fileURLToPath(import.meta.url));
3813
+ var pkg = JSON.parse(readFileSync11(join14(__dirname, "..", "package.json"), "utf-8"));
3814
+ if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync12("/etc/ssl/certs/ca-certificates.crt")) {
3547
3815
  process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
3548
3816
  }
3549
3817
  var create3 = defineCommand19({