deepspace 0.4.3 → 0.4.4

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
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli/cli.ts
4
- import { defineCommand as defineCommand19, runMain } from "citty";
4
+ import { defineCommand as defineCommand21, runMain } from "citty";
5
5
  import { execSync as execSync5 } from "child_process";
6
- import { readFileSync as readFileSync11, existsSync as existsSync12 } from "fs";
7
- import { dirname as dirname3, join as join14 } from "path";
8
- import { fileURLToPath } from "url";
6
+ import { readFileSync as readFileSync13, existsSync as existsSync14 } from "fs";
7
+ import { dirname as dirname4, join as join16 } from "path";
8
+ import { fileURLToPath as fileURLToPath2 } from "url";
9
9
 
10
10
  // src/cli/commands/login.ts
11
11
  import { defineCommand } from "citty";
@@ -122,9 +122,6 @@ function resolveAppNameForEnv(config, envName) {
122
122
  }
123
123
  return resolveAppName(envBlock.name);
124
124
  }
125
- function devVarsPathFor(appDir, envName) {
126
- return envName ? join(appDir, `.dev.vars.${envName}`) : join(appDir, ".dev.vars");
127
- }
128
125
  var WranglerConfigError = class extends Error {
129
126
  constructor(path, cause) {
130
127
  const detail = cause instanceof Error ? cause.message : String(cause);
@@ -235,8 +232,22 @@ var SDK_MANAGED_KEYS = /* @__PURE__ */ new Set([
235
232
  "OWNER_USER_ID",
236
233
  "APP_OWNER_JWT",
237
234
  "APP_IDENTITY_TOKEN",
235
+ "APP_SECRETS_KEY",
236
+ "APP_SECRETS_LOCAL_FALLBACK",
238
237
  "ALLOW_DEBUG_ROUTES"
239
238
  ]);
239
+ var MANAGED_ENV_HEADER = "# Managed by `deepspace secrets` and regenerated by `deepspace dev`/`deepspace test`.";
240
+ var MANAGED_ENV_SYNCED_HEADER = "# DeepSpace synced secrets - mirrored with the deployed app secrets store.";
241
+ var MANAGED_ENV_DEV_ONLY_HEADER = "# DeepSpace dev-only secrets - used by `deepspace dev`; never synced unless pushed.";
242
+ function localEnvPath(appDir) {
243
+ return join2(appDir, ".env");
244
+ }
245
+ function legacyDevVarsPath(appDir, wranglerEnv) {
246
+ return join2(appDir, wranglerEnv ? `.dev.vars.${wranglerEnv}` : ".dev.vars");
247
+ }
248
+ function hasLegacyDevVars(appDir, wranglerEnv) {
249
+ return existsSync2(legacyDevVarsPath(appDir, wranglerEnv));
250
+ }
240
251
  var DEV_VARS_DIVIDER = "# --- not managed by the SDK; preserved across dev/test runs ---";
241
252
  function extractCustomDevVars(content) {
242
253
  const lines = content.split("\n");
@@ -343,6 +354,189 @@ function parseDevVars(content) {
343
354
  }
344
355
  return result;
345
356
  }
357
+ function parseDevVarsStrict(content, file = ".env") {
358
+ const result = {};
359
+ const lines = content.split("\n");
360
+ let i = 0;
361
+ while (i < lines.length) {
362
+ const line = lines[i];
363
+ const trimmed = line.trim();
364
+ if (!trimmed || trimmed.startsWith("#")) {
365
+ i++;
366
+ continue;
367
+ }
368
+ const eq = line.indexOf("=");
369
+ if (eq < 0) {
370
+ throw new Error(`${file}: invalid dotenv syntax on line ${i + 1}. Expected KEY=value.`);
371
+ }
372
+ const keyMatch = line.slice(0, eq).match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*$/);
373
+ if (!keyMatch) {
374
+ throw new Error(`${file}: invalid variable name on line ${i + 1}.`);
375
+ }
376
+ const key = keyMatch[1];
377
+ let value = line.slice(eq + 1);
378
+ let closed = !value.startsWith('"') || hasUnescapedQuote(value.slice(1));
379
+ if (!closed) {
380
+ i++;
381
+ while (i < lines.length) {
382
+ value += "\n" + lines[i];
383
+ if (hasUnescapedQuote(lines[i])) {
384
+ closed = true;
385
+ i++;
386
+ break;
387
+ }
388
+ i++;
389
+ }
390
+ } else {
391
+ i++;
392
+ }
393
+ if (value.startsWith('"') && !closed) {
394
+ throw new Error(`${file}: unterminated quoted value for key "${key}".`);
395
+ }
396
+ if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
397
+ value = value.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, "\\");
398
+ }
399
+ result[key] = value;
400
+ }
401
+ return result;
402
+ }
403
+ function quoteEnvValue(value) {
404
+ return /^[A-Za-z0-9_./:@-]*$/.test(value) && !value.includes("\n") ? value : `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
405
+ }
406
+ function stripSdkManaged(vars) {
407
+ const out = {};
408
+ for (const [name, value] of Object.entries(vars)) {
409
+ if (!SDK_MANAGED_KEYS.has(name)) out[name] = value;
410
+ }
411
+ return out;
412
+ }
413
+ function sortRecord(input) {
414
+ const out = {};
415
+ for (const key of Object.keys(input).sort()) out[key] = input[key];
416
+ return out;
417
+ }
418
+ function readManagedEnvSections(appDir) {
419
+ const path = localEnvPath(appDir);
420
+ if (!existsSync2(path)) return { sdk: {}, synced: {}, devOnly: {} };
421
+ return parseManagedEnvSections(readFileSync2(path, "utf-8"));
422
+ }
423
+ function parseManagedEnvSections(content) {
424
+ const sections = { sdk: {}, synced: {}, devOnly: {} };
425
+ const legacyAppLines = [];
426
+ let target = "sdk";
427
+ const lines = content.split("\n");
428
+ for (let i = 0; i < lines.length; i++) {
429
+ const line = lines[i];
430
+ if (line.trim() === MANAGED_ENV_SYNCED_HEADER.trim()) {
431
+ target = "synced";
432
+ continue;
433
+ }
434
+ if (line.trim() === MANAGED_ENV_DEV_ONLY_HEADER.trim()) {
435
+ target = "devOnly";
436
+ continue;
437
+ }
438
+ if (line.trim().startsWith("#")) continue;
439
+ const name = assignmentName(line);
440
+ if (!name) {
441
+ if (line.trim()) legacyAppLines.push(line);
442
+ continue;
443
+ }
444
+ const block = [line];
445
+ const eq = line.indexOf("=");
446
+ const valStart = eq >= 0 ? line.slice(eq + 1) : "";
447
+ if (valStart.startsWith('"') && !hasUnescapedQuote(valStart.slice(1))) {
448
+ i++;
449
+ while (i < lines.length) {
450
+ block.push(lines[i]);
451
+ const closed = hasUnescapedQuote(lines[i]);
452
+ if (closed) break;
453
+ i++;
454
+ }
455
+ }
456
+ if (target === "sdk" && !SDK_MANAGED_KEYS.has(name)) {
457
+ legacyAppLines.push(...block);
458
+ continue;
459
+ }
460
+ const parsed = parseDevVarsStrict(block.join("\n"), ".env");
461
+ Object.assign(sections[target], parsed);
462
+ }
463
+ if (legacyAppLines.length) {
464
+ Object.assign(sections.synced, stripSdkManaged(parseDevVarsStrict(legacyAppLines.join("\n"), ".env")));
465
+ }
466
+ return sections;
467
+ }
468
+ function assignmentName(line) {
469
+ const eq = line.indexOf("=");
470
+ if (eq < 0) return null;
471
+ const match = line.slice(0, eq).match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*$/);
472
+ return match?.[1] ?? null;
473
+ }
474
+ function readManagedAppEnv(appDir) {
475
+ const sections = readManagedEnvSections(appDir);
476
+ return { ...sections.synced, ...sections.devOnly };
477
+ }
478
+ function readManagedSyncedEnv(appDir) {
479
+ return { ...readManagedEnvSections(appDir).synced };
480
+ }
481
+ function readManagedDevOnlyEnv(appDir) {
482
+ return { ...readManagedEnvSections(appDir).devOnly };
483
+ }
484
+ function writeManagedEnvSections(appDir, sections) {
485
+ ensureLocalEnvIgnored(appDir);
486
+ const lines = [
487
+ MANAGED_ENV_HEADER,
488
+ "# Do not commit this file. Edit secrets with `deepspace secrets`, not by hand."
489
+ ];
490
+ for (const [name, value] of Object.entries(sortRecord(sections.sdk))) {
491
+ lines.push(`${name}=${quoteEnvValue(value)}`);
492
+ }
493
+ lines.push("", MANAGED_ENV_SYNCED_HEADER);
494
+ for (const [name, value] of Object.entries(sortRecord(sections.synced))) {
495
+ lines.push(`${name}=${quoteEnvValue(value)}`);
496
+ }
497
+ lines.push("", MANAGED_ENV_DEV_ONLY_HEADER);
498
+ for (const [name, value] of Object.entries(sortRecord(sections.devOnly))) {
499
+ lines.push(`${name}=${quoteEnvValue(value)}`);
500
+ }
501
+ writeFileSync(localEnvPath(appDir), lines.join("\n") + "\n", { mode: 384 });
502
+ }
503
+ function ensureLocalEnvIgnored(appDir) {
504
+ const path = join2(appDir, ".gitignore");
505
+ const existing = existsSync2(path) ? readFileSync2(path, "utf-8") : "";
506
+ const lines = new Set(existing.split("\n").map((line) => line.trim()));
507
+ const additions = [".env", ".dev.vars"].filter((entry) => !lines.has(entry));
508
+ if (!additions.length) return;
509
+ const prefix = existing && !existing.endsWith("\n") ? existing + "\n" : existing;
510
+ writeFileSync(path, `${prefix}${additions.join("\n")}
511
+ `);
512
+ }
513
+ function patchManagedSyncedEnv(appDir, set2 = {}, removeNames = []) {
514
+ const sections = readManagedEnvSections(appDir);
515
+ for (const [name, value] of Object.entries(set2)) sections.synced[name] = value;
516
+ for (const name of removeNames) delete sections.synced[name];
517
+ writeManagedEnvSections(appDir, sections);
518
+ }
519
+ function patchManagedDevOnlyEnv(appDir, set2 = {}, removeNames = []) {
520
+ const sections = readManagedEnvSections(appDir);
521
+ for (const [name, value] of Object.entries(set2)) sections.devOnly[name] = value;
522
+ for (const name of removeNames) delete sections.devOnly[name];
523
+ writeManagedEnvSections(appDir, sections);
524
+ }
525
+ function replaceManagedSyncedEnv(appDir, syncedVars) {
526
+ const sections = readManagedEnvSections(appDir);
527
+ sections.synced = stripSdkManaged(syncedVars);
528
+ writeManagedEnvSections(appDir, sections);
529
+ }
530
+ function moveManagedDevOnlyToSynced(appDir, names) {
531
+ const sections = readManagedEnvSections(appDir);
532
+ for (const name of names) {
533
+ if (name in sections.devOnly) {
534
+ sections.synced[name] = sections.devOnly[name];
535
+ delete sections.devOnly[name];
536
+ }
537
+ }
538
+ writeManagedEnvSections(appDir, sections);
539
+ }
346
540
  async function writeDevVars(appDir, env, ownerId, callerJwt, wranglerEnv) {
347
541
  const urls = ENVS[env];
348
542
  const publicKey = await fetchPublicKey(urls.auth);
@@ -358,11 +552,18 @@ async function writeDevVars(appDir, env, ownerId, callerJwt, wranglerEnv) {
358
552
  `OWNER_USER_ID=${ownerId}`,
359
553
  `APP_OWNER_JWT=${appOwnerJwt}`,
360
554
  ...appIdentityToken ? [`APP_IDENTITY_TOKEN=${appIdentityToken}`] : [],
555
+ `APP_SECRETS_LOCAL_FALLBACK=true`,
361
556
  // Open /api/debug/* (proxied to RecordRoom) for dev + tests only.
362
557
  // Production secrets never set this, so deployed apps 404 the route.
363
558
  `ALLOW_DEBUG_ROUTES=true`
364
559
  ].join("\n");
365
- const devVarsPath = join2(appDir, wranglerEnv ? `.dev.vars.${wranglerEnv}` : ".dev.vars");
560
+ const sdkEnv = parseDevVarsStrict(sdkVars, "SDK env");
561
+ if (!hasLegacyDevVars(appDir, wranglerEnv)) {
562
+ const sections = readManagedEnvSections(appDir);
563
+ writeManagedEnvSections(appDir, { ...sections, sdk: sdkEnv });
564
+ return;
565
+ }
566
+ const devVarsPath = legacyDevVarsPath(appDir, wranglerEnv);
366
567
  const existing = existsSync2(devVarsPath) ? readFileSync2(devVarsPath, "utf-8") : "";
367
568
  const custom = extractCustomDevVars(existing);
368
569
  const body = custom ? `${sdkVars}
@@ -571,7 +772,7 @@ var logout_default = defineCommand2({
571
772
  });
572
773
 
573
774
  // src/cli/commands/dev.ts
574
- import { defineCommand as defineCommand3 } from "citty";
775
+ import { defineCommand as defineCommand4 } from "citty";
575
776
  import { resolve as resolve3, basename } from "path";
576
777
  import spawn from "cross-spawn";
577
778
 
@@ -746,9 +947,525 @@ function preflightWindowsWorkerd(appDir) {
746
947
  }
747
948
  }
748
949
 
950
+ // src/cli/commands/secrets.ts
951
+ import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
952
+ import { join as join8 } from "path";
953
+ import { defineCommand as defineCommand3 } from "citty";
954
+ var SECRET_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
955
+ var ERROR_SNIPPET_CHARS = 500;
956
+ var COLLABORATOR_ROUTES_404 = "Platform API does not expose app-collaborator routes; deploy/update api-worker before using collaborator commands.";
957
+ var COLLABORATOR_SCHEMA_NOT_MIGRATED = "Platform API is missing app-collaborator database tables. Run api-worker /_migrate with x-migration-secret, then retry collaborator commands.";
958
+ function parseAssignment(value) {
959
+ const eq = value.indexOf("=");
960
+ if (eq <= 0) throw new Error("Use KEY=value.");
961
+ const name = value.slice(0, eq);
962
+ if (!SECRET_NAME_RE.test(name)) throw new Error(`Invalid secret name "${name}".`);
963
+ if (SDK_MANAGED_KEYS.has(name)) throw new Error(`${name} is managed by DeepSpace and cannot be set as an app secret.`);
964
+ return [name, value.slice(eq + 1)];
965
+ }
966
+ function devTargetPath() {
967
+ return join8(process.cwd(), ".dev.vars");
968
+ }
969
+ function readDevTarget() {
970
+ if (!hasLegacyDevVars(process.cwd())) {
971
+ return readManagedAppEnv(process.cwd());
972
+ }
973
+ const path = devTargetPath();
974
+ if (!existsSync8(path)) return {};
975
+ return stripSdkManaged(parseDevVars(splitDevTarget(readFileSync7(path, "utf-8")).custom));
976
+ }
977
+ function readSyncedTarget() {
978
+ if (hasLegacyDevVars(process.cwd())) return readDevTarget();
979
+ return readManagedSyncedEnv(process.cwd());
980
+ }
981
+ function readDevOnlyTarget() {
982
+ if (hasLegacyDevVars(process.cwd())) return readDevTarget();
983
+ return readManagedDevOnlyEnv(process.cwd());
984
+ }
985
+ function writeLocalPatch(set2 = {}, removeNames = []) {
986
+ for (const name of [...Object.keys(set2), ...removeNames]) {
987
+ if (SDK_MANAGED_KEYS.has(name)) {
988
+ throw new Error(`${name} is managed by DeepSpace and cannot be set as an app secret.`);
989
+ }
990
+ }
991
+ if (hasLegacyDevVars(process.cwd())) writeDevTargetPatch(set2, removeNames);
992
+ else {
993
+ patchManagedDevOnlyEnv(process.cwd(), set2, removeNames);
994
+ }
995
+ }
996
+ function quote(value) {
997
+ return quoteEnvValue(value);
998
+ }
999
+ function splitLocal(raw) {
1000
+ const idx = raw.indexOf(DEV_VARS_DIVIDER);
1001
+ if (idx < 0) return { top: "", custom: raw };
1002
+ const after = raw.slice(idx + DEV_VARS_DIVIDER.length);
1003
+ return {
1004
+ top: raw.slice(0, idx).trimEnd(),
1005
+ custom: after.startsWith("\n") ? after.slice(1) : after
1006
+ };
1007
+ }
1008
+ function splitDevTarget(raw) {
1009
+ const idx = raw.indexOf(DEV_VARS_DIVIDER);
1010
+ if (idx >= 0) return splitLocal(raw);
1011
+ const lines = raw.split("\n");
1012
+ const top = [];
1013
+ const custom = [];
1014
+ let target = top;
1015
+ let i = 0;
1016
+ while (i < lines.length) {
1017
+ const line = lines[i];
1018
+ const name = assignmentName2(line);
1019
+ const eq = line.indexOf("=");
1020
+ const valStart = eq >= 0 ? line.slice(eq + 1) : "";
1021
+ const block = [line];
1022
+ i++;
1023
+ if (name && valStart.startsWith('"') && !hasUnescapedQuote2(valStart.slice(1))) {
1024
+ while (i < lines.length) {
1025
+ block.push(lines[i]);
1026
+ const closed = hasUnescapedQuote2(lines[i]);
1027
+ i++;
1028
+ if (closed) break;
1029
+ }
1030
+ }
1031
+ if (name && !SDK_MANAGED_KEYS.has(name)) target = custom;
1032
+ target.push(...block);
1033
+ }
1034
+ while (top.length && top[top.length - 1] === "") top.pop();
1035
+ while (custom.length && custom[custom.length - 1] === "") custom.pop();
1036
+ return { top: top.join("\n"), custom: custom.join("\n") };
1037
+ }
1038
+ function assignmentName2(line) {
1039
+ const eq = line.indexOf("=");
1040
+ if (eq < 0) return null;
1041
+ const match = line.slice(0, eq).match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*$/);
1042
+ return match?.[1] ?? null;
1043
+ }
1044
+ function hasUnescapedQuote2(s) {
1045
+ let escaped = false;
1046
+ for (const ch of s) {
1047
+ if (escaped) {
1048
+ escaped = false;
1049
+ continue;
1050
+ }
1051
+ if (ch === "\\") {
1052
+ escaped = true;
1053
+ continue;
1054
+ }
1055
+ if (ch === '"') return true;
1056
+ }
1057
+ return false;
1058
+ }
1059
+ function rewriteCustomSection(custom, set2, remove2) {
1060
+ const lines = custom.split("\n");
1061
+ const out = [];
1062
+ const pending = new Set(Object.keys(set2));
1063
+ let i = 0;
1064
+ while (i < lines.length) {
1065
+ const line = lines[i];
1066
+ const name = assignmentName2(line);
1067
+ const eq = line.indexOf("=");
1068
+ const valStart = eq >= 0 ? line.slice(eq + 1) : "";
1069
+ const block = [line];
1070
+ i++;
1071
+ if (name && valStart.startsWith('"') && !hasUnescapedQuote2(valStart.slice(1))) {
1072
+ while (i < lines.length) {
1073
+ block.push(lines[i]);
1074
+ const closed = hasUnescapedQuote2(lines[i]);
1075
+ i++;
1076
+ if (closed) break;
1077
+ }
1078
+ }
1079
+ if (name && (remove2.has(name) || pending.has(name))) {
1080
+ if (pending.has(name)) {
1081
+ out.push(`${name}=${quote(set2[name])}`);
1082
+ pending.delete(name);
1083
+ }
1084
+ continue;
1085
+ }
1086
+ out.push(...block);
1087
+ }
1088
+ while (out.length && out[out.length - 1] === "") out.pop();
1089
+ for (const name of Array.from(pending).sort()) {
1090
+ out.push(`${name}=${quote(set2[name])}`);
1091
+ }
1092
+ return out.join("\n");
1093
+ }
1094
+ function writeDevTargetPatch(set2 = {}, removeNames = []) {
1095
+ const path = devTargetPath();
1096
+ const existing = existsSync8(path) ? readFileSync7(path, "utf-8") : "";
1097
+ const { top, custom: existingCustom } = splitDevTarget(existing);
1098
+ const custom = rewriteCustomSection(existingCustom, set2, new Set(removeNames));
1099
+ const body = top ? `${top}
1100
+
1101
+ ${DEV_VARS_DIVIDER}
1102
+ ${custom}${custom ? "\n" : ""}` : `${DEV_VARS_DIVIDER}
1103
+ ${custom}${custom ? "\n" : ""}`;
1104
+ writeFileSync5(path, body);
1105
+ }
1106
+ async function remoteDeployState(appName, token2) {
1107
+ const res = await fetch(`${process.env.DEEPSPACE_DEPLOY_URL ?? "https://deploy-worker.deep.space"}/api/deploy/${encodeURIComponent(appName)}`, {
1108
+ headers: { Authorization: `Bearer ${token2}` }
1109
+ });
1110
+ const text2 = await res.text().catch(() => "");
1111
+ let body = {};
1112
+ if (text2) {
1113
+ try {
1114
+ body = JSON.parse(text2);
1115
+ } catch (err) {
1116
+ const message = err instanceof Error ? err.message : String(err);
1117
+ throw new Error(`Could not determine deploy status for ${appName}: invalid JSON response (${message}).`);
1118
+ }
1119
+ }
1120
+ if (!res.ok) {
1121
+ const detail = body.error ?? (text2 || "unknown error");
1122
+ throw new Error(`Could not determine deploy status for ${appName} (${res.status}): ${detail}`);
1123
+ }
1124
+ if (body.deployed === true) return "deployed";
1125
+ if (body.deployed === false) return "not-deployed";
1126
+ throw new Error(`Could not determine deploy status for ${appName}: response missing "deployed".`);
1127
+ }
1128
+ async function remoteAvailable(appName, token2) {
1129
+ return await remoteDeployState(appName, token2) === "deployed";
1130
+ }
1131
+ async function mintScopedSecretsToken(appName, token2) {
1132
+ const body = await collaboratorFetch(appName, token2, "/secrets-token", { method: "POST" });
1133
+ if (!body.token) throw new Error("Platform API did not return an app-secret access token.");
1134
+ return body.token;
1135
+ }
1136
+ async function remoteFetch(appName, token2, path = "", init = {}) {
1137
+ const scopedToken = await mintScopedSecretsToken(appName, token2);
1138
+ const res = await fetch(`https://${appName}.app.space/_deepspace/secrets${path}`, {
1139
+ ...init,
1140
+ headers: {
1141
+ "Content-Type": "application/json",
1142
+ Authorization: `Bearer ${scopedToken}`,
1143
+ ...init.headers ?? {}
1144
+ }
1145
+ });
1146
+ const contentType = res.headers.get("content-type") ?? "unknown";
1147
+ const text2 = await res.text().catch(() => "");
1148
+ let body = null;
1149
+ let parseError = null;
1150
+ if (text2 && contentType.toLowerCase().includes("json")) {
1151
+ try {
1152
+ body = JSON.parse(text2);
1153
+ } catch (err) {
1154
+ parseError = err instanceof Error ? err.message : String(err);
1155
+ }
1156
+ }
1157
+ if (!res.ok) {
1158
+ if (body?.error) throw new Error(body.error);
1159
+ const snippet = text2 ? ` Body: ${text2.slice(0, ERROR_SNIPPET_CHARS)}` : " Body: <empty>";
1160
+ const parseDetail2 = parseError ? ` JSON parse failed: ${parseError}.` : "";
1161
+ throw new Error(`Remote secrets request failed (${res.status}; content-type: ${contentType}).${parseDetail2}${snippet}`);
1162
+ }
1163
+ if (body) return body;
1164
+ if (!text2) return {};
1165
+ const parseDetail = parseError ? ` JSON parse failed: ${parseError}.` : "";
1166
+ throw new Error(`Remote secrets response was not JSON (${res.status}; content-type: ${contentType}).${parseDetail} Body: ${text2.slice(0, ERROR_SNIPPET_CHARS)}`);
1167
+ }
1168
+ function apiUrl() {
1169
+ return process.env.DEEPSPACE_API_URL ?? ENVS.prod.api;
1170
+ }
1171
+ async function collaboratorFetch(appName, token2, path, init = {}) {
1172
+ const url = `${apiUrl()}/api/app-collaborators/${encodeURIComponent(appName)}${path}`;
1173
+ const res = await fetch(url, {
1174
+ ...init,
1175
+ headers: {
1176
+ "Content-Type": "application/json",
1177
+ Authorization: `Bearer ${token2}`,
1178
+ ...init.headers ?? {}
1179
+ }
1180
+ });
1181
+ const text2 = await res.text().catch(() => "");
1182
+ let body = {};
1183
+ if (text2) {
1184
+ try {
1185
+ body = JSON.parse(text2);
1186
+ } catch {
1187
+ if (res.status === 404) throw new Error(COLLABORATOR_ROUTES_404);
1188
+ if (!res.ok) throw new Error(`Collaborator request failed (${res.status}): ${text2.slice(0, ERROR_SNIPPET_CHARS)}`);
1189
+ throw new Error(`Collaborator response was not JSON (${res.status}): ${text2.slice(0, ERROR_SNIPPET_CHARS)}`);
1190
+ }
1191
+ }
1192
+ if (!res.ok) {
1193
+ if (res.status === 404 && (!body.error || body.error === "Not found" || body.error === "not_found")) {
1194
+ throw new Error(COLLABORATOR_ROUTES_404);
1195
+ }
1196
+ if (body.error === "platform_schema_not_migrated") {
1197
+ const diagnostic = body;
1198
+ const action = typeof diagnostic.action === "string" ? ` ${diagnostic.action}` : "";
1199
+ throw new Error(`${COLLABORATOR_SCHEMA_NOT_MIGRATED}${action}`);
1200
+ }
1201
+ throw new Error(body.error ?? `Collaborator request failed (${res.status})`);
1202
+ }
1203
+ return body;
1204
+ }
1205
+ async function requireRemoteSecretsAvailable(appName, token2) {
1206
+ const state = await remoteDeployState(appName, token2);
1207
+ if (state === "deployed") return;
1208
+ throw new Error(
1209
+ "Remote secrets are available after the first deploy. Deploy this app first, then run `deepspace secrets push`; `deepspace secrets set --dev` writes dev-only secrets for `deepspace dev` without touching remote."
1210
+ );
1211
+ }
1212
+ function diffMaps(local, remote) {
1213
+ const localOnly = [];
1214
+ const remoteOnly = [];
1215
+ const changed = [];
1216
+ const matching = [];
1217
+ for (const [name, value] of Object.entries(local)) {
1218
+ if (!(name in remote)) localOnly.push(name);
1219
+ else if (remote[name] !== value) changed.push(name);
1220
+ else matching.push(name);
1221
+ }
1222
+ for (const name of Object.keys(remote)) {
1223
+ if (!(name in local)) remoteOnly.push(name);
1224
+ }
1225
+ return { localOnly, remoteOnly, changed, matching };
1226
+ }
1227
+ function diffNames(local, remote) {
1228
+ const localSet = new Set(local);
1229
+ const remoteSet = new Set(remote);
1230
+ return {
1231
+ localOnly: local.filter((name) => !remoteSet.has(name)).sort(),
1232
+ remoteOnly: remote.filter((name) => !localSet.has(name)).sort()
1233
+ };
1234
+ }
1235
+ function statusLine(d, remote) {
1236
+ if (!remote) return "Secrets: remote unavailable until first deploy; dev-only .env section updated.";
1237
+ if (!d || !d.localOnly.length && !d.remoteOnly.length && !d.changed.length) {
1238
+ return "Secrets: synced .env section matches remote.";
1239
+ }
1240
+ return `Secrets: ${d.localOnly.length} local-only, ${d.remoteOnly.length} remote-only, ${d.changed.length} changed. Run \`deepspace secrets diff\`.`;
1241
+ }
1242
+ function nameStatusLine(d) {
1243
+ if (!d.localOnly.length && !d.remoteOnly.length) {
1244
+ return "Secrets: synced .env section has the same names as remote.";
1245
+ }
1246
+ return `Secrets: ${d.localOnly.length} local-only, ${d.remoteOnly.length} remote-only. Run \`deepspace secrets diff\` to compare values.`;
1247
+ }
1248
+ async function compare(appName, token2) {
1249
+ if (!await remoteAvailable(appName, token2)) return null;
1250
+ const local = readSyncedTarget();
1251
+ const remote = await remoteFetch(appName, token2, "/pull");
1252
+ return diffMaps(local, remote.secrets ?? {});
1253
+ }
1254
+ var set = defineCommand3({
1255
+ meta: { name: "set", description: "Set an app secret" },
1256
+ args: {
1257
+ assignment: { type: "positional", required: true, description: "KEY=value" },
1258
+ dev: {
1259
+ type: "boolean",
1260
+ default: false,
1261
+ description: "Write only to the dev-only .env section used by `deepspace dev`; never sync to the deployed app"
1262
+ }
1263
+ },
1264
+ async run({ args }) {
1265
+ const [name, value] = parseAssignment(String(args.assignment));
1266
+ const token2 = await ensureToken();
1267
+ const appName = requireAppName(void 0);
1268
+ if (args.dev) {
1269
+ writeLocalPatch({ [name]: value });
1270
+ console.log("Secrets: updated dev-only .env section for `deepspace dev`; remote was not changed.");
1271
+ return;
1272
+ }
1273
+ const state = await remoteDeployState(appName, token2);
1274
+ if (state === "not-deployed") {
1275
+ writeLocalPatch({ [name]: value });
1276
+ console.log("Secrets: app is not deployed; wrote to dev-only .env section. Run `deepspace secrets push` after deploy.");
1277
+ return;
1278
+ }
1279
+ await remoteFetch(appName, token2, "", { method: "PATCH", body: JSON.stringify({ set: { [name]: value } }) });
1280
+ if (hasLegacyDevVars(process.cwd())) writeDevTargetPatch({ [name]: value });
1281
+ else patchManagedSyncedEnv(process.cwd(), { [name]: value });
1282
+ console.log("Secrets: updated remote and synced .env section.");
1283
+ }
1284
+ });
1285
+ var unset = defineCommand3({
1286
+ meta: { name: "unset", description: "Remove an app secret" },
1287
+ args: {
1288
+ name: { type: "positional", required: true, description: "Secret name" },
1289
+ dev: {
1290
+ type: "boolean",
1291
+ default: false,
1292
+ description: "Remove only from the dev-only .env section used by `deepspace dev`; never sync to the deployed app"
1293
+ }
1294
+ },
1295
+ async run({ args }) {
1296
+ const name = String(args.name);
1297
+ if (!SECRET_NAME_RE.test(name)) throw new Error(`Invalid secret name "${name}".`);
1298
+ const token2 = await ensureToken();
1299
+ const appName = requireAppName(void 0);
1300
+ if (args.dev) {
1301
+ writeLocalPatch({}, [name]);
1302
+ console.log("Secrets: removed from dev-only .env section; remote was not changed.");
1303
+ return;
1304
+ }
1305
+ const state = await remoteDeployState(appName, token2);
1306
+ if (state === "not-deployed") {
1307
+ writeLocalPatch({}, [name]);
1308
+ console.log("Secrets: app is not deployed; removed from dev-only .env section if present.");
1309
+ return;
1310
+ }
1311
+ await remoteFetch(appName, token2, "", { method: "PATCH", body: JSON.stringify({ delete: [name] }) });
1312
+ if (hasLegacyDevVars(process.cwd())) writeDevTargetPatch({}, [name]);
1313
+ else patchManagedSyncedEnv(process.cwd(), {}, [name]);
1314
+ console.log("Secrets: removed from remote and synced .env section.");
1315
+ }
1316
+ });
1317
+ var list = defineCommand3({
1318
+ meta: { name: "list", description: "List app secret names and metadata" },
1319
+ async run() {
1320
+ const token2 = await ensureToken();
1321
+ const appName = requireAppName(void 0);
1322
+ await requireRemoteSecretsAvailable(appName, token2);
1323
+ const body = await remoteFetch(appName, token2);
1324
+ for (const item of body.secrets ?? []) console.log(`${item.name} version=${item.version} updated=${item.updatedAt}`);
1325
+ const d = diffNames(Object.keys(readSyncedTarget()), (body.secrets ?? []).map((item) => item.name));
1326
+ console.log(nameStatusLine(d));
1327
+ }
1328
+ });
1329
+ var pull = defineCommand3({
1330
+ meta: { name: "pull", description: "Pull remote app secrets into the synced .env section" },
1331
+ async run() {
1332
+ const token2 = await ensureToken();
1333
+ const appName = requireAppName(void 0);
1334
+ await requireRemoteSecretsAvailable(appName, token2);
1335
+ const body = await remoteFetch(appName, token2, "/pull");
1336
+ const remoteSecrets = body.secrets ?? {};
1337
+ if (hasLegacyDevVars(process.cwd())) {
1338
+ writeDevTargetPatch(remoteSecrets);
1339
+ } else {
1340
+ replaceManagedSyncedEnv(process.cwd(), remoteSecrets);
1341
+ }
1342
+ const count = Object.keys(remoteSecrets).length;
1343
+ console.log(`Secrets: pulled ${count} remote secret${count === 1 ? "" : "s"} into synced .env section.`);
1344
+ }
1345
+ });
1346
+ var push = defineCommand3({
1347
+ meta: { name: "push", description: "Push dev-only .env secrets to remote and move them to synced" },
1348
+ async run() {
1349
+ const token2 = await ensureToken();
1350
+ const appName = requireAppName(void 0);
1351
+ const local = readDevOnlyTarget();
1352
+ await requireRemoteSecretsAvailable(appName, token2);
1353
+ const names = Object.keys(local);
1354
+ if (!names.length) {
1355
+ console.log("Secrets: no dev-only .env secrets to push.");
1356
+ return;
1357
+ }
1358
+ await remoteFetch(appName, token2, "", { method: "PATCH", body: JSON.stringify({ set: local }) });
1359
+ if (hasLegacyDevVars(process.cwd())) {
1360
+ console.log(`Secrets: pushed ${names.length} dev-only secret${names.length === 1 ? "" : "s"} to remote.`);
1361
+ } else {
1362
+ moveManagedDevOnlyToSynced(process.cwd(), names);
1363
+ console.log(`Secrets: pushed ${names.length} dev-only secret${names.length === 1 ? "" : "s"} to remote and moved ${names.length === 1 ? "it" : "them"} to synced .env section.`);
1364
+ }
1365
+ }
1366
+ });
1367
+ var diff = defineCommand3({
1368
+ meta: { name: "diff", description: "Compare synced .env section with remote app secrets" },
1369
+ async run() {
1370
+ const token2 = await ensureToken();
1371
+ const appName = requireAppName(void 0);
1372
+ await requireRemoteSecretsAvailable(appName, token2);
1373
+ const d = await compare(appName, token2);
1374
+ for (const name of d?.localOnly ?? []) console.log(`local-only ${name}`);
1375
+ for (const name of d?.remoteOnly ?? []) console.log(`remote-only ${name}`);
1376
+ for (const name of d?.changed ?? []) console.log(`changed ${name}`);
1377
+ console.log(statusLine(d, true));
1378
+ }
1379
+ });
1380
+ var collaboratorsList = defineCommand3({
1381
+ meta: { name: "list", description: "List app secret collaborators" },
1382
+ async run() {
1383
+ const token2 = await ensureToken();
1384
+ const appName = requireAppName(void 0);
1385
+ const body = await collaboratorFetch(appName, token2, "/collaborators");
1386
+ for (const item of body.collaborators ?? []) {
1387
+ console.log(`${item.emailDisplay} ${item.userId} created=${new Date(item.createdAt).toISOString()}`);
1388
+ }
1389
+ if (!body.collaborators?.length) console.log("No secrets collaborators.");
1390
+ }
1391
+ });
1392
+ var collaboratorsAdd = defineCommand3({
1393
+ meta: { name: "add", description: "Add an existing DeepSpace user as a secrets collaborator" },
1394
+ args: {
1395
+ email: { type: "positional", required: true, description: "Collaborator email" }
1396
+ },
1397
+ async run({ args }) {
1398
+ const token2 = await ensureToken();
1399
+ const appName = requireAppName(void 0);
1400
+ const body = await collaboratorFetch(appName, token2, "/collaborators", {
1401
+ method: "POST",
1402
+ body: JSON.stringify({ email: String(args.email) })
1403
+ });
1404
+ console.log(`Added ${body.collaborator.emailDisplay} (${body.collaborator.userId}) as a secrets collaborator.`);
1405
+ }
1406
+ });
1407
+ var collaboratorsRemove = defineCommand3({
1408
+ meta: { name: "remove", description: "Remove a secrets collaborator" },
1409
+ args: {
1410
+ target: { type: "positional", required: true, description: "Collaborator email or user id" }
1411
+ },
1412
+ async run({ args }) {
1413
+ const token2 = await ensureToken();
1414
+ const appName = requireAppName(void 0);
1415
+ const target = String(args.target);
1416
+ let userId = target;
1417
+ if (target.includes("@")) {
1418
+ const body = await collaboratorFetch(appName, token2, "/collaborators");
1419
+ const normalized = target.trim().toLowerCase();
1420
+ const match = body.collaborators.find((item) => item.emailNormalized === normalized);
1421
+ if (!match) throw new Error(`No collaborator found for ${target}.`);
1422
+ userId = match.userId;
1423
+ }
1424
+ await collaboratorFetch(appName, token2, `/collaborators/${encodeURIComponent(userId)}`, { method: "DELETE" });
1425
+ console.log(`Removed secrets collaborator ${target}.`);
1426
+ }
1427
+ });
1428
+ var invitesList = defineCommand3({
1429
+ meta: { name: "list", description: "List app secret collaborator invites" },
1430
+ async run() {
1431
+ const token2 = await ensureToken();
1432
+ const appName = requireAppName(void 0);
1433
+ const body = await collaboratorFetch(appName, token2, "/invites");
1434
+ for (const invite of body.invites ?? []) {
1435
+ const status2 = invite.revokedAt ? "revoked" : invite.acceptedAt ? "accepted" : new Date(invite.expiresAt).getTime() <= Date.now() ? "expired" : "pending";
1436
+ console.log(`${invite.id} ${invite.emailDisplay} status=${status2} expires=${new Date(invite.expiresAt).toISOString()}`);
1437
+ }
1438
+ if (!body.invites?.length) console.log("No secrets collaborator invites.");
1439
+ }
1440
+ });
1441
+ var invitesRevoke = defineCommand3({
1442
+ meta: { name: "revoke", description: "Revoke a pending app secret collaborator invite" },
1443
+ args: {
1444
+ inviteId: { type: "positional", required: true, description: "Invite id" }
1445
+ },
1446
+ async run({ args }) {
1447
+ const token2 = await ensureToken();
1448
+ const appName = requireAppName(void 0);
1449
+ await collaboratorFetch(appName, token2, `/invites/${encodeURIComponent(String(args.inviteId))}`, { method: "DELETE" });
1450
+ console.log(`Revoked secrets collaborator invite ${String(args.inviteId)}.`);
1451
+ }
1452
+ });
1453
+ var invites = defineCommand3({
1454
+ meta: { name: "invites", description: "Manage app secret collaborator invites" },
1455
+ subCommands: { list: invitesList, revoke: invitesRevoke }
1456
+ });
1457
+ var collaborators = defineCommand3({
1458
+ meta: { name: "collaborators", description: "Manage app secret collaborators" },
1459
+ subCommands: { list: collaboratorsList, add: collaboratorsAdd, remove: collaboratorsRemove, invites }
1460
+ });
1461
+ var secrets_default = defineCommand3({
1462
+ meta: { name: "secrets", description: "Manage app-owned DeepSpace secrets" },
1463
+ subCommands: { set, unset, list, pull, push, diff, collaborators }
1464
+ });
1465
+
749
1466
  // src/cli/commands/dev.ts
750
1467
  var DEFAULT_PORT = 5173;
751
- var dev_default = defineCommand3({
1468
+ var dev_default = defineCommand4({
752
1469
  meta: {
753
1470
  name: "dev",
754
1471
  description: "Start local development server"
@@ -766,13 +1483,18 @@ var dev_default = defineCommand3({
766
1483
  },
767
1484
  env: {
768
1485
  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.",
1486
+ description: "wrangler.toml [env.<name>] block to run (e.g. --env staging). Legacy apps read .dev.vars.<name>; new apps use managed .env.",
770
1487
  required: false
771
1488
  },
772
1489
  port: {
773
1490
  type: "string",
774
1491
  description: `Port to bind (default ${DEFAULT_PORT}, or $DEEPSPACE_PORT)`,
775
1492
  required: false
1493
+ },
1494
+ "no-pull-env": {
1495
+ type: "boolean",
1496
+ description: "Skip pulling remote app secrets into the synced .env section on startup",
1497
+ default: false
776
1498
  }
777
1499
  },
778
1500
  async run({ args }) {
@@ -813,6 +1535,21 @@ var dev_default = defineCommand3({
813
1535
  if (wranglerEnv) console.log(`Wrangler env: ${wranglerEnv}`);
814
1536
  console.log(`Port: ${port}`);
815
1537
  await writeDevVars(appDir, env, payload.sub, token2, wranglerEnv);
1538
+ if (env === "dev" && !args["no-pull-env"] && !hasLegacyDevVars(appDir, wranglerEnv)) {
1539
+ const appName = resolveCurrentAppName(appDir, wranglerEnv);
1540
+ if (appName) {
1541
+ try {
1542
+ if (await remoteAvailable(appName, token2)) {
1543
+ const body = await remoteFetch(appName, token2, "/pull");
1544
+ replaceManagedSyncedEnv(appDir, body.secrets ?? {});
1545
+ console.log("Secrets: pulled remote app secrets into synced .env section.");
1546
+ }
1547
+ } catch (err) {
1548
+ const message = err instanceof Error ? err.message : String(err);
1549
+ console.warn(`Secrets: remote app secrets pull skipped (${message}).`);
1550
+ }
1551
+ }
1552
+ }
816
1553
  preflightWindowsWorkerd(appDir);
817
1554
  console.log("Starting dev server...\n");
818
1555
  const vite = spawn(
@@ -822,9 +1559,10 @@ var dev_default = defineCommand3({
822
1559
  cwd: appDir,
823
1560
  stdio: "inherit",
824
1561
  // 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.
1562
+ // applies `[env.<name>]` overrides. Legacy .dev.vars apps use
1563
+ // `.dev.vars.<name>` instead of `.dev.vars`; new apps use managed .env.
1564
+ // Set only when --env was passed so the default invocation behaves
1565
+ // exactly as before.
828
1566
  env: {
829
1567
  ...process.env,
830
1568
  DEEPSPACE_PORT: String(port),
@@ -847,15 +1585,23 @@ function resolvePort(arg) {
847
1585
  }
848
1586
  return n;
849
1587
  }
1588
+ function resolveCurrentAppName(appDir, wranglerEnv) {
1589
+ try {
1590
+ const resolved = resolveAppNameForEnv(readWranglerConfig(appDir), wranglerEnv);
1591
+ return resolved.ok ? resolved.name : null;
1592
+ } catch {
1593
+ return null;
1594
+ }
1595
+ }
850
1596
 
851
1597
  // src/cli/commands/kill.ts
852
- import { defineCommand as defineCommand4 } from "citty";
1598
+ import { defineCommand as defineCommand5 } from "citty";
853
1599
  import { spawnSync } from "child_process";
854
1600
  import { setTimeout as wait } from "timers/promises";
855
1601
  var DEFAULT_PORT2 = 5173;
856
1602
  var SIGTERM_GRACE_MS = 1500;
857
1603
  var IS_WIN = process.platform === "win32";
858
- var kill_default = defineCommand4({
1604
+ var kill_default = defineCommand5({
859
1605
  meta: {
860
1606
  name: "kill",
861
1607
  description: "Kill the local dev server and any orphaned workerd processes"
@@ -1010,7 +1756,7 @@ function isAlive(pid) {
1010
1756
  }
1011
1757
 
1012
1758
  // src/cli/commands/test.ts
1013
- import { defineCommand as defineCommand5 } from "citty";
1759
+ import { defineCommand as defineCommand6 } from "citty";
1014
1760
  import { resolve as resolve4 } from "path";
1015
1761
  import { sync as spawnSync3 } from "cross-spawn";
1016
1762
 
@@ -1040,7 +1786,7 @@ function ensurePlaywright(appDir) {
1040
1786
 
1041
1787
  // src/cli/commands/test.ts
1042
1788
  var DEFAULT_PORT3 = 5173;
1043
- var test_default = defineCommand5({
1789
+ var test_default = defineCommand6({
1044
1790
  meta: {
1045
1791
  name: "test",
1046
1792
  description: "Run tests for your DeepSpace app"
@@ -1155,10 +1901,10 @@ function resolvePort3(arg) {
1155
1901
  }
1156
1902
 
1157
1903
  // src/cli/commands/screenshot.ts
1158
- import { defineCommand as defineCommand6 } from "citty";
1904
+ import { defineCommand as defineCommand7 } from "citty";
1159
1905
  import { sync as spawnSync4 } from "cross-spawn";
1160
1906
  import { resolve as resolve5 } from "path";
1161
- var screenshot_default = defineCommand6({
1907
+ var screenshot_default = defineCommand7({
1162
1908
  meta: {
1163
1909
  name: "screenshot",
1164
1910
  description: "Take a Playwright screenshot, installing Chromium on demand"
@@ -1202,29 +1948,29 @@ var screenshot_default = defineCommand6({
1202
1948
  });
1203
1949
 
1204
1950
  // src/cli/commands/test-accounts.ts
1205
- import { defineCommand as defineCommand7 } from "citty";
1951
+ import { defineCommand as defineCommand8 } from "citty";
1206
1952
  import * as p3 from "@clack/prompts";
1207
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
1208
- import { join as join8 } from "path";
1953
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
1954
+ import { join as join9 } from "path";
1209
1955
  import { homedir as homedir4 } from "os";
1210
1956
  var SESSION_COOKIE2 = "__Secure-better-auth.session_token";
1211
1957
  var AUTH_URL4 = process.env.DEEPSPACE_AUTH_URL ?? ENVS.prod.auth;
1212
- var DIR3 = join8(homedir4(), ".deepspace");
1213
- var ACCOUNTS_PATH = join8(DIR3, "test-accounts.json");
1958
+ var DIR3 = join9(homedir4(), ".deepspace");
1959
+ var ACCOUNTS_PATH = join9(DIR3, "test-accounts.json");
1214
1960
  function loadAccounts() {
1215
- if (!existsSync8(ACCOUNTS_PATH)) return [];
1961
+ if (!existsSync9(ACCOUNTS_PATH)) return [];
1216
1962
  try {
1217
- return JSON.parse(readFileSync7(ACCOUNTS_PATH, "utf-8"));
1963
+ return JSON.parse(readFileSync8(ACCOUNTS_PATH, "utf-8"));
1218
1964
  } catch {
1219
1965
  return [];
1220
1966
  }
1221
1967
  }
1222
1968
  function saveAccounts(accounts) {
1223
1969
  mkdirSync4(DIR3, { recursive: true });
1224
- writeFileSync5(ACCOUNTS_PATH, JSON.stringify(accounts, null, 2), { mode: 384 });
1970
+ writeFileSync6(ACCOUNTS_PATH, JSON.stringify(accounts, null, 2), { mode: 384 });
1225
1971
  }
1226
1972
  function sessionCookie() {
1227
- const token2 = readFileSync7(SESSION_PATH2, "utf-8").trim();
1973
+ const token2 = readFileSync8(SESSION_PATH2, "utf-8").trim();
1228
1974
  return `${SESSION_COOKIE2}=${encodeURIComponent(token2)}`;
1229
1975
  }
1230
1976
  async function fetchRemoteAccounts() {
@@ -1247,7 +1993,7 @@ async function deleteRemote(id) {
1247
1993
  throw new Error(data.error ?? `DELETE returned ${res.status}`);
1248
1994
  }
1249
1995
  }
1250
- var create = defineCommand7({
1996
+ var create = defineCommand8({
1251
1997
  meta: {
1252
1998
  name: "create",
1253
1999
  description: "Create a test account"
@@ -1317,7 +2063,7 @@ var create = defineCommand7({
1317
2063
  Saved to ${ACCOUNTS_PATH}`);
1318
2064
  }
1319
2065
  });
1320
- var list = defineCommand7({
2066
+ var list2 = defineCommand8({
1321
2067
  meta: {
1322
2068
  name: "list",
1323
2069
  description: "List your test accounts"
@@ -1352,7 +2098,7 @@ var list = defineCommand7({
1352
2098
  }
1353
2099
  }
1354
2100
  });
1355
- var del = defineCommand7({
2101
+ var del = defineCommand8({
1356
2102
  meta: {
1357
2103
  name: "delete",
1358
2104
  description: "Delete a test account by --email or --id"
@@ -1409,7 +2155,7 @@ var del = defineCommand7({
1409
2155
  console.log(`Test account deleted${targetEmail ? `: ${targetEmail}` : `: ${targetId}`}`);
1410
2156
  }
1411
2157
  });
1412
- var clear = defineCommand7({
2158
+ var clear = defineCommand8({
1413
2159
  meta: {
1414
2160
  name: "clear",
1415
2161
  description: "Delete all your test accounts (or those matching --label)"
@@ -1475,25 +2221,25 @@ Failed:`);
1475
2221
  }
1476
2222
  }
1477
2223
  });
1478
- var test_accounts_default = defineCommand7({
2224
+ var test_accounts_default = defineCommand8({
1479
2225
  meta: {
1480
2226
  name: "test-accounts",
1481
2227
  description: "Manage test accounts for development"
1482
2228
  },
1483
2229
  subCommands: {
1484
2230
  create,
1485
- list,
2231
+ list: list2,
1486
2232
  delete: del,
1487
2233
  clear
1488
2234
  }
1489
2235
  });
1490
2236
 
1491
2237
  // src/cli/commands/deploy.ts
1492
- import { defineCommand as defineCommand8 } from "citty";
2238
+ import { defineCommand as defineCommand9 } from "citty";
1493
2239
  import { execSync as execSync2 } from "child_process";
1494
2240
  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";
2241
+ import { readFileSync as readFileSync9, readdirSync as readdirSync2, existsSync as existsSync10 } from "fs";
2242
+ import { join as join10, resolve as resolve6, dirname as dirname2 } from "path";
1497
2243
  import { pathToFileURL } from "url";
1498
2244
  import * as p4 from "@clack/prompts";
1499
2245
 
@@ -1522,6 +2268,7 @@ var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
1522
2268
  "AUTH_WORKER_URL",
1523
2269
  "APP_IDENTITY_TOKEN",
1524
2270
  "APP_OWNER_JWT",
2271
+ "APP_SECRETS_KEY",
1525
2272
  "USAGE_EVENTS"
1526
2273
  ]);
1527
2274
  function validateBindingManifest(manifest) {
@@ -1702,6 +2449,7 @@ function asObjectArray(v) {
1702
2449
  // src/cli/commands/deploy.ts
1703
2450
  var DEPLOY_URL = process.env.DEEPSPACE_DEPLOY_URL ?? ENVS.prod.deploy;
1704
2451
  var API_URL = process.env.DEEPSPACE_API_URL ?? ENVS.prod.api;
2452
+ var SECRET_NAME_RE2 = /^[A-Za-z_][A-Za-z0-9_]*$/;
1705
2453
  var CLOUDFLARE_DEPLOY_ERROR_HINT = "Deployment reached the DeepSpace deploy worker, but Cloudflare's deployment control-plane API failed. This is often caused by a Cloudflare Dashboard/API incident or Workers for Platforms entitlement outage. Already deployed apps should keep serving; wait for Cloudflare to recover and retry.";
1706
2454
  var RESERVED_RUN_WORKER_FIRST = /* @__PURE__ */ new Set([
1707
2455
  "/api/*",
@@ -1744,6 +2492,14 @@ function formatDeployWorkerError(status2, error) {
1744
2492
 
1745
2493
  Underlying error: ${detail}`;
1746
2494
  }
2495
+ function legacyDevVarsAppSecretsRoomWarning(userSecrets, doManifest) {
2496
+ if (!Object.keys(userSecrets).length) return null;
2497
+ const hasAppSecretsRoom = (doManifest ?? []).some(
2498
+ (binding) => binding.binding === "SECRETS_ROOMS" || binding.className === "AppSecretsRoom"
2499
+ );
2500
+ if (!hasAppSecretsRoom) return null;
2501
+ return "Legacy .dev.vars secrets are still being deployed as direct Worker bindings even though AppSecretsRoom is configured. Move these values with `deepspace secrets set` to manage them through the app secrets store.";
2502
+ }
1747
2503
  var MAX_GROUP_BYTES = 40 * 1024 * 1024;
1748
2504
  var GROUP_BYTES = (() => {
1749
2505
  const raw = Number(process.env.DEEPSPACE_DEPLOY_GROUP_BYTES);
@@ -1797,7 +2553,7 @@ async function abortStagedUpload(appName, uploadId, totalGroups, token2) {
1797
2553
  } catch {
1798
2554
  }
1799
2555
  }
1800
- var deploy_default = defineCommand8({
2556
+ var deploy_default = defineCommand9({
1801
2557
  meta: {
1802
2558
  name: "deploy",
1803
2559
  description: "Build and deploy your DeepSpace app"
@@ -1861,18 +2617,18 @@ var deploy_default = defineCommand8({
1861
2617
  process.exit(1);
1862
2618
  }
1863
2619
  s.stop("Built");
1864
- const deployConfigPath = join9(appDir, ".wrangler", "deploy", "config.json");
1865
- if (!existsSync9(deployConfigPath)) {
2620
+ const deployConfigPath = join10(appDir, ".wrangler", "deploy", "config.json");
2621
+ if (!existsSync10(deployConfigPath)) {
1866
2622
  p4.cancel("Build output config not found at .wrangler/deploy/config.json");
1867
2623
  process.exit(1);
1868
2624
  }
1869
- const deployConfig = JSON.parse(readFileSync8(deployConfigPath, "utf-8"));
2625
+ const deployConfig = JSON.parse(readFileSync9(deployConfigPath, "utf-8"));
1870
2626
  const outputWranglerPath = resolve6(dirname2(deployConfigPath), deployConfig.configPath);
1871
- if (!existsSync9(outputWranglerPath)) {
2627
+ if (!existsSync10(outputWranglerPath)) {
1872
2628
  p4.cancel(`Output wrangler.json not found at ${outputWranglerPath}`);
1873
2629
  process.exit(1);
1874
2630
  }
1875
- const outputConfig = JSON.parse(readFileSync8(outputWranglerPath, "utf-8"));
2631
+ const outputConfig = JSON.parse(readFileSync9(outputWranglerPath, "utf-8"));
1876
2632
  if (outputConfig.name && outputConfig.name !== appName) {
1877
2633
  const envHint = envName ? ` (--env ${envName})` : "";
1878
2634
  p4.cancel(
@@ -1881,20 +2637,20 @@ var deploy_default = defineCommand8({
1881
2637
  process.exit(1);
1882
2638
  }
1883
2639
  const workerDir = dirname2(outputWranglerPath);
1884
- const workerBundlePath = join9(workerDir, outputConfig.main);
2640
+ const workerBundlePath = join10(workerDir, outputConfig.main);
1885
2641
  const clientDir = outputConfig.assets?.directory ? resolve6(workerDir, outputConfig.assets.directory) : null;
1886
- if (!existsSync9(workerBundlePath)) {
2642
+ if (!existsSync10(workerBundlePath)) {
1887
2643
  p4.cancel(`Worker bundle not found at ${workerBundlePath}`);
1888
2644
  process.exit(1);
1889
2645
  }
1890
- if (!clientDir || !existsSync9(clientDir)) {
2646
+ if (!clientDir || !existsSync10(clientDir)) {
1891
2647
  p4.cancel(`Client assets not found at ${clientDir}`);
1892
2648
  process.exit(1);
1893
2649
  }
1894
2650
  s.start("Collecting assets...");
1895
2651
  const assets = collectAssets(clientDir);
1896
2652
  s.stop(`Collected ${assets.length} assets`);
1897
- const workerJs = readFileSync8(workerBundlePath, "utf-8");
2653
+ const workerJs = readFileSync9(workerBundlePath, "utf-8");
1898
2654
  const doBindings = outputConfig.durable_objects?.bindings;
1899
2655
  const sqliteClasses = new Set(
1900
2656
  outputConfig.migrations?.flatMap((m) => m.new_sqlite_classes ?? []) ?? []
@@ -1921,40 +2677,20 @@ var deploy_default = defineCommand8({
1921
2677
  `Custom bindings: ${customBindings.map((b) => `${b.name} (${b.type})`).join(", ")}`
1922
2678
  );
1923
2679
  }
1924
- const devVarsPath = devVarsPathFor(appDir, envName);
1925
2680
  let userSecrets = {};
1926
- if (existsSync9(devVarsPath)) {
1927
- const customSection = extractCustomDevVars(readFileSync8(devVarsPath, "utf-8"));
1928
- if (customSection) {
1929
- try {
1930
- userSecrets = parseDevVars(customSection);
1931
- } catch (err) {
1932
- p4.cancel(`.dev.vars: ${err.message}`);
1933
- process.exit(1);
1934
- }
1935
- }
1936
- }
1937
- const userSecretNames = Object.keys(userSecrets);
1938
- const declaredBindingNames = /* @__PURE__ */ new Set([
1939
- ...customBindings.map((b) => b.name),
1940
- ...(doManifest ?? []).map((d) => d.binding)
1941
- ]);
1942
- for (const name of userSecretNames) {
1943
- if (RESERVED_BINDING_NAMES.has(name)) {
1944
- p4.cancel(
1945
- `.dev.vars: "${name}" is a reserved binding name and cannot be used as an app secret.`
1946
- );
1947
- process.exit(1);
1948
- }
1949
- if (declaredBindingNames.has(name)) {
1950
- p4.cancel(
1951
- `.dev.vars: "${name}" collides with a binding declared in wrangler.toml \u2014 rename one or the other.`
1952
- );
2681
+ if (hasLegacyDevVars(appDir, envName)) {
2682
+ try {
2683
+ userSecrets = readLegacyUserSecrets(appDir, envName, customBindings, doManifest);
2684
+ } catch (err) {
2685
+ const message = err instanceof Error ? err.message : String(err);
2686
+ p4.cancel(message);
1953
2687
  process.exit(1);
1954
2688
  }
1955
2689
  }
1956
- if (userSecretNames.length) {
1957
- p4.log.info(`App secrets (from .dev.vars): ${userSecretNames.join(", ")}`);
2690
+ if (Object.keys(userSecrets).length) {
2691
+ p4.log.info(`Legacy .dev.vars secrets: ${Object.keys(userSecrets).sort().join(", ")}`);
2692
+ const warning = legacyDevVarsAppSecretsRoomWarning(userSecrets, doManifest);
2693
+ if (warning) p4.log.warn(warning);
1958
2694
  }
1959
2695
  s.start(`Deploying to ${appName}.app.space...`);
1960
2696
  const extraRoutes = extractRunWorkerFirst(wranglerConfig);
@@ -2018,12 +2754,12 @@ var deploy_default = defineCommand8({
2018
2754
  if (customBindings.length) {
2019
2755
  form.append("bindingManifest", JSON.stringify(customBindings));
2020
2756
  }
2021
- if (userSecretNames.length) {
2022
- form.append("userSecrets", JSON.stringify(userSecrets));
2023
- }
2024
2757
  if (extraRoutes.length) {
2025
2758
  form.append("extraRunWorkerFirst", JSON.stringify(extraRoutes));
2026
2759
  }
2760
+ if (Object.keys(userSecrets).length) {
2761
+ form.append("userSecrets", JSON.stringify(userSecrets));
2762
+ }
2027
2763
  return form;
2028
2764
  };
2029
2765
  let res;
@@ -2060,6 +2796,7 @@ var deploy_default = defineCommand8({
2060
2796
  s.stop("Deployed (edge propagation still in progress after 90s)");
2061
2797
  p4.log.warn("First requests may briefly hit the assets transitional page; retry in a minute.");
2062
2798
  p4.log.success(`Live at: ${body.url}`);
2799
+ p4.log.info("Secrets: local dev-only values are not pushed automatically. Run `deepspace secrets push` when you want to sync them.");
2063
2800
  await syncSubscriptionPlans(appDir, appName, token2);
2064
2801
  await syncOneTimeProducts(appDir, appName, token2);
2065
2802
  p4.outro("Done");
@@ -2068,21 +2805,43 @@ var deploy_default = defineCommand8({
2068
2805
  }
2069
2806
  s.stop("Deployed!");
2070
2807
  p4.log.success(`Live at: ${body.url}`);
2808
+ p4.log.info("Secrets: local dev-only values are not pushed automatically. Run `deepspace secrets push` when you want to sync them.");
2071
2809
  await syncSubscriptionPlans(appDir, appName, token2);
2072
2810
  await syncOneTimeProducts(appDir, appName, token2);
2073
2811
  p4.outro("Done");
2074
2812
  }
2075
2813
  });
2814
+ function readLegacyUserSecrets(appDir, wranglerEnv, customBindings, doManifest) {
2815
+ const path = join10(appDir, wranglerEnv ? `.dev.vars.${wranglerEnv}` : ".dev.vars");
2816
+ if (!existsSync10(path)) return {};
2817
+ const vars = parseDevVars(extractCustomDevVars(readFileSync9(path, "utf-8")));
2818
+ const reserved = /* @__PURE__ */ new Set([
2819
+ ...SDK_MANAGED_KEYS,
2820
+ ...customBindings.map((b) => b.name),
2821
+ ...(doManifest ?? []).flatMap((b) => [b.binding, b.className])
2822
+ ]);
2823
+ const out = {};
2824
+ for (const [name, value] of Object.entries(vars)) {
2825
+ if (!SECRET_NAME_RE2.test(name)) {
2826
+ throw new Error(`.dev.vars: invalid secret name "${name}".`);
2827
+ }
2828
+ if (reserved.has(name)) {
2829
+ throw new Error(`.dev.vars: "${name}" is reserved and cannot be deployed as a user secret.`);
2830
+ }
2831
+ out[name] = value;
2832
+ }
2833
+ return out;
2834
+ }
2076
2835
  async function syncSubscriptionPlans(appDir, appName, token2) {
2077
- const subsPath = join9(appDir, "src", "subscriptions.ts");
2078
- if (!existsSync9(subsPath)) return;
2836
+ const subsPath = join10(appDir, "src", "subscriptions.ts");
2837
+ if (!existsSync10(subsPath)) return;
2079
2838
  const s = createSpinner();
2080
2839
  s.start("Syncing subscription plans...");
2081
2840
  let plans;
2082
2841
  try {
2083
2842
  const esbuild = await import("esbuild");
2084
- const outDir = join9(appDir, ".wrangler", "deploy");
2085
- const outFile = join9(outDir, "subscriptions.bundle.mjs");
2843
+ const outDir = join10(appDir, ".wrangler", "deploy");
2844
+ const outFile = join10(outDir, "subscriptions.bundle.mjs");
2086
2845
  await esbuild.build({
2087
2846
  entryPoints: [subsPath],
2088
2847
  bundle: true,
@@ -2223,15 +2982,15 @@ current billing period? (Skip to leave them grandfathered.)`
2223
2982
  }
2224
2983
  }
2225
2984
  async function syncOneTimeProducts(appDir, appName, token2) {
2226
- const productsPath = join9(appDir, "src", "products.ts");
2227
- if (!existsSync9(productsPath)) return;
2985
+ const productsPath = join10(appDir, "src", "products.ts");
2986
+ if (!existsSync10(productsPath)) return;
2228
2987
  const s = createSpinner();
2229
2988
  s.start("Syncing one-time products...");
2230
2989
  let products;
2231
2990
  try {
2232
2991
  const esbuild = await import("esbuild");
2233
- const outDir = join9(appDir, ".wrangler", "deploy");
2234
- const outFile = join9(outDir, "products.bundle.mjs");
2992
+ const outDir = join10(appDir, ".wrangler", "deploy");
2993
+ const outFile = join10(outDir, "products.bundle.mjs");
2235
2994
  await esbuild.build({
2236
2995
  entryPoints: [productsPath],
2237
2996
  bundle: true,
@@ -2305,14 +3064,14 @@ function collectAssets(dir) {
2305
3064
  function walk(d, prefix) {
2306
3065
  for (const entry of readdirSync2(d, { withFileTypes: true })) {
2307
3066
  if (entry.name === ".assetsignore") continue;
2308
- const full = join9(d, entry.name);
3067
+ const full = join10(d, entry.name);
2309
3068
  const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
2310
3069
  if (entry.isDirectory()) {
2311
3070
  walk(full, rel);
2312
3071
  } else {
2313
3072
  assets.push({
2314
3073
  path: "/" + rel,
2315
- contentBase64: readFileSync8(full).toString("base64")
3074
+ contentBase64: readFileSync9(full).toString("base64")
2316
3075
  });
2317
3076
  }
2318
3077
  }
@@ -2322,10 +3081,10 @@ function collectAssets(dir) {
2322
3081
  }
2323
3082
 
2324
3083
  // src/cli/commands/undeploy.ts
2325
- import { defineCommand as defineCommand9 } from "citty";
3084
+ import { defineCommand as defineCommand10 } from "citty";
2326
3085
  import * as p5 from "@clack/prompts";
2327
3086
  var DEPLOY_URL2 = process.env.DEEPSPACE_DEPLOY_URL ?? ENVS.prod.deploy;
2328
- var undeploy_default = defineCommand9({
3087
+ var undeploy_default = defineCommand10({
2329
3088
  meta: {
2330
3089
  name: "undeploy",
2331
3090
  description: "Remove a deployed DeepSpace app"
@@ -2386,8 +3145,8 @@ var undeploy_default = defineCommand9({
2386
3145
  });
2387
3146
 
2388
3147
  // src/cli/commands/whoami.ts
2389
- import { defineCommand as defineCommand10 } from "citty";
2390
- var whoami_default = defineCommand10({
3148
+ import { defineCommand as defineCommand11 } from "citty";
3149
+ var whoami_default = defineCommand11({
2391
3150
  meta: {
2392
3151
  name: "whoami",
2393
3152
  description: "Show the currently logged-in user"
@@ -2442,18 +3201,18 @@ var whoami_default = defineCommand10({
2442
3201
  });
2443
3202
 
2444
3203
  // src/cli/commands/add.ts
2445
- import { defineCommand as defineCommand11 } from "citty";
2446
- import { existsSync as existsSync10 } from "fs";
2447
- import { resolve as resolve7, join as join10 } from "path";
3204
+ import { defineCommand as defineCommand12 } from "citty";
3205
+ import { existsSync as existsSync11 } from "fs";
3206
+ import { resolve as resolve7, join as join11 } from "path";
2448
3207
  import { execSync as execSync3 } from "child_process";
2449
3208
  function findScript(from) {
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;
3209
+ const inNodeModules = join11(from, "node_modules", "deepspace", "scripts", "add-feature.cjs");
3210
+ if (existsSync11(inNodeModules)) return inNodeModules;
3211
+ const inDotDeepspace = join11(from, ".deepspace", "scripts", "add-feature.cjs");
3212
+ if (existsSync11(inDotDeepspace)) return inDotDeepspace;
2454
3213
  return null;
2455
3214
  }
2456
- var add_default = defineCommand11({
3215
+ var add_default = defineCommand12({
2457
3216
  meta: {
2458
3217
  name: "add",
2459
3218
  description: "Add a feature to your DeepSpace app"
@@ -2488,8 +3247,8 @@ var add_default = defineCommand11({
2488
3247
  if (!script) {
2489
3248
  console.error("Could not find the deepspace feature installer.");
2490
3249
  console.error("Looked in:");
2491
- console.error(` ${join10(appDir, "node_modules", "deepspace", "scripts", "add-feature.cjs")}`);
2492
- console.error(` ${join10(appDir, ".deepspace", "scripts", "add-feature.cjs")}`);
3250
+ console.error(` ${join11(appDir, "node_modules", "deepspace", "scripts", "add-feature.cjs")}`);
3251
+ console.error(` ${join11(appDir, ".deepspace", "scripts", "add-feature.cjs")}`);
2493
3252
  console.error("Are you in a DeepSpace app directory? Try `npm install` first.");
2494
3253
  process.exit(1);
2495
3254
  }
@@ -2511,10 +3270,10 @@ var add_default = defineCommand11({
2511
3270
  });
2512
3271
 
2513
3272
  // src/cli/commands/skills.ts
2514
- import { defineCommand as defineCommand12 } from "citty";
3273
+ import { defineCommand as defineCommand13 } from "citty";
2515
3274
  import spawn2 from "cross-spawn";
2516
3275
  import { mkdirSync as mkdirSync5 } from "fs";
2517
- import { join as join11 } from "path";
3276
+ import { join as join12 } from "path";
2518
3277
  import { homedir as homedir5 } from "os";
2519
3278
  var SKILL_REPO = "deepdotspace/deepspace-skill";
2520
3279
  function runSkillsAdd(extraArgs) {
@@ -2533,9 +3292,9 @@ function runSkillsAdd(extraArgs) {
2533
3292
  });
2534
3293
  }
2535
3294
  function precreateClaudeDir(baseDir) {
2536
- mkdirSync5(join11(baseDir, ".claude"), { recursive: true });
3295
+ mkdirSync5(join12(baseDir, ".claude"), { recursive: true });
2537
3296
  }
2538
- var add = defineCommand12({
3297
+ var add = defineCommand13({
2539
3298
  meta: {
2540
3299
  name: "add",
2541
3300
  description: "Install the DeepSpace skill for your coding agent"
@@ -2577,7 +3336,7 @@ var add = defineCommand12({
2577
3336
  if (code !== 0) process.exit(code);
2578
3337
  }
2579
3338
  });
2580
- var skills_default = defineCommand12({
3339
+ var skills_default = defineCommand13({
2581
3340
  meta: {
2582
3341
  name: "skills",
2583
3342
  description: "Manage the DeepSpace agent skill (Claude Code, Codex, OpenCode, Cursor, ...)"
@@ -2588,7 +3347,7 @@ var skills_default = defineCommand12({
2588
3347
  });
2589
3348
 
2590
3349
  // src/cli/commands/domain.ts
2591
- import { defineCommand as defineCommand13 } from "citty";
3350
+ import { defineCommand as defineCommand14 } from "citty";
2592
3351
  import { execSync as execSync4 } from "child_process";
2593
3352
  var API_URL2 = process.env.DEEPSPACE_API_URL ?? ENVS.prod.api;
2594
3353
  async function api(token2, path, init) {
@@ -2600,16 +3359,16 @@ async function api(token2, path, init) {
2600
3359
  ...init?.headers ?? {}
2601
3360
  }
2602
3361
  });
2603
- const text = await res.text();
3362
+ const text2 = await res.text();
2604
3363
  if (!res.ok) {
2605
- let msg = text;
3364
+ let msg = text2;
2606
3365
  try {
2607
- msg = JSON.parse(text).error ?? text;
3366
+ msg = JSON.parse(text2).error ?? text2;
2608
3367
  } catch {
2609
3368
  }
2610
3369
  throw new Error(`API ${path} (${res.status}): ${msg}`);
2611
3370
  }
2612
- return JSON.parse(text);
3371
+ return JSON.parse(text2);
2613
3372
  }
2614
3373
  function scrubInternal(value) {
2615
3374
  if (Array.isArray(value)) return value.map(scrubInternal);
@@ -2644,7 +3403,7 @@ async function confirm3(message) {
2644
3403
  });
2645
3404
  });
2646
3405
  }
2647
- var search = defineCommand13({
3406
+ var search = defineCommand14({
2648
3407
  meta: { name: "search", description: "Search for available domains" },
2649
3408
  args: {
2650
3409
  query: { type: "positional", description: "Domain or keyword", required: true },
@@ -2674,7 +3433,7 @@ var search = defineCommand13({
2674
3433
  }
2675
3434
  }
2676
3435
  });
2677
- var buy = defineCommand13({
3436
+ var buy = defineCommand14({
2678
3437
  meta: { name: "buy", description: "Buy a domain and attach it to an app" },
2679
3438
  args: {
2680
3439
  domain: { type: "positional", description: "Domain to buy", required: true },
@@ -2763,7 +3522,7 @@ var buy = defineCommand13({
2763
3522
  process.exit(1);
2764
3523
  }
2765
3524
  });
2766
- var list2 = defineCommand13({
3525
+ var list3 = defineCommand14({
2767
3526
  meta: { name: "list", description: "List your domains" },
2768
3527
  args: {
2769
3528
  json: { type: "boolean", description: "Emit JSON" }
@@ -2787,7 +3546,7 @@ var list2 = defineCommand13({
2787
3546
  }
2788
3547
  }
2789
3548
  });
2790
- var status = defineCommand13({
3549
+ var status = defineCommand14({
2791
3550
  meta: { name: "status", description: "Show details for one domain" },
2792
3551
  args: {
2793
3552
  domain: { type: "positional", description: "Domain", required: true },
@@ -2815,7 +3574,7 @@ var status = defineCommand13({
2815
3574
  console.log(`Charged: ${fmtCents(found.chargedCents)}/yr`);
2816
3575
  }
2817
3576
  });
2818
- var attach = defineCommand13({
3577
+ var attach = defineCommand14({
2819
3578
  meta: { name: "attach", description: "Re-point a domain at a different app" },
2820
3579
  args: {
2821
3580
  domain: { type: "positional", description: "Domain to re-point", required: true },
@@ -2846,7 +3605,7 @@ var attach = defineCommand13({
2846
3605
  console.log(`\u2713 ${args.domain} now points at ${targetApp}`);
2847
3606
  }
2848
3607
  });
2849
- var detach = defineCommand13({
3608
+ var detach = defineCommand14({
2850
3609
  meta: {
2851
3610
  name: "detach",
2852
3611
  description: "Stop routing the domain (keeps the registration; auto-renew unchanged)"
@@ -2876,7 +3635,7 @@ var detach = defineCommand13({
2876
3635
  console.log(`\u2713 ${args.domain} detached. Use \`deepspace domain attach\` to re-route, or \`renew --auto off\` to stop auto-renewal.`);
2877
3636
  }
2878
3637
  });
2879
- var renew = defineCommand13({
3638
+ var renew = defineCommand14({
2880
3639
  meta: { name: "renew", description: "Toggle auto-renewal at the registrar" },
2881
3640
  args: {
2882
3641
  domain: { type: "positional", description: "Domain", required: true },
@@ -2910,16 +3669,16 @@ function openBrowser2(url) {
2910
3669
  } catch {
2911
3670
  }
2912
3671
  }
2913
- var domain_default = defineCommand13({
3672
+ var domain_default = defineCommand14({
2914
3673
  meta: { name: "domain", description: "Buy and manage custom domains" },
2915
- subCommands: { search, buy, list: list2, status, attach, detach, renew }
3674
+ subCommands: { search, buy, list: list3, status, attach, detach, renew }
2916
3675
  });
2917
3676
 
2918
3677
  // src/cli/commands/integrations.ts
2919
- import { defineCommand as defineCommand14 } from "citty";
3678
+ import { defineCommand as defineCommand15 } from "citty";
2920
3679
 
2921
3680
  // src/cli/commands/_invoke-impl.ts
2922
- import { readFileSync as readFileSync9 } from "fs";
3681
+ import { readFileSync as readFileSync10 } from "fs";
2923
3682
  var API_URL3 = process.env.DEEPSPACE_API_URL ?? ENVS.prod.api;
2924
3683
  var DEFAULT_TIMEOUT_MS = 12e4;
2925
3684
  function parseTarget(target) {
@@ -2940,7 +3699,7 @@ function resolveBody(opts) {
2940
3699
  if (opts.body != null) {
2941
3700
  raw = opts.body;
2942
3701
  } else if (opts.bodyFile != null) {
2943
- raw = opts.bodyFile === "-" ? readFileSync9(0, "utf-8") : readFileSync9(opts.bodyFile, "utf-8");
3702
+ raw = opts.bodyFile === "-" ? readFileSync10(0, "utf-8") : readFileSync10(opts.bodyFile, "utf-8");
2944
3703
  } else {
2945
3704
  return "{}";
2946
3705
  }
@@ -3091,7 +3850,7 @@ async function runInvoke(args) {
3091
3850
  }
3092
3851
 
3093
3852
  // src/cli/commands/integrations.ts
3094
- var list3 = defineCommand14({
3853
+ var list4 = defineCommand15({
3095
3854
  meta: {
3096
3855
  name: "list",
3097
3856
  description: "List all available integrations"
@@ -3112,7 +3871,7 @@ var list3 = defineCommand14({
3112
3871
  }
3113
3872
  }
3114
3873
  });
3115
- var info = defineCommand14({
3874
+ var info = defineCommand15({
3116
3875
  meta: {
3117
3876
  name: "info",
3118
3877
  description: "Print schema + example body for an integration endpoint"
@@ -3138,7 +3897,7 @@ var info = defineCommand14({
3138
3897
  }
3139
3898
  }
3140
3899
  });
3141
- var invoke = defineCommand14({
3900
+ var invoke = defineCommand15({
3142
3901
  meta: {
3143
3902
  name: "invoke",
3144
3903
  description: "Invoke a platform integration as the logged-in user"
@@ -3191,21 +3950,21 @@ var invoke = defineCommand14({
3191
3950
  }
3192
3951
  }
3193
3952
  });
3194
- var integrations_default = defineCommand14({
3953
+ var integrations_default = defineCommand15({
3195
3954
  meta: {
3196
3955
  name: "integrations",
3197
3956
  description: "List, inspect, and invoke platform integrations"
3198
3957
  },
3199
3958
  subCommands: {
3200
- list: list3,
3959
+ list: list4,
3201
3960
  info,
3202
3961
  invoke
3203
3962
  }
3204
3963
  });
3205
3964
 
3206
3965
  // src/cli/commands/invoke.ts
3207
- import { defineCommand as defineCommand15 } from "citty";
3208
- var invoke_default = defineCommand15({
3966
+ import { defineCommand as defineCommand16 } from "citty";
3967
+ var invoke_default = defineCommand16({
3209
3968
  meta: {
3210
3969
  name: "invoke",
3211
3970
  description: "Invoke a platform integration as the logged-in user"
@@ -3278,7 +4037,7 @@ var invoke_default = defineCommand15({
3278
4037
  });
3279
4038
 
3280
4039
  // src/cli/commands/managed-repos.ts
3281
- import { defineCommand as defineCommand16 } from "citty";
4040
+ import { defineCommand as defineCommand17 } from "citty";
3282
4041
  var API_URL4 = process.env.DEEPSPACE_API_URL ?? ENVS.prod.api;
3283
4042
  var SIMPLE_APP_ID = /^[a-z0-9][a-z0-9_.:-]{0,119}$/i;
3284
4043
  var SIMPLE_APP_NAME = /^[a-z0-9][a-z0-9_-]{0,119}$/i;
@@ -3291,16 +4050,16 @@ async function api2(token2, path, init) {
3291
4050
  ...init?.headers ?? {}
3292
4051
  }
3293
4052
  });
3294
- const text = await res.text();
4053
+ const text2 = await res.text();
3295
4054
  if (!res.ok) {
3296
- let msg = text;
4055
+ let msg = text2;
3297
4056
  try {
3298
- msg = JSON.parse(text).error ?? text;
4057
+ msg = JSON.parse(text2).error ?? text2;
3299
4058
  } catch {
3300
4059
  }
3301
4060
  throw new Error(`API ${path} (${res.status}): ${msg}`);
3302
4061
  }
3303
- return JSON.parse(text);
4062
+ return JSON.parse(text2);
3304
4063
  }
3305
4064
  function defaultAppId(appName) {
3306
4065
  return `cli-${slugId(appName)}`;
@@ -3337,7 +4096,7 @@ async function confirm4(message) {
3337
4096
  });
3338
4097
  });
3339
4098
  }
3340
- var list4 = defineCommand16({
4099
+ var list5 = defineCommand17({
3341
4100
  meta: {
3342
4101
  name: "list",
3343
4102
  description: "List your DeepSpace-managed repos and quota"
@@ -3367,7 +4126,7 @@ var list4 = defineCommand16({
3367
4126
  }
3368
4127
  }
3369
4128
  });
3370
- var create2 = defineCommand16({
4129
+ var create2 = defineCommand17({
3371
4130
  meta: {
3372
4131
  name: "create",
3373
4132
  description: "Create a private DeepSpace-managed GitHub repo"
@@ -3419,7 +4178,7 @@ var create2 = defineCommand16({
3419
4178
  printRepo(result.repo);
3420
4179
  }
3421
4180
  });
3422
- var token = defineCommand16({
4181
+ var token = defineCommand17({
3423
4182
  meta: {
3424
4183
  name: "token",
3425
4184
  description: "Mint a short-lived GitHub token for a DeepSpace-managed repo"
@@ -3454,7 +4213,7 @@ var token = defineCommand16({
3454
4213
  console.log(`Clone: git clone https://x-access-token:${encodeURIComponent(result.token.token)}@github.com/${result.repo.fullName}.git`);
3455
4214
  }
3456
4215
  });
3457
- var remove = defineCommand16({
4216
+ var remove = defineCommand17({
3458
4217
  meta: {
3459
4218
  name: "delete",
3460
4219
  description: "Delete a DeepSpace-managed GitHub repo"
@@ -3507,13 +4266,13 @@ var remove = defineCommand16({
3507
4266
  );
3508
4267
  }
3509
4268
  });
3510
- var managed_repos_default = defineCommand16({
4269
+ var managed_repos_default = defineCommand17({
3511
4270
  meta: {
3512
4271
  name: "managed-repos",
3513
4272
  description: "Create and manage DeepSpace-owned GitHub repos"
3514
4273
  },
3515
4274
  subCommands: {
3516
- list: list4,
4275
+ list: list5,
3517
4276
  create: create2,
3518
4277
  token,
3519
4278
  delete: remove
@@ -3521,13 +4280,13 @@ var managed_repos_default = defineCommand16({
3521
4280
  });
3522
4281
 
3523
4282
  // src/cli/commands/library.ts
3524
- import { defineCommand as defineCommand17 } from "citty";
3525
- import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
3526
- import { join as join12, resolve as resolve8 } from "path";
4283
+ import { defineCommand as defineCommand18 } from "citty";
4284
+ import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
4285
+ import { join as join13, resolve as resolve8 } from "path";
3527
4286
  import { parse as parseToml3 } from "smol-toml";
3528
4287
  import * as p6 from "@clack/prompts";
3529
4288
  var DEFAULT_LIBRARY_APP = "deepdotspace-site";
3530
- var publish = defineCommand17({
4289
+ var publish = defineCommand18({
3531
4290
  meta: {
3532
4291
  name: "publish",
3533
4292
  description: "Publish the current app to the DeepSpace library"
@@ -3586,7 +4345,7 @@ var publish = defineCommand17({
3586
4345
  },
3587
4346
  async run({ args }) {
3588
4347
  const json = !!args.json;
3589
- const intro5 = (s) => {
4348
+ const intro6 = (s) => {
3590
4349
  if (!json) p6.intro(s);
3591
4350
  };
3592
4351
  const info2 = (s) => {
@@ -3597,15 +4356,15 @@ var publish = defineCommand17({
3597
4356
  else p6.cancel(msg);
3598
4357
  process.exit(1);
3599
4358
  };
3600
- intro5("Publishing to the DeepSpace library");
4359
+ intro6("Publishing to the DeepSpace library");
3601
4360
  let appName = typeof args.app === "string" && args.app.trim() ? args.app.trim() : void 0;
3602
4361
  if (!appName) {
3603
4362
  const appDir = resolve8(typeof args.dir === "string" && args.dir ? args.dir : ".");
3604
- const wranglerPath = join12(appDir, "wrangler.toml");
3605
- if (!existsSync11(wranglerPath)) {
4363
+ const wranglerPath = join13(appDir, "wrangler.toml");
4364
+ if (!existsSync12(wranglerPath)) {
3606
4365
  return die("No --app provided and no wrangler.toml found. Either pass --app <name> or run from your app's directory.");
3607
4366
  }
3608
- const cfg = parseToml3(readFileSync10(wranglerPath, "utf-8"));
4367
+ const cfg = parseToml3(readFileSync11(wranglerPath, "utf-8"));
3609
4368
  const nameRes = resolveAppName(cfg.name);
3610
4369
  if (!nameRes.ok) return die(`wrangler.toml: ${nameRes.reason}`);
3611
4370
  appName = nameRes.name;
@@ -3666,7 +4425,7 @@ var publish = defineCommand17({
3666
4425
  p6.outro("Done");
3667
4426
  }
3668
4427
  });
3669
- var unpublish = defineCommand17({
4428
+ var unpublish = defineCommand18({
3670
4429
  meta: {
3671
4430
  name: "unpublish",
3672
4431
  description: "Remove a library entry (owner or admin only)"
@@ -3725,7 +4484,7 @@ var unpublish = defineCommand17({
3725
4484
  p6.outro("Done");
3726
4485
  }
3727
4486
  });
3728
- var library_default = defineCommand17({
4487
+ var library_default = defineCommand18({
3729
4488
  meta: {
3730
4489
  name: "library",
3731
4490
  description: "Manage your DeepSpace library entries"
@@ -3737,12 +4496,12 @@ var library_default = defineCommand17({
3737
4496
  });
3738
4497
 
3739
4498
  // src/cli/commands/admin.ts
3740
- import { defineCommand as defineCommand18 } from "citty";
3741
- import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
4499
+ import { defineCommand as defineCommand19 } from "citty";
4500
+ import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
3742
4501
  import { homedir as homedir6 } from "os";
3743
- import { join as join13 } from "path";
4502
+ import { join as join14 } from "path";
3744
4503
  var AUTH_URL5 = process.env.DEEPSPACE_AUTH_URL ?? ENVS.prod.auth;
3745
- var impersonate = defineCommand18({
4504
+ var impersonate = defineCommand19({
3746
4505
  meta: {
3747
4506
  name: "impersonate",
3748
4507
  description: "Store a short-lived CLI token for another user"
@@ -3790,15 +4549,15 @@ var impersonate = defineCommand18({
3790
4549
  console.error(`Failed: ${data.error ?? `HTTP ${res.status}`}`);
3791
4550
  process.exit(1);
3792
4551
  }
3793
- const dir = join13(homedir6(), ".deepspace");
4552
+ const dir = join14(homedir6(), ".deepspace");
3794
4553
  mkdirSync6(dir, { recursive: true });
3795
- writeFileSync6(TOKEN_PATH2, data.token, { mode: 384 });
4554
+ writeFileSync7(TOKEN_PATH2, data.token, { mode: 384 });
3796
4555
  console.log(`Impersonating ${data.user.name ?? data.user.email ?? data.user.id}`);
3797
4556
  console.log(`Email: ${data.user.email ?? "(none)"}`);
3798
4557
  console.log(`UserID: ${data.user.id}`);
3799
4558
  }
3800
4559
  });
3801
- var admin_default = defineCommand18({
4560
+ var admin_default = defineCommand19({
3802
4561
  meta: {
3803
4562
  name: "admin",
3804
4563
  description: "Maintainer-only admin commands"
@@ -3808,13 +4567,276 @@ var admin_default = defineCommand18({
3808
4567
  }
3809
4568
  });
3810
4569
 
4570
+ // src/cli/commands/feedback.ts
4571
+ import { defineCommand as defineCommand20 } from "citty";
4572
+ import * as p7 from "@clack/prompts";
4573
+ import { existsSync as existsSync13, readFileSync as readFileSync12 } from "fs";
4574
+ import { dirname as dirname3, join as join15 } from "path";
4575
+ import { fileURLToPath } from "url";
4576
+ import { release } from "os";
4577
+ var API_URL5 = process.env.DEEPSPACE_API_URL ?? ENVS.prod.api;
4578
+ var TYPES = ["bug", "feature", "other"];
4579
+ var TYPE_LABELS = {
4580
+ bug: "Bug report",
4581
+ feature: "Feature request",
4582
+ other: "Other feedback"
4583
+ };
4584
+ var MAX_TITLE_LEN = 200;
4585
+ var MAX_BODY_LEN = 1e4;
4586
+ var MAX_CONTEXT_LEN = 200;
4587
+ async function api3(token2, path, init) {
4588
+ const res = await fetch(`${API_URL5}${path}`, {
4589
+ ...init,
4590
+ headers: {
4591
+ Authorization: `Bearer ${token2}`,
4592
+ "Content-Type": "application/json",
4593
+ ...init?.headers ?? {}
4594
+ }
4595
+ });
4596
+ const text2 = await res.text();
4597
+ if (!res.ok) {
4598
+ let msg = text2;
4599
+ try {
4600
+ msg = JSON.parse(text2).error ?? text2;
4601
+ } catch {
4602
+ }
4603
+ throw new Error(`API ${path} (${res.status}): ${msg}`);
4604
+ }
4605
+ return JSON.parse(text2);
4606
+ }
4607
+ function normalizeType(raw) {
4608
+ const value = (raw ?? "bug").toLowerCase();
4609
+ if (TYPES.includes(value)) return value;
4610
+ throw new Error(`Invalid --type "${raw}". Use one of: ${TYPES.join(", ")}.`);
4611
+ }
4612
+ function readCliVersion() {
4613
+ try {
4614
+ const here = dirname3(fileURLToPath(import.meta.url));
4615
+ const candidates = [
4616
+ join15(here, "..", "package.json"),
4617
+ join15(here, "..", "..", "..", "package.json")
4618
+ ];
4619
+ for (const path of candidates) {
4620
+ if (!existsSync13(path)) continue;
4621
+ const pkg2 = JSON.parse(readFileSync12(path, "utf-8"));
4622
+ if (pkg2.name === "deepspace" && pkg2.version) return pkg2.version;
4623
+ }
4624
+ } catch {
4625
+ }
4626
+ return "unknown";
4627
+ }
4628
+ function detectAppName2() {
4629
+ try {
4630
+ const cwd = process.cwd();
4631
+ if (!hasWranglerConfig(cwd)) return void 0;
4632
+ const config = readWranglerConfig(cwd);
4633
+ return typeof config.name === "string" ? config.name : void 0;
4634
+ } catch {
4635
+ return void 0;
4636
+ }
4637
+ }
4638
+ function clampContext(value) {
4639
+ return value.length > MAX_CONTEXT_LEN ? value.slice(0, MAX_CONTEXT_LEN) : value;
4640
+ }
4641
+ function collectContext() {
4642
+ const appName = detectAppName2();
4643
+ return {
4644
+ cliVersion: clampContext(readCliVersion()),
4645
+ nodeVersion: clampContext(process.version),
4646
+ platform: clampContext(`${process.platform} ${release()}`),
4647
+ ...appName ? { appName: clampContext(appName) } : {}
4648
+ };
4649
+ }
4650
+ function buildFeedbackPayload(input) {
4651
+ const title = input.title.trim();
4652
+ const body = input.body.trim();
4653
+ if (!title) throw new Error("A title is required.");
4654
+ if (!body) throw new Error("A message/description is required.");
4655
+ if (title.length > MAX_TITLE_LEN) {
4656
+ throw new Error(
4657
+ `Title must be ${MAX_TITLE_LEN} characters or fewer (got ${title.length}).`
4658
+ );
4659
+ }
4660
+ if (body.length > MAX_BODY_LEN) {
4661
+ throw new Error(
4662
+ `Description must be ${MAX_BODY_LEN} characters or fewer (got ${body.length}).`
4663
+ );
4664
+ }
4665
+ return {
4666
+ type: normalizeType(input.type),
4667
+ title,
4668
+ body,
4669
+ ...input.context
4670
+ };
4671
+ }
4672
+ var feedback_default = defineCommand20({
4673
+ meta: {
4674
+ name: "feedback",
4675
+ description: "Submit a bug report, feature request, or feedback to DeepSpace"
4676
+ },
4677
+ args: {
4678
+ title: {
4679
+ type: "positional",
4680
+ description: "Short summary (prompted if omitted in a terminal)",
4681
+ required: false
4682
+ },
4683
+ type: {
4684
+ type: "string",
4685
+ alias: "t",
4686
+ description: `Report type: ${TYPES.join(" | ")} (default: bug)`,
4687
+ required: false
4688
+ },
4689
+ message: {
4690
+ type: "string",
4691
+ alias: "m",
4692
+ description: "Details / description (prompted if omitted in a terminal)",
4693
+ required: false
4694
+ },
4695
+ yes: {
4696
+ type: "boolean",
4697
+ description: "Skip the confirmation prompt",
4698
+ default: false
4699
+ },
4700
+ json: {
4701
+ type: "boolean",
4702
+ description: "Emit JSON instead of human output",
4703
+ default: false
4704
+ }
4705
+ },
4706
+ async run({ args }) {
4707
+ const interactive = process.stdin.isTTY && !args.json;
4708
+ const fail = (message) => {
4709
+ if (args.json) {
4710
+ process.stdout.write(JSON.stringify({ ok: false, error: message }) + "\n");
4711
+ } else {
4712
+ console.error(message);
4713
+ }
4714
+ process.exit(1);
4715
+ };
4716
+ let type;
4717
+ try {
4718
+ type = normalizeType(args.type ? String(args.type) : void 0);
4719
+ } catch (err) {
4720
+ return fail(err.message);
4721
+ }
4722
+ let title = args.title ? String(args.title) : "";
4723
+ let body = args.message ? String(args.message) : "";
4724
+ if ((!title || !body) && !interactive) {
4725
+ return fail(
4726
+ 'Provide a title and --message to submit non-interactively, e.g. `deepspace feedback "Title" --message "Details"`.'
4727
+ );
4728
+ }
4729
+ if (interactive && (!args.type || !title || !body)) {
4730
+ p7.intro("DeepSpace Feedback");
4731
+ if (!args.type) {
4732
+ const picked = await p7.select({
4733
+ message: "What kind of feedback is this?",
4734
+ options: TYPES.map((t) => ({ value: t, label: TYPE_LABELS[t] })),
4735
+ initialValue: type
4736
+ });
4737
+ if (p7.isCancel(picked)) {
4738
+ p7.cancel("Cancelled.");
4739
+ process.exit(0);
4740
+ }
4741
+ type = picked;
4742
+ }
4743
+ if (!title) {
4744
+ const t = await p7.text({
4745
+ message: "Title",
4746
+ placeholder: "Short summary of the issue",
4747
+ validate: (v) => {
4748
+ if (!v?.trim()) return "Title is required";
4749
+ if (v.length > MAX_TITLE_LEN)
4750
+ return `Title must be ${MAX_TITLE_LEN} characters or fewer`;
4751
+ return void 0;
4752
+ }
4753
+ });
4754
+ if (p7.isCancel(t)) {
4755
+ p7.cancel("Cancelled.");
4756
+ process.exit(0);
4757
+ }
4758
+ title = String(t);
4759
+ }
4760
+ if (!body) {
4761
+ const m = await p7.text({
4762
+ message: "Details",
4763
+ placeholder: "What happened? Steps to reproduce, what you expected\u2026",
4764
+ validate: (v) => {
4765
+ if (!v?.trim()) return "A description is required";
4766
+ if (v.length > MAX_BODY_LEN)
4767
+ return `Description must be ${MAX_BODY_LEN} characters or fewer`;
4768
+ return void 0;
4769
+ }
4770
+ });
4771
+ if (p7.isCancel(m)) {
4772
+ p7.cancel("Cancelled.");
4773
+ process.exit(0);
4774
+ }
4775
+ body = String(m);
4776
+ }
4777
+ }
4778
+ let payload;
4779
+ try {
4780
+ payload = buildFeedbackPayload({
4781
+ type,
4782
+ title,
4783
+ body,
4784
+ context: collectContext()
4785
+ });
4786
+ } catch (err) {
4787
+ return fail(err.message);
4788
+ }
4789
+ if (interactive && !args.yes) {
4790
+ p7.note(
4791
+ `${TYPE_LABELS[payload.type]}
4792
+ ${payload.title}
4793
+
4794
+ ${payload.body}
4795
+
4796
+ Context: deepspace ${payload.cliVersion} \xB7 ${payload.nodeVersion} \xB7 ${payload.platform}` + (payload.appName ? ` \xB7 app ${payload.appName}` : ""),
4797
+ "Review"
4798
+ );
4799
+ const ok = await p7.confirm({ message: "Send this to the DeepSpace team?" });
4800
+ if (p7.isCancel(ok) || !ok) {
4801
+ p7.cancel("Not sent.");
4802
+ process.exit(0);
4803
+ }
4804
+ }
4805
+ let token2;
4806
+ try {
4807
+ token2 = await ensureToken();
4808
+ } catch (err) {
4809
+ return fail(err.message ?? "Not signed in. Run `deepspace login`.");
4810
+ }
4811
+ let result;
4812
+ try {
4813
+ result = await api3(token2, "/api/feedback", {
4814
+ method: "POST",
4815
+ body: JSON.stringify(payload)
4816
+ });
4817
+ } catch (err) {
4818
+ return fail(err.message);
4819
+ }
4820
+ if (args.json) {
4821
+ process.stdout.write(JSON.stringify({ ok: true, report: result.report }) + "\n");
4822
+ return;
4823
+ }
4824
+ if (interactive) {
4825
+ p7.outro(`Thanks! Submitted as ${result.report.id}.`);
4826
+ } else {
4827
+ console.log(`Submitted feedback ${result.report.id} (${result.report.type}).`);
4828
+ }
4829
+ console.log(`Track triage status at ${DASHBOARD_URL}.`);
4830
+ }
4831
+ });
4832
+
3811
4833
  // src/cli/cli.ts
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")) {
4834
+ var __dirname = dirname4(fileURLToPath2(import.meta.url));
4835
+ var pkg = JSON.parse(readFileSync13(join16(__dirname, "..", "package.json"), "utf-8"));
4836
+ if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync14("/etc/ssl/certs/ca-certificates.crt")) {
3815
4837
  process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
3816
4838
  }
3817
- var create3 = defineCommand19({
4839
+ var create3 = defineCommand21({
3818
4840
  meta: {
3819
4841
  name: "create",
3820
4842
  description: "Create a new DeepSpace app"
@@ -3833,7 +4855,7 @@ var create3 = defineCommand19({
3833
4855
  execSync5(`npm create deepspace@latest${name}`, { stdio: "inherit" });
3834
4856
  }
3835
4857
  });
3836
- var main = defineCommand19({
4858
+ var main = defineCommand21({
3837
4859
  meta: {
3838
4860
  name: "deepspace",
3839
4861
  version: pkg.version,
@@ -3858,7 +4880,9 @@ var main = defineCommand19({
3858
4880
  undeploy: undeploy_default,
3859
4881
  domain: domain_default,
3860
4882
  library: library_default,
3861
- admin: admin_default
4883
+ admin: admin_default,
4884
+ feedback: feedback_default,
4885
+ secrets: secrets_default
3862
4886
  }
3863
4887
  // No `run()` here — citty cascades parent run() AFTER subcommand finishes,
3864
4888
  // which corrupts agent-friendly output (`--json`) and prints noise on every