deepspace 0.5.5 → 0.5.7

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
@@ -2,15 +2,15 @@
2
2
 
3
3
  // src/cli/cli.ts
4
4
  import { defineCommand as defineCommand22, runMain } from "citty";
5
- import { execSync as execSync4 } from "child_process";
6
- import { readFileSync as readFileSync15, existsSync as existsSync14 } from "fs";
7
- import { dirname as dirname4, join as join16 } from "path";
8
- import { fileURLToPath as fileURLToPath2 } from "url";
5
+ import { spawnSync as spawnSync6 } from "child_process";
6
+ import { readFileSync as readFileSync17, existsSync as existsSync14 } from "fs";
7
+ import { dirname as dirname5, join as join15 } from "path";
8
+ import { fileURLToPath as fileURLToPath3 } from "url";
9
9
 
10
10
  // src/cli/lib/cli-errors.ts
11
11
  var API_ERROR_HINTS = {
12
12
  not_app_owner: "Only the app owner can do this.",
13
- app_not_found: "App not found. Check the app name (wrangler.toml `name`) and that it has been deployed.",
13
+ app_not_found: "App not found. 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 apps`. (`domain` commands take the deployed app *name* instead.)",
14
14
  not_app_owner_or_collaborator: "You must be the app owner or a collaborator to do this.",
15
15
  test_account_cannot_be_collaborator: "Test accounts cannot be added as collaborators. Use a real DeepSpace account.",
16
16
  user_not_found: "No DeepSpace user with that email. They need to log in to DeepSpace at least once (`deepspace login`, or sign in to any app) before they can be referenced by email."
@@ -25,8 +25,11 @@ ${hint}` : message;
25
25
  }
26
26
  function renderCliError(err) {
27
27
  console.error(formatCliError(err));
28
- if (process.env.DEBUG && err instanceof Error && err.stack) {
29
- console.error("\n" + err.stack);
28
+ if (process.env.DEBUG) {
29
+ const { apiPath, status: status3 } = err;
30
+ if (apiPath) console.error(`
31
+ API ${apiPath}${status3 ? ` (${status3})` : ""}`);
32
+ if (err instanceof Error && err.stack) console.error("\n" + err.stack);
30
33
  }
31
34
  process.exit(1);
32
35
  }
@@ -51,7 +54,7 @@ function wrapCommandErrors(cmd) {
51
54
 
52
55
  // src/cli/commands/login.ts
53
56
  import { defineCommand } from "citty";
54
- import { writeFileSync as writeFileSync5, mkdirSync } from "fs";
57
+ import { writeFileSync as writeFileSync5, mkdirSync, readFileSync as readFileSync4 } from "fs";
55
58
  import { join as join3 } from "path";
56
59
  import { homedir } from "os";
57
60
  import { randomBytes, createHash } from "crypto";
@@ -437,23 +440,56 @@ function parseSecretsUpload(content) {
437
440
  return out2;
438
441
  }
439
442
  const out = {};
440
- for (const rawLine of trimmed.split("\n")) {
441
- const line = rawLine.trim();
443
+ const lines = trimmed.split(/\r?\n/);
444
+ for (let i = 0; i < lines.length; i++) {
445
+ const line = lines[i].trim();
442
446
  if (!line || line.startsWith("#")) continue;
443
447
  const eq = line.indexOf("=");
444
448
  if (eq <= 0) continue;
445
449
  const key = line.slice(0, eq).trim();
446
450
  if (!SECRET_NAME_RE.test(key)) continue;
447
451
  validateSecretName(key);
448
- let value = line.slice(eq + 1).trim();
449
- if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
450
- value = unescapeDotenv(value.slice(1, -1));
452
+ const raw = line.slice(eq + 1).trim();
453
+ const quote = raw[0] === '"' || raw[0] === "'" ? raw[0] : null;
454
+ let value;
455
+ if (!quote) {
456
+ value = raw;
457
+ } else if (endsWithUnescapedQuote(raw.slice(1), quote)) {
458
+ const inner = raw.slice(1, -1);
459
+ value = quote === '"' ? unescapeDotenv(inner) : inner;
460
+ } else {
461
+ const startLine = i + 1;
462
+ const parts = [raw.slice(1)];
463
+ let closed = false;
464
+ while (++i < lines.length) {
465
+ const cont = lines[i].trimEnd();
466
+ if (endsWithUnescapedQuote(cont, quote)) {
467
+ parts.push(cont.slice(0, -1));
468
+ closed = true;
469
+ break;
470
+ }
471
+ parts.push(lines[i]);
472
+ }
473
+ if (!closed) {
474
+ throw new Error(
475
+ `Unterminated quoted value for "${key}" (opens on line ${startLine}). Close the ${quote === '"' ? "double" : "single"} quote, or escape newlines as \\n on one line.`
476
+ );
477
+ }
478
+ value = parts.join("\n");
479
+ if (quote === '"') value = unescapeDotenv(value);
451
480
  }
452
481
  validateSecretValue(key, value);
453
482
  out[key] = value;
454
483
  }
455
484
  return out;
456
485
  }
486
+ function endsWithUnescapedQuote(s, q) {
487
+ if (!s.endsWith(q)) return false;
488
+ if (q === "'") return true;
489
+ let backslashes = 0;
490
+ for (let j = s.length - 2; j >= 0 && s[j] === "\\"; j--) backslashes++;
491
+ return backslashes % 2 === 0;
492
+ }
457
493
  function unescapeDotenv(v) {
458
494
  let out = "";
459
495
  for (let i = 0; i < v.length; i++) {
@@ -1072,19 +1108,38 @@ var login_default = defineCommand({
1072
1108
  args: {
1073
1109
  email: {
1074
1110
  type: "string",
1075
- description: "Email address (non-interactive mode)",
1111
+ description: "Email address (non-interactive mode; or $DEEPSPACE_EMAIL)",
1076
1112
  required: false
1077
1113
  },
1078
1114
  password: {
1079
1115
  type: "string",
1080
- description: "Password (non-interactive mode)",
1116
+ description: "Password (discouraged \u2014 visible in ps/history; prefer --password-stdin or $DEEPSPACE_PASSWORD)",
1081
1117
  required: false
1118
+ },
1119
+ "password-stdin": {
1120
+ type: "boolean",
1121
+ description: "Read the password from stdin instead of --password",
1122
+ default: false
1082
1123
  }
1083
1124
  },
1084
1125
  async run({ args }) {
1085
- if (args.email && args.password) {
1086
- console.log(`Signing in as ${args.email}...`);
1087
- await doEmailLogin(args.email, args.password);
1126
+ const interactive = Boolean(process.stdin.isTTY);
1127
+ const { email, password } = resolveLoginCredentials({
1128
+ emailArg: args.email,
1129
+ passwordArg: args.password,
1130
+ envEmail: interactive ? void 0 : process.env.DEEPSPACE_EMAIL,
1131
+ envPassword: interactive ? void 0 : process.env.DEEPSPACE_PASSWORD,
1132
+ passwordStdin: args["password-stdin"] ? readPasswordFromStdin() : void 0
1133
+ });
1134
+ const passwordIntent = Boolean(args["password-stdin"]) || args.password !== void 0 || password !== void 0;
1135
+ const decision = loginModeDecision({ email, password, passwordIntent });
1136
+ if (decision.mode === "error") {
1137
+ console.error(decision.message);
1138
+ process.exit(1);
1139
+ }
1140
+ if (decision.mode === "password") {
1141
+ console.log(`Signing in as ${email}...`);
1142
+ await doEmailLogin(email, password);
1088
1143
  console.log("Logged in");
1089
1144
  return;
1090
1145
  }
@@ -1135,6 +1190,34 @@ plans on the web dashboard:
1135
1190
  p2.outro("Done");
1136
1191
  }
1137
1192
  });
1193
+ function resolveLoginCredentials(opts) {
1194
+ const email = opts.emailArg || opts.envEmail || void 0;
1195
+ const password = opts.passwordStdin !== void 0 ? opts.passwordStdin : opts.passwordArg || opts.envPassword || void 0;
1196
+ return { email, password };
1197
+ }
1198
+ function loginModeDecision(opts) {
1199
+ if (opts.email && opts.password) return { mode: "password" };
1200
+ if (!opts.passwordIntent) return { mode: "oauth" };
1201
+ if (!opts.email) {
1202
+ return {
1203
+ mode: "error",
1204
+ message: "Non-interactive login needs an email: --email <you@example.com> (or $DEEPSPACE_EMAIL)."
1205
+ };
1206
+ }
1207
+ return {
1208
+ mode: "error",
1209
+ message: "Non-interactive login needs a non-empty password: --password-stdin, $DEEPSPACE_PASSWORD, or --password."
1210
+ };
1211
+ }
1212
+ function readPasswordFromStdin() {
1213
+ if (process.stdin.isTTY) {
1214
+ console.error(
1215
+ '--password-stdin expects the password piped on stdin, e.g. `printf %s "$PW" | deepspace login --email you@x.com --password-stdin`.'
1216
+ );
1217
+ process.exit(1);
1218
+ }
1219
+ return readFileSync4(0, "utf-8").replace(/\r?\n$/, "");
1220
+ }
1138
1221
  async function pollForCompletion(sessionId, codeVerifier) {
1139
1222
  const maxAttempts = 120;
1140
1223
  const interval = 5e3;
@@ -1205,7 +1288,7 @@ async function doEmailLogin(email, password) {
1205
1288
 
1206
1289
  // src/cli/commands/logout.ts
1207
1290
  import { defineCommand as defineCommand2 } from "citty";
1208
- import { existsSync as existsSync4, readFileSync as readFileSync4, rmSync as rmSync2 } from "fs";
1291
+ import { existsSync as existsSync4, readFileSync as readFileSync5, rmSync as rmSync2 } from "fs";
1209
1292
  import { join as join4 } from "path";
1210
1293
  import { homedir as homedir2 } from "os";
1211
1294
  var AUTH_URL2 = process.env.DEEPSPACE_AUTH_URL ?? PLATFORM_URLS.auth;
@@ -1218,7 +1301,7 @@ var logout_default = defineCommand2({
1218
1301
  description: "Sign out and remove cached credentials"
1219
1302
  },
1220
1303
  async run() {
1221
- const sessionToken = existsSync4(SESSION_PATH) ? readFileSync4(SESSION_PATH, "utf-8").trim() : null;
1304
+ const sessionToken = existsSync4(SESSION_PATH) ? readFileSync5(SESSION_PATH, "utf-8").trim() : null;
1222
1305
  if (sessionToken) {
1223
1306
  try {
1224
1307
  await fetch(`${AUTH_URL2}/api/auth/sign-out`, {
@@ -1248,11 +1331,11 @@ var logout_default = defineCommand2({
1248
1331
 
1249
1332
  // src/cli/commands/dev.ts
1250
1333
  import { defineCommand as defineCommand3 } from "citty";
1251
- import { resolve as resolve4, basename, join as join9 } from "path";
1334
+ import { resolve as resolve4, basename as basename2, join as join9 } from "path";
1252
1335
  import spawn from "cross-spawn";
1253
1336
 
1254
1337
  // src/cli/auth.ts
1255
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync6, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
1338
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
1256
1339
  import { join as join5 } from "path";
1257
1340
  import { homedir as homedir3 } from "os";
1258
1341
 
@@ -1272,7 +1355,7 @@ var SESSION_PATH2 = join5(DIR2, "session");
1272
1355
  var TOKEN_PATH2 = join5(DIR2, "token");
1273
1356
  async function ensureToken() {
1274
1357
  if (existsSync5(TOKEN_PATH2)) {
1275
- const existing = readFileSync5(TOKEN_PATH2, "utf-8").trim();
1358
+ const existing = readFileSync6(TOKEN_PATH2, "utf-8").trim();
1276
1359
  if (isTokenValid(existing)) {
1277
1360
  return existing;
1278
1361
  }
@@ -1280,7 +1363,7 @@ async function ensureToken() {
1280
1363
  if (!existsSync5(SESSION_PATH2)) {
1281
1364
  throw new Error("Not logged in. Run `deepspace login` first.");
1282
1365
  }
1283
- const sessionToken = readFileSync5(SESSION_PATH2, "utf-8").trim();
1366
+ const sessionToken = readFileSync6(SESSION_PATH2, "utf-8").trim();
1284
1367
  const token = await exchangeSession(AUTH_URL3, sessionToken);
1285
1368
  if (!token) {
1286
1369
  throw new Error("Session expired. Run `deepspace login` to re-authenticate.");
@@ -1301,20 +1384,56 @@ function isTokenValid(jwt) {
1301
1384
  // src/cli/lib/app-context.ts
1302
1385
  import {
1303
1386
  existsSync as existsSync6,
1304
- readFileSync as readFileSync6,
1387
+ readFileSync as readFileSync7,
1305
1388
  readdirSync,
1306
1389
  writeFileSync as writeFileSync7,
1307
1390
  mkdirSync as mkdirSync3,
1308
1391
  renameSync,
1309
1392
  unlinkSync
1310
1393
  } from "fs";
1311
- import { join as join6, resolve as resolve2, dirname, isAbsolute, sep } from "path";
1394
+ import { join as join6, resolve as resolve2, dirname, basename, isAbsolute, sep } from "path";
1312
1395
  import { parse as parseToml3 } from "smol-toml";
1396
+
1397
+ // src/cli/lib/api.ts
1398
+ var ApiError = class extends Error {
1399
+ constructor(message, status3, code, apiPath) {
1400
+ super(message);
1401
+ this.status = status3;
1402
+ this.code = code;
1403
+ this.apiPath = apiPath;
1404
+ }
1405
+ };
1406
+ async function apiFetch(baseUrl, token, path, init) {
1407
+ const res = await fetch(`${baseUrl}${path}`, {
1408
+ ...init,
1409
+ headers: {
1410
+ Authorization: `Bearer ${token}`,
1411
+ "Content-Type": "application/json",
1412
+ ...init?.headers ?? {}
1413
+ }
1414
+ });
1415
+ const text2 = await res.text();
1416
+ if (!res.ok) {
1417
+ let msg = text2;
1418
+ let code;
1419
+ try {
1420
+ const body = JSON.parse(text2);
1421
+ msg = body.error ?? text2;
1422
+ code = body.code;
1423
+ } catch {
1424
+ }
1425
+ throw new ApiError(msg || `Request failed (${res.status})`, res.status, code, path);
1426
+ }
1427
+ return text2 ? JSON.parse(text2) : {};
1428
+ }
1429
+
1430
+ // src/cli/lib/app-context.ts
1431
+ var STRICT_APP_ID_RE = /^app_[0-9A-HJKMNP-TV-Z]{26}$/;
1313
1432
  function detectAppName(cwd = process.cwd()) {
1314
1433
  const wranglerPath = join6(resolve2(cwd), "wrangler.toml");
1315
1434
  if (!existsSync6(wranglerPath)) return null;
1316
1435
  try {
1317
- const cfg = parseToml3(readFileSync6(wranglerPath, "utf-8"));
1436
+ const cfg = parseToml3(readFileSync7(wranglerPath, "utf-8"));
1318
1437
  return typeof cfg.name === "string" && cfg.name.length > 0 ? cfg.name : null;
1319
1438
  } catch (err) {
1320
1439
  throw new Error(
@@ -1363,18 +1482,42 @@ function writeLaunchFile(launchPath, config) {
1363
1482
  function readLaunchFile(launchPath) {
1364
1483
  const empty = { version: "0.0.1", configurations: [] };
1365
1484
  if (!existsSync6(launchPath)) return empty;
1366
- const raw = readFileSync6(launchPath, "utf-8").trim();
1485
+ const raw = readFileSync7(launchPath, "utf-8").trim();
1367
1486
  if (!raw) return empty;
1368
1487
  const parsed = JSON.parse(raw);
1369
1488
  if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.configurations)) return null;
1370
1489
  return parsed;
1371
1490
  }
1372
- function writeLaunchConfigIfMissing(appDir, appName, port) {
1491
+ function writeLaunchConfigIfMissing(appDir, appName, port, opts = {}) {
1373
1492
  const launchPath = join6(appDir, ".claude", "launch.json");
1374
1493
  try {
1375
1494
  const config = readLaunchFile(launchPath);
1376
1495
  if (!config) return;
1377
- if (config.configurations.some((c) => c?.name === appName)) return;
1496
+ const existing = config.configurations.find((c) => c?.name === appName);
1497
+ if (existing) {
1498
+ if (!opts.updatePort) return;
1499
+ let changed = false;
1500
+ if (Number(existing.port) !== port) {
1501
+ existing.port = port;
1502
+ changed = true;
1503
+ }
1504
+ if (Array.isArray(existing.runtimeArgs)) {
1505
+ const ra = existing.runtimeArgs.map(String);
1506
+ const pi = ra.indexOf("--port");
1507
+ if (pi >= 0 && pi + 1 < ra.length) {
1508
+ if (ra[pi + 1] !== String(port)) {
1509
+ ra[pi + 1] = String(port);
1510
+ changed = true;
1511
+ }
1512
+ } else {
1513
+ ra.push("--port", String(port));
1514
+ changed = true;
1515
+ }
1516
+ if (changed) existing.runtimeArgs = ra;
1517
+ }
1518
+ if (changed) writeLaunchFile(launchPath, config);
1519
+ return;
1520
+ }
1378
1521
  config.configurations.push({
1379
1522
  name: appName,
1380
1523
  runtimeExecutable: "npx",
@@ -1385,6 +1528,18 @@ function writeLaunchConfigIfMissing(appDir, appName, port) {
1385
1528
  } catch {
1386
1529
  }
1387
1530
  }
1531
+ function resolveAppLaunchPort(appDir) {
1532
+ try {
1533
+ const config = readLaunchFile(join6(appDir, ".claude", "launch.json"));
1534
+ if (!config) return null;
1535
+ const appName = detectAppName(appDir) ?? basename(appDir);
1536
+ const entry = config.configurations.find((c) => c?.name === appName) ?? (config.configurations.length === 1 ? config.configurations[0] : void 0);
1537
+ const port = Number(entry?.port);
1538
+ return Number.isInteger(port) && port > 0 ? port : null;
1539
+ } catch {
1540
+ return null;
1541
+ }
1542
+ }
1388
1543
  function detectClaudeWorktree(appDir) {
1389
1544
  const segments = resolve2(appDir).split(sep);
1390
1545
  for (let i = segments.length - 3; i >= 1; i--) {
@@ -1420,7 +1575,7 @@ function upsertWorktreeLaunchConfig(mainRepoRoot, worktreeName, appDir, opts) {
1420
1575
  let port = opts.port;
1421
1576
  if (opts.probePort) {
1422
1577
  const taken = new Set(
1423
- config.configurations.map((c) => Number(c?.port)).filter((p8) => Number.isInteger(p8) && p8 > 0)
1578
+ config.configurations.map((c) => Number(c?.port)).filter((p10) => Number.isInteger(p10) && p10 > 0)
1424
1579
  );
1425
1580
  for (let i = 0; i < WORKTREE_PORT_RANGE && taken.has(port); i++) {
1426
1581
  port = WORKTREE_PORT_MIN + (port - WORKTREE_PORT_MIN + 1) % WORKTREE_PORT_RANGE;
@@ -1473,9 +1628,36 @@ function requireAppIdArg(explicit) {
1473
1628
  }
1474
1629
  return id;
1475
1630
  }
1631
+ function matchAppSelector(apps, selector) {
1632
+ const byId = apps.find((a) => a.appId === selector);
1633
+ if (byId) return byId.appId;
1634
+ const byName = apps.find((a) => a.name === selector);
1635
+ if (byName) return byName.appId;
1636
+ return STRICT_APP_ID_RE.test(selector) ? selector : null;
1637
+ }
1638
+ async function resolveAppSelector(deployUrl, token, selector) {
1639
+ if (STRICT_APP_ID_RE.test(selector)) return selector;
1640
+ const { apps } = await apiFetch(deployUrl, token, "/api/apps");
1641
+ const id = matchAppSelector(apps, selector);
1642
+ if (!id) {
1643
+ throw new Error(`No app "${selector}" in your account. Run \`deepspace apps\` to list your apps.`);
1644
+ }
1645
+ return id;
1646
+ }
1647
+ async function resolveAppTarget(deployUrl, token, explicit) {
1648
+ if (explicit) return resolveAppSelector(deployUrl, token, explicit);
1649
+ const appDir = findAppDir();
1650
+ const id = appDir ? readAppId(appDir) : null;
1651
+ if (!id) {
1652
+ throw new Error(
1653
+ "No app id. Run from an app directory whose wrangler.toml carries DEEPSPACE_APP_ID, or pass --app <id or name>."
1654
+ );
1655
+ }
1656
+ return id;
1657
+ }
1476
1658
 
1477
1659
  // src/cli/lib/install-status.ts
1478
- import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
1660
+ import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
1479
1661
  import { join as join7 } from "path";
1480
1662
  function ensureInstallReady(appDir) {
1481
1663
  if (existsSync7(join7(appDir, "node_modules", "deepspace", "package.json"))) return;
@@ -1486,13 +1668,19 @@ function ensureInstallReady(appDir) {
1486
1668
  const logPath = join7(sentinelDir, "install.log");
1487
1669
  if (existsSync7(errPath)) {
1488
1670
  console.error("Background install failed:");
1489
- console.error(readFileSync7(errPath, "utf-8").trimEnd());
1671
+ console.error(readFileSync8(errPath, "utf-8").trimEnd());
1490
1672
  if (existsSync7(logPath)) console.error(`
1491
1673
  Full log: ${logPath}`);
1492
1674
  console.error("\nRun `npm install` (or `bun install`) manually, then retry.");
1493
1675
  process.exit(1);
1494
1676
  }
1495
1677
  if (existsSync7(startedPath) && !existsSync7(donePath)) {
1678
+ if (!installWorkerAlive(sentinelDir)) {
1679
+ console.error("The background install is no longer running and never finished.");
1680
+ if (existsSync7(logPath)) console.error(`See what happened: ${logPath}`);
1681
+ console.error("Run `npm install` (or `bun install`) manually, then retry.");
1682
+ process.exit(1);
1683
+ }
1496
1684
  console.error("Dependencies are still installing in the background.");
1497
1685
  if (existsSync7(logPath)) console.error(`Tail progress: tail -f ${logPath}`);
1498
1686
  console.error("Retry once it finishes.");
@@ -1501,6 +1689,18 @@ Full log: ${logPath}`);
1501
1689
  console.error("Dependencies not installed. Run `npm install` (or `bun install`) first.");
1502
1690
  process.exit(1);
1503
1691
  }
1692
+ function installWorkerAlive(sentinelDir) {
1693
+ const pidPath = join7(sentinelDir, "install.pid");
1694
+ if (!existsSync7(pidPath)) return true;
1695
+ const pid = Number.parseInt(readFileSync8(pidPath, "utf-8").trim(), 10);
1696
+ if (!Number.isFinite(pid) || pid <= 0) return true;
1697
+ try {
1698
+ process.kill(pid, 0);
1699
+ return true;
1700
+ } catch (err) {
1701
+ return err.code === "EPERM";
1702
+ }
1703
+ }
1504
1704
 
1505
1705
  // src/cli/lib/preflight.ts
1506
1706
  import { execFileSync } from "child_process";
@@ -1578,7 +1778,16 @@ function removeMacosJunk(dir) {
1578
1778
  }
1579
1779
 
1580
1780
  // src/cli/lib/port.ts
1781
+ import { createServer } from "net";
1581
1782
  var DEFAULT_PORT = 5173;
1783
+ function checkPortAvailable(port, host = "0.0.0.0") {
1784
+ return new Promise((resolve10) => {
1785
+ const srv = createServer();
1786
+ srv.once("error", () => resolve10(false));
1787
+ srv.once("listening", () => srv.close(() => resolve10(true)));
1788
+ srv.listen(port, host);
1789
+ });
1790
+ }
1582
1791
  function resolvePort(arg) {
1583
1792
  const raw = arg ?? process.env.DEEPSPACE_PORT;
1584
1793
  if (!raw) return DEFAULT_PORT;
@@ -1605,6 +1814,7 @@ var dev_default = defineCommand3({
1605
1814
  },
1606
1815
  env: {
1607
1816
  type: "string",
1817
+ alias: "e",
1608
1818
  description: "wrangler.toml [env.<name>] block to run (e.g. --env staging). Applies the env's overrides at build time.",
1609
1819
  required: false
1610
1820
  },
@@ -1662,7 +1872,9 @@ var dev_default = defineCommand3({
1662
1872
  );
1663
1873
  }
1664
1874
  }
1665
- writeLaunchConfigIfMissing(appDir, detectAppName(appDir) ?? basename(appDir), port);
1875
+ writeLaunchConfigIfMissing(appDir, detectAppName(appDir) ?? basename2(appDir), port, {
1876
+ updatePort: explicitPort
1877
+ });
1666
1878
  ensureInstallReady(appDir);
1667
1879
  let token;
1668
1880
  try {
@@ -1710,6 +1922,12 @@ var dev_default = defineCommand3({
1710
1922
  process.exit(1);
1711
1923
  }
1712
1924
  preflightWindowsWorkerd(appDir);
1925
+ if (!await checkPortAvailable(port)) {
1926
+ console.error(`Port ${port} is already in use.`);
1927
+ const killCmd = port === DEFAULT_PORT ? "deepspace kill" : `deepspace kill --port ${port}`;
1928
+ console.error(`Free it with \`${killCmd}\`, or start on another port: \`deepspace dev --port <other>\`.`);
1929
+ process.exit(1);
1930
+ }
1713
1931
  console.log("Starting dev server...\n");
1714
1932
  const vite = spawn("npx", ["vite", "--port", String(port), "--strictPort", "--host"], {
1715
1933
  cwd: appDir,
@@ -1732,10 +1950,16 @@ var dev_default = defineCommand3({
1732
1950
  // src/cli/commands/kill.ts
1733
1951
  import { defineCommand as defineCommand4 } from "citty";
1734
1952
  import { spawnSync } from "child_process";
1735
- import { readFileSync as readFileSync8, readdirSync as readdirSync3, readlinkSync } from "fs";
1953
+ import { readFileSync as readFileSync9, readdirSync as readdirSync3, readlinkSync } from "fs";
1736
1954
  import { setTimeout as wait } from "timers/promises";
1737
1955
  var SIGTERM_GRACE_MS = 1500;
1738
1956
  var IS_WIN = process.platform === "win32";
1957
+ function pickKillPort(opts) {
1958
+ if (opts.explicit != null) return opts.explicit;
1959
+ if (opts.worktree != null) return opts.worktree;
1960
+ if (opts.env != null) return opts.env;
1961
+ return opts.appLaunch ?? DEFAULT_PORT;
1962
+ }
1739
1963
  var kill_default = defineCommand4({
1740
1964
  meta: {
1741
1965
  name: "kill",
@@ -1754,21 +1978,38 @@ var kill_default = defineCommand4({
1754
1978
  }
1755
1979
  },
1756
1980
  async run({ args }) {
1757
- const port = (args.port ? null : resolveWorktreePort(process.cwd())) ?? resolvePort(args.port);
1981
+ const cwd = process.cwd();
1982
+ const port = pickKillPort({
1983
+ explicit: args.port ? resolvePort(args.port) : null,
1984
+ worktree: resolveWorktreePort(cwd),
1985
+ // resolvePort() reads + validates $DEEPSPACE_PORT; only consult it when set.
1986
+ env: process.env.DEEPSPACE_PORT ? resolvePort() : null,
1987
+ appLaunch: resolveAppLaunchPort(findAppDir(cwd) ?? cwd)
1988
+ });
1758
1989
  const targets = /* @__PURE__ */ new Set();
1759
- for (const pid of listenerPids(port)) targets.add(pid);
1990
+ const listeners = enumerateListeners(port);
1991
+ for (const pid of listeners.pids) targets.add(pid);
1760
1992
  for (const pid of [...targets]) {
1761
1993
  for (const child of childPids(pid)) targets.add(child);
1762
1994
  }
1995
+ let swept = false;
1763
1996
  if (args.all) {
1764
- for (const pid of processPidsByName(["workerd", "wrangler", "vite"])) {
1765
- targets.add(pid);
1766
- }
1997
+ const sweep = sweepByName(["workerd", "wrangler", "vite"]);
1998
+ swept = sweep.swept;
1999
+ for (const pid of sweep.pids) targets.add(pid);
1767
2000
  }
1768
2001
  if (targets.size === 0) {
1769
- console.log(
1770
- args.all ? `Nothing listening on :${port} and no stray workerd/wrangler/vite processes found.` : `Nothing listening on :${port}. (Pass --all to sweep stray workerd/wrangler.)`
1771
- );
2002
+ const outcome = noTargetsMessage({
2003
+ enumerated: listeners.enumerated,
2004
+ swept,
2005
+ all: args.all,
2006
+ port
2007
+ });
2008
+ if (!outcome.ok) {
2009
+ console.error(outcome.message);
2010
+ process.exit(1);
2011
+ }
2012
+ console.log(outcome.message);
1772
2013
  return;
1773
2014
  }
1774
2015
  for (const pid of targets) {
@@ -1794,37 +2035,74 @@ var kill_default = defineCommand4({
1794
2035
  );
1795
2036
  process.exit(1);
1796
2037
  }
1797
- console.log(`Port ${port} is free.`);
2038
+ if (args.all) {
2039
+ console.log(`Killed ${targets.size} process(es) (workerd/wrangler/vite sweep). :${port} is free.`);
2040
+ } else {
2041
+ console.log(`Port ${port} is free.`);
2042
+ }
1798
2043
  }
1799
2044
  });
1800
- function listenerPids(port) {
2045
+ function noTargetsMessage(opts) {
2046
+ const { enumerated, swept, all, port } = opts;
2047
+ const couldObserve = enumerated || all && swept;
2048
+ if (!couldObserve) {
2049
+ return {
2050
+ ok: false,
2051
+ message: `Couldn't determine what's running on :${port}: no lsof/pgrep and /proc is unavailable, so nothing was inspected or killed. Install lsof (or procps for pgrep), or find and kill the process manually.`
2052
+ };
2053
+ }
2054
+ return {
2055
+ ok: true,
2056
+ message: all ? `Nothing listening on :${port} and no stray workerd/wrangler/vite processes found.` : `Nothing listening on :${port}. (Pass --all to sweep stray workerd/wrangler.)`
2057
+ };
2058
+ }
2059
+ function enumerateListeners(port) {
1801
2060
  if (IS_WIN) {
1802
- return runPowershell(
2061
+ const r = runPowershellChecked(
1803
2062
  `Get-NetTCPConnection -LocalPort ${port} -State Listen -ErrorAction SilentlyContinue | Select-Object -ExpandProperty OwningProcess`
1804
2063
  );
2064
+ return { pids: r.pids, enumerated: r.ran };
2065
+ }
2066
+ const lsof = runLsof(port);
2067
+ if (lsof.ran) return { pids: lsof.pids, enumerated: true };
2068
+ const proc = listenerPidsViaProc(port);
2069
+ return { pids: proc.pids, enumerated: proc.available };
2070
+ }
2071
+ function listenerPids(port) {
2072
+ return enumerateListeners(port).pids;
2073
+ }
2074
+ function runLsof(port) {
2075
+ try {
2076
+ const r = spawnSync("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-t"], {
2077
+ encoding: "utf-8"
2078
+ });
2079
+ if (r.error) return { pids: [], ran: false };
2080
+ return { pids: r.status === 0 ? parsePidLines(r.stdout) : [], ran: true };
2081
+ } catch {
2082
+ return { pids: [], ran: false };
1805
2083
  }
1806
- const viaLsof = run(["lsof", "-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-t"]);
1807
- if (viaLsof.length) return viaLsof;
1808
- return listenerPidsViaProc(port);
1809
2084
  }
1810
2085
  function listenerPidsViaProc(port) {
2086
+ let available = false;
1811
2087
  try {
1812
2088
  const hexPort = port.toString(16).toUpperCase().padStart(4, "0");
1813
2089
  const inodes = /* @__PURE__ */ new Set();
1814
2090
  for (const f of ["/proc/net/tcp", "/proc/net/tcp6"]) {
1815
2091
  let data;
1816
2092
  try {
1817
- data = readFileSync8(f, "utf-8");
2093
+ data = readFileSync9(f, "utf-8");
1818
2094
  } catch {
1819
2095
  continue;
1820
2096
  }
2097
+ available = true;
1821
2098
  for (const line of data.split("\n").slice(1)) {
1822
2099
  const cols = line.trim().split(/\s+/);
1823
2100
  if (cols[3] !== "0A") continue;
1824
2101
  if (cols[1]?.split(":")[1] === hexPort && cols[9]) inodes.add(cols[9]);
1825
2102
  }
1826
2103
  }
1827
- if (!inodes.size) return [];
2104
+ if (!available) return { pids: [], available: false };
2105
+ if (!inodes.size) return { pids: [], available: true };
1828
2106
  const pids = /* @__PURE__ */ new Set();
1829
2107
  for (const pidDir of readdirSync3("/proc")) {
1830
2108
  if (!/^\d+$/.test(pidDir)) continue;
@@ -1848,9 +2126,9 @@ function listenerPidsViaProc(port) {
1848
2126
  }
1849
2127
  }
1850
2128
  }
1851
- return [...pids];
2129
+ return { pids: [...pids], available: true };
1852
2130
  } catch {
1853
- return [];
2131
+ return { pids: [], available };
1854
2132
  }
1855
2133
  }
1856
2134
  function childPids(parentPid) {
@@ -1861,22 +2139,31 @@ function childPids(parentPid) {
1861
2139
  }
1862
2140
  return run(["pgrep", "-P", String(parentPid)]);
1863
2141
  }
1864
- function processPidsByName(names) {
2142
+ function sweepByName(names) {
1865
2143
  const all = /* @__PURE__ */ new Set();
1866
2144
  if (IS_WIN) {
1867
2145
  const orClauses = names.map((n) => `($_.Name -like '*${n}*') -or ($_.CommandLine -like '*${n}*')`).join(" -or ");
1868
2146
  const script = `Get-CimInstance Win32_Process | Where-Object { ${orClauses} } | Select-Object -ExpandProperty ProcessId`;
1869
- for (const pid of runPowershell(script)) {
1870
- if (pid !== process.pid) all.add(pid);
1871
- }
1872
- return [...all];
2147
+ const r = runPowershellChecked(script);
2148
+ for (const pid of r.pids) if (pid !== process.pid) all.add(pid);
2149
+ return { pids: [...all], swept: r.ran };
1873
2150
  }
2151
+ let swept = false;
1874
2152
  for (const name of names) {
1875
- for (const pid of run(["pgrep", "-f", name])) {
1876
- if (pid !== process.pid) all.add(pid);
1877
- }
2153
+ const r = runPgrep(["-f", name]);
2154
+ if (r.ran) swept = true;
2155
+ for (const pid of r.pids) if (pid !== process.pid) all.add(pid);
2156
+ }
2157
+ return { pids: [...all], swept };
2158
+ }
2159
+ function runPgrep(pgrepArgs) {
2160
+ try {
2161
+ const r = spawnSync("pgrep", pgrepArgs, { encoding: "utf-8" });
2162
+ if (r.error) return { pids: [], ran: false };
2163
+ return { pids: r.status === 0 ? parsePidLines(r.stdout) : [], ran: r.status === 0 || r.status === 1 };
2164
+ } catch {
2165
+ return { pids: [], ran: false };
1878
2166
  }
1879
- return [...all];
1880
2167
  }
1881
2168
  function run(cmd) {
1882
2169
  try {
@@ -1888,15 +2175,18 @@ function run(cmd) {
1888
2175
  }
1889
2176
  }
1890
2177
  function runPowershell(script) {
2178
+ return runPowershellChecked(script).pids;
2179
+ }
2180
+ function runPowershellChecked(script) {
1891
2181
  const exe = pwshOrPowershell();
1892
2182
  try {
1893
2183
  const r = spawnSync(exe, ["-NoProfile", "-NonInteractive", "-Command", script], {
1894
2184
  encoding: "utf-8"
1895
2185
  });
1896
- if (r.status !== 0) return [];
1897
- return parsePidLines(r.stdout);
2186
+ if (r.error) return { pids: [], ran: false };
2187
+ return { pids: r.status === 0 ? parsePidLines(r.stdout) : [], ran: r.status === 0 };
1898
2188
  } catch {
1899
- return [];
2189
+ return { pids: [], ran: false };
1900
2190
  }
1901
2191
  }
1902
2192
  function pwshOrPowershell() {
@@ -1975,6 +2265,7 @@ var test_default = defineCommand5({
1975
2265
  },
1976
2266
  env: {
1977
2267
  type: "string",
2268
+ alias: "e",
1978
2269
  description: "wrangler.toml [env.<name>] block to test (uses secrets config <name> by default).",
1979
2270
  required: false
1980
2271
  }
@@ -2010,8 +2301,8 @@ var test_default = defineCommand5({
2010
2301
  token = await ensureToken();
2011
2302
  const payload = decodeJwtPayload(token);
2012
2303
  ownerId = payload.sub;
2013
- } catch {
2014
- console.error("`deepspace test` requires you to be logged in. Run `deepspace login` first.");
2304
+ } catch (err) {
2305
+ console.error(err instanceof Error ? err.message : "Not logged in. Run `deepspace login` first.");
2015
2306
  process.exit(1);
2016
2307
  }
2017
2308
  let generatedSecretsCache;
@@ -2172,7 +2463,7 @@ var screenshot_default = defineCommand6({
2172
2463
  // src/cli/commands/test-accounts.ts
2173
2464
  import { defineCommand as defineCommand7 } from "citty";
2174
2465
  import * as p3 from "@clack/prompts";
2175
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync8, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
2466
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync8, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
2176
2467
  import { join as join10 } from "path";
2177
2468
  import { homedir as homedir4 } from "os";
2178
2469
  var SESSION_COOKIE2 = "__Secure-better-auth.session_token";
@@ -2182,7 +2473,7 @@ var ACCOUNTS_PATH = join10(DIR3, "test-accounts.json");
2182
2473
  function loadAccounts() {
2183
2474
  if (!existsSync9(ACCOUNTS_PATH)) return [];
2184
2475
  try {
2185
- return JSON.parse(readFileSync9(ACCOUNTS_PATH, "utf-8"));
2476
+ return JSON.parse(readFileSync10(ACCOUNTS_PATH, "utf-8"));
2186
2477
  } catch {
2187
2478
  return [];
2188
2479
  }
@@ -2192,7 +2483,7 @@ function saveAccounts(accounts) {
2192
2483
  writeFileSync8(ACCOUNTS_PATH, JSON.stringify(accounts, null, 2), { mode: 384 });
2193
2484
  }
2194
2485
  function sessionCookie() {
2195
- const token = readFileSync9(SESSION_PATH2, "utf-8").trim();
2486
+ const token = readFileSync10(SESSION_PATH2, "utf-8").trim();
2196
2487
  return `${SESSION_COOKIE2}=${encodeURIComponent(token)}`;
2197
2488
  }
2198
2489
  async function fetchRemoteAccounts() {
@@ -2462,7 +2753,7 @@ var test_accounts_default = defineCommand7({
2462
2753
  import { defineCommand as defineCommand8 } from "citty";
2463
2754
  import { execSync as execSync2 } from "child_process";
2464
2755
  import { randomUUID } from "crypto";
2465
- import { readFileSync as readFileSync10, readdirSync as readdirSync4, existsSync as existsSync10 } from "fs";
2756
+ import { readFileSync as readFileSync11, readdirSync as readdirSync4, existsSync as existsSync10 } from "fs";
2466
2757
  import { join as join11, resolve as resolve7, dirname as dirname2 } from "path";
2467
2758
  import { pathToFileURL } from "url";
2468
2759
  import * as p4 from "@clack/prompts";
@@ -2519,6 +2810,13 @@ var GROUP_BYTES = (() => {
2519
2810
  const v = Number.isInteger(raw) && raw > 0 ? raw : 3 * 1024 * 1024;
2520
2811
  return Math.min(v, MAX_GROUP_BYTES);
2521
2812
  })();
2813
+ function classifyDevVarsSecrets(opts) {
2814
+ const store = new Set(opts.storeSecretNames);
2815
+ const strayKeys = opts.handEditedDevVarKeys.filter((k) => !store.has(k));
2816
+ if (strayKeys.length === 0) return { kind: "ok" };
2817
+ if (opts.storeSecretNames.length === 0 && !opts.allowMissing) return { kind: "block", strayKeys };
2818
+ return { kind: "warn", strayKeys };
2819
+ }
2522
2820
  function packAssetGroups(assets, maxBytes) {
2523
2821
  const groups = [];
2524
2822
  let current = [];
@@ -2582,6 +2880,7 @@ var deploy_default = defineCommand8({
2582
2880
  },
2583
2881
  env: {
2584
2882
  type: "string",
2883
+ alias: "e",
2585
2884
  description: "wrangler.toml [env.<name>] block to deploy (e.g. --env staging). Omit to deploy the top-level config.",
2586
2885
  required: false
2587
2886
  },
@@ -2589,6 +2888,11 @@ var deploy_default = defineCommand8({
2589
2888
  type: "boolean",
2590
2889
  description: "Confirm that a changed wrangler `name` renames this app (its URL moves; data, secrets, and collaborators travel). Without this flag an interactive prompt asks.",
2591
2890
  default: false
2891
+ },
2892
+ "allow-missing-secrets": {
2893
+ type: "boolean",
2894
+ description: "Deploy even though hand-edited .dev.vars secrets are absent from the store (they will NOT be deployed, and any a previous deploy set are dropped).",
2895
+ default: false
2592
2896
  }
2593
2897
  },
2594
2898
  async run({ args }) {
@@ -2638,7 +2942,7 @@ var deploy_default = defineCommand8({
2638
2942
  p4.log.info(`Id: ${appId}`);
2639
2943
  const readCustomDevVarsAt = (path) => {
2640
2944
  if (!existsSync10(path)) return {};
2641
- const section = extractCustomDevVars(stripGeneratedSecretsCache(readFileSync10(path, "utf-8")));
2945
+ const section = extractCustomDevVars(stripGeneratedSecretsCache(readFileSync11(path, "utf-8")));
2642
2946
  if (!section) return {};
2643
2947
  try {
2644
2948
  return parseDevVars(section);
@@ -2703,13 +3007,13 @@ var deploy_default = defineCommand8({
2703
3007
  p4.cancel("Build output config not found at .wrangler/deploy/config.json");
2704
3008
  process.exit(1);
2705
3009
  }
2706
- const deployConfig = JSON.parse(readFileSync10(deployConfigPath, "utf-8"));
3010
+ const deployConfig = JSON.parse(readFileSync11(deployConfigPath, "utf-8"));
2707
3011
  const outputWranglerPath = resolve7(dirname2(deployConfigPath), deployConfig.configPath);
2708
3012
  if (!existsSync10(outputWranglerPath)) {
2709
3013
  p4.cancel(`Output wrangler.json not found at ${outputWranglerPath}`);
2710
3014
  process.exit(1);
2711
3015
  }
2712
- const outputConfig = JSON.parse(readFileSync10(outputWranglerPath, "utf-8"));
3016
+ const outputConfig = JSON.parse(readFileSync11(outputWranglerPath, "utf-8"));
2713
3017
  if (outputConfig.name && outputConfig.name !== appName) {
2714
3018
  const envHint = envName ? ` (--env ${envName})` : "";
2715
3019
  p4.cancel(
@@ -2731,7 +3035,7 @@ var deploy_default = defineCommand8({
2731
3035
  s.start("Collecting assets...");
2732
3036
  const assets = collectAssets(clientDir);
2733
3037
  s.stop(`Collected ${assets.length} assets`);
2734
- const workerJs = readFileSync10(workerBundlePath, "utf-8");
3038
+ const workerJs = readFileSync11(workerBundlePath, "utf-8");
2735
3039
  const doBindings = outputConfig.durable_objects?.bindings;
2736
3040
  const sqliteClasses = new Set(
2737
3041
  outputConfig.migrations?.flatMap(
@@ -2765,10 +3069,27 @@ var deploy_default = defineCommand8({
2765
3069
  });
2766
3070
  const userSecrets = linkedSecrets ? { ...linkedSecrets.values } : {};
2767
3071
  const handEditedDevVars = linkedSecrets ? localOnlyDevVars : readCustomDevVarsAt(devVarsPath);
2768
- const strayDevVars = Object.keys(handEditedDevVars).filter((k) => !(k in userSecrets));
2769
- if (strayDevVars.length) {
3072
+ const secretsDecision = classifyDevVarsSecrets({
3073
+ storeSecretNames: Object.keys(userSecrets),
3074
+ handEditedDevVarKeys: Object.keys(handEditedDevVars),
3075
+ allowMissing: Boolean(args["allow-missing-secrets"])
3076
+ });
3077
+ if (secretsDecision.kind === "block") {
3078
+ const uploadCmd = envName ? `deepspace secrets upload .dev.vars -e ${envName}` : "deepspace secrets upload .dev.vars";
3079
+ const keys = secretsDecision.strayKeys.join(", ");
3080
+ p4.cancel(
3081
+ `Refusing to deploy: the app store has no secrets, but .dev.vars has ${keys}.
3082
+ Deploying now would ship the app with no secrets \u2014 replacing any a previous deploy set (they would be dropped from production).
3083
+ Upload them to the store first:
3084
+ ${uploadCmd}
3085
+ then redeploy. (Pass --allow-missing-secrets to deploy without them.)`
3086
+ );
3087
+ process.exit(1);
3088
+ }
3089
+ if (secretsDecision.kind === "warn") {
3090
+ const stray = secretsDecision.strayKeys;
2770
3091
  p4.log.warn(
2771
- `.dev.vars has ${strayDevVars.join(", ")} not in the app store \u2014 ${strayDevVars.length === 1 ? "it is" : "they are"} NOT deployed. \`.dev.vars\` is a local dev cache, not deploy config. Move ${strayDevVars.length === 1 ? "it" : "them"} into the store with \`deepspace secrets set KEY=value\` (or \`deepspace secrets upload .dev.vars\`), then redeploy.`
3092
+ `.dev.vars has ${stray.join(", ")} not in the app store \u2014 ${stray.length === 1 ? "it is" : "they are"} NOT deployed. \`.dev.vars\` is a local dev cache, not deploy config. Move ${stray.length === 1 ? "it" : "them"} into the store with \`deepspace secrets set KEY=value\` (or \`deepspace secrets upload .dev.vars\`), then redeploy.`
2772
3093
  );
2773
3094
  }
2774
3095
  const userSecretNames = Object.keys(userSecrets);
@@ -2866,6 +3187,7 @@ var deploy_default = defineCommand8({
2866
3187
  if (userSecretNames.length) {
2867
3188
  form.append("userSecrets", JSON.stringify(userSecrets));
2868
3189
  }
3190
+ form.append("secretsAuthoritative", "true");
2869
3191
  if (extraRoutes.length) {
2870
3192
  form.append("extraRunWorkerFirst", JSON.stringify(extraRoutes));
2871
3193
  }
@@ -2895,6 +3217,12 @@ var deploy_default = defineCommand8({
2895
3217
  let body = await res.json().catch(() => ({}));
2896
3218
  if (res.status === 409 && body.code === "rename_required" && !confirmRename) {
2897
3219
  s.stop("Rename confirmation needed");
3220
+ if (!process.stdin.isTTY) {
3221
+ await bailDeploy(
3222
+ `This deploy renames the app: ${body.fromHost} \u2192 ${body.toHost}. Confirmation needs an interactive terminal \u2014 re-run with --rename to approve the rename, or \`deepspace init --new-id\` if you meant a separate app.`,
3223
+ null
3224
+ );
3225
+ }
2898
3226
  const yes = await p4.confirm({
2899
3227
  message: `This deploy renames the app: ${body.fromHost} \u2192 ${body.toHost}. The URL changes and the old one stops serving right away; data, secrets, and collaborators travel with it. (Meant a separate app? Run \`deepspace init --new-id\` instead.) Rename?`
2900
3228
  });
@@ -3171,7 +3499,7 @@ function collectAssets(dir) {
3171
3499
  } else {
3172
3500
  assets.push({
3173
3501
  path: "/" + rel,
3174
- contentBase64: readFileSync10(full).toString("base64")
3502
+ contentBase64: readFileSync11(full).toString("base64")
3175
3503
  });
3176
3504
  }
3177
3505
  }
@@ -3192,7 +3520,7 @@ var undeploy_default = defineCommand9({
3192
3520
  args: {
3193
3521
  name: {
3194
3522
  type: "positional",
3195
- description: "App id to undeploy (reads DEEPSPACE_APP_ID from wrangler.toml if omitted)",
3523
+ description: "App id or subdomain name to undeploy (reads DEEPSPACE_APP_ID from wrangler.toml if omitted)",
3196
3524
  required: false
3197
3525
  },
3198
3526
  env: {
@@ -3203,18 +3531,6 @@ var undeploy_default = defineCommand9({
3203
3531
  },
3204
3532
  async run({ args }) {
3205
3533
  const envName = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
3206
- let appId = args.name;
3207
- if (appId && !APP_ID_RE.test(appId)) {
3208
- p5.cancel(`"${appId}" is not an app id (app_\u2026). Undeploy targets the id \u2014 run from the app directory, or find it in wrangler.toml.`);
3209
- process.exit(1);
3210
- }
3211
- if (!appId && hasWranglerConfig(process.cwd())) {
3212
- appId = readAppId(process.cwd(), envName) ?? void 0;
3213
- }
3214
- if (!appId) {
3215
- p5.cancel("Provide an app id or run from a DeepSpace app directory with a DEEPSPACE_APP_ID.");
3216
- process.exit(1);
3217
- }
3218
3534
  let token;
3219
3535
  try {
3220
3536
  token = await ensureToken();
@@ -3222,6 +3538,22 @@ var undeploy_default = defineCommand9({
3222
3538
  p5.cancel(err instanceof Error ? err.message : String(err));
3223
3539
  process.exit(1);
3224
3540
  }
3541
+ let appId;
3542
+ const positional = typeof args.name === "string" && args.name.trim() ? args.name.trim() : void 0;
3543
+ if (positional) {
3544
+ try {
3545
+ appId = await resolveAppSelector(DEPLOY_URL4, token, positional);
3546
+ } catch (err) {
3547
+ p5.cancel(err instanceof Error ? err.message : String(err));
3548
+ process.exit(1);
3549
+ }
3550
+ } else if (hasWranglerConfig(process.cwd())) {
3551
+ appId = readAppId(process.cwd(), envName) ?? void 0;
3552
+ }
3553
+ if (!appId) {
3554
+ p5.cancel("Provide an app id or subdomain name, or run from a DeepSpace app directory with a DEEPSPACE_APP_ID.");
3555
+ process.exit(1);
3556
+ }
3225
3557
  p5.intro(`Undeploying ${appId}`);
3226
3558
  const s = createSpinner();
3227
3559
  s.start("Removing...");
@@ -3258,24 +3590,28 @@ var whoami_default = defineCommand10({
3258
3590
  }
3259
3591
  },
3260
3592
  async run({ args }) {
3593
+ const failLoggedOut = (msg) => {
3594
+ if (args.json) console.log(JSON.stringify({ loggedIn: false, error: msg }));
3595
+ else console.error(msg);
3596
+ process.exit(1);
3597
+ };
3261
3598
  let jwt;
3262
3599
  try {
3263
3600
  jwt = await ensureToken();
3264
3601
  } catch (err) {
3265
- console.error(err instanceof Error ? err.message : "Not signed in. Run `deepspace login`.");
3266
- process.exit(1);
3602
+ failLoggedOut(err instanceof Error ? err.message : "Not logged in. Run `deepspace login` first.");
3267
3603
  }
3268
3604
  let payload;
3269
3605
  try {
3270
3606
  payload = decodeJwtPayload(jwt);
3271
3607
  } catch {
3272
- console.error("Malformed session token. Run `deepspace login` again.");
3273
- process.exit(1);
3608
+ failLoggedOut("Malformed session token. Run `deepspace login` again.");
3274
3609
  }
3275
3610
  const isTestAccount = !!payload.email?.endsWith("@deepspace.test");
3276
3611
  const accountType = isTestAccount ? "test-account" : payload.email ? "developer" : "unknown";
3277
3612
  if (args.json) {
3278
3613
  console.log(JSON.stringify({
3614
+ loggedIn: true,
3279
3615
  name: payload.name ?? null,
3280
3616
  email: payload.email ?? null,
3281
3617
  userId: payload.sub ?? null,
@@ -3299,11 +3635,48 @@ var whoami_default = defineCommand10({
3299
3635
  }
3300
3636
  });
3301
3637
 
3302
- // src/cli/commands/add.ts
3638
+ // src/cli/commands/apps.ts
3303
3639
  import { defineCommand as defineCommand11 } from "citty";
3304
- import { existsSync as existsSync11 } from "fs";
3305
- import { resolve as resolve8, join as join12 } from "path";
3306
- import { execSync as execSync3 } from "child_process";
3640
+ var DEPLOY_URL5 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
3641
+ var apps_default = defineCommand11({
3642
+ meta: {
3643
+ name: "apps",
3644
+ description: "List your apps (deployed and registered)"
3645
+ },
3646
+ args: {
3647
+ json: {
3648
+ type: "boolean",
3649
+ description: "Emit JSON instead of human output",
3650
+ default: false
3651
+ }
3652
+ },
3653
+ async run({ args }) {
3654
+ const token = await ensureToken();
3655
+ const { apps } = await apiFetch(DEPLOY_URL5, token, "/api/apps");
3656
+ if (args.json) {
3657
+ process.stdout.write(JSON.stringify(apps, null, 2) + "\n");
3658
+ return;
3659
+ }
3660
+ if (!apps.length) {
3661
+ console.log("No apps yet. Create one with `npx create-deepspace <name>` and `deepspace deploy`.");
3662
+ return;
3663
+ }
3664
+ const nameWidth = Math.max(4, ...apps.map((a) => (a.name ?? "\u2014").length));
3665
+ const idWidth = Math.max(6, ...apps.map((a) => a.appId.length));
3666
+ console.log(`${"NAME".padEnd(nameWidth)} ${"APP ID".padEnd(idWidth)} URL`);
3667
+ for (const a of apps) {
3668
+ const url = a.url ?? "(not deployed)";
3669
+ console.log(`${(a.name ?? "\u2014").padEnd(nameWidth)} ${a.appId.padEnd(idWidth)} ${url}`);
3670
+ }
3671
+ }
3672
+ });
3673
+
3674
+ // src/cli/commands/add.ts
3675
+ import { defineCommand as defineCommand12 } from "citty";
3676
+ import { existsSync as existsSync11, readFileSync as readFileSync12 } from "fs";
3677
+ import { resolve as resolve8, join as join12, dirname as dirname3 } from "path";
3678
+ import { fileURLToPath } from "url";
3679
+ import { spawnSync as spawnSync5 } from "child_process";
3307
3680
  function findScript(from) {
3308
3681
  const inNodeModules = join12(from, "node_modules", "deepspace", "scripts", "add-feature.cjs");
3309
3682
  if (existsSync11(inNodeModules)) return inNodeModules;
@@ -3311,7 +3684,31 @@ function findScript(from) {
3311
3684
  if (existsSync11(inDotDeepspace)) return inDotDeepspace;
3312
3685
  return null;
3313
3686
  }
3314
- var add_default = defineCommand11({
3687
+ function runningScript() {
3688
+ const here = dirname3(fileURLToPath(import.meta.url));
3689
+ const script = join12(here, "..", "scripts", "add-feature.cjs");
3690
+ return existsSync11(script) ? script : null;
3691
+ }
3692
+ function isDeepSpaceApp(dir) {
3693
+ if (existsSync11(join12(dir, "wrangler.toml"))) return true;
3694
+ const pkgPath = join12(dir, "package.json");
3695
+ if (!existsSync11(pkgPath)) return false;
3696
+ try {
3697
+ const pkg2 = JSON.parse(readFileSync12(pkgPath, "utf-8"));
3698
+ return Boolean(pkg2.dependencies?.deepspace || pkg2.devDependencies?.deepspace);
3699
+ } catch {
3700
+ return false;
3701
+ }
3702
+ }
3703
+ function runInstaller(script, passArgs, cwd) {
3704
+ const res = spawnSync5(process.execPath, [script, ...passArgs], { stdio: "inherit", cwd });
3705
+ if (res.error) {
3706
+ console.error(`Could not run the feature installer: ${res.error.message}`);
3707
+ process.exit(1);
3708
+ }
3709
+ process.exit(res.status ?? 1);
3710
+ }
3711
+ var add_default = defineCommand12({
3315
3712
  meta: {
3316
3713
  name: "add",
3317
3714
  description: "Add a feature to your DeepSpace app"
@@ -3328,6 +3725,11 @@ var add_default = defineCommand11({
3328
3725
  description: "Show details about a feature",
3329
3726
  required: false
3330
3727
  },
3728
+ install: {
3729
+ type: "boolean",
3730
+ description: "Run your package manager after adding dependencies",
3731
+ required: false
3732
+ },
3331
3733
  feature: {
3332
3734
  type: "positional",
3333
3735
  description: "Feature to install",
@@ -3341,6 +3743,27 @@ var add_default = defineCommand11({
3341
3743
  },
3342
3744
  run({ args }) {
3343
3745
  const appDir = resolve8(args.dir ?? ".");
3746
+ if (args.list || args.info || !args.feature) {
3747
+ const script2 = runningScript();
3748
+ if (!script2) {
3749
+ console.error("Could not find the deepspace feature catalog.");
3750
+ console.error("Reinstall deepspace (`npm i -g deepspace`) or run inside a DeepSpace app.");
3751
+ process.exit(1);
3752
+ }
3753
+ const passArgs2 = args.list ? ["--list"] : args.info ? ["--info", String(args.info)] : ["--help"];
3754
+ runInstaller(script2, passArgs2, process.cwd());
3755
+ }
3756
+ if (!existsSync11(appDir)) {
3757
+ console.error(`Target directory not found: ${appDir}`);
3758
+ console.error("Create a new app first: deepspace create <name>");
3759
+ process.exit(1);
3760
+ }
3761
+ if (!isDeepSpaceApp(appDir)) {
3762
+ console.error(`Not a DeepSpace app: ${appDir}`);
3763
+ console.error('(no wrangler.toml and no "deepspace" dependency found)');
3764
+ console.error("Create one with: deepspace create <name>");
3765
+ process.exit(1);
3766
+ }
3344
3767
  ensureInstallReady(appDir);
3345
3768
  const script = findScript(appDir);
3346
3769
  if (!script) {
@@ -3351,136 +3774,14 @@ var add_default = defineCommand11({
3351
3774
  console.error("Are you in a DeepSpace app directory? Try `npm install` first.");
3352
3775
  process.exit(1);
3353
3776
  }
3354
- const passArgs = [];
3355
- if (args.list) {
3356
- passArgs.push("--list");
3357
- } else if (args.info) {
3358
- passArgs.push("--info", args.info);
3359
- } else if (args.feature) {
3360
- passArgs.push(args.feature, appDir);
3361
- } else {
3362
- passArgs.push("--help");
3363
- }
3364
- execSync3(`node ${JSON.stringify(script)} ${passArgs.map((a) => JSON.stringify(a)).join(" ")}`, {
3365
- stdio: "inherit",
3366
- cwd: appDir
3367
- });
3368
- }
3369
- });
3370
-
3371
- // src/cli/commands/skills.ts
3372
- import { defineCommand as defineCommand12 } from "citty";
3373
- import spawn2 from "cross-spawn";
3374
- import { mkdirSync as mkdirSync5 } from "fs";
3375
- import { join as join13 } from "path";
3376
- import { homedir as homedir5 } from "os";
3377
- var SKILL_REPO = "deepdotspace/deepspace-skill";
3378
- function runSkillsAdd(extraArgs) {
3379
- return new Promise((resolve10) => {
3380
- const child = spawn2(
3381
- "npx",
3382
- ["-y", "skills@latest", "add", SKILL_REPO, ...extraArgs],
3383
- { stdio: "inherit" }
3384
- );
3385
- child.on("close", (code) => resolve10(code ?? 1));
3386
- child.on("error", (err) => {
3387
- console.error(`Failed to invoke npx: ${err.message}`);
3388
- console.error("Is Node.js installed and on PATH?");
3389
- resolve10(1);
3390
- });
3391
- });
3392
- }
3393
- function precreateClaudeDir(baseDir) {
3394
- mkdirSync5(join13(baseDir, ".claude"), { recursive: true });
3395
- }
3396
- var add = defineCommand12({
3397
- meta: {
3398
- name: "add",
3399
- description: "Install the DeepSpace skill for your coding agent"
3400
- },
3401
- args: {
3402
- global: {
3403
- type: "boolean",
3404
- alias: "g",
3405
- description: "Install globally (~/.agents/skills/) instead of into the current directory",
3406
- default: false
3407
- },
3408
- yes: {
3409
- type: "boolean",
3410
- alias: "y",
3411
- description: "Skip prompts (assume yes for any confirmation)",
3412
- default: false
3413
- },
3414
- agent: {
3415
- type: "string",
3416
- description: "Target a single agent (e.g. claude-code, codex, opencode). Default: install for all detected agents.",
3417
- required: false
3418
- }
3419
- },
3420
- async run({ args }) {
3421
- const extra = [];
3422
- if (args.global) extra.push("-g");
3423
- if (args.yes) extra.push("-y");
3424
- if (args.agent) extra.push("--agent", args.agent);
3425
- if (!args.agent || args.agent === "claude-code") {
3426
- const baseDir = args.global ? homedir5() : process.cwd();
3427
- try {
3428
- precreateClaudeDir(baseDir);
3429
- } catch (err) {
3430
- console.warn(`
3431
- Note: could not pre-create .claude/ for Claude Code: ${err instanceof Error ? err.message : String(err)}`);
3432
- }
3433
- }
3434
- const code = await runSkillsAdd(extra);
3435
- if (code !== 0) process.exit(code);
3436
- }
3437
- });
3438
- var skills_default = defineCommand12({
3439
- meta: {
3440
- name: "skills",
3441
- description: "Manage the DeepSpace agent skill (Claude Code, Codex, OpenCode, Cursor, ...)"
3442
- },
3443
- subCommands: {
3444
- add
3777
+ const passArgs = [args.feature, appDir];
3778
+ if (args.install) passArgs.push("--install");
3779
+ runInstaller(script, passArgs, appDir);
3445
3780
  }
3446
3781
  });
3447
3782
 
3448
3783
  // src/cli/commands/domain.ts
3449
3784
  import { defineCommand as defineCommand13 } from "citty";
3450
-
3451
- // src/cli/lib/api.ts
3452
- var ApiError = class extends Error {
3453
- constructor(message, status3, code) {
3454
- super(message);
3455
- this.status = status3;
3456
- this.code = code;
3457
- }
3458
- };
3459
- async function apiFetch(baseUrl, token, path, init) {
3460
- const res = await fetch(`${baseUrl}${path}`, {
3461
- ...init,
3462
- headers: {
3463
- Authorization: `Bearer ${token}`,
3464
- "Content-Type": "application/json",
3465
- ...init?.headers ?? {}
3466
- }
3467
- });
3468
- const text2 = await res.text();
3469
- if (!res.ok) {
3470
- let msg = text2;
3471
- let code;
3472
- try {
3473
- const body = JSON.parse(text2);
3474
- msg = body.error ?? text2;
3475
- code = body.code;
3476
- } catch {
3477
- }
3478
- throw new ApiError(`API ${path} (${res.status}): ${msg}`, res.status, code);
3479
- }
3480
- return text2 ? JSON.parse(text2) : {};
3481
- }
3482
-
3483
- // src/cli/commands/domain.ts
3484
3785
  var API_URL3 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
3485
3786
  var api = (token, path, init) => apiFetch(API_URL3, token, path, init);
3486
3787
  function scrubInternal(value) {
@@ -3817,18 +4118,19 @@ function isTestAccountEmail(email) {
3817
4118
 
3818
4119
  // src/cli/commands/collaborators.ts
3819
4120
  var API_URL4 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
4121
+ var DEPLOY_URL6 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
3820
4122
  function api2(token, path, init) {
3821
4123
  return apiFetch(API_URL4, token, path, init);
3822
4124
  }
3823
4125
  var list3 = defineCommand14({
3824
4126
  meta: { name: "list", description: "List collaborators on your app" },
3825
4127
  args: {
3826
- app: { type: "string", alias: "a", description: "App id (defaults to ./wrangler.toml)" },
4128
+ app: { type: "string", alias: "a", description: "App id or name (defaults to ./wrangler.toml)" },
3827
4129
  json: { type: "boolean", description: "Emit JSON" }
3828
4130
  },
3829
4131
  async run({ args }) {
3830
4132
  const token = await ensureToken();
3831
- const app = requireAppIdArg(args.app);
4133
+ const app = await resolveAppTarget(DEPLOY_URL6, token, args.app);
3832
4134
  const { collaborators } = await api2(
3833
4135
  token,
3834
4136
  `/api/app-collaborators/${encodeURIComponent(app)}`
@@ -3841,12 +4143,13 @@ var list3 = defineCommand14({
3841
4143
  console.log(`No collaborators on ${app}. Add one with \`deepspace collaborators add <email>\`.`);
3842
4144
  return;
3843
4145
  }
4146
+ console.log(`COLLABORATORS ON ${app}`);
3844
4147
  for (const c of collaborators) {
3845
- console.log(`${c.emailDisplay.padEnd(32)} ${c.userId}`);
4148
+ console.log(` ${c.emailDisplay}`);
3846
4149
  }
3847
4150
  }
3848
4151
  });
3849
- var add2 = defineCommand14({
4152
+ var add = defineCommand14({
3850
4153
  meta: { name: "add", description: "Authorize a DeepSpace user to deploy your app" },
3851
4154
  args: {
3852
4155
  email: {
@@ -3854,12 +4157,12 @@ var add2 = defineCommand14({
3854
4157
  description: "Collaborator email (must already be a DeepSpace user)",
3855
4158
  required: true
3856
4159
  },
3857
- app: { type: "string", alias: "a", description: "App id (defaults to ./wrangler.toml)" },
4160
+ app: { type: "string", alias: "a", description: "App id or name (defaults to ./wrangler.toml)" },
3858
4161
  json: { type: "boolean", description: "Emit JSON" }
3859
4162
  },
3860
4163
  async run({ args }) {
3861
4164
  const token = await ensureToken();
3862
- const app = requireAppIdArg(args.app);
4165
+ const app = await resolveAppTarget(DEPLOY_URL6, token, args.app);
3863
4166
  if (isTestAccountEmail(args.email)) {
3864
4167
  console.error(
3865
4168
  "Test accounts (@deepspace.test) cannot be added as collaborators. Use a real DeepSpace account."
@@ -3882,12 +4185,12 @@ var remove = defineCommand14({
3882
4185
  meta: { name: "remove", description: "Remove a collaborator from your app" },
3883
4186
  args: {
3884
4187
  email: { type: "positional", description: "Collaborator email", required: true },
3885
- app: { type: "string", alias: "a", description: "App id (defaults to ./wrangler.toml)" },
4188
+ app: { type: "string", alias: "a", description: "App id or name (defaults to ./wrangler.toml)" },
3886
4189
  json: { type: "boolean", description: "Emit JSON" }
3887
4190
  },
3888
4191
  async run({ args }) {
3889
4192
  const token = await ensureToken();
3890
- const app = requireAppIdArg(args.app);
4193
+ const app = await resolveAppTarget(DEPLOY_URL6, token, args.app);
3891
4194
  const { collaborators } = await api2(
3892
4195
  token,
3893
4196
  `/api/app-collaborators/${encodeURIComponent(app)}`
@@ -3912,28 +4215,57 @@ var remove = defineCommand14({
3912
4215
  });
3913
4216
  var collaborators_default = defineCommand14({
3914
4217
  meta: { name: "collaborators", description: "Manage who can deploy your app" },
3915
- subCommands: { list: list3, add: add2, remove }
4218
+ subCommands: { list: list3, add, remove }
3916
4219
  });
3917
4220
 
3918
4221
  // src/cli/commands/transfer.ts
3919
4222
  import { defineCommand as defineCommand15 } from "citty";
4223
+ import * as p6 from "@clack/prompts";
3920
4224
  var API_URL5 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
3921
- var DEPLOY_URL5 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
4225
+ var DEPLOY_URL7 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
3922
4226
  var offer = defineCommand15({
3923
4227
  meta: { name: "offer", description: "Offer this app to another DeepSpace user" },
3924
4228
  args: {
3925
4229
  email: { type: "positional", description: "Recipient email", required: true },
3926
- app: { type: "string", alias: "a", description: "App id (defaults to ./wrangler.toml)" }
4230
+ app: { type: "string", alias: "a", description: "App id or name (defaults to ./wrangler.toml)" },
4231
+ replace: {
4232
+ type: "boolean",
4233
+ description: "Replace a pending offer to someone else without asking"
4234
+ }
3927
4235
  },
3928
4236
  async run({ args }) {
3929
4237
  const token = await ensureToken();
3930
- const app = requireAppIdArg(args.app);
4238
+ const app = await resolveAppTarget(DEPLOY_URL7, token, args.app);
4239
+ const pending = await apiFetch(
4240
+ API_URL5,
4241
+ token,
4242
+ `/api/app-transfers/${encodeURIComponent(app)}`
4243
+ );
4244
+ const pendingTo = pending.transfer?.toEmailDisplay;
4245
+ if (pendingTo && pendingTo.toLowerCase() !== args.email.trim().toLowerCase() && !args.replace) {
4246
+ if (!process.stdin.isTTY) {
4247
+ console.error(
4248
+ `A pending offer to ${pendingTo} already exists for ${app}. Re-run with --replace to replace it, or \`deepspace transfer cancel\` first.`
4249
+ );
4250
+ process.exit(1);
4251
+ }
4252
+ const yes = await p6.confirm({
4253
+ message: `A pending offer to ${pendingTo} exists \u2014 replace it with ${args.email}?`
4254
+ });
4255
+ if (p6.isCancel(yes) || !yes) {
4256
+ console.log(`Kept the pending offer to ${pendingTo}.`);
4257
+ process.exit(1);
4258
+ }
4259
+ }
3931
4260
  const { expiresAt } = await apiFetch(
3932
4261
  API_URL5,
3933
4262
  token,
3934
4263
  `/api/app-transfers/${encodeURIComponent(app)}`,
3935
4264
  { method: "POST", body: JSON.stringify({ email: args.email }) }
3936
4265
  );
4266
+ if (pendingTo && pendingTo.toLowerCase() !== args.email.trim().toLowerCase()) {
4267
+ console.log(`\u25B2 Replaced the pending offer to ${pendingTo}.`);
4268
+ }
3937
4269
  console.log(
3938
4270
  `\u2713 Offered ${app} to ${args.email} (expires ${expiresAt}).
3939
4271
  They accept with: deepspace transfer accept --app ${app}`
@@ -3943,11 +4275,11 @@ var offer = defineCommand15({
3943
4275
  var status2 = defineCommand15({
3944
4276
  meta: { name: "status", description: "Show the pending transfer offer, if any" },
3945
4277
  args: {
3946
- app: { type: "string", alias: "a", description: "App id (defaults to ./wrangler.toml)" }
4278
+ app: { type: "string", alias: "a", description: "App id or name (defaults to ./wrangler.toml)" }
3947
4279
  },
3948
4280
  async run({ args }) {
3949
4281
  const token = await ensureToken();
3950
- const app = requireAppIdArg(args.app);
4282
+ const app = await resolveAppTarget(DEPLOY_URL7, token, args.app);
3951
4283
  const { transfer } = await apiFetch(API_URL5, token, `/api/app-transfers/${encodeURIComponent(app)}`);
3952
4284
  if (!transfer) {
3953
4285
  console.log(`No pending transfer for ${app}.`);
@@ -3964,7 +4296,7 @@ var accept = defineCommand15({
3964
4296
  async run({ args }) {
3965
4297
  const token = await ensureToken();
3966
4298
  const app = requireAppIdArg(args.app);
3967
- await apiFetch(DEPLOY_URL5, token, `/api/apps/${encodeURIComponent(app)}/transfer/accept`, {
4299
+ await apiFetch(DEPLOY_URL7, token, `/api/apps/${encodeURIComponent(app)}/transfer/accept`, {
3968
4300
  method: "POST"
3969
4301
  });
3970
4302
  console.log(
@@ -3975,11 +4307,11 @@ var accept = defineCommand15({
3975
4307
  var cancel4 = defineCommand15({
3976
4308
  meta: { name: "cancel", description: "Cancel/decline the pending offer (either party)" },
3977
4309
  args: {
3978
- app: { type: "string", alias: "a", description: "App id (defaults to ./wrangler.toml)" }
4310
+ app: { type: "string", alias: "a", description: "App id or name (defaults to ./wrangler.toml)" }
3979
4311
  },
3980
4312
  async run({ args }) {
3981
4313
  const token = await ensureToken();
3982
- const app = requireAppIdArg(args.app);
4314
+ const app = await resolveAppTarget(DEPLOY_URL7, token, args.app);
3983
4315
  await apiFetch(API_URL5, token, `/api/app-transfers/${encodeURIComponent(app)}`, { method: "DELETE" });
3984
4316
  console.log(`\u2713 Transfer offer for ${app} cancelled.`);
3985
4317
  }
@@ -3993,7 +4325,8 @@ var transfer_default = defineCommand15({
3993
4325
  import { defineCommand as defineCommand16 } from "citty";
3994
4326
 
3995
4327
  // src/cli/commands/_invoke-impl.ts
3996
- import { readFileSync as readFileSync11 } from "fs";
4328
+ import { readFileSync as readFileSync13 } from "fs";
4329
+ import * as p7 from "@clack/prompts";
3997
4330
  var API_URL6 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
3998
4331
  var DEFAULT_TIMEOUT_MS = 12e4;
3999
4332
  function parseTarget(target) {
@@ -4018,7 +4351,7 @@ function resolveBody(opts) {
4018
4351
  if (opts.body != null) {
4019
4352
  raw = opts.body;
4020
4353
  } else if (opts.bodyFile != null) {
4021
- raw = opts.bodyFile === "-" ? readFileSync11(0, "utf-8") : readFileSync11(opts.bodyFile, "utf-8");
4354
+ raw = opts.bodyFile === "-" ? readFileSync13(0, "utf-8") : readFileSync13(opts.bodyFile, "utf-8");
4022
4355
  } else {
4023
4356
  return "{}";
4024
4357
  }
@@ -4052,6 +4385,17 @@ function formatCurrency(value, currency) {
4052
4385
  if (currency === "USD") return `$${value}`;
4053
4386
  return `${value} ${currency}`;
4054
4387
  }
4388
+ function billingUnit(model) {
4389
+ if (model === "per_token") return "per token";
4390
+ if (model === "per_call") return "per call";
4391
+ return model.startsWith("per_") ? model.replace(/^per_/, "per ").replace(/_/g, " ") : model;
4392
+ }
4393
+ function isInteractive(stdin, stdout) {
4394
+ return Boolean(stdin.isTTY && stdout.isTTY);
4395
+ }
4396
+ function shouldConfirmCost(opts) {
4397
+ return !opts.json && !opts.yes && opts.isTTY && opts.baseCost > 0;
4398
+ }
4055
4399
  async function runList(args) {
4056
4400
  const catalog = await fetchCatalog({ summary: true });
4057
4401
  if (args.json) {
@@ -4097,7 +4441,7 @@ async function runInfo(args) {
4097
4441
  }
4098
4442
  console.log(`${integration}/${endpoint}`);
4099
4443
  console.log(
4100
- ` billing: ${info2.billing.model}, ${formatCurrency(info2.billing.baseCost, info2.billing.currency)} per call`
4444
+ ` billing: ${formatCurrency(info2.billing.baseCost, info2.billing.currency)} ${billingUnit(info2.billing.model)}`
4101
4445
  );
4102
4446
  console.log();
4103
4447
  console.log("Input schema:");
@@ -4112,6 +4456,31 @@ async function runInvoke(args) {
4112
4456
  const { integration, endpoint } = parseTarget(args.target);
4113
4457
  const body = resolveBody({ body: args.body, bodyFile: args.bodyFile });
4114
4458
  const timeoutMs = args.timeout ?? DEFAULT_TIMEOUT_MS;
4459
+ const interactive = isInteractive(process.stdin, process.stdout);
4460
+ if (!args.json && !args.yes && interactive) {
4461
+ let info2 = null;
4462
+ try {
4463
+ info2 = findEndpoint(await fetchCatalog({ summary: true }), integration, endpoint);
4464
+ } catch {
4465
+ console.error("Note: could not verify the call cost; proceeding. Pass --yes to skip this check.");
4466
+ info2 = null;
4467
+ }
4468
+ if (info2 && shouldConfirmCost({
4469
+ json: !!args.json,
4470
+ yes: !!args.yes,
4471
+ isTTY: interactive,
4472
+ baseCost: info2.billing.baseCost
4473
+ })) {
4474
+ const cost = formatCurrency(info2.billing.baseCost, info2.billing.currency);
4475
+ const ok2 = await p7.confirm({
4476
+ message: `${integration}/${endpoint} costs ${cost} ${billingUnit(info2.billing.model)}, billed to your account. Continue?`
4477
+ });
4478
+ if (p7.isCancel(ok2) || !ok2) {
4479
+ p7.cancel("Cancelled \u2014 no call made.");
4480
+ process.exit(0);
4481
+ }
4482
+ }
4483
+ }
4115
4484
  let jwt;
4116
4485
  try {
4117
4486
  jwt = await ensureToken();
@@ -4254,6 +4623,12 @@ var invoke = defineCommand16({
4254
4623
  type: "string",
4255
4624
  description: "Request timeout in milliseconds (default: 120000)",
4256
4625
  required: false
4626
+ },
4627
+ yes: {
4628
+ type: "boolean",
4629
+ alias: "y",
4630
+ description: "Skip the paid-call cost confirmation",
4631
+ default: false
4257
4632
  }
4258
4633
  },
4259
4634
  async run({ args }) {
@@ -4267,7 +4642,8 @@ var invoke = defineCommand16({
4267
4642
  body: args.body,
4268
4643
  bodyFile: args["body-file"],
4269
4644
  json: args.json,
4270
- timeout
4645
+ timeout,
4646
+ yes: args.yes
4271
4647
  });
4272
4648
  } catch (err) {
4273
4649
  console.error(err instanceof Error ? err.message : String(err));
@@ -4331,6 +4707,12 @@ var invoke_default = defineCommand17({
4331
4707
  type: "string",
4332
4708
  description: "Request timeout in milliseconds (default: 120000)",
4333
4709
  required: false
4710
+ },
4711
+ yes: {
4712
+ type: "boolean",
4713
+ alias: "y",
4714
+ description: "Skip the paid-call cost confirmation",
4715
+ default: false
4334
4716
  }
4335
4717
  },
4336
4718
  async run({ args }) {
@@ -4352,7 +4734,8 @@ var invoke_default = defineCommand17({
4352
4734
  body: args.body,
4353
4735
  bodyFile: args["body-file"],
4354
4736
  json: args.json,
4355
- timeout
4737
+ timeout,
4738
+ yes: args.yes
4356
4739
  });
4357
4740
  } catch (err) {
4358
4741
  console.error(err instanceof Error ? err.message : String(err));
@@ -4363,10 +4746,10 @@ var invoke_default = defineCommand17({
4363
4746
 
4364
4747
  // src/cli/commands/library.ts
4365
4748
  import { defineCommand as defineCommand18 } from "citty";
4366
- import { existsSync as existsSync12, readFileSync as readFileSync12 } from "fs";
4367
- import { join as join14, resolve as resolve9 } from "path";
4749
+ import { existsSync as existsSync12, readFileSync as readFileSync14 } from "fs";
4750
+ import { join as join13, resolve as resolve9 } from "path";
4368
4751
  import { parse as parseToml4 } from "smol-toml";
4369
- import * as p6 from "@clack/prompts";
4752
+ import * as p8 from "@clack/prompts";
4370
4753
  var DEFAULT_LIBRARY_APP = "deepdotspace-site";
4371
4754
  var publish = defineCommand18({
4372
4755
  meta: {
@@ -4428,25 +4811,25 @@ var publish = defineCommand18({
4428
4811
  async run({ args }) {
4429
4812
  const json = !!args.json;
4430
4813
  const intro6 = (s) => {
4431
- if (!json) p6.intro(s);
4814
+ if (!json) p8.intro(s);
4432
4815
  };
4433
4816
  const info2 = (s) => {
4434
- if (!json) p6.log.info(s);
4817
+ if (!json) p8.log.info(s);
4435
4818
  };
4436
4819
  const die = (msg) => {
4437
4820
  if (json) console.error(JSON.stringify({ ok: false, error: msg }));
4438
- else p6.cancel(msg);
4821
+ else p8.cancel(msg);
4439
4822
  process.exit(1);
4440
4823
  };
4441
4824
  intro6("Publishing to the DeepSpace library");
4442
4825
  let appName = typeof args.app === "string" && args.app.trim() ? args.app.trim() : void 0;
4443
4826
  if (!appName) {
4444
4827
  const appDir = resolve9(typeof args.dir === "string" && args.dir ? args.dir : ".");
4445
- const wranglerPath = join14(appDir, "wrangler.toml");
4828
+ const wranglerPath = join13(appDir, "wrangler.toml");
4446
4829
  if (!existsSync12(wranglerPath)) {
4447
4830
  return die("No --app provided and no wrangler.toml found. Either pass --app <name> or run from your app's directory.");
4448
4831
  }
4449
- const cfg = parseToml4(readFileSync12(wranglerPath, "utf-8"));
4832
+ const cfg = parseToml4(readFileSync14(wranglerPath, "utf-8"));
4450
4833
  const nameRes = resolveAppName(cfg.name);
4451
4834
  if (!nameRes.ok) return die(`wrangler.toml: ${nameRes.reason}`);
4452
4835
  appName = nameRes.name;
@@ -4502,9 +4885,9 @@ var publish = defineCommand18({
4502
4885
  if (!parsed.success) {
4503
4886
  return die(parsed.error ?? `Publish failed (HTTP ${res.status}).`);
4504
4887
  }
4505
- p6.log.success(`Published ${appName} as ${parsed.data?.templateHandle} (v${parsed.data?.version})`);
4506
- if (parsed.data?.url) p6.log.message(parsed.data.url);
4507
- p6.outro("Done");
4888
+ p8.log.success(`Published ${appName} as ${parsed.data?.templateHandle} (v${parsed.data?.version})`);
4889
+ if (parsed.data?.url) p8.log.message(parsed.data.url);
4890
+ p8.outro("Done");
4508
4891
  }
4509
4892
  });
4510
4893
  var unpublish = defineCommand18({
@@ -4533,10 +4916,10 @@ var unpublish = defineCommand18({
4533
4916
  const json = !!args.json;
4534
4917
  const die = (msg) => {
4535
4918
  if (json) console.error(JSON.stringify({ ok: false, error: msg }));
4536
- else p6.cancel(msg);
4919
+ else p8.cancel(msg);
4537
4920
  process.exit(1);
4538
4921
  };
4539
- if (!json) p6.intro("Unpublishing library entry");
4922
+ if (!json) p8.intro("Unpublishing library entry");
4540
4923
  let token;
4541
4924
  try {
4542
4925
  token = await ensureToken();
@@ -4562,8 +4945,8 @@ var unpublish = defineCommand18({
4562
4945
  process.exit(parsed.success ? 0 : 1);
4563
4946
  }
4564
4947
  if (!parsed.success) return die(parsed.error ?? `HTTP ${res.status}`);
4565
- p6.log.success(`Unpublished ${parsed.data?.templateHandle}`);
4566
- p6.outro("Done");
4948
+ p8.log.success(`Unpublished ${parsed.data?.templateHandle}`);
4949
+ p8.outro("Done");
4567
4950
  }
4568
4951
  });
4569
4952
  var library_default = defineCommand18({
@@ -4579,10 +4962,10 @@ var library_default = defineCommand18({
4579
4962
 
4580
4963
  // src/cli/commands/feedback.ts
4581
4964
  import { defineCommand as defineCommand19 } from "citty";
4582
- import * as p7 from "@clack/prompts";
4583
- import { existsSync as existsSync13, readFileSync as readFileSync13 } from "fs";
4584
- import { dirname as dirname3, join as join15 } from "path";
4585
- import { fileURLToPath } from "url";
4965
+ import * as p9 from "@clack/prompts";
4966
+ import { existsSync as existsSync13, readFileSync as readFileSync15 } from "fs";
4967
+ import { dirname as dirname4, join as join14 } from "path";
4968
+ import { fileURLToPath as fileURLToPath2 } from "url";
4586
4969
  import { release } from "os";
4587
4970
  var API_URL7 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
4588
4971
  var TYPES = ["bug", "feature", "other"];
@@ -4602,14 +4985,14 @@ function normalizeType(raw) {
4602
4985
  }
4603
4986
  function readCliVersion() {
4604
4987
  try {
4605
- const here = dirname3(fileURLToPath(import.meta.url));
4988
+ const here = dirname4(fileURLToPath2(import.meta.url));
4606
4989
  const candidates = [
4607
- join15(here, "..", "package.json"),
4608
- join15(here, "..", "..", "..", "package.json")
4990
+ join14(here, "..", "package.json"),
4991
+ join14(here, "..", "..", "..", "package.json")
4609
4992
  ];
4610
4993
  for (const path of candidates) {
4611
4994
  if (!existsSync13(path)) continue;
4612
- const pkg2 = JSON.parse(readFileSync13(path, "utf-8"));
4995
+ const pkg2 = JSON.parse(readFileSync15(path, "utf-8"));
4613
4996
  if (pkg2.name === "deepspace" && pkg2.version) return pkg2.version;
4614
4997
  }
4615
4998
  } catch {
@@ -4714,21 +5097,21 @@ var feedback_default = defineCommand19({
4714
5097
  );
4715
5098
  }
4716
5099
  if (interactive && (!args.type || !title || !body)) {
4717
- p7.intro("DeepSpace Feedback");
5100
+ p9.intro("DeepSpace Feedback");
4718
5101
  if (!args.type) {
4719
- const picked = await p7.select({
5102
+ const picked = await p9.select({
4720
5103
  message: "What kind of feedback is this?",
4721
5104
  options: TYPES.map((t) => ({ value: t, label: TYPE_LABELS[t] })),
4722
5105
  initialValue: type
4723
5106
  });
4724
- if (p7.isCancel(picked)) {
4725
- p7.cancel("Cancelled.");
5107
+ if (p9.isCancel(picked)) {
5108
+ p9.cancel("Cancelled.");
4726
5109
  process.exit(0);
4727
5110
  }
4728
5111
  type = picked;
4729
5112
  }
4730
5113
  if (!title) {
4731
- const t = await p7.text({
5114
+ const t = await p9.text({
4732
5115
  message: "Title",
4733
5116
  placeholder: "Short summary of the issue",
4734
5117
  validate: (v) => {
@@ -4738,14 +5121,14 @@ var feedback_default = defineCommand19({
4738
5121
  return void 0;
4739
5122
  }
4740
5123
  });
4741
- if (p7.isCancel(t)) {
4742
- p7.cancel("Cancelled.");
5124
+ if (p9.isCancel(t)) {
5125
+ p9.cancel("Cancelled.");
4743
5126
  process.exit(0);
4744
5127
  }
4745
5128
  title = String(t);
4746
5129
  }
4747
5130
  if (!body) {
4748
- const m = await p7.text({
5131
+ const m = await p9.text({
4749
5132
  message: "Details",
4750
5133
  placeholder: "What happened? Steps to reproduce, what you expected\u2026",
4751
5134
  validate: (v) => {
@@ -4755,8 +5138,8 @@ var feedback_default = defineCommand19({
4755
5138
  return void 0;
4756
5139
  }
4757
5140
  });
4758
- if (p7.isCancel(m)) {
4759
- p7.cancel("Cancelled.");
5141
+ if (p9.isCancel(m)) {
5142
+ p9.cancel("Cancelled.");
4760
5143
  process.exit(0);
4761
5144
  }
4762
5145
  body = String(m);
@@ -4774,7 +5157,7 @@ var feedback_default = defineCommand19({
4774
5157
  return fail2(err.message);
4775
5158
  }
4776
5159
  if (interactive && !args.yes) {
4777
- p7.note(
5160
+ p9.note(
4778
5161
  `${TYPE_LABELS[payload.type]}
4779
5162
  ${payload.title}
4780
5163
 
@@ -4783,9 +5166,9 @@ ${payload.body}
4783
5166
  Context: deepspace ${payload.cliVersion} \xB7 ${payload.nodeVersion} \xB7 ${payload.platform}` + (payload.appName ? ` \xB7 app ${payload.appName}` : ""),
4784
5167
  "Review"
4785
5168
  );
4786
- const ok = await p7.confirm({ message: "Send this to the DeepSpace team?" });
4787
- if (p7.isCancel(ok) || !ok) {
4788
- p7.cancel("Not sent.");
5169
+ const ok = await p9.confirm({ message: "Send this to the DeepSpace team?" });
5170
+ if (p9.isCancel(ok) || !ok) {
5171
+ p9.cancel("Not sent.");
4789
5172
  process.exit(0);
4790
5173
  }
4791
5174
  }
@@ -4809,7 +5192,7 @@ Context: deepspace ${payload.cliVersion} \xB7 ${payload.nodeVersion} \xB7 ${payl
4809
5192
  return;
4810
5193
  }
4811
5194
  if (interactive) {
4812
- p7.outro(`Thanks! Submitted as ${result.report.id}.`);
5195
+ p9.outro(`Thanks! Submitted as ${result.report.id}.`);
4813
5196
  } else {
4814
5197
  console.log(`Submitted feedback ${result.report.id} (${result.report.type}).`);
4815
5198
  }
@@ -4819,8 +5202,18 @@ Context: deepspace ${payload.cliVersion} \xB7 ${payload.nodeVersion} \xB7 ${payl
4819
5202
 
4820
5203
  // src/cli/commands/secrets.ts
4821
5204
  import { defineCommand as defineCommand20 } from "citty";
4822
- import { readFileSync as readFileSync14 } from "fs";
4823
- var DEPLOY_URL6 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
5205
+ import { readFileSync as readFileSync16 } from "fs";
5206
+
5207
+ // src/cli/lib/citty-args.ts
5208
+ function dedupePositionals(bound, rest) {
5209
+ const restArr = Array.isArray(rest) ? rest : [];
5210
+ return [.../* @__PURE__ */ new Set([bound, ...restArr])].filter(
5211
+ (v) => typeof v === "string" && v.length > 0
5212
+ );
5213
+ }
5214
+
5215
+ // src/cli/commands/secrets.ts
5216
+ var DEPLOY_URL8 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
4824
5217
  var COMMON_ARGS = {
4825
5218
  app: {
4826
5219
  type: "string",
@@ -4877,6 +5270,7 @@ function warnIfDebugRoutesEnabled(secrets) {
4877
5270
  return true;
4878
5271
  }
4879
5272
  var APPLY_HINT = "Run `deepspace deploy` to apply \u2014 secrets take effect at deploy time.";
5273
+ var LOCAL_DEV_HINT = "Running `deepspace dev`? Restart it to load the change locally.";
4880
5274
  var list5 = defineCommand20({
4881
5275
  meta: { name: "list", description: "List masked secrets in a config" },
4882
5276
  args: {
@@ -4891,7 +5285,7 @@ var list5 = defineCommand20({
4891
5285
  async run({ args }) {
4892
5286
  try {
4893
5287
  const t = await resolveTarget(args);
4894
- const { secrets } = await listSecrets(DEPLOY_URL6, t.token, t.appId, t.configName);
5288
+ const { secrets } = await listSecrets(DEPLOY_URL8, t.token, t.appId, t.configName);
4895
5289
  if (args.json) {
4896
5290
  console.log(JSON.stringify({ appId: t.appId, config: t.configName, secrets }, null, 2));
4897
5291
  return;
@@ -4917,9 +5311,7 @@ var set = defineCommand20({
4917
5311
  async run({ args }) {
4918
5312
  try {
4919
5313
  const t = await resolveTarget(args);
4920
- const pairs = [.../* @__PURE__ */ new Set([args.secret, ...args._ ?? []])].filter(
4921
- (v) => typeof v === "string" && v.length > 0
4922
- );
5314
+ const pairs = dedupePositionals(args.secret, args._);
4923
5315
  const secrets = {};
4924
5316
  const dupes = [];
4925
5317
  for (const pair of pairs) {
@@ -4938,12 +5330,15 @@ var set = defineCommand20({
4938
5330
  }
4939
5331
  if (Object.keys(secrets).length === 1) {
4940
5332
  const [[k, v]] = Object.entries(secrets);
4941
- await setSecret(DEPLOY_URL6, t.token, t.appId, t.configName, k, v);
5333
+ await setSecret(DEPLOY_URL8, t.token, t.appId, t.configName, k, v);
4942
5334
  } else {
4943
- await uploadSecrets(DEPLOY_URL6, t.token, t.appId, t.configName, secrets, false);
5335
+ await uploadSecrets(DEPLOY_URL8, t.token, t.appId, t.configName, secrets, false);
4944
5336
  }
4945
5337
  console.log(`Set ${Object.keys(secrets).length} secret${Object.keys(secrets).length === 1 ? "" : "s"} in ${t.configName}.`);
4946
- if (!warnIfDebugRoutesEnabled(secrets)) console.log(APPLY_HINT);
5338
+ if (!warnIfDebugRoutesEnabled(secrets)) {
5339
+ console.log(APPLY_HINT);
5340
+ console.log(LOCAL_DEV_HINT);
5341
+ }
4947
5342
  } catch (err) {
4948
5343
  fail(err);
4949
5344
  }
@@ -4961,11 +5356,11 @@ var get = defineCommand20({
4961
5356
  const t = await resolveTarget(args);
4962
5357
  const key = validateSecretName(args.key);
4963
5358
  if (args.plain) {
4964
- const { value } = await getSecretPlain(DEPLOY_URL6, t.token, t.appId, t.configName, key);
5359
+ const { value } = await getSecretPlain(DEPLOY_URL8, t.token, t.appId, t.configName, key);
4965
5360
  process.stdout.write(process.stdout.isTTY ? value + "\n" : value);
4966
5361
  return;
4967
5362
  }
4968
- const { secrets } = await listSecrets(DEPLOY_URL6, t.token, t.appId, t.configName);
5363
+ const { secrets } = await listSecrets(DEPLOY_URL8, t.token, t.appId, t.configName);
4969
5364
  const item = secrets.find((s) => s.key === key);
4970
5365
  if (!item) fail(new Error(`Secret "${key}" not found in ${t.configName}`));
4971
5366
  console.log(`${item.key} (v${item.version}, ${item.updatedAt})`);
@@ -4983,15 +5378,13 @@ var del2 = defineCommand20({
4983
5378
  async run({ args }) {
4984
5379
  try {
4985
5380
  const t = await resolveTarget(args);
4986
- const keys = [.../* @__PURE__ */ new Set([args.key, ...args._ ?? []])].filter(
4987
- (v) => typeof v === "string" && v.length > 0
4988
- );
5381
+ const keys = dedupePositionals(args.key, args._);
4989
5382
  let deleted = 0;
4990
5383
  let absent = 0;
4991
5384
  for (const key of keys) {
4992
5385
  const name = validateSecretName(key);
4993
5386
  try {
4994
- await deleteSecret(DEPLOY_URL6, t.token, t.appId, t.configName, name);
5387
+ await deleteSecret(DEPLOY_URL8, t.token, t.appId, t.configName, name);
4995
5388
  deleted++;
4996
5389
  } catch (err) {
4997
5390
  if (err?.status === 404) absent++;
@@ -5020,10 +5413,10 @@ var upload = defineCommand20({
5020
5413
  async run({ args }) {
5021
5414
  try {
5022
5415
  const t = await resolveTarget(args);
5023
- const content = args.file === "-" ? readFileSync14(0, "utf-8") : readFileSync14(args.file, "utf-8");
5416
+ const content = args.file === "-" ? readFileSync16(0, "utf-8") : readFileSync16(args.file, "utf-8");
5024
5417
  const secrets = parseSecretsUpload(content);
5025
5418
  if (Object.keys(secrets).length === 0) fail(new Error("No secrets found in the input."));
5026
- await uploadSecrets(DEPLOY_URL6, t.token, t.appId, t.configName, secrets, args.replace);
5419
+ await uploadSecrets(DEPLOY_URL8, t.token, t.appId, t.configName, secrets, args.replace);
5027
5420
  console.log(`Uploaded ${Object.keys(secrets).length} secrets to ${t.configName}.`);
5028
5421
  if (!warnIfDebugRoutesEnabled(secrets)) console.log(APPLY_HINT);
5029
5422
  } catch (err) {
@@ -5048,7 +5441,7 @@ var download = defineCommand20({
5048
5441
  if (!["dotenv", "json", "shell"].includes(format)) {
5049
5442
  fail(new Error(`Unknown format "${args.format}" \u2014 use dotenv, json, or shell.`));
5050
5443
  }
5051
- const { secrets } = await fetchSecretsValues(DEPLOY_URL6, t.token, t.appId, t.configName);
5444
+ const { secrets } = await fetchSecretsValues(DEPLOY_URL8, t.token, t.appId, t.configName);
5052
5445
  process.stdout.write(formatSecretsDownload(secrets, format));
5053
5446
  } catch (err) {
5054
5447
  fail(err);
@@ -5065,7 +5458,7 @@ var pull = defineCommand20({
5065
5458
  if (!appDir) fail(new Error("Run from a DeepSpace app directory (one containing wrangler.toml)."));
5066
5459
  const t = await resolveTarget(args);
5067
5460
  const ownerId = decodeJwtPayload(t.token).sub;
5068
- const pulled = await pullAppSecretsCache(DEPLOY_URL6, t.token, t.appId, t.configName);
5461
+ const pulled = await pullAppSecretsCache(DEPLOY_URL8, t.token, t.appId, t.configName);
5069
5462
  if (!pulled) {
5070
5463
  console.log(`No secrets in ${t.configName} yet \u2014 nothing to pull.`);
5071
5464
  return;
@@ -5089,7 +5482,7 @@ var configsList = defineCommand20({
5089
5482
  async run({ args }) {
5090
5483
  try {
5091
5484
  const t = await resolveTarget(args);
5092
- const { configs: configs2 } = await listConfigs(DEPLOY_URL6, t.token, t.appId);
5485
+ const { configs: configs2 } = await listConfigs(DEPLOY_URL8, t.token, t.appId);
5093
5486
  if (configs2.length === 0) {
5094
5487
  console.log("No configs yet \u2014 the first `secrets set` creates one.");
5095
5488
  return;
@@ -5114,7 +5507,7 @@ var configsCreate = defineCommand20({
5114
5507
  const t = await resolveTarget(args);
5115
5508
  const name = validateConfigName(args.name);
5116
5509
  const copyFrom = args["copy-from"] || void 0;
5117
- const { configs: configs2 } = await listConfigs(DEPLOY_URL6, t.token, t.appId);
5510
+ const { configs: configs2 } = await listConfigs(DEPLOY_URL8, t.token, t.appId);
5118
5511
  if (configs2.some((c) => c.name === name)) {
5119
5512
  if (copyFrom) {
5120
5513
  fail(
@@ -5126,7 +5519,7 @@ var configsCreate = defineCommand20({
5126
5519
  console.log(`Config ${name} already exists.`);
5127
5520
  return;
5128
5521
  }
5129
- await createConfig(DEPLOY_URL6, t.token, t.appId, name, copyFrom);
5522
+ await createConfig(DEPLOY_URL8, t.token, t.appId, name, copyFrom);
5130
5523
  console.log(`Created ${name}.`);
5131
5524
  } catch (err) {
5132
5525
  fail(err);
@@ -5143,7 +5536,7 @@ var configsDelete = defineCommand20({
5143
5536
  try {
5144
5537
  const t = await resolveTarget(args);
5145
5538
  const name = validateConfigName(args.name);
5146
- await deleteConfig(DEPLOY_URL6, t.token, t.appId, name);
5539
+ await deleteConfig(DEPLOY_URL8, t.token, t.appId, name);
5147
5540
  console.log(`Deleted ${name}.`);
5148
5541
  } catch (err) {
5149
5542
  fail(err);
@@ -5215,28 +5608,36 @@ var init_default = defineCommand21({
5215
5608
  });
5216
5609
 
5217
5610
  // src/cli/cli.ts
5218
- var __dirname = dirname4(fileURLToPath2(import.meta.url));
5219
- var pkg = JSON.parse(readFileSync15(join16(__dirname, "..", "package.json"), "utf-8"));
5611
+ var __dirname = dirname5(fileURLToPath3(import.meta.url));
5612
+ var pkg = JSON.parse(readFileSync17(join15(__dirname, "..", "package.json"), "utf-8"));
5220
5613
  if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync14("/etc/ssl/certs/ca-certificates.crt")) {
5221
5614
  process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
5222
5615
  }
5223
5616
  var create2 = defineCommand22({
5224
5617
  meta: {
5225
5618
  name: "create",
5226
- description: "Create a new DeepSpace app"
5619
+ description: "Create a new DeepSpace app (runs create-deepspace; all flags are forwarded)"
5227
5620
  },
5228
5621
  args: {
5229
5622
  name: {
5230
5623
  type: "positional",
5231
5624
  description: "App name",
5232
5625
  required: false
5626
+ },
5627
+ interactive: {
5628
+ type: "boolean",
5629
+ description: "Prompt for options instead of using defaults"
5630
+ },
5631
+ local: {
5632
+ type: "string",
5633
+ description: "Use a local SDK monorepo checkout instead of the published package"
5233
5634
  }
5234
5635
  },
5235
- run({ args }) {
5236
- const name = args.name ? ` ${args.name}` : "";
5237
- console.log(`Running: npm create deepspace${name}
5238
- `);
5239
- execSync4(`npm create deepspace@latest${name}`, { stdio: "inherit" });
5636
+ run({ rawArgs }) {
5637
+ const scaffolder = `create-deepspace@${pkg.version}`;
5638
+ console.log(`Running: npx ${scaffolder} ${rawArgs.join(" ")}`.trimEnd() + "\n");
5639
+ const res = spawnSync6("npx", ["-y", scaffolder, ...rawArgs], { stdio: "inherit" });
5640
+ process.exit(res.status ?? 1);
5240
5641
  }
5241
5642
  });
5242
5643
  var main = defineCommand22({
@@ -5251,13 +5652,13 @@ var main = defineCommand22({
5251
5652
  login: login_default,
5252
5653
  logout: logout_default,
5253
5654
  whoami: whoami_default,
5655
+ apps: apps_default,
5254
5656
  dev: dev_default,
5255
5657
  kill: kill_default,
5256
5658
  test: test_default,
5257
5659
  screenshot: screenshot_default,
5258
5660
  "test-accounts": test_accounts_default,
5259
5661
  add: add_default,
5260
- skills: skills_default,
5261
5662
  integrations: integrations_default,
5262
5663
  invoke: invoke_default,
5263
5664
  deploy: deploy_default,