deepspace 0.5.1 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli/cli.ts
4
- import { defineCommand as defineCommand20, runMain } from "citty";
4
+ import { defineCommand as defineCommand22, runMain } from "citty";
5
5
  import { execSync as execSync4 } from "child_process";
6
- import { readFileSync as readFileSync14, existsSync as existsSync15 } from "fs";
7
- import { dirname as dirname4, join as join17 } from "path";
6
+ import { readFileSync as readFileSync14, existsSync as existsSync14 } from "fs";
7
+ import { dirname as dirname4, join as join16 } from "path";
8
8
  import { fileURLToPath as fileURLToPath2 } from "url";
9
9
 
10
10
  // src/cli/lib/cli-errors.ts
@@ -17,9 +17,10 @@ var API_ERROR_HINTS = {
17
17
  };
18
18
  function formatCliError(err) {
19
19
  const message = err instanceof Error ? err.message : String(err);
20
- const slug = /^API \S+ \(\d+\): ([a-z0-9_]+)$/.exec(message)?.[1];
20
+ const code = err?.code;
21
+ const slug = typeof code === "string" ? code : /^API \S+ \(\d+\): ([a-z0-9_]+)$/.exec(message)?.[1];
21
22
  const hint = slug ? API_ERROR_HINTS[slug] : void 0;
22
- return hint ? `${message}
23
+ return hint && !message.includes(hint) ? `${message}
23
24
  ${hint}` : message;
24
25
  }
25
26
  function renderCliError(err) {
@@ -50,8 +51,8 @@ function wrapCommandErrors(cmd) {
50
51
 
51
52
  // src/cli/commands/login.ts
52
53
  import { defineCommand } from "citty";
53
- import { writeFileSync as writeFileSync4, mkdirSync } from "fs";
54
- import { join as join2 } from "path";
54
+ import { writeFileSync as writeFileSync5, mkdirSync } from "fs";
55
+ import { join as join3 } from "path";
55
56
  import { homedir } from "os";
56
57
  import { randomBytes, createHash } from "crypto";
57
58
  import * as p2 from "@clack/prompts";
@@ -74,12 +75,288 @@ function createSpinner() {
74
75
  }
75
76
 
76
77
  // src/cli/env.ts
77
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
78
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
79
+
80
+ // src/cli/lib/secrets.ts
81
+ var GENERATED_SECRETS_DIVIDER = "# --- DeepSpace secrets cache: generated by `npx deepspace secrets`; do not edit manually ---";
82
+ var SECRET_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
83
+ var CONFIG_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
84
+ var MAX_SECRET_VALUE_BYTES = 32 * 1024;
85
+ function validateConfigName(configName) {
86
+ if (!CONFIG_NAME_RE.test(configName)) {
87
+ throw new Error(
88
+ `Invalid config "${configName}". Use letters, numbers, underscores, or hyphens; start with a letter or number.`
89
+ );
90
+ }
91
+ return configName;
92
+ }
93
+ function validateSecretName(name) {
94
+ if (!SECRET_NAME_RE.test(name)) {
95
+ throw new Error(
96
+ `Invalid secret name "${name}". Use letters, numbers, and underscores; start with a letter or underscore.`
97
+ );
98
+ }
99
+ return name;
100
+ }
101
+ function validateSecretValue(key, value) {
102
+ if (new TextEncoder().encode(value).byteLength > MAX_SECRET_VALUE_BYTES) {
103
+ throw new Error(`Secret "${key}" exceeds ${MAX_SECRET_VALUE_BYTES} bytes`);
104
+ }
105
+ }
106
+ function defaultConfigNameForEnv(wranglerEnv) {
107
+ return wranglerEnv ?? "prd";
108
+ }
109
+ async function secretsApi(deployUrl, token, path, init = {}) {
110
+ const res = await fetch(`${deployUrl}/api/secrets${path}`, {
111
+ ...init,
112
+ headers: {
113
+ Authorization: `Bearer ${token}`,
114
+ "Content-Type": "application/json",
115
+ ...init.headers ?? {}
116
+ }
117
+ });
118
+ const body = await res.json().catch(() => ({}));
119
+ if (!res.ok) {
120
+ throw new Error(`Secrets API ${path} (${res.status}): ${body.error ?? "request failed"}`);
121
+ }
122
+ return body;
123
+ }
124
+ function listSecrets(deployUrl, token, appId, configName) {
125
+ return secretsApi(deployUrl, token, `/${appId}/configs/${encodeURIComponent(configName)}`);
126
+ }
127
+ function fetchSecretsValues(deployUrl, token, appId, configName) {
128
+ return secretsApi(deployUrl, token, `/${appId}/configs/${encodeURIComponent(configName)}/values`);
129
+ }
130
+ function getSecretPlain(deployUrl, token, appId, configName, key) {
131
+ return secretsApi(
132
+ deployUrl,
133
+ token,
134
+ `/${appId}/configs/${encodeURIComponent(configName)}/secrets/${encodeURIComponent(key)}?plain=true`
135
+ );
136
+ }
137
+ function setSecret(deployUrl, token, appId, configName, key, value) {
138
+ return secretsApi(
139
+ deployUrl,
140
+ token,
141
+ `/${appId}/configs/${encodeURIComponent(configName)}/secrets/${encodeURIComponent(key)}`,
142
+ { method: "PUT", body: JSON.stringify({ value }) }
143
+ );
144
+ }
145
+ function deleteSecret(deployUrl, token, appId, configName, key) {
146
+ return secretsApi(
147
+ deployUrl,
148
+ token,
149
+ `/${appId}/configs/${encodeURIComponent(configName)}/secrets/${encodeURIComponent(key)}`,
150
+ { method: "DELETE" }
151
+ );
152
+ }
153
+ function uploadSecrets(deployUrl, token, appId, configName, secrets, replace) {
154
+ return secretsApi(
155
+ deployUrl,
156
+ token,
157
+ `/${appId}/configs/${encodeURIComponent(configName)}/secrets`,
158
+ { method: "PUT", body: JSON.stringify({ secrets, replace }) }
159
+ );
160
+ }
161
+ function listConfigs(deployUrl, token, appId) {
162
+ return secretsApi(deployUrl, token, `/${appId}/configs`);
163
+ }
164
+ function createConfig(deployUrl, token, appId, configName, copyFrom) {
165
+ return secretsApi(deployUrl, token, `/${appId}/configs`, {
166
+ method: "POST",
167
+ body: JSON.stringify({ name: configName, ...copyFrom ? { copyFrom } : {} })
168
+ });
169
+ }
170
+ function deleteConfig(deployUrl, token, appId, configName) {
171
+ return secretsApi(deployUrl, token, `/${appId}/configs/${encodeURIComponent(configName)}`, {
172
+ method: "DELETE"
173
+ });
174
+ }
175
+ async function pullAppSecretsCache(deployUrl, token, appId, configName) {
176
+ try {
177
+ const { secrets } = await fetchSecretsValues(deployUrl, token, appId, configName);
178
+ return { appId, configName, values: secrets };
179
+ } catch (err) {
180
+ const msg = err instanceof Error ? err.message : String(err);
181
+ if (msg.includes("(404)")) return null;
182
+ throw err;
183
+ }
184
+ }
185
+ function quoteDotenvValue(value) {
186
+ if (/^[A-Za-z0-9_./:@+-]*$/.test(value) && value !== "") return value;
187
+ const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r");
188
+ return `"${escaped}"`;
189
+ }
190
+ function renderSecretsCache(secrets, source) {
191
+ const keys = Object.keys(secrets).sort();
192
+ const lines = [
193
+ GENERATED_SECRETS_DIVIDER,
194
+ `# app ${source.appId} \xB7 config ${source.configName}`,
195
+ "# Use `npx deepspace secrets` to change these values."
196
+ ];
197
+ for (const key of keys) {
198
+ validateSecretValue(key, secrets[key]);
199
+ lines.push(`${key}=${quoteDotenvValue(secrets[key])}`);
200
+ }
201
+ return lines.join("\n");
202
+ }
203
+ function stripGeneratedSecretsCache(content) {
204
+ const idx = content.indexOf(GENERATED_SECRETS_DIVIDER);
205
+ if (idx === -1) return content;
206
+ return content.slice(0, idx).trimEnd() + "\n";
207
+ }
208
+ function parseSecretsUpload(content) {
209
+ const trimmed = content.trim();
210
+ if (!trimmed) return {};
211
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
212
+ const parsed = JSON.parse(trimmed);
213
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
214
+ throw new Error("JSON upload must be an object of KEY=value strings.");
215
+ }
216
+ const out2 = {};
217
+ for (const [key, value] of Object.entries(parsed)) {
218
+ validateSecretName(key);
219
+ if (typeof value !== "string") throw new Error(`Secret "${key}" must be a string`);
220
+ validateSecretValue(key, value);
221
+ out2[key] = value;
222
+ }
223
+ return out2;
224
+ }
225
+ const out = {};
226
+ for (const rawLine of trimmed.split("\n")) {
227
+ const line = rawLine.trim();
228
+ if (!line || line.startsWith("#")) continue;
229
+ const eq = line.indexOf("=");
230
+ if (eq <= 0) continue;
231
+ const key = line.slice(0, eq).trim();
232
+ if (!SECRET_NAME_RE.test(key)) continue;
233
+ let value = line.slice(eq + 1).trim();
234
+ if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
235
+ value = unescapeDotenv(value.slice(1, -1));
236
+ }
237
+ validateSecretValue(key, value);
238
+ out[key] = value;
239
+ }
240
+ return out;
241
+ }
242
+ function unescapeDotenv(v) {
243
+ let out = "";
244
+ for (let i = 0; i < v.length; i++) {
245
+ if (v[i] === "\\" && i + 1 < v.length) {
246
+ const c = v[++i];
247
+ out += c === "n" ? "\n" : c === "r" ? "\r" : c;
248
+ } else {
249
+ out += v[i];
250
+ }
251
+ }
252
+ return out;
253
+ }
254
+ function formatSecretsDownload(secrets, format) {
255
+ const entries = Object.entries(secrets).sort(([a], [b]) => a.localeCompare(b));
256
+ if (format === "json") {
257
+ return JSON.stringify(Object.fromEntries(entries), null, 2) + "\n";
258
+ }
259
+ if (format === "shell") {
260
+ return entries.map(([k, v]) => `export ${k}=${quoteDotenvValue(v)}`).join("\n") + "\n";
261
+ }
262
+ return entries.map(([k, v]) => `${k}=${quoteDotenvValue(v)}`).join("\n") + "\n";
263
+ }
264
+ async function refreshSecretsCache(deployUrl, token, appId, wranglerEnv) {
265
+ const configName = defaultConfigNameForEnv(wranglerEnv);
266
+ const pulled = await pullAppSecretsCache(deployUrl, token, appId, configName);
267
+ if (!pulled) return null;
268
+ const count = Object.keys(pulled.values).length;
269
+ return {
270
+ pulled,
271
+ rendered: renderSecretsCache(pulled.values, pulled),
272
+ summary: `Secrets: ${pulled.configName} (${count} secret${count === 1 ? "" : "s"})`
273
+ };
274
+ }
275
+
276
+ // src/cli/lib/app-identity.ts
277
+ import { existsSync, readFileSync, writeFileSync } from "fs";
278
+ import { join, resolve } from "path";
279
+ import { parse as parseToml } from "smol-toml";
280
+ var APP_ID_RE = /^app_[0-9A-HJKMNP-TV-Z]{26}$/;
281
+ var CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
282
+ function mintAppId(now = Date.now()) {
283
+ let ts = "";
284
+ let t = now;
285
+ for (let i = 0; i < 10; i++) {
286
+ ts = CROCKFORD[t % 32] + ts;
287
+ t = Math.floor(t / 32);
288
+ }
289
+ const rand = new Uint8Array(10);
290
+ crypto.getRandomValues(rand);
291
+ let rs = "";
292
+ let acc = 0;
293
+ let bits = 0;
294
+ for (const byte of rand) {
295
+ acc = acc << 8 | byte;
296
+ bits += 8;
297
+ while (bits >= 5) {
298
+ bits -= 5;
299
+ rs += CROCKFORD[acc >> bits & 31];
300
+ }
301
+ }
302
+ return `app_${ts}${rs}`.slice(0, 30);
303
+ }
304
+ function readAppId(cwd = process.cwd(), wranglerEnv) {
305
+ const wranglerPath = join(resolve(cwd), "wrangler.toml");
306
+ if (!existsSync(wranglerPath)) return null;
307
+ let cfg;
308
+ try {
309
+ cfg = parseToml(readFileSync(wranglerPath, "utf-8"));
310
+ } catch (err) {
311
+ throw new Error(
312
+ `Could not parse ${wranglerPath}: ${err instanceof Error ? err.message : String(err)}`
313
+ );
314
+ }
315
+ const vars = wranglerEnv ? cfg.env?.[wranglerEnv]?.vars : cfg.vars;
316
+ const id = vars?.DEEPSPACE_APP_ID;
317
+ return typeof id === "string" && APP_ID_RE.test(id) ? id : null;
318
+ }
319
+ function writeAppId(cwd, appId, opts = {}) {
320
+ const wranglerPath = join(resolve(cwd), "wrangler.toml");
321
+ if (!existsSync(wranglerPath)) {
322
+ throw new Error(`No wrangler.toml in ${resolve(cwd)}`);
323
+ }
324
+ const existing = readAppId(cwd, opts.wranglerEnv);
325
+ if (existing && !opts.force) {
326
+ if (existing === appId) return;
327
+ throw new Error(
328
+ `wrangler.toml already carries ${existing}. The app id is immutable \u2014 use --new-id only to fork this repo as a separate app.`
329
+ );
330
+ }
331
+ let src = readFileSync(wranglerPath, "utf-8");
332
+ const header = opts.wranglerEnv ? `[env.${opts.wranglerEnv}.vars]` : "[vars]";
333
+ const line = `DEEPSPACE_APP_ID = "${appId}"`;
334
+ const headerRe = new RegExp(`^\\${header.replace(/\./g, "\\.")}[ \\t]*$`, "m");
335
+ const idLineRe = /^DEEPSPACE_APP_ID\s*=.*$/m;
336
+ const match = headerRe.exec(src);
337
+ if (match) {
338
+ const blockStart = match.index + match[0].length;
339
+ const rest = src.slice(blockStart);
340
+ const nextSection = rest.search(/^\s*\[/m);
341
+ const blockEnd = nextSection === -1 ? src.length : blockStart + nextSection;
342
+ const block = src.slice(blockStart, blockEnd);
343
+ const newBlock = idLineRe.test(block) ? block.replace(idLineRe, line) : `
344
+ ${line}${block}`;
345
+ src = src.slice(0, blockStart) + newBlock + src.slice(blockEnd);
346
+ } else {
347
+ src = src.trimEnd() + `
348
+
349
+ ${header}
350
+ ${line}
351
+ `;
352
+ }
353
+ writeFileSync(wranglerPath, src);
354
+ }
78
355
 
79
356
  // src/cli/lib/secure-file.ts
80
- import { chmodSync, writeFileSync } from "fs";
357
+ import { chmodSync, writeFileSync as writeFileSync2 } from "fs";
81
358
  function writeSecretFileSync(path, content) {
82
- writeFileSync(path, content, { mode: 384 });
359
+ writeFileSync2(path, content, { mode: 384 });
83
360
  try {
84
361
  chmodSync(path, 384);
85
362
  } catch {
@@ -87,9 +364,9 @@ function writeSecretFileSync(path, content) {
87
364
  }
88
365
 
89
366
  // src/cli/lib/wrangler-env.ts
90
- import { existsSync, readFileSync, rmSync, writeFileSync as writeFileSync2 } from "fs";
91
- import { join } from "path";
92
- import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
367
+ import { existsSync as existsSync2, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync3 } from "fs";
368
+ import { join as join2 } from "path";
369
+ import { parse as parseToml2, stringify as stringifyToml } from "smol-toml";
93
370
 
94
371
  // src/server/rooms/app-name.ts
95
372
  var APP_NAME_RULES = {
@@ -187,35 +464,35 @@ function resolveAppNameForEnv(config, envName) {
187
464
  }
188
465
  return resolveAppName(envBlock.name);
189
466
  }
190
- function devVarsPathFor(appDir2, envName, opts = {}) {
191
- if (opts.sharedDevVarsCache || !envName) return join(appDir2, ".dev.vars");
192
- return join(appDir2, `.dev.vars.${envName}`);
467
+ function devVarsPathFor(appDir, envName, opts = {}) {
468
+ if (opts.sharedDevVarsCache || !envName) return join2(appDir, ".dev.vars");
469
+ return join2(appDir, `.dev.vars.${envName}`);
193
470
  }
194
- function legacyEnvDevVarsPathFor(appDir2, envName) {
195
- return envName ? join(appDir2, `.dev.vars.${envName}`) : void 0;
471
+ function legacyEnvDevVarsPathFor(appDir, envName) {
472
+ return envName ? join2(appDir, `.dev.vars.${envName}`) : void 0;
196
473
  }
197
- function warnIfLegacyEnvDevVarsExists(appDir2, envName, warn = console.warn) {
198
- const legacyPath = legacyEnvDevVarsPathFor(appDir2, envName);
199
- if (!legacyPath || !existsSync(legacyPath)) return;
474
+ function warnIfLegacyEnvDevVarsExists(appDir, envName, warn = console.warn) {
475
+ const legacyPath = legacyEnvDevVarsPathFor(appDir, envName);
476
+ if (!legacyPath || !existsSync2(legacyPath)) return;
200
477
  warn(
201
- `Warning: ${legacyPath} exists, but DeepSpace now uses ${devVarsPathFor(appDir2, envName, { sharedDevVarsCache: true })} for every environment. This run will ignore ${legacyPath}; move any secrets you still need into .dev.vars, then remove the env-specific file when you're ready.`
478
+ `Warning: ${legacyPath} exists, but DeepSpace now uses ${devVarsPathFor(appDir, envName, { sharedDevVarsCache: true })} for every environment. This run will ignore ${legacyPath}; move any secrets you still need into .dev.vars, then remove the env-specific file when you're ready.`
202
479
  );
203
480
  }
204
- function prepareWranglerEnvConfig(appDir2, envName, opts = {}) {
481
+ function prepareWranglerEnvConfig(appDir, envName, opts = {}) {
205
482
  if (!envName) return { cleanup() {
206
483
  } };
207
484
  if (!opts.sharedDevVarsCache) return { wranglerEnv: envName, cleanup() {
208
485
  } };
209
- warnIfLegacyEnvDevVarsExists(appDir2, envName, opts.warn);
210
- const config = readWranglerConfig(appDir2);
486
+ warnIfLegacyEnvDevVarsExists(appDir, envName, opts.warn);
487
+ const config = readWranglerConfig(appDir);
211
488
  const resolved = resolveAppNameForEnv(config, envName);
212
489
  if (!resolved.ok) throw new Error(resolved.reason);
213
490
  const flattened = flattenWranglerEnvConfig(config, envName);
214
- const configPath = join(
215
- appDir2,
491
+ const configPath = join2(
492
+ appDir,
216
493
  `.wrangler.deepspace.${safeGeneratedConfigSegment(envName)}.${process.pid}.${++generatedConfigSeq}.toml`
217
494
  );
218
- writeFileSync2(
495
+ writeFileSync3(
219
496
  configPath,
220
497
  [
221
498
  "# Generated by DeepSpace for this CLI run.",
@@ -271,17 +548,17 @@ var WranglerConfigError = class extends Error {
271
548
  this.name = "WranglerConfigError";
272
549
  }
273
550
  };
274
- function readWranglerConfig(appDir2) {
275
- const path = join(appDir2, "wrangler.toml");
276
- const raw = readFileSync(path, "utf-8");
551
+ function readWranglerConfig(appDir) {
552
+ const path = join2(appDir, "wrangler.toml");
553
+ const raw = readFileSync2(path, "utf-8");
277
554
  try {
278
- return parseToml(raw);
555
+ return parseToml2(raw);
279
556
  } catch (err) {
280
557
  throw new WranglerConfigError(path, err);
281
558
  }
282
559
  }
283
- function hasWranglerConfig(appDir2) {
284
- return existsSync(join(appDir2, "wrangler.toml"));
560
+ function hasWranglerConfig(appDir) {
561
+ return existsSync2(join2(appDir, "wrangler.toml"));
285
562
  }
286
563
 
287
564
  // src/cli/env.ts
@@ -303,21 +580,12 @@ async function fetchPublicKey(authUrl) {
303
580
  }
304
581
  return data.publicKey;
305
582
  }
306
- function readAppName(appDir2, wranglerEnv2) {
307
- if (!hasWranglerConfig(appDir2)) {
308
- throw new Error("No wrangler.toml found. Are you in a DeepSpace app directory?");
309
- }
310
- const config = readWranglerConfig(appDir2);
311
- const resolved = resolveAppNameForEnv(config, wranglerEnv2);
312
- if (!resolved.ok) throw new Error(resolved.reason);
313
- const rawName = wranglerEnv2 ? config.env?.[wranglerEnv2]?.name : config.name;
314
- if (rawName !== resolved.name) {
315
- const where = wranglerEnv2 ? `[env.${wranglerEnv2}].name` : "`name`";
316
- throw new Error(
317
- `wrangler.toml: ${where} "${rawName}" is not in canonical form. Update it to "${resolved.name}" and re-run.`
318
- );
319
- }
320
- return resolved.name;
583
+ function requireAppIdFor(appDir, wranglerEnv) {
584
+ const id = readAppId(appDir, wranglerEnv);
585
+ if (id) return id;
586
+ throw new Error(
587
+ "wrangler.toml has no DEEPSPACE_APP_ID" + (wranglerEnv ? ` for [env.${wranglerEnv}]` : "") + ". Run `deepspace init` (or `deepspace init --env <name>`) and retry."
588
+ );
321
589
  }
322
590
  async function mintAppOwnerJwt(authUrl, callerJwt, appName) {
323
591
  const res = await fetch(`${authUrl}/api/auth/mint-app-token`, {
@@ -326,7 +594,7 @@ async function mintAppOwnerJwt(authUrl, callerJwt, appName) {
326
594
  "Content-Type": "application/json",
327
595
  Authorization: `Bearer ${callerJwt}`
328
596
  },
329
- body: JSON.stringify({ appName })
597
+ body: JSON.stringify({ appId: appName })
330
598
  });
331
599
  if (!res.ok) {
332
600
  const err = await res.text().catch(() => "");
@@ -340,6 +608,7 @@ async function mintAppOwnerJwt(authUrl, callerJwt, appName) {
340
608
  }
341
609
  async function fetchAppIdentityToken(deployUrl, callerJwt, appName) {
342
610
  const res = await fetch(`${deployUrl}/api/apps/${encodeURIComponent(appName)}/identity-token`, {
611
+ // appName carries the appId
343
612
  method: "POST",
344
613
  headers: { Authorization: `Bearer ${callerJwt}` }
345
614
  });
@@ -368,7 +637,6 @@ var SDK_MANAGED_KEYS = /* @__PURE__ */ new Set([
368
637
  "ALLOW_DEBUG_ROUTES"
369
638
  ]);
370
639
  var DEV_VARS_DIVIDER = "# --- not managed by the SDK; preserved across dev/test runs ---";
371
- var GENERATED_SECRETS_DIVIDER = "# --- DeepSpace secrets cache: generated by `npx deepspace secrets`; do not edit manually ---";
372
640
  function extractCustomDevVars(content) {
373
641
  const lines = content.split("\n");
374
642
  const out = [];
@@ -409,11 +677,6 @@ function extractCustomDevVars(content) {
409
677
  while (out.length && out[out.length - 1].trim() === "") out.pop();
410
678
  return out.join("\n");
411
679
  }
412
- function stripGeneratedSecretsCache(content) {
413
- const idx = content.indexOf(GENERATED_SECRETS_DIVIDER);
414
- if (idx < 0) return content;
415
- return content.slice(0, idx).replace(/\s+$/u, "");
416
- }
417
680
  function removeDevVarsKeys(content, keys) {
418
681
  const lines = content.split("\n");
419
682
  const out = [];
@@ -529,12 +792,12 @@ function unescapeQuotedDevVar(value) {
529
792
  }
530
793
  return out;
531
794
  }
532
- async function writeDevVars(appDir2, ownerId, callerJwt, wranglerEnv2, opts = {}) {
795
+ async function writeDevVars(appDir, ownerId, callerJwt, wranglerEnv, opts = {}) {
533
796
  const urls = PLATFORM_URLS;
534
797
  const publicKey = await fetchPublicKey(urls.auth);
535
- const appName = readAppName(appDir2, wranglerEnv2);
536
- const appOwnerJwt = await mintAppOwnerJwt(urls.auth, callerJwt, appName);
537
- const appIdentityToken = await fetchAppIdentityToken(urls.deploy, callerJwt, appName);
798
+ const appId = opts.appId ?? requireAppIdFor(appDir, wranglerEnv);
799
+ const appOwnerJwt = await mintAppOwnerJwt(urls.auth, callerJwt, appId);
800
+ const appIdentityToken = await fetchAppIdentityToken(urls.deploy, callerJwt, appId);
538
801
  const sdkVars = [
539
802
  `AUTH_JWT_PUBLIC_KEY="${publicKey}"`,
540
803
  `AUTH_JWT_ISSUER=${urls.auth}/api/auth`,
@@ -549,20 +812,27 @@ async function writeDevVars(appDir2, ownerId, callerJwt, wranglerEnv2, opts = {}
549
812
  `ALLOW_DEBUG_ROUTES=true`
550
813
  ].join("\n");
551
814
  const useSharedDevVarsCache = opts.sharedDevVarsCache ?? opts.generatedSecretsCache !== void 0;
552
- const devVarsPath = devVarsPathFor(appDir2, wranglerEnv2, {
815
+ const devVarsPath = devVarsPathFor(appDir, wranglerEnv, {
553
816
  sharedDevVarsCache: useSharedDevVarsCache
554
817
  });
555
- const existing = existsSync2(devVarsPath) ? readFileSync2(devVarsPath, "utf-8") : "";
556
- const custom = opts.generatedSecretsCache ?? extractCustomDevVars(stripGeneratedSecretsCache(existing));
557
- const body = custom ? opts.generatedSecretsCache ? `${sdkVars}
818
+ const existing = existsSync3(devVarsPath) ? readFileSync3(devVarsPath, "utf-8") : "";
819
+ const custom = opts.generatedSecretsCache ?? extractCustomDevVars(stripGeneratedSecretsCache(existing).trimEnd());
820
+ let body;
821
+ if (!custom) {
822
+ body = `${sdkVars}
823
+ `;
824
+ } else if (opts.generatedSecretsCache) {
825
+ body = `${sdkVars}
558
826
 
559
827
  ${custom}
560
- ` : `${sdkVars}
828
+ `;
829
+ } else {
830
+ body = `${sdkVars}
561
831
 
562
832
  ${DEV_VARS_DIVIDER}
563
833
  ${custom}
564
- ` : `${sdkVars}
565
834
  `;
835
+ }
566
836
  writeSecretFileSync(devVarsPath, body);
567
837
  }
568
838
 
@@ -688,13 +958,13 @@ async function pollForCompletion(sessionId, codeVerifier) {
688
958
  return null;
689
959
  }
690
960
  function storeCredentials(sessionToken, jwt) {
691
- const dir = join2(homedir(), ".deepspace");
961
+ const dir = join3(homedir(), ".deepspace");
692
962
  mkdirSync(dir, { recursive: true, mode: 448 });
693
- writeFileSync4(join2(dir, "session"), sessionToken, { mode: 384 });
694
- writeFileSync4(join2(dir, "token"), jwt, { mode: 384 });
963
+ writeFileSync5(join3(dir, "session"), sessionToken, { mode: 384 });
964
+ writeFileSync5(join3(dir, "token"), jwt, { mode: 384 });
695
965
  }
696
966
  function sleep(ms) {
697
- return new Promise((resolve11) => setTimeout(resolve11, ms));
967
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
698
968
  }
699
969
  function base64url(bytes) {
700
970
  return bytes.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
@@ -725,20 +995,20 @@ async function doEmailLogin(email, password) {
725
995
 
726
996
  // src/cli/commands/logout.ts
727
997
  import { defineCommand as defineCommand2 } from "citty";
728
- import { existsSync as existsSync3, readFileSync as readFileSync3, rmSync as rmSync2 } from "fs";
729
- import { join as join3 } from "path";
998
+ import { existsSync as existsSync4, readFileSync as readFileSync4, rmSync as rmSync2 } from "fs";
999
+ import { join as join4 } from "path";
730
1000
  import { homedir as homedir2 } from "os";
731
1001
  var AUTH_URL2 = process.env.DEEPSPACE_AUTH_URL ?? PLATFORM_URLS.auth;
732
- var DIR = join3(homedir2(), ".deepspace");
733
- var SESSION_PATH = join3(DIR, "session");
734
- var TOKEN_PATH = join3(DIR, "token");
1002
+ var DIR = join4(homedir2(), ".deepspace");
1003
+ var SESSION_PATH = join4(DIR, "session");
1004
+ var TOKEN_PATH = join4(DIR, "token");
735
1005
  var logout_default = defineCommand2({
736
1006
  meta: {
737
1007
  name: "logout",
738
1008
  description: "Sign out and remove cached credentials"
739
1009
  },
740
1010
  async run() {
741
- const sessionToken = existsSync3(SESSION_PATH) ? readFileSync3(SESSION_PATH, "utf-8").trim() : null;
1011
+ const sessionToken = existsSync4(SESSION_PATH) ? readFileSync4(SESSION_PATH, "utf-8").trim() : null;
742
1012
  if (sessionToken) {
743
1013
  try {
744
1014
  await fetch(`${AUTH_URL2}/api/auth/sign-out`, {
@@ -753,7 +1023,7 @@ var logout_default = defineCommand2({
753
1023
  }
754
1024
  let removed = 0;
755
1025
  for (const path of [SESSION_PATH, TOKEN_PATH]) {
756
- if (existsSync3(path)) {
1026
+ if (existsSync4(path)) {
757
1027
  rmSync2(path);
758
1028
  removed++;
759
1029
  }
@@ -772,8 +1042,8 @@ import { resolve as resolve4, basename, join as join9 } from "path";
772
1042
  import spawn from "cross-spawn";
773
1043
 
774
1044
  // src/cli/auth.ts
775
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync5, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
776
- import { join as join4 } from "path";
1045
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync6, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
1046
+ import { join as join5 } from "path";
777
1047
  import { homedir as homedir3 } from "os";
778
1048
 
779
1049
  // src/cli/jwt.ts
@@ -787,26 +1057,26 @@ function decodeJwtPayload(token) {
787
1057
 
788
1058
  // src/cli/auth.ts
789
1059
  var AUTH_URL3 = process.env.DEEPSPACE_AUTH_URL ?? PLATFORM_URLS.auth;
790
- var DIR2 = join4(homedir3(), ".deepspace");
791
- var SESSION_PATH2 = join4(DIR2, "session");
792
- var TOKEN_PATH2 = join4(DIR2, "token");
1060
+ var DIR2 = join5(homedir3(), ".deepspace");
1061
+ var SESSION_PATH2 = join5(DIR2, "session");
1062
+ var TOKEN_PATH2 = join5(DIR2, "token");
793
1063
  async function ensureToken() {
794
- if (existsSync4(TOKEN_PATH2)) {
795
- const existing = readFileSync4(TOKEN_PATH2, "utf-8").trim();
1064
+ if (existsSync5(TOKEN_PATH2)) {
1065
+ const existing = readFileSync5(TOKEN_PATH2, "utf-8").trim();
796
1066
  if (isTokenValid(existing)) {
797
1067
  return existing;
798
1068
  }
799
1069
  }
800
- if (!existsSync4(SESSION_PATH2)) {
1070
+ if (!existsSync5(SESSION_PATH2)) {
801
1071
  throw new Error("Not logged in. Run `deepspace login` first.");
802
1072
  }
803
- const sessionToken = readFileSync4(SESSION_PATH2, "utf-8").trim();
1073
+ const sessionToken = readFileSync5(SESSION_PATH2, "utf-8").trim();
804
1074
  const token = await exchangeSession(AUTH_URL3, sessionToken);
805
1075
  if (!token) {
806
1076
  throw new Error("Session expired. Run `deepspace login` to re-authenticate.");
807
1077
  }
808
1078
  mkdirSync2(DIR2, { recursive: true, mode: 448 });
809
- writeFileSync5(TOKEN_PATH2, token, { mode: 384 });
1079
+ writeFileSync6(TOKEN_PATH2, token, { mode: 384 });
810
1080
  return token;
811
1081
  }
812
1082
  function isTokenValid(jwt) {
@@ -820,24 +1090,26 @@ function isTokenValid(jwt) {
820
1090
 
821
1091
  // src/cli/lib/app-context.ts
822
1092
  import {
823
- existsSync as existsSync5,
824
- readFileSync as readFileSync5,
1093
+ existsSync as existsSync6,
1094
+ readFileSync as readFileSync6,
825
1095
  readdirSync,
826
- writeFileSync as writeFileSync6,
1096
+ writeFileSync as writeFileSync7,
827
1097
  mkdirSync as mkdirSync3,
828
1098
  renameSync,
829
1099
  unlinkSync
830
1100
  } from "fs";
831
- import { join as join5, resolve, dirname, isAbsolute, sep } from "path";
832
- import { parse as parseToml2 } from "smol-toml";
1101
+ import { join as join6, resolve as resolve2, dirname, isAbsolute, sep } from "path";
1102
+ import { parse as parseToml3 } from "smol-toml";
833
1103
  function detectAppName(cwd = process.cwd()) {
834
- const wranglerPath = join5(resolve(cwd), "wrangler.toml");
835
- if (!existsSync5(wranglerPath)) return null;
1104
+ const wranglerPath = join6(resolve2(cwd), "wrangler.toml");
1105
+ if (!existsSync6(wranglerPath)) return null;
836
1106
  try {
837
- const cfg = parseToml2(readFileSync5(wranglerPath, "utf-8"));
1107
+ const cfg = parseToml3(readFileSync6(wranglerPath, "utf-8"));
838
1108
  return typeof cfg.name === "string" && cfg.name.length > 0 ? cfg.name : null;
839
- } catch {
840
- return null;
1109
+ } catch (err) {
1110
+ throw new Error(
1111
+ `Could not parse ${wranglerPath}: ${err instanceof Error ? err.message : String(err)}`
1112
+ );
841
1113
  }
842
1114
  }
843
1115
  function requireAppName(explicit, cwd) {
@@ -849,9 +1121,9 @@ function requireAppName(explicit, cwd) {
849
1121
  );
850
1122
  }
851
1123
  function findAppDir(start = process.cwd()) {
852
- let dir = resolve(start);
1124
+ let dir = resolve2(start);
853
1125
  for (; ; ) {
854
- if (existsSync5(join5(dir, "wrangler.toml"))) return dir;
1126
+ if (existsSync6(join6(dir, "wrangler.toml"))) return dir;
855
1127
  const parent = dirname(dir);
856
1128
  if (parent === dir) return null;
857
1129
  dir = parent;
@@ -859,7 +1131,7 @@ function findAppDir(start = process.cwd()) {
859
1131
  }
860
1132
  function findChildApps(dir) {
861
1133
  try {
862
- return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync5(join5(dir, e.name, "wrangler.toml"))).map((e) => e.name).sort();
1134
+ return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync6(join6(dir, e.name, "wrangler.toml"))).map((e) => e.name).sort();
863
1135
  } catch {
864
1136
  return [];
865
1137
  }
@@ -867,7 +1139,7 @@ function findChildApps(dir) {
867
1139
  function writeLaunchFile(launchPath, config) {
868
1140
  mkdirSync3(dirname(launchPath), { recursive: true });
869
1141
  const tmpPath = `${launchPath}.${process.pid}.tmp`;
870
- writeFileSync6(tmpPath, JSON.stringify(config, null, 2) + "\n");
1142
+ writeFileSync7(tmpPath, JSON.stringify(config, null, 2) + "\n");
871
1143
  try {
872
1144
  renameSync(tmpPath, launchPath);
873
1145
  } catch (err) {
@@ -880,15 +1152,15 @@ function writeLaunchFile(launchPath, config) {
880
1152
  }
881
1153
  function readLaunchFile(launchPath) {
882
1154
  const empty = { version: "0.0.1", configurations: [] };
883
- if (!existsSync5(launchPath)) return empty;
884
- const raw = readFileSync5(launchPath, "utf-8").trim();
1155
+ if (!existsSync6(launchPath)) return empty;
1156
+ const raw = readFileSync6(launchPath, "utf-8").trim();
885
1157
  if (!raw) return empty;
886
1158
  const parsed = JSON.parse(raw);
887
1159
  if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.configurations)) return null;
888
1160
  return parsed;
889
1161
  }
890
- function writeLaunchConfigIfMissing(appDir2, appName, port) {
891
- const launchPath = join5(appDir2, ".claude", "launch.json");
1162
+ function writeLaunchConfigIfMissing(appDir, appName, port) {
1163
+ const launchPath = join6(appDir, ".claude", "launch.json");
892
1164
  try {
893
1165
  const config = readLaunchFile(launchPath);
894
1166
  if (!config) return;
@@ -903,8 +1175,8 @@ function writeLaunchConfigIfMissing(appDir2, appName, port) {
903
1175
  } catch {
904
1176
  }
905
1177
  }
906
- function detectClaudeWorktree(appDir2) {
907
- const segments = resolve(appDir2).split(sep);
1178
+ function detectClaudeWorktree(appDir) {
1179
+ const segments = resolve2(appDir).split(sep);
908
1180
  for (let i = segments.length - 3; i >= 1; i--) {
909
1181
  if (segments[i] === ".claude" && segments[i + 1] === "worktrees" && segments[i + 2]) {
910
1182
  return {
@@ -923,16 +1195,16 @@ function deriveWorktreePort(worktreeName) {
923
1195
  for (const ch of worktreeName) hash = hash * 31 + ch.charCodeAt(0) >>> 0;
924
1196
  return WORKTREE_PORT_MIN + hash % WORKTREE_PORT_RANGE;
925
1197
  }
926
- function upsertWorktreeLaunchConfig(mainRepoRoot, worktreeName, appDir2, opts) {
927
- const launchPath = join5(mainRepoRoot, ".claude", "launch.json");
1198
+ function upsertWorktreeLaunchConfig(mainRepoRoot, worktreeName, appDir, opts) {
1199
+ const launchPath = join6(mainRepoRoot, ".claude", "launch.json");
928
1200
  const entryName = WORKTREE_ENTRY_PREFIX + worktreeName;
929
1201
  try {
930
1202
  const config = readLaunchFile(launchPath);
931
1203
  if (!config) return null;
932
- const worktreesRoot = join5(mainRepoRoot, ".claude", "worktrees") + sep;
1204
+ const worktreesRoot = join6(mainRepoRoot, ".claude", "worktrees") + sep;
933
1205
  config.configurations = config.configurations.filter((c) => {
934
1206
  if (c?.name === entryName) return false;
935
- const isStaleWorktreeEntry = typeof c?.name === "string" && c.name.startsWith(WORKTREE_ENTRY_PREFIX) && typeof c.cwd === "string" && isAbsolute(c.cwd) && c.cwd.startsWith(worktreesRoot) && !existsSync5(c.cwd);
1207
+ const isStaleWorktreeEntry = typeof c?.name === "string" && c.name.startsWith(WORKTREE_ENTRY_PREFIX) && typeof c.cwd === "string" && isAbsolute(c.cwd) && c.cwd.startsWith(worktreesRoot) && !existsSync6(c.cwd);
936
1208
  return !isStaleWorktreeEntry;
937
1209
  });
938
1210
  let port = opts.port;
@@ -953,7 +1225,7 @@ function upsertWorktreeLaunchConfig(mainRepoRoot, worktreeName, appDir2, opts) {
953
1225
  // the Claude Code desktop launch.json reference alongside `port`) and
954
1226
  // verified end-to-end: with it set, the preview server builds from the
955
1227
  // worktree instead of the session's main repo.
956
- cwd: resolve(appDir2)
1228
+ cwd: resolve2(appDir)
957
1229
  });
958
1230
  writeLaunchFile(launchPath, config);
959
1231
  return { entryName, port };
@@ -961,11 +1233,11 @@ function upsertWorktreeLaunchConfig(mainRepoRoot, worktreeName, appDir2, opts) {
961
1233
  return null;
962
1234
  }
963
1235
  }
964
- function resolveWorktreePort(appDir2) {
965
- const worktree = detectClaudeWorktree(appDir2);
1236
+ function resolveWorktreePort(appDir) {
1237
+ const worktree = detectClaudeWorktree(appDir);
966
1238
  if (!worktree) return null;
967
1239
  try {
968
- const config = readLaunchFile(join5(worktree.mainRepoRoot, ".claude", "launch.json"));
1240
+ const config = readLaunchFile(join6(worktree.mainRepoRoot, ".claude", "launch.json"));
969
1241
  const entry = config?.configurations.find(
970
1242
  (c) => c?.name === WORKTREE_ENTRY_PREFIX + worktree.worktreeName
971
1243
  );
@@ -975,28 +1247,44 @@ function resolveWorktreePort(appDir2) {
975
1247
  }
976
1248
  return deriveWorktreePort(worktree.worktreeName);
977
1249
  }
1250
+ function requireAppIdArg(explicit) {
1251
+ if (explicit) {
1252
+ if (!APP_ID_RE.test(explicit)) {
1253
+ throw new Error(`"${explicit}" is not an app id (app_\u2026). Pass --app <appId>.`);
1254
+ }
1255
+ return explicit;
1256
+ }
1257
+ const appDir = findAppDir();
1258
+ const id = appDir ? readAppId(appDir) : null;
1259
+ if (!id) {
1260
+ throw new Error(
1261
+ "No app id. Run from an app directory whose wrangler.toml carries DEEPSPACE_APP_ID, or pass --app <appId>."
1262
+ );
1263
+ }
1264
+ return id;
1265
+ }
978
1266
 
979
1267
  // src/cli/lib/install-status.ts
980
- import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
981
- import { join as join6 } from "path";
982
- function ensureInstallReady(appDir2) {
983
- if (existsSync6(join6(appDir2, "node_modules", "deepspace", "package.json"))) return;
984
- const sentinelDir = join6(appDir2, ".deepspace");
985
- const errPath = join6(sentinelDir, "install.err");
986
- const startedPath = join6(sentinelDir, "install.started");
987
- const donePath = join6(sentinelDir, "install.done");
988
- const logPath = join6(sentinelDir, "install.log");
989
- if (existsSync6(errPath)) {
1268
+ import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
1269
+ import { join as join7 } from "path";
1270
+ function ensureInstallReady(appDir) {
1271
+ if (existsSync7(join7(appDir, "node_modules", "deepspace", "package.json"))) return;
1272
+ const sentinelDir = join7(appDir, ".deepspace");
1273
+ const errPath = join7(sentinelDir, "install.err");
1274
+ const startedPath = join7(sentinelDir, "install.started");
1275
+ const donePath = join7(sentinelDir, "install.done");
1276
+ const logPath = join7(sentinelDir, "install.log");
1277
+ if (existsSync7(errPath)) {
990
1278
  console.error("Background install failed:");
991
- console.error(readFileSync6(errPath, "utf-8").trimEnd());
992
- if (existsSync6(logPath)) console.error(`
1279
+ console.error(readFileSync7(errPath, "utf-8").trimEnd());
1280
+ if (existsSync7(logPath)) console.error(`
993
1281
  Full log: ${logPath}`);
994
1282
  console.error("\nRun `npm install` (or `bun install`) manually, then retry.");
995
1283
  process.exit(1);
996
1284
  }
997
- if (existsSync6(startedPath) && !existsSync6(donePath)) {
1285
+ if (existsSync7(startedPath) && !existsSync7(donePath)) {
998
1286
  console.error("Dependencies are still installing in the background.");
999
- if (existsSync6(logPath)) console.error(`Tail progress: tail -f ${logPath}`);
1287
+ if (existsSync7(logPath)) console.error(`Tail progress: tail -f ${logPath}`);
1000
1288
  console.error("Retry once it finishes.");
1001
1289
  process.exit(1);
1002
1290
  }
@@ -1006,9 +1294,9 @@ Full log: ${logPath}`);
1006
1294
 
1007
1295
  // src/cli/lib/preflight.ts
1008
1296
  import { execFileSync } from "child_process";
1009
- import { existsSync as existsSync7 } from "fs";
1297
+ import { existsSync as existsSync8 } from "fs";
1010
1298
  import * as nodeModule from "module";
1011
- import { resolve as resolve2 } from "path";
1299
+ import { resolve as resolve3 } from "path";
1012
1300
  function preflightNodeVersion(command, hasRegisterHooks = typeof nodeModule.registerHooks === "function") {
1013
1301
  if (hasRegisterHooks) return;
1014
1302
  console.error(
@@ -1018,23 +1306,23 @@ function preflightNodeVersion(command, hasRegisterHooks = typeof nodeModule.regi
1018
1306
  process.exit(1);
1019
1307
  }
1020
1308
  var DLL_NOT_FOUND = -1073741515;
1021
- function preflightWindowsWorkerd(appDir2) {
1309
+ function preflightWindowsWorkerd(appDir) {
1022
1310
  if (process.platform !== "win32") return;
1023
- const workerdExe = resolve2(
1024
- appDir2,
1311
+ const workerdExe = resolve3(
1312
+ appDir,
1025
1313
  "node_modules",
1026
1314
  "@cloudflare",
1027
1315
  "workerd-windows-64",
1028
1316
  "bin",
1029
1317
  "workerd.exe"
1030
1318
  );
1031
- if (!existsSync7(workerdExe)) return;
1319
+ if (!existsSync8(workerdExe)) return;
1032
1320
  try {
1033
1321
  execFileSync(workerdExe, ["--help"], { stdio: "pipe", timeout: 1e4 });
1034
1322
  return;
1035
1323
  } catch (err) {
1036
- const status2 = err.status;
1037
- if (status2 !== DLL_NOT_FOUND) return;
1324
+ const status3 = err.status;
1325
+ if (status3 !== DLL_NOT_FOUND) return;
1038
1326
  console.error("");
1039
1327
  console.error(" workerd (Cloudflare Workers runtime) cannot start on this system.");
1040
1328
  console.error(" It needs the Microsoft Visual C++ Redistributable, which is not installed.");
@@ -1052,7 +1340,7 @@ function preflightWindowsWorkerd(appDir2) {
1052
1340
 
1053
1341
  // src/cli/lib/macos-junk.ts
1054
1342
  import { readdirSync as readdirSync2, rmSync as rmSync3 } from "fs";
1055
- import { join as join7 } from "path";
1343
+ import { join as join8 } from "path";
1056
1344
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".wrangler", ".vite"]);
1057
1345
  function removeMacosJunk(dir) {
1058
1346
  let removed = 0;
@@ -1063,7 +1351,7 @@ function removeMacosJunk(dir) {
1063
1351
  return removed;
1064
1352
  }
1065
1353
  for (const entry of entries) {
1066
- const path = join7(dir, entry.name);
1354
+ const path = join8(dir, entry.name);
1067
1355
  if (entry.isDirectory()) {
1068
1356
  if (!SKIP_DIRS.has(entry.name)) removed += removeMacosJunk(path);
1069
1357
  continue;
@@ -1079,741 +1367,17 @@ function removeMacosJunk(dir) {
1079
1367
  return removed;
1080
1368
  }
1081
1369
 
1082
- // src/cli/lib/secrets.ts
1083
- import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "fs";
1084
- import { join as join8, resolve as resolve3 } from "path";
1085
-
1086
- // src/server/rooms/binding-manifest.ts
1087
- var AUTO_PROVISION_SENTINEL = "auto";
1088
- var VECTORIZE_METRICS = /* @__PURE__ */ new Set(["cosine", "euclidean", "dot-product"]);
1089
- var ALLOWED_BINDING_TYPES = /* @__PURE__ */ new Set([
1090
- "vectorize",
1091
- "ai",
1092
- "r2_bucket",
1093
- "kv_namespace",
1094
- "d1",
1095
- "queue",
1096
- "browser_rendering",
1097
- "analytics_engine",
1098
- "hyperdrive"
1099
- ]);
1100
- var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
1101
- "ASSETS",
1102
- "PLATFORM_WORKER",
1103
- "API_WORKER",
1104
- "APP_NAME",
1105
- "OWNER_USER_ID",
1106
- "AUTH_JWT_PUBLIC_KEY",
1107
- "AUTH_JWT_ISSUER",
1108
- "AUTH_WORKER_URL",
1109
- "APP_IDENTITY_TOKEN",
1110
- "APP_OWNER_JWT",
1111
- "USAGE_EVENTS"
1112
- ]);
1113
- function validateBindingManifest(manifest) {
1114
- if (!Array.isArray(manifest)) {
1115
- return { valid: false, errors: [{ reason: "Manifest must be an array" }] };
1116
- }
1117
- const errors = [];
1118
- const seenNames = /* @__PURE__ */ new Set();
1119
- for (const entry of manifest) {
1120
- if (!entry || typeof entry !== "object") {
1121
- errors.push({ reason: `Entry must be an object (got ${entry === null ? "null" : typeof entry})` });
1122
- continue;
1123
- }
1124
- const e = entry;
1125
- const type = e.type;
1126
- const name = e.name;
1127
- if (typeof type !== "string" || !ALLOWED_BINDING_TYPES.has(type)) {
1128
- errors.push({
1129
- reason: `Disallowed binding type: ${typeof type === "string" ? type : `(${typeof type})`}. Allowed: ${[...ALLOWED_BINDING_TYPES].sort().join(", ")}`
1130
- });
1131
- continue;
1132
- }
1133
- if (typeof name !== "string" || !name) {
1134
- errors.push({ reason: `Binding of type "${type}" missing 'name' (string)` });
1135
- continue;
1136
- }
1137
- if (RESERVED_BINDING_NAMES.has(name)) {
1138
- errors.push({ reason: `Binding name "${name}" is reserved by the SDK` });
1139
- continue;
1140
- }
1141
- if (seenNames.has(name)) {
1142
- errors.push({ reason: `Duplicate binding name "${name}"` });
1143
- continue;
1144
- }
1145
- seenNames.add(name);
1146
- const binding = entry;
1147
- const fieldErr = requiredFieldError(binding);
1148
- if (fieldErr) errors.push({ binding, reason: fieldErr });
1370
+ // src/cli/lib/port.ts
1371
+ var DEFAULT_PORT = 5173;
1372
+ function resolvePort(arg) {
1373
+ const raw = arg ?? process.env.DEEPSPACE_PORT;
1374
+ if (!raw) return DEFAULT_PORT;
1375
+ const n = Number(raw);
1376
+ if (!Number.isInteger(n) || n < 1 || n > 65535) {
1377
+ console.error(`Invalid port: ${raw}. Must be an integer between 1 and 65535.`);
1378
+ process.exit(1);
1149
1379
  }
1150
- if (errors.length) return { valid: false, errors };
1151
- return { valid: true, bindings: manifest };
1152
- }
1153
- function requiredFieldError(b) {
1154
- switch (b.type) {
1155
- case "vectorize": {
1156
- if (!b.index_name) return `vectorize binding "${b.name}" missing index_name (or "auto")`;
1157
- if (b.index_name === AUTO_PROVISION_SENTINEL) {
1158
- if (typeof b.dimensions !== "number" || b.dimensions <= 0) {
1159
- return `vectorize binding "${b.name}" with index_name="auto" requires "dimensions" (positive number)`;
1160
- }
1161
- if (!b.metric || !VECTORIZE_METRICS.has(b.metric)) {
1162
- return `vectorize binding "${b.name}" with index_name="auto" requires "metric" (one of: ${[...VECTORIZE_METRICS].join(", ")})`;
1163
- }
1164
- }
1165
- return null;
1166
- }
1167
- case "r2_bucket":
1168
- return b.bucket_name ? null : `r2_bucket binding "${b.name}" missing bucket_name (or "auto")`;
1169
- case "kv_namespace": {
1170
- if (!b.namespace_id) return `kv_namespace binding "${b.name}" missing namespace_id (or "auto")`;
1171
- if (b.namespace_id === AUTO_PROVISION_SENTINEL && !b.title) {
1172
- return `kv_namespace binding "${b.name}" with namespace_id="auto" requires "title"`;
1173
- }
1174
- return null;
1175
- }
1176
- case "d1": {
1177
- if (!b.id) return `d1 binding "${b.name}" missing id (or "auto")`;
1178
- if (b.id === AUTO_PROVISION_SENTINEL && !b.database_name) {
1179
- return `d1 binding "${b.name}" with id="auto" requires "database_name"`;
1180
- }
1181
- return null;
1182
- }
1183
- case "queue":
1184
- return b.queue_name ? null : `queue binding "${b.name}" missing queue_name (or "auto")`;
1185
- case "hyperdrive":
1186
- if (!b.id) return `hyperdrive binding "${b.name}" missing id`;
1187
- if (b.id === AUTO_PROVISION_SENTINEL) {
1188
- return `hyperdrive binding "${b.name}": auto-provisioning not yet supported (provide a real id)`;
1189
- }
1190
- return null;
1191
- case "ai":
1192
- case "browser_rendering":
1193
- case "analytics_engine":
1194
- return null;
1195
- // no required fields beyond `name`
1196
- default: {
1197
- const _exhaustive = b;
1198
- void _exhaustive;
1199
- return null;
1200
- }
1201
- }
1202
- }
1203
- function bindingManifestFromOutputConfig(outputConfig) {
1204
- const out = [];
1205
- const aiBinding = pluckString(outputConfig.ai, "binding");
1206
- if (aiBinding) out.push({ type: "ai", name: aiBinding });
1207
- for (const v of asObjectArray(outputConfig.vectorize)) {
1208
- const name = pluckString(v, "binding");
1209
- const index_name = pluckString(v, "index_name");
1210
- if (!name || !index_name) continue;
1211
- const dimensions = pluckNumber(v, "dimensions");
1212
- const metric = pluckString(v, "metric");
1213
- out.push({
1214
- type: "vectorize",
1215
- name,
1216
- index_name,
1217
- ...dimensions != null && { dimensions },
1218
- ...metric && (metric === "cosine" || metric === "euclidean" || metric === "dot-product") && { metric }
1219
- });
1220
- }
1221
- for (const r of asObjectArray(outputConfig.r2_buckets)) {
1222
- const name = pluckString(r, "binding");
1223
- const bucket_name = pluckString(r, "bucket_name");
1224
- if (name && bucket_name) out.push({ type: "r2_bucket", name, bucket_name });
1225
- }
1226
- for (const k of asObjectArray(outputConfig.kv_namespaces)) {
1227
- const name = pluckString(k, "binding");
1228
- const namespace_id = pluckString(k, "id");
1229
- if (!name || !namespace_id) continue;
1230
- const title = pluckString(k, "title");
1231
- out.push({
1232
- type: "kv_namespace",
1233
- name,
1234
- namespace_id,
1235
- ...title && { title }
1236
- });
1237
- }
1238
- for (const d of asObjectArray(outputConfig.d1_databases)) {
1239
- const name = pluckString(d, "binding");
1240
- const id = pluckString(d, "database_id");
1241
- if (!name || !id) continue;
1242
- const database_name = pluckString(d, "database_name");
1243
- out.push({
1244
- type: "d1",
1245
- name,
1246
- id,
1247
- ...database_name && { database_name }
1248
- });
1249
- }
1250
- const queues = isObject(outputConfig.queues) ? outputConfig.queues : null;
1251
- for (const q of asObjectArray(queues?.producers)) {
1252
- const name = pluckString(q, "binding");
1253
- const queue_name = pluckString(q, "queue");
1254
- if (name && queue_name) out.push({ type: "queue", name, queue_name });
1255
- }
1256
- const browserBinding = pluckString(outputConfig.browser, "binding");
1257
- if (browserBinding) out.push({ type: "browser_rendering", name: browserBinding });
1258
- for (const a of asObjectArray(outputConfig.analytics_engine_datasets)) {
1259
- const name = pluckString(a, "binding");
1260
- const dataset = pluckString(a, "dataset");
1261
- if (name) out.push({ type: "analytics_engine", name, dataset: dataset ?? void 0 });
1262
- }
1263
- for (const h of asObjectArray(outputConfig.hyperdrive)) {
1264
- const name = pluckString(h, "binding");
1265
- const id = pluckString(h, "id");
1266
- if (name && id) out.push({ type: "hyperdrive", name, id });
1267
- }
1268
- return out;
1269
- }
1270
- function isObject(v) {
1271
- return v !== null && typeof v === "object" && !Array.isArray(v);
1272
- }
1273
- function pluckString(v, key) {
1274
- if (!isObject(v)) return null;
1275
- const val = v[key];
1276
- return typeof val === "string" && val.length > 0 ? val : null;
1277
- }
1278
- function pluckNumber(v, key) {
1279
- if (!isObject(v)) return null;
1280
- const val = v[key];
1281
- return typeof val === "number" && Number.isFinite(val) ? val : null;
1282
- }
1283
- function asObjectArray(v) {
1284
- if (!Array.isArray(v)) return [];
1285
- return v.filter(isObject);
1286
- }
1287
-
1288
- // src/cli/lib/secrets.ts
1289
- var DEEPSPACE_SECRETS_PROJECT_VAR = "DEEPSPACE_SECRETS_PROJECT";
1290
- var DEEPSPACE_SECRETS_CONFIG_VAR = "DEEPSPACE_SECRETS_CONFIG";
1291
- var GENERATED_SECRETS_DIVIDER2 = "# --- DeepSpace secrets cache: generated by `npx deepspace secrets`; do not edit manually ---";
1292
- var REQUIRED_SECRETS_BEGIN = "# --- DeepSpace detected secrets (names only; values live in `npx deepspace secrets`) ---";
1293
- var REQUIRED_SECRETS_END = "# --- end DeepSpace detected secrets ---";
1294
- var SECRET_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
1295
- var PROJECT_NAME_RE = /^[a-z0-9][a-z0-9-]{0,62}$/;
1296
- var CONFIG_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
1297
- var MAX_SECRET_VALUE_BYTES = 32 * 1024;
1298
- function validateProjectName(projectName) {
1299
- const trimmed = projectName.trim();
1300
- if (!PROJECT_NAME_RE.test(trimmed)) {
1301
- throw new Error(
1302
- `Invalid secrets project "${projectName}". Use lowercase letters, numbers, and hyphens; start with a letter or number.`
1303
- );
1304
- }
1305
- return trimmed;
1306
- }
1307
- function validateConfigName(configName) {
1308
- const trimmed = configName.trim();
1309
- if (!CONFIG_NAME_RE.test(trimmed)) {
1310
- throw new Error(
1311
- `Invalid secrets config "${configName}". Use letters, numbers, underscores, or hyphens; start with a letter or number.`
1312
- );
1313
- }
1314
- return trimmed;
1315
- }
1316
- function validateSecretName(name) {
1317
- const trimmed = name.trim();
1318
- if (!SECRET_NAME_RE.test(trimmed)) {
1319
- throw new Error(
1320
- `Invalid secret name "${name}". Secret names must use letters, numbers, and underscores, and may not start with a number.`
1321
- );
1322
- }
1323
- if (RESERVED_BINDING_NAMES.has(trimmed)) {
1324
- throw new Error(`"${trimmed}" is reserved by DeepSpace and cannot be used as an app secret.`);
1325
- }
1326
- return trimmed;
1327
- }
1328
- function validateSecretValue(key, value) {
1329
- const bytes = new TextEncoder().encode(value).byteLength;
1330
- if (bytes > MAX_SECRET_VALUE_BYTES) {
1331
- throw new Error(`Secret "${key}" exceeds ${MAX_SECRET_VALUE_BYTES} bytes.`);
1332
- }
1333
- }
1334
- function defaultConfigNameForEnv(wranglerEnv2) {
1335
- return wranglerEnv2 ? validateConfigName(wranglerEnv2) : "prd";
1336
- }
1337
- function readTopLevelAppName(appDir2) {
1338
- const cfg = readWranglerConfig(appDir2);
1339
- const resolved = resolveAppNameForEnv(cfg, void 0);
1340
- if (!resolved.ok) throw new Error(resolved.reason);
1341
- if (cfg.name !== resolved.name) {
1342
- throw new Error(
1343
- `wrangler.toml: \`name\` "${cfg.name}" is not in canonical form. Update it to "${resolved.name}" and re-run.`
1344
- );
1345
- }
1346
- return resolved.name;
1347
- }
1348
- function resolveSecretsAppName(appDir2, wranglerEnv2) {
1349
- const cfg = readWranglerConfig(appDir2);
1350
- const resolved = resolveAppNameForEnv(cfg, wranglerEnv2);
1351
- if (!resolved.ok) throw new Error(resolved.reason);
1352
- return resolved.name;
1353
- }
1354
- function stringVar(value) {
1355
- return typeof value === "string" && value.trim() ? value.trim() : void 0;
1356
- }
1357
- function resolveSecretsLink(appDir2, wranglerEnv2) {
1358
- const cfg = readWranglerConfig(appDir2);
1359
- const defaultProjectName = readTopLevelAppName(appDir2);
1360
- const defaultConfigName = defaultConfigNameForEnv(wranglerEnv2);
1361
- const topVars = cfg.vars ?? {};
1362
- const envVars = wranglerEnv2 ? cfg.env?.[wranglerEnv2]?.vars ?? {} : {};
1363
- const projectName = stringVar(envVars[DEEPSPACE_SECRETS_PROJECT_VAR]) ?? stringVar(topVars[DEEPSPACE_SECRETS_PROJECT_VAR]);
1364
- if (!projectName) {
1365
- return { linked: false, defaultProjectName, defaultConfigName, wranglerEnv: wranglerEnv2 };
1366
- }
1367
- const configName = stringVar(envVars[DEEPSPACE_SECRETS_CONFIG_VAR]) ?? (wranglerEnv2 ? defaultConfigName : stringVar(topVars[DEEPSPACE_SECRETS_CONFIG_VAR]) ?? "prd");
1368
- return {
1369
- linked: true,
1370
- projectName: validateProjectName(projectName),
1371
- configName: validateConfigName(configName),
1372
- wranglerEnv: wranglerEnv2
1373
- };
1374
- }
1375
- function resolveSecretsTarget(appDir2, opts) {
1376
- const link = resolveSecretsLink(appDir2, opts.wranglerEnv);
1377
- const projectName = validateProjectName(
1378
- opts.project ?? (link.linked ? link.projectName : link.defaultProjectName)
1379
- );
1380
- const configName = validateConfigName(
1381
- opts.config ?? (link.linked ? link.configName : link.defaultConfigName)
1382
- );
1383
- return { projectName, configName, wranglerEnv: opts.wranglerEnv };
1384
- }
1385
- function renderSecretsCache(secrets, source) {
1386
- const keys = Object.keys(secrets).sort();
1387
- const lines = [
1388
- GENERATED_SECRETS_DIVIDER2,
1389
- `# Source: project=${source.projectName} config=${source.configName}`,
1390
- `# Pulled: ${source.pulledAt ?? (/* @__PURE__ */ new Date()).toISOString()}`,
1391
- "# Use `npx deepspace secrets` to change these values."
1392
- ];
1393
- for (const key of keys) {
1394
- validateSecretName(key);
1395
- validateSecretValue(key, secrets[key]);
1396
- lines.push(`${key}=${quoteDotenvValue(secrets[key])}`);
1397
- }
1398
- return lines.join("\n");
1399
- }
1400
- function stripGeneratedSecretsCache2(content) {
1401
- const idx = content.indexOf(GENERATED_SECRETS_DIVIDER2);
1402
- if (idx < 0) return content.replace(/\s+$/u, "");
1403
- return content.slice(0, idx).replace(/\s+$/u, "");
1404
- }
1405
- function formatSecretsDownload(secrets, format) {
1406
- const entries = Object.entries(secrets).sort(([a], [b]) => a.localeCompare(b));
1407
- switch (format) {
1408
- case "json":
1409
- case "dotnet-json":
1410
- return JSON.stringify(Object.fromEntries(entries), null, 2) + "\n";
1411
- case "env":
1412
- return entries.map(([key, value]) => `${key}=${quoteDotenvValue(value)}`).join("\n") + "\n";
1413
- case "env-no-quotes":
1414
- case "docker":
1415
- return entries.map(([key, value]) => `${key}=${value}`).join("\n") + "\n";
1416
- case "yaml":
1417
- return entries.map(([key, value]) => `${key}: ${JSON.stringify(value)}`).join("\n") + "\n";
1418
- }
1419
- }
1420
- function writeSecretsCacheFile(appDir2, secrets, source) {
1421
- const devVarsPath = secretsCacheFilePath(appDir2);
1422
- const existing = existsSync8(devVarsPath) ? readFileSync7(devVarsPath, "utf-8") : "";
1423
- const head = stripGeneratedSecretsCache2(existing);
1424
- const rendered = renderSecretsCache(secrets, source);
1425
- writeSecretFileSync(devVarsPath, head ? `${head}
1426
-
1427
- ${rendered}
1428
- ` : `${rendered}
1429
- `);
1430
- }
1431
- function clearSecretsCacheFile(appDir2) {
1432
- const devVarsPath = secretsCacheFilePath(appDir2);
1433
- if (!existsSync8(devVarsPath)) return;
1434
- const existing = readFileSync7(devVarsPath, "utf-8");
1435
- const next = stripGeneratedSecretsCache2(existing);
1436
- writeSecretFileSync(devVarsPath, next ? `${next}
1437
- ` : "");
1438
- }
1439
- function secretsTargetMatchesLink(appDir2, target) {
1440
- const link = resolveSecretsLink(appDir2, target.wranglerEnv);
1441
- return link.linked && link.projectName === target.projectName && link.configName === target.configName;
1442
- }
1443
- function appScopedSecretsApiOptions(appDir2, target) {
1444
- const link = resolveSecretsLink(appDir2, target.wranglerEnv);
1445
- if (!link.linked || link.projectName !== target.projectName) return {};
1446
- return { appName: resolveSecretsAppName(appDir2, target.wranglerEnv) };
1447
- }
1448
- function secretsCacheFilePath(appDir2) {
1449
- return join8(appDir2, ".dev.vars");
1450
- }
1451
- async function pullLinkedSecretsCache(deployUrl, token, appDir2, wranglerEnv2) {
1452
- const link = resolveSecretsLink(appDir2, wranglerEnv2);
1453
- if (!link.linked) return null;
1454
- const values = await fetchSecretsValues(deployUrl, token, link.projectName, link.configName, {
1455
- appName: resolveSecretsAppName(appDir2, wranglerEnv2)
1456
- });
1457
- return {
1458
- linked: true,
1459
- projectName: link.projectName,
1460
- configName: link.configName,
1461
- wranglerEnv: wranglerEnv2,
1462
- values
1463
- };
1464
- }
1465
- async function fetchSecretsValues(deployUrl, token, projectName, configName, options = {}) {
1466
- const body = await secretsApi(
1467
- deployUrl,
1468
- token,
1469
- `/api/secrets/${encodeURIComponent(projectName)}/configs/${encodeURIComponent(configName)}/values`,
1470
- {},
1471
- options
1472
- );
1473
- return body.secrets;
1474
- }
1475
- async function listSecrets(deployUrl, token, projectName, configName, options = {}) {
1476
- return secretsApi(
1477
- deployUrl,
1478
- token,
1479
- `/api/secrets/${encodeURIComponent(projectName)}/configs/${encodeURIComponent(configName)}`,
1480
- {},
1481
- options
1482
- );
1483
- }
1484
- async function getSecretPlain(deployUrl, token, projectName, configName, key, options = {}) {
1485
- const body = await secretsApi(
1486
- deployUrl,
1487
- token,
1488
- `/api/secrets/${encodeURIComponent(projectName)}/configs/${encodeURIComponent(configName)}/secrets/${encodeURIComponent(key)}?plain=true`,
1489
- {},
1490
- options
1491
- );
1492
- return body.value;
1493
- }
1494
- async function deleteSecret(deployUrl, token, projectName, configName, key, options = {}) {
1495
- await secretsApi(
1496
- deployUrl,
1497
- token,
1498
- `/api/secrets/${encodeURIComponent(projectName)}/configs/${encodeURIComponent(configName)}/secrets/${encodeURIComponent(key)}`,
1499
- { method: "DELETE" },
1500
- options
1501
- );
1502
- }
1503
- async function uploadSecrets(deployUrl, token, projectName, configName, secrets, replace, options = {}) {
1504
- await secretsApi(
1505
- deployUrl,
1506
- token,
1507
- `/api/secrets/${encodeURIComponent(projectName)}/configs/${encodeURIComponent(configName)}/secrets`,
1508
- {
1509
- method: "PUT",
1510
- body: JSON.stringify({ secrets, replace })
1511
- },
1512
- options
1513
- );
1514
- }
1515
- async function listConfigs(deployUrl, token, projectName, options = {}) {
1516
- const body = await secretsApi(
1517
- deployUrl,
1518
- token,
1519
- `/api/secrets/${encodeURIComponent(projectName)}/configs`,
1520
- {},
1521
- options
1522
- );
1523
- return body.configs;
1524
- }
1525
- async function createConfig(deployUrl, token, projectName, configName, copyFromConfigName, options = {}) {
1526
- return secretsApi(
1527
- deployUrl,
1528
- token,
1529
- `/api/secrets/${encodeURIComponent(projectName)}/configs`,
1530
- {
1531
- method: "POST",
1532
- body: JSON.stringify({
1533
- name: configName,
1534
- ...copyFromConfigName ? { copyFrom: copyFromConfigName } : {}
1535
- })
1536
- },
1537
- options
1538
- );
1539
- }
1540
- async function deleteConfig(deployUrl, token, projectName, configName, options = {}) {
1541
- await secretsApi(
1542
- deployUrl,
1543
- token,
1544
- `/api/secrets/${encodeURIComponent(projectName)}/configs/${encodeURIComponent(configName)}`,
1545
- { method: "DELETE" },
1546
- options
1547
- );
1548
- }
1549
- async function setServerSecretsLink(deployUrl, token, appName, projectName) {
1550
- await secretsApi(deployUrl, token, `/api/secrets-links/${encodeURIComponent(appName)}`, {
1551
- method: "PUT",
1552
- body: JSON.stringify({ projectName })
1553
- });
1554
- }
1555
- async function clearServerSecretsLink(deployUrl, token, appName) {
1556
- await secretsApi(deployUrl, token, `/api/secrets-links/${encodeURIComponent(appName)}`, {
1557
- method: "DELETE"
1558
- });
1559
- }
1560
- function parseSecretsUpload(content) {
1561
- const trimmed = content.trim();
1562
- if (!trimmed) return {};
1563
- if (trimmed.startsWith("{")) {
1564
- const parsed2 = JSON.parse(trimmed);
1565
- if (!parsed2 || typeof parsed2 !== "object" || Array.isArray(parsed2)) {
1566
- throw new Error("JSON upload must be an object of KEY=value strings.");
1567
- }
1568
- const out2 = {};
1569
- for (const [key, value] of Object.entries(parsed2)) {
1570
- const name = validateSecretName(key);
1571
- if (typeof value !== "string") {
1572
- throw new Error(`Secret "${name}" must be a string.`);
1573
- }
1574
- validateSecretValue(name, value);
1575
- out2[name] = value;
1576
- }
1577
- return out2;
1578
- }
1579
- const parsed = parseDevVars(content);
1580
- const out = {};
1581
- for (const [key, value] of Object.entries(parsed)) {
1582
- const name = validateSecretName(key);
1583
- validateSecretValue(name, value);
1584
- out[name] = value;
1585
- }
1586
- return out;
1587
- }
1588
- function writeWranglerSecretsLink(appDir2, target, opts = {}) {
1589
- const path = join8(resolve3(appDir2), "wrangler.toml");
1590
- const raw = readFileSync7(path, "utf-8");
1591
- const table = target.wranglerEnv ? `env.${target.wranglerEnv}.vars` : "vars";
1592
- const vars = {
1593
- [DEEPSPACE_SECRETS_CONFIG_VAR]: target.configName
1594
- };
1595
- if (!target.wranglerEnv || opts.projectOverride) {
1596
- vars[DEEPSPACE_SECRETS_PROJECT_VAR] = target.projectName;
1597
- } else {
1598
- const link = resolveSecretsLink(appDir2, void 0);
1599
- if (!link.linked) {
1600
- vars[DEEPSPACE_SECRETS_PROJECT_VAR] = target.projectName;
1601
- }
1602
- }
1603
- writeFileSync7(path, setTomlVars(raw, table, vars));
1604
- }
1605
- function removeWranglerSecretsLink(appDir2, wranglerEnv2) {
1606
- removeWranglerSecretsLinkKeys(appDir2, wranglerEnv2, ["project", "config"]);
1607
- }
1608
- function removeWranglerSecretsLinkKeys(appDir2, wranglerEnv2, keys) {
1609
- const path = join8(resolve3(appDir2), "wrangler.toml");
1610
- const raw = readFileSync7(path, "utf-8");
1611
- const table = wranglerEnv2 ? `env.${wranglerEnv2}.vars` : "vars";
1612
- const vars = {};
1613
- if (keys.includes("project")) vars[DEEPSPACE_SECRETS_PROJECT_VAR] = void 0;
1614
- if (keys.includes("config")) vars[DEEPSPACE_SECRETS_CONFIG_VAR] = void 0;
1615
- if (Object.keys(vars).length === 0) return;
1616
- writeFileSync7(path, setTomlVars(raw, table, vars));
1617
- }
1618
- function updateWranglerRequiredSecrets(appDir2, secretNames, opts = {}) {
1619
- const path = join8(resolve3(appDir2), "wrangler.toml");
1620
- const raw = readFileSync7(path, "utf-8");
1621
- const existing = readRequiredSecretNamesFromToml(raw);
1622
- const incoming = normalizeRequiredSecretNames(secretNames);
1623
- const next = opts.replace ? incoming : normalizeRequiredSecretNames([...existing, ...incoming]);
1624
- writeFileSync7(
1625
- path,
1626
- setRequiredSecretNamesInToml(raw, next, existing.length > 0 || hasRequiredSecretsBlock(raw))
1627
- );
1628
- return next;
1629
- }
1630
- function removeWranglerRequiredSecrets(appDir2, secretNames) {
1631
- const path = join8(resolve3(appDir2), "wrangler.toml");
1632
- const raw = readFileSync7(path, "utf-8");
1633
- const remove2 = new Set(normalizeRequiredSecretNames(secretNames));
1634
- const next = readRequiredSecretNamesFromToml(raw).filter((key) => !remove2.has(key));
1635
- writeFileSync7(path, setRequiredSecretNamesInToml(raw, next, hasRequiredSecretsBlock(raw)));
1636
- return next;
1637
- }
1638
- function quoteDotenvValue(value) {
1639
- if (value === "") return '""';
1640
- if (/^[^\s"'#=]+$/u.test(value)) return value;
1641
- return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
1642
- }
1643
- var SecretsApiError = class extends Error {
1644
- status;
1645
- detail;
1646
- constructor(path, status2, detail) {
1647
- super(`Secrets API ${path} (${status2}): ${detail}`);
1648
- this.name = "SecretsApiError";
1649
- this.status = status2;
1650
- this.detail = detail;
1651
- }
1652
- };
1653
- async function secretsApi(deployUrl, token, path, init = {}, options = {}) {
1654
- const scopedPath = withSecretsApiOptions(path, options);
1655
- let res = await fetch(`${deployUrl}${scopedPath}`, {
1656
- ...init,
1657
- headers: {
1658
- Authorization: `Bearer ${token}`,
1659
- "Content-Type": "application/json",
1660
- ...init.headers ?? {}
1661
- }
1662
- });
1663
- let text2 = await res.text();
1664
- const parsedError = parseSecretsApiError(text2);
1665
- if (!res.ok && res.status === 404 && parsedError === "app_not_found" && options.appName) {
1666
- res = await fetch(`${deployUrl}${path}`, {
1667
- ...init,
1668
- headers: {
1669
- Authorization: `Bearer ${token}`,
1670
- "Content-Type": "application/json",
1671
- ...init.headers ?? {}
1672
- }
1673
- });
1674
- text2 = await res.text();
1675
- }
1676
- if (!res.ok) {
1677
- let detail = text2;
1678
- try {
1679
- detail = JSON.parse(text2).error ?? text2;
1680
- } catch {
1681
- }
1682
- throw new SecretsApiError(path, res.status, detail);
1683
- }
1684
- if (!text2) return void 0;
1685
- return JSON.parse(text2);
1686
- }
1687
- function withSecretsApiOptions(path, options) {
1688
- if (!options.appName) return path;
1689
- const sep2 = path.includes("?") ? "&" : "?";
1690
- return `${path}${sep2}appName=${encodeURIComponent(options.appName)}`;
1691
- }
1692
- function parseSecretsApiError(text2) {
1693
- try {
1694
- const parsed = JSON.parse(text2);
1695
- return typeof parsed.error === "string" ? parsed.error : null;
1696
- } catch {
1697
- return null;
1698
- }
1699
- }
1700
- function setTomlVars(raw, table, vars) {
1701
- const lines = raw.replace(/\r\n/g, "\n").split("\n");
1702
- const header = `[${table}]`;
1703
- const headerRe = new RegExp(`^\\s*\\[${escapeRegExp(table)}\\]\\s*(?:#.*)?$`);
1704
- let start = lines.findIndex((line) => headerRe.test(line));
1705
- if (start < 0) {
1706
- const additions = Object.entries(vars).filter(([, value]) => value !== void 0).map(([key, value]) => `${key} = ${JSON.stringify(value)}`);
1707
- const prefix = raw.trimEnd();
1708
- return `${prefix}${prefix ? "\n\n" : ""}${header}
1709
- ${additions.join("\n")}
1710
- `;
1711
- }
1712
- let end = start + 1;
1713
- while (end < lines.length && !/^\s*\[\[?[^\]]+\]\]?\s*(?:#.*)?$/u.test(lines[end])) end++;
1714
- const tableLines = lines.slice(start + 1, end);
1715
- const seen = /* @__PURE__ */ new Set();
1716
- const nextTableLines = [];
1717
- for (const line of tableLines) {
1718
- const key = Object.keys(vars).find(
1719
- (name) => new RegExp(`^\\s*${escapeRegExp(name)}\\s*=`).test(line)
1720
- );
1721
- if (!key) {
1722
- nextTableLines.push(line);
1723
- continue;
1724
- }
1725
- seen.add(key);
1726
- const value = vars[key];
1727
- if (value !== void 0) nextTableLines.push(`${key} = ${JSON.stringify(value)}`);
1728
- }
1729
- for (const [key, value] of Object.entries(vars)) {
1730
- if (!seen.has(key) && value !== void 0) {
1731
- nextTableLines.push(`${key} = ${JSON.stringify(value)}`);
1732
- }
1733
- }
1734
- lines.splice(start + 1, end - start - 1, ...nextTableLines);
1735
- return lines.join("\n").replace(/\s*$/u, "\n");
1736
- }
1737
- function readRequiredSecretNamesFromToml(raw) {
1738
- const block = findRequiredSecretsBlock(raw);
1739
- if (!block) return [];
1740
- const names = [];
1741
- for (const line of block.lines.slice(block.start + 1, block.end)) {
1742
- const match = /^\s*#\s+([A-Za-z_][A-Za-z0-9_]*)\s*$/u.exec(line);
1743
- if (match) names.push(validateSecretName(match[1]));
1744
- }
1745
- return normalizeRequiredSecretNames(names);
1746
- }
1747
- function setRequiredSecretNamesInToml(raw, names, keepEmptyBlock) {
1748
- const normalized = raw.replace(/\r\n/g, "\n").replace(/\s*$/u, "\n");
1749
- const block = findRequiredSecretsBlock(normalized);
1750
- if (names.length === 0 && !block && !keepEmptyBlock) return normalized;
1751
- const rendered = renderRequiredSecretsBlock(names);
1752
- if (block) {
1753
- const lines2 = [...block.lines];
1754
- lines2.splice(block.start, block.end - block.start + 1, ...rendered);
1755
- return lines2.join("\n").replace(/\s*$/u, "\n");
1756
- }
1757
- const lines = normalized.split("\n");
1758
- if (lines[lines.length - 1] === "") lines.pop();
1759
- const insertAt = requiredSecretsInsertIndex(lines);
1760
- const insertion = insertAt < lines.length ? [...rendered, ""] : lines.length > 0 ? ["", ...rendered] : rendered;
1761
- lines.splice(insertAt, 0, ...insertion);
1762
- return lines.join("\n").replace(/\s*$/u, "\n");
1763
- }
1764
- function renderRequiredSecretsBlock(names) {
1765
- return [
1766
- REQUIRED_SECRETS_BEGIN,
1767
- ...names.length > 0 ? names.map((name) => `# ${name}`) : ["# (none declared)"],
1768
- REQUIRED_SECRETS_END
1769
- ];
1770
- }
1771
- function normalizeRequiredSecretNames(secretNames) {
1772
- return Array.from(new Set(Array.from(secretNames, (name) => validateSecretName(name)))).sort(
1773
- (a, b) => a.localeCompare(b)
1774
- );
1775
- }
1776
- function hasRequiredSecretsBlock(raw) {
1777
- return findRequiredSecretsBlock(raw) !== null;
1778
- }
1779
- function findRequiredSecretsBlock(raw) {
1780
- const lines = raw.replace(/\r\n/g, "\n").split("\n");
1781
- const start = lines.findIndex((line) => requiredSecretsBeginRe().test(line));
1782
- if (start < 0) return null;
1783
- const end = lines.findIndex((line, index) => index > start && requiredSecretsEndRe().test(line));
1784
- return { lines, start, end: end < 0 ? start : end };
1785
- }
1786
- function requiredSecretsInsertIndex(lines) {
1787
- const varsHeader = /^\s*\[vars\]\s*(?:#.*)?$/u;
1788
- const tableHeader = /^\s*\[\[?[^\]]+\]\]?\s*(?:#.*)?$/u;
1789
- const varsStart = lines.findIndex((line) => varsHeader.test(line));
1790
- if (varsStart < 0) return lines.length;
1791
- let end = varsStart + 1;
1792
- while (end < lines.length && !tableHeader.test(lines[end])) end++;
1793
- while (end > varsStart + 1 && lines[end - 1].trim() === "") end--;
1794
- return end;
1795
- }
1796
- function requiredSecretsBeginRe() {
1797
- return /^# --- DeepSpace (?:detected|required) secrets\b.*---$/u;
1798
- }
1799
- function requiredSecretsEndRe() {
1800
- return /^# --- end DeepSpace (?:detected|required) secrets ---$/u;
1801
- }
1802
- function escapeRegExp(value) {
1803
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1804
- }
1805
-
1806
- // src/cli/lib/port.ts
1807
- var DEFAULT_PORT = 5173;
1808
- function resolvePort(arg) {
1809
- const raw = arg ?? process.env.DEEPSPACE_PORT;
1810
- if (!raw) return DEFAULT_PORT;
1811
- const n = Number(raw);
1812
- if (!Number.isInteger(n) || n < 1 || n > 65535) {
1813
- console.error(`Invalid port: ${raw}. Must be an integer between 1 and 65535.`);
1814
- process.exit(1);
1815
- }
1816
- return n;
1380
+ return n;
1817
1381
  }
1818
1382
 
1819
1383
  // src/cli/commands/dev.ts
@@ -1842,10 +1406,10 @@ var dev_default = defineCommand3({
1842
1406
  },
1843
1407
  async run({ args }) {
1844
1408
  preflightNodeVersion("dev");
1845
- const wranglerEnv2 = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
1409
+ const wranglerEnv = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
1846
1410
  const start = resolve4(args.dir ?? ".");
1847
- const appDir2 = findAppDir(start);
1848
- if (!appDir2) {
1411
+ const appDir = findAppDir(start);
1412
+ if (!appDir) {
1849
1413
  console.error(`No wrangler.toml found at or above ${start}.`);
1850
1414
  const children = findChildApps(start);
1851
1415
  if (children.length > 0) {
@@ -1856,20 +1420,20 @@ var dev_default = defineCommand3({
1856
1420
  }
1857
1421
  process.exit(1);
1858
1422
  }
1859
- const junk = removeMacosJunk(appDir2);
1423
+ const junk = removeMacosJunk(appDir);
1860
1424
  if (junk > 0) console.log(`Removed ${junk} macOS metadata file(s) (._*, .DS_Store)`);
1861
- const worktree = detectClaudeWorktree(appDir2);
1425
+ const worktree = detectClaudeWorktree(appDir);
1862
1426
  const explicitPort = Boolean(args.port);
1863
1427
  let port = worktree && !explicitPort ? deriveWorktreePort(worktree.worktreeName) : resolvePort(args.port);
1864
1428
  if (worktree) {
1865
1429
  const upserted = upsertWorktreeLaunchConfig(
1866
1430
  worktree.mainRepoRoot,
1867
1431
  worktree.worktreeName,
1868
- appDir2,
1432
+ appDir,
1869
1433
  {
1870
1434
  port,
1871
1435
  probePort: !explicitPort,
1872
- extraArgs: [...wranglerEnv2 ? ["--env", wranglerEnv2] : []]
1436
+ extraArgs: [...wranglerEnv ? ["--env", wranglerEnv] : []]
1873
1437
  }
1874
1438
  );
1875
1439
  if (upserted) {
@@ -1888,8 +1452,8 @@ var dev_default = defineCommand3({
1888
1452
  );
1889
1453
  }
1890
1454
  }
1891
- writeLaunchConfigIfMissing(appDir2, detectAppName(appDir2) ?? basename(appDir2), port);
1892
- ensureInstallReady(appDir2);
1455
+ writeLaunchConfigIfMissing(appDir, detectAppName(appDir) ?? basename(appDir), port);
1456
+ ensureInstallReady(appDir);
1893
1457
  let token;
1894
1458
  try {
1895
1459
  token = await ensureToken();
@@ -1905,39 +1469,40 @@ var dev_default = defineCommand3({
1905
1469
  process.exit(1);
1906
1470
  }
1907
1471
  console.log(`Logged in as ${payload.name ?? payload.email}`);
1908
- if (wranglerEnv2) console.log(`Wrangler env: ${wranglerEnv2}`);
1472
+ if (wranglerEnv) console.log(`Wrangler env: ${wranglerEnv}`);
1909
1473
  console.log(`Port: ${port}`);
1910
1474
  let generatedSecretsCache;
1911
- try {
1912
- const linkedSecrets = await pullLinkedSecretsCache(DEPLOY_URL, token, appDir2, wranglerEnv2);
1913
- if (linkedSecrets) {
1914
- generatedSecretsCache = renderSecretsCache(linkedSecrets.values, linkedSecrets);
1915
- console.log(
1916
- `Secrets: ${linkedSecrets.projectName}/${linkedSecrets.configName} (${Object.keys(linkedSecrets.values).length})`
1475
+ const appIdForSecrets = readAppId(appDir, wranglerEnv);
1476
+ if (appIdForSecrets) {
1477
+ try {
1478
+ const refreshed = await refreshSecretsCache(DEPLOY_URL, token, appIdForSecrets, wranglerEnv);
1479
+ if (refreshed) {
1480
+ generatedSecretsCache = refreshed.rendered;
1481
+ console.log(refreshed.summary);
1482
+ }
1483
+ } catch (err) {
1484
+ console.error(
1485
+ `Failed to refresh app secrets: ${err instanceof Error ? err.message : String(err)}`
1917
1486
  );
1487
+ process.exit(1);
1918
1488
  }
1919
- } catch (err) {
1920
- console.error(
1921
- `Failed to refresh linked secrets: ${err instanceof Error ? err.message : String(err)}`
1922
- );
1923
- process.exit(1);
1924
1489
  }
1925
1490
  const sharedDevVarsCache = generatedSecretsCache !== void 0;
1926
- await writeDevVars(appDir2, payload.sub, token, wranglerEnv2, {
1491
+ await writeDevVars(appDir, payload.sub, token, wranglerEnv, {
1927
1492
  generatedSecretsCache,
1928
1493
  sharedDevVarsCache
1929
1494
  });
1930
1495
  let wranglerConfig;
1931
1496
  try {
1932
- wranglerConfig = prepareWranglerEnvConfig(appDir2, wranglerEnv2, { sharedDevVarsCache });
1497
+ wranglerConfig = prepareWranglerEnvConfig(appDir, wranglerEnv, { sharedDevVarsCache });
1933
1498
  } catch (err) {
1934
1499
  console.error(err instanceof Error ? err.message : String(err));
1935
1500
  process.exit(1);
1936
1501
  }
1937
- preflightWindowsWorkerd(appDir2);
1502
+ preflightWindowsWorkerd(appDir);
1938
1503
  console.log("Starting dev server...\n");
1939
1504
  const vite = spawn("npx", ["vite", "--port", String(port), "--strictPort", "--host"], {
1940
- cwd: appDir2,
1505
+ cwd: appDir,
1941
1506
  stdio: "inherit",
1942
1507
  env: wranglerViteEnv(process.env, wranglerConfig, { DEEPSPACE_PORT: String(port) })
1943
1508
  });
@@ -2110,18 +1675,18 @@ import { sync as spawnSync3 } from "cross-spawn";
2110
1675
  // src/cli/lib/playwright.ts
2111
1676
  import { execSync } from "child_process";
2112
1677
  import { sync as spawnSync2 } from "cross-spawn";
2113
- function ensurePlaywright(appDir2) {
1678
+ function ensurePlaywright(appDir) {
2114
1679
  try {
2115
- execSync("npx playwright --version", { cwd: appDir2, stdio: "pipe" });
1680
+ execSync("npx playwright --version", { cwd: appDir, stdio: "pipe" });
2116
1681
  } catch {
2117
1682
  console.log("Installing Playwright...");
2118
- execSync("npm install -D @playwright/test", { cwd: appDir2, stdio: "pipe" });
1683
+ execSync("npm install -D @playwright/test", { cwd: appDir, stdio: "pipe" });
2119
1684
  }
2120
1685
  const isLinux = process.platform === "linux";
2121
1686
  const isRoot = isLinux && typeof process.getuid === "function" && process.getuid() === 0;
2122
1687
  const args = isRoot ? ["install", "--with-deps", "chromium"] : ["install", "chromium"];
2123
1688
  const result = spawnSync2("npx", ["playwright", ...args], {
2124
- cwd: appDir2,
1689
+ cwd: appDir,
2125
1690
  stdio: "inherit"
2126
1691
  });
2127
1692
  if (result.status !== 0) {
@@ -2158,10 +1723,10 @@ var test_default = defineCommand5({
2158
1723
  async run({ args }) {
2159
1724
  preflightNodeVersion("test");
2160
1725
  const suite = args.suite ?? "default";
2161
- const wranglerEnv2 = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
1726
+ const wranglerEnv = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
2162
1727
  const start = resolve5(".");
2163
- const appDir2 = findAppDir(start);
2164
- if (!appDir2) {
1728
+ const appDir = findAppDir(start);
1729
+ if (!appDir) {
2165
1730
  console.error(`No wrangler.toml found at or above ${start}.`);
2166
1731
  const children = findChildApps(start);
2167
1732
  if (children.length > 0) {
@@ -2172,14 +1737,14 @@ var test_default = defineCommand5({
2172
1737
  }
2173
1738
  process.exit(1);
2174
1739
  }
2175
- const worktreePort = args.port ? null : resolveWorktreePort(appDir2);
1740
+ const worktreePort = args.port ? null : resolveWorktreePort(appDir);
2176
1741
  const port = worktreePort ?? resolvePort(args.port);
2177
1742
  if (worktreePort && process.env.DEEPSPACE_PORT) {
2178
1743
  console.log(
2179
1744
  `Ignoring DEEPSPACE_PORT=${process.env.DEEPSPACE_PORT} inside a worktree \u2014 targeting per-worktree port ${port}. Pass --port to override.`
2180
1745
  );
2181
1746
  }
2182
- ensureInstallReady(appDir2);
1747
+ ensureInstallReady(appDir);
2183
1748
  let token;
2184
1749
  let ownerId;
2185
1750
  try {
@@ -2191,72 +1756,73 @@ var test_default = defineCommand5({
2191
1756
  process.exit(1);
2192
1757
  }
2193
1758
  let generatedSecretsCache;
2194
- try {
2195
- const linkedSecrets = await pullLinkedSecretsCache(DEPLOY_URL2, token, appDir2, wranglerEnv2);
2196
- if (linkedSecrets) {
2197
- generatedSecretsCache = renderSecretsCache(linkedSecrets.values, linkedSecrets);
2198
- console.log(
2199
- `Secrets: ${linkedSecrets.projectName}/${linkedSecrets.configName} (${Object.keys(linkedSecrets.values).length})`
1759
+ const appIdForSecrets = readAppId(appDir, wranglerEnv);
1760
+ if (appIdForSecrets) {
1761
+ try {
1762
+ const refreshed = await refreshSecretsCache(DEPLOY_URL2, token, appIdForSecrets, wranglerEnv);
1763
+ if (refreshed) {
1764
+ generatedSecretsCache = refreshed.rendered;
1765
+ console.log(refreshed.summary);
1766
+ }
1767
+ } catch (err) {
1768
+ console.error(
1769
+ `Failed to refresh app secrets: ${err instanceof Error ? err.message : String(err)}`
2200
1770
  );
1771
+ process.exit(1);
2201
1772
  }
2202
- } catch (err) {
2203
- console.error(
2204
- `Failed to refresh linked secrets: ${err instanceof Error ? err.message : String(err)}`
2205
- );
2206
- process.exit(1);
2207
1773
  }
2208
1774
  const sharedDevVarsCache = generatedSecretsCache !== void 0;
2209
- await writeDevVars(appDir2, ownerId, token, wranglerEnv2, {
1775
+ await writeDevVars(appDir, ownerId, token, wranglerEnv, {
2210
1776
  generatedSecretsCache,
2211
1777
  sharedDevVarsCache
2212
1778
  });
2213
1779
  if (suite !== "unit") {
2214
- preflightWindowsWorkerd(appDir2);
2215
- ensurePlaywright(appDir2);
1780
+ preflightWindowsWorkerd(appDir);
1781
+ ensurePlaywright(appDir);
2216
1782
  }
2217
1783
  let exitCode = 0;
2218
1784
  switch (suite) {
2219
1785
  case "smoke":
2220
1786
  exitCode = runPlaywright(
2221
- appDir2,
1787
+ appDir,
2222
1788
  ["tests/smoke.spec.ts"],
2223
1789
  port,
2224
- wranglerEnv2,
1790
+ wranglerEnv,
2225
1791
  sharedDevVarsCache
2226
1792
  );
2227
1793
  break;
2228
1794
  case "api":
2229
1795
  exitCode = runPlaywright(
2230
- appDir2,
1796
+ appDir,
2231
1797
  ["tests/api.spec.ts"],
2232
1798
  port,
2233
- wranglerEnv2,
1799
+ wranglerEnv,
2234
1800
  sharedDevVarsCache
2235
1801
  );
2236
1802
  break;
2237
1803
  case "e2e":
2238
- exitCode = runPlaywright(appDir2, [], port, wranglerEnv2, sharedDevVarsCache);
1804
+ exitCode = runPlaywright(appDir, [], port, wranglerEnv, sharedDevVarsCache);
2239
1805
  break;
2240
1806
  case "unit":
2241
- exitCode = runVitest(appDir2);
1807
+ exitCode = runVitest(appDir);
2242
1808
  break;
2243
1809
  case "all":
2244
- exitCode = runVitest(appDir2);
1810
+ exitCode = runVitest(appDir);
2245
1811
  if (exitCode === 0)
2246
- exitCode = runPlaywright(appDir2, [], port, wranglerEnv2, sharedDevVarsCache);
1812
+ exitCode = runPlaywright(appDir, [], port, wranglerEnv, sharedDevVarsCache);
2247
1813
  break;
2248
1814
  case "default":
2249
1815
  exitCode = runPlaywright(
2250
- appDir2,
1816
+ appDir,
2251
1817
  ["tests/smoke.spec.ts", "tests/api.spec.ts"],
2252
1818
  port,
2253
- wranglerEnv2,
1819
+ wranglerEnv,
2254
1820
  sharedDevVarsCache
2255
1821
  );
2256
1822
  break;
2257
1823
  default:
2258
1824
  if (suite.endsWith(".spec.ts")) {
2259
- exitCode = runPlaywright(appDir2, [suite], port, wranglerEnv2, sharedDevVarsCache);
1825
+ exitCode = runPlaywright(appDir, [suite], port, wranglerEnv, sharedDevVarsCache);
2260
1826
  } else {
2261
1827
  console.error(`Unknown test suite: ${suite}`);
2262
1828
  console.error("Available: smoke, api, e2e, unit, all");
@@ -2266,10 +1832,10 @@ var test_default = defineCommand5({
2266
1832
  process.exit(exitCode);
2267
1833
  }
2268
1834
  });
2269
- function runPlaywright(appDir2, testFiles, port, wranglerEnv2, sharedDevVarsCache = false) {
1835
+ function runPlaywright(appDir, testFiles, port, wranglerEnv, sharedDevVarsCache = false) {
2270
1836
  let wranglerConfig;
2271
1837
  try {
2272
- wranglerConfig = prepareWranglerEnvConfig(appDir2, wranglerEnv2, { sharedDevVarsCache });
1838
+ wranglerConfig = prepareWranglerEnvConfig(appDir, wranglerEnv, { sharedDevVarsCache });
2273
1839
  } catch (err) {
2274
1840
  console.error(err instanceof Error ? err.message : String(err));
2275
1841
  return 1;
@@ -2279,7 +1845,7 @@ function runPlaywright(appDir2, testFiles, port, wranglerEnv2, sharedDevVarsCach
2279
1845
  "npx",
2280
1846
  ["playwright", "test", "--config", "tests/playwright.config.ts", ...testFiles],
2281
1847
  {
2282
- cwd: appDir2,
1848
+ cwd: appDir,
2283
1849
  stdio: "inherit",
2284
1850
  env: wranglerViteEnv(process.env, wranglerConfig, { DEEPSPACE_PORT: String(port) })
2285
1851
  }
@@ -2289,9 +1855,9 @@ function runPlaywright(appDir2, testFiles, port, wranglerEnv2, sharedDevVarsCach
2289
1855
  wranglerConfig.cleanup();
2290
1856
  }
2291
1857
  }
2292
- function runVitest(appDir2) {
1858
+ function runVitest(appDir) {
2293
1859
  const result = spawnSync3("npx", ["vitest", "run", "--passWithNoTests"], {
2294
- cwd: appDir2,
1860
+ cwd: appDir,
2295
1861
  stdio: "inherit"
2296
1862
  });
2297
1863
  return result.status ?? 1;
@@ -2329,15 +1895,15 @@ var screenshot_default = defineCommand6({
2329
1895
  }
2330
1896
  },
2331
1897
  run({ args }) {
2332
- const appDir2 = resolve6(".");
2333
- ensurePlaywright(appDir2);
1898
+ const appDir = resolve6(".");
1899
+ ensurePlaywright(appDir);
2334
1900
  const playwrightArgs = ["playwright", "screenshot", args.url, args.output];
2335
1901
  if (args["full-page"]) playwrightArgs.push("--full-page");
2336
1902
  if (args["wait-for-timeout"]) {
2337
1903
  playwrightArgs.push("--wait-for-timeout", String(args["wait-for-timeout"]));
2338
1904
  }
2339
1905
  const result = spawnSync4("npx", playwrightArgs, {
2340
- cwd: appDir2,
1906
+ cwd: appDir,
2341
1907
  stdio: "inherit"
2342
1908
  });
2343
1909
  process.exit(result.status ?? 1);
@@ -2631,16 +2197,220 @@ var test_accounts_default = defineCommand7({
2631
2197
  delete: del,
2632
2198
  clear
2633
2199
  }
2634
- });
2200
+ });
2201
+
2202
+ // src/cli/commands/deploy.ts
2203
+ import { defineCommand as defineCommand8 } from "citty";
2204
+ import { execSync as execSync2 } from "child_process";
2205
+ import { randomUUID } from "crypto";
2206
+ import { readFileSync as readFileSync9, readdirSync as readdirSync3, existsSync as existsSync10, writeFileSync as writeFileSync9 } from "fs";
2207
+ import { join as join11, resolve as resolve7, dirname as dirname2 } from "path";
2208
+ import { pathToFileURL } from "url";
2209
+ import * as p4 from "@clack/prompts";
2210
+
2211
+ // src/server/rooms/binding-manifest.ts
2212
+ var AUTO_PROVISION_SENTINEL = "auto";
2213
+ var VECTORIZE_METRICS = /* @__PURE__ */ new Set(["cosine", "euclidean", "dot-product"]);
2214
+ var ALLOWED_BINDING_TYPES = /* @__PURE__ */ new Set([
2215
+ "vectorize",
2216
+ "ai",
2217
+ "r2_bucket",
2218
+ "kv_namespace",
2219
+ "d1",
2220
+ "queue",
2221
+ "browser_rendering",
2222
+ "analytics_engine",
2223
+ "hyperdrive"
2224
+ ]);
2225
+ var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
2226
+ "ASSETS",
2227
+ "PLATFORM_WORKER",
2228
+ "API_WORKER",
2229
+ "APP_NAME",
2230
+ "OWNER_USER_ID",
2231
+ "AUTH_JWT_PUBLIC_KEY",
2232
+ "AUTH_JWT_ISSUER",
2233
+ "AUTH_WORKER_URL",
2234
+ "APP_IDENTITY_TOKEN",
2235
+ "APP_OWNER_JWT",
2236
+ "USAGE_EVENTS"
2237
+ ]);
2238
+ function validateBindingManifest(manifest) {
2239
+ if (!Array.isArray(manifest)) {
2240
+ return { valid: false, errors: [{ reason: "Manifest must be an array" }] };
2241
+ }
2242
+ const errors = [];
2243
+ const seenNames = /* @__PURE__ */ new Set();
2244
+ for (const entry of manifest) {
2245
+ if (!entry || typeof entry !== "object") {
2246
+ errors.push({ reason: `Entry must be an object (got ${entry === null ? "null" : typeof entry})` });
2247
+ continue;
2248
+ }
2249
+ const e = entry;
2250
+ const type = e.type;
2251
+ const name = e.name;
2252
+ if (typeof type !== "string" || !ALLOWED_BINDING_TYPES.has(type)) {
2253
+ errors.push({
2254
+ reason: `Disallowed binding type: ${typeof type === "string" ? type : `(${typeof type})`}. Allowed: ${[...ALLOWED_BINDING_TYPES].sort().join(", ")}`
2255
+ });
2256
+ continue;
2257
+ }
2258
+ if (typeof name !== "string" || !name) {
2259
+ errors.push({ reason: `Binding of type "${type}" missing 'name' (string)` });
2260
+ continue;
2261
+ }
2262
+ if (RESERVED_BINDING_NAMES.has(name)) {
2263
+ errors.push({ reason: `Binding name "${name}" is reserved by the SDK` });
2264
+ continue;
2265
+ }
2266
+ if (seenNames.has(name)) {
2267
+ errors.push({ reason: `Duplicate binding name "${name}"` });
2268
+ continue;
2269
+ }
2270
+ seenNames.add(name);
2271
+ const binding = entry;
2272
+ const fieldErr = requiredFieldError(binding);
2273
+ if (fieldErr) errors.push({ binding, reason: fieldErr });
2274
+ }
2275
+ if (errors.length) return { valid: false, errors };
2276
+ return { valid: true, bindings: manifest };
2277
+ }
2278
+ function requiredFieldError(b) {
2279
+ switch (b.type) {
2280
+ case "vectorize": {
2281
+ if (!b.index_name) return `vectorize binding "${b.name}" missing index_name (or "auto")`;
2282
+ if (b.index_name === AUTO_PROVISION_SENTINEL) {
2283
+ if (typeof b.dimensions !== "number" || b.dimensions <= 0) {
2284
+ return `vectorize binding "${b.name}" with index_name="auto" requires "dimensions" (positive number)`;
2285
+ }
2286
+ if (!b.metric || !VECTORIZE_METRICS.has(b.metric)) {
2287
+ return `vectorize binding "${b.name}" with index_name="auto" requires "metric" (one of: ${[...VECTORIZE_METRICS].join(", ")})`;
2288
+ }
2289
+ }
2290
+ return null;
2291
+ }
2292
+ case "r2_bucket":
2293
+ return b.bucket_name ? null : `r2_bucket binding "${b.name}" missing bucket_name (or "auto")`;
2294
+ case "kv_namespace": {
2295
+ if (!b.namespace_id) return `kv_namespace binding "${b.name}" missing namespace_id (or "auto")`;
2296
+ if (b.namespace_id === AUTO_PROVISION_SENTINEL && !b.title) {
2297
+ return `kv_namespace binding "${b.name}" with namespace_id="auto" requires "title"`;
2298
+ }
2299
+ return null;
2300
+ }
2301
+ case "d1": {
2302
+ if (!b.id) return `d1 binding "${b.name}" missing id (or "auto")`;
2303
+ if (b.id === AUTO_PROVISION_SENTINEL && !b.database_name) {
2304
+ return `d1 binding "${b.name}" with id="auto" requires "database_name"`;
2305
+ }
2306
+ return null;
2307
+ }
2308
+ case "queue":
2309
+ return b.queue_name ? null : `queue binding "${b.name}" missing queue_name (or "auto")`;
2310
+ case "hyperdrive":
2311
+ if (!b.id) return `hyperdrive binding "${b.name}" missing id`;
2312
+ if (b.id === AUTO_PROVISION_SENTINEL) {
2313
+ return `hyperdrive binding "${b.name}": auto-provisioning not yet supported (provide a real id)`;
2314
+ }
2315
+ return null;
2316
+ case "ai":
2317
+ case "browser_rendering":
2318
+ case "analytics_engine":
2319
+ return null;
2320
+ // no required fields beyond `name`
2321
+ default: {
2322
+ const _exhaustive = b;
2323
+ void _exhaustive;
2324
+ return null;
2325
+ }
2326
+ }
2327
+ }
2328
+ function bindingManifestFromOutputConfig(outputConfig) {
2329
+ const out = [];
2330
+ const aiBinding = pluckString(outputConfig.ai, "binding");
2331
+ if (aiBinding) out.push({ type: "ai", name: aiBinding });
2332
+ for (const v of asObjectArray(outputConfig.vectorize)) {
2333
+ const name = pluckString(v, "binding");
2334
+ const index_name = pluckString(v, "index_name");
2335
+ if (!name || !index_name) continue;
2336
+ const dimensions = pluckNumber(v, "dimensions");
2337
+ const metric = pluckString(v, "metric");
2338
+ out.push({
2339
+ type: "vectorize",
2340
+ name,
2341
+ index_name,
2342
+ ...dimensions != null && { dimensions },
2343
+ ...metric && (metric === "cosine" || metric === "euclidean" || metric === "dot-product") && { metric }
2344
+ });
2345
+ }
2346
+ for (const r of asObjectArray(outputConfig.r2_buckets)) {
2347
+ const name = pluckString(r, "binding");
2348
+ const bucket_name = pluckString(r, "bucket_name");
2349
+ if (name && bucket_name) out.push({ type: "r2_bucket", name, bucket_name });
2350
+ }
2351
+ for (const k of asObjectArray(outputConfig.kv_namespaces)) {
2352
+ const name = pluckString(k, "binding");
2353
+ const namespace_id = pluckString(k, "id");
2354
+ if (!name || !namespace_id) continue;
2355
+ const title = pluckString(k, "title");
2356
+ out.push({
2357
+ type: "kv_namespace",
2358
+ name,
2359
+ namespace_id,
2360
+ ...title && { title }
2361
+ });
2362
+ }
2363
+ for (const d of asObjectArray(outputConfig.d1_databases)) {
2364
+ const name = pluckString(d, "binding");
2365
+ const id = pluckString(d, "database_id");
2366
+ if (!name || !id) continue;
2367
+ const database_name = pluckString(d, "database_name");
2368
+ out.push({
2369
+ type: "d1",
2370
+ name,
2371
+ id,
2372
+ ...database_name && { database_name }
2373
+ });
2374
+ }
2375
+ const queues = isObject(outputConfig.queues) ? outputConfig.queues : null;
2376
+ for (const q of asObjectArray(queues?.producers)) {
2377
+ const name = pluckString(q, "binding");
2378
+ const queue_name = pluckString(q, "queue");
2379
+ if (name && queue_name) out.push({ type: "queue", name, queue_name });
2380
+ }
2381
+ const browserBinding = pluckString(outputConfig.browser, "binding");
2382
+ if (browserBinding) out.push({ type: "browser_rendering", name: browserBinding });
2383
+ for (const a of asObjectArray(outputConfig.analytics_engine_datasets)) {
2384
+ const name = pluckString(a, "binding");
2385
+ const dataset = pluckString(a, "dataset");
2386
+ if (name) out.push({ type: "analytics_engine", name, dataset: dataset ?? void 0 });
2387
+ }
2388
+ for (const h of asObjectArray(outputConfig.hyperdrive)) {
2389
+ const name = pluckString(h, "binding");
2390
+ const id = pluckString(h, "id");
2391
+ if (name && id) out.push({ type: "hyperdrive", name, id });
2392
+ }
2393
+ return out;
2394
+ }
2395
+ function isObject(v) {
2396
+ return v !== null && typeof v === "object" && !Array.isArray(v);
2397
+ }
2398
+ function pluckString(v, key) {
2399
+ if (!isObject(v)) return null;
2400
+ const val = v[key];
2401
+ return typeof val === "string" && val.length > 0 ? val : null;
2402
+ }
2403
+ function pluckNumber(v, key) {
2404
+ if (!isObject(v)) return null;
2405
+ const val = v[key];
2406
+ return typeof val === "number" && Number.isFinite(val) ? val : null;
2407
+ }
2408
+ function asObjectArray(v) {
2409
+ if (!Array.isArray(v)) return [];
2410
+ return v.filter(isObject);
2411
+ }
2635
2412
 
2636
2413
  // src/cli/commands/deploy.ts
2637
- import { defineCommand as defineCommand8 } from "citty";
2638
- import { execSync as execSync2 } from "child_process";
2639
- import { randomUUID } from "crypto";
2640
- import { readFileSync as readFileSync9, readdirSync as readdirSync3, existsSync as existsSync10 } from "fs";
2641
- import { join as join11, resolve as resolve7, dirname as dirname2 } from "path";
2642
- import { pathToFileURL } from "url";
2643
- import * as p4 from "@clack/prompts";
2644
2414
  var DEPLOY_URL3 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
2645
2415
  var API_URL = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
2646
2416
  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.";
@@ -2670,8 +2440,8 @@ function extractRunWorkerFirst(cfg) {
2670
2440
  function errMessage(err) {
2671
2441
  return err instanceof Error ? err.message : String(err);
2672
2442
  }
2673
- function isLikelyCloudflareDeployFailure(status2, error) {
2674
- if (status2 >= 500) return true;
2443
+ function isLikelyCloudflareDeployFailure(status3, error) {
2444
+ if (status3 >= 500) return true;
2675
2445
  if (!error) return false;
2676
2446
  return [
2677
2447
  "Upload session failed",
@@ -2681,9 +2451,9 @@ function isLikelyCloudflareDeployFailure(status2, error) {
2681
2451
  "Internal Server Error"
2682
2452
  ].some((needle) => error.includes(needle));
2683
2453
  }
2684
- function formatDeployWorkerError(status2, error) {
2685
- const detail = error ?? `Deployment error (${status2})`;
2686
- if (!isLikelyCloudflareDeployFailure(status2, error)) return detail;
2454
+ function formatDeployWorkerError(status3, error) {
2455
+ const detail = error ?? `Deployment error (${status3})`;
2456
+ if (!isLikelyCloudflareDeployFailure(status3, error)) return detail;
2687
2457
  return `${CLOUDFLARE_DEPLOY_ERROR_HINT}
2688
2458
 
2689
2459
  Underlying error: ${detail}`;
@@ -2735,10 +2505,10 @@ async function postWithRetry(url, makeInit, {
2735
2505
  if (lastRes) return lastRes;
2736
2506
  throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
2737
2507
  }
2738
- async function abortStagedUpload(appName, uploadId, totalGroups, token) {
2508
+ async function abortStagedUpload(appId, uploadId, totalGroups, token) {
2739
2509
  try {
2740
2510
  await fetch(
2741
- `${DEPLOY_URL3}/api/deploy/${appName}/assets?uploadId=${uploadId}&totalGroups=${totalGroups}`,
2511
+ `${DEPLOY_URL3}/api/deploy/${appId}/assets?uploadId=${uploadId}&totalGroups=${totalGroups}`,
2742
2512
  { method: "DELETE", headers: { Authorization: `Bearer ${token}` } }
2743
2513
  );
2744
2514
  } catch {
@@ -2759,19 +2529,24 @@ var deploy_default = defineCommand8({
2759
2529
  type: "string",
2760
2530
  description: "wrangler.toml [env.<name>] block to deploy (e.g. --env staging). Omit to deploy the top-level config.",
2761
2531
  required: false
2532
+ },
2533
+ rename: {
2534
+ type: "boolean",
2535
+ 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.",
2536
+ default: false
2762
2537
  }
2763
2538
  },
2764
2539
  async run({ args }) {
2765
2540
  preflightNodeVersion("deploy");
2766
- const appDir2 = resolve7(args.dir ?? ".");
2541
+ const appDir = resolve7(args.dir ?? ".");
2767
2542
  const envName = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
2768
- if (!hasWranglerConfig(appDir2)) {
2543
+ if (!hasWranglerConfig(appDir)) {
2769
2544
  console.error("No wrangler.toml found. Are you in a DeepSpace app directory?");
2770
2545
  process.exit(1);
2771
2546
  }
2772
- ensureInstallReady(appDir2);
2547
+ ensureInstallReady(appDir);
2773
2548
  p4.intro("Deploying DeepSpace app");
2774
- const wranglerConfig = readWranglerConfig(appDir2);
2549
+ const wranglerConfig = readWranglerConfig(appDir);
2775
2550
  const nameRes = resolveAppNameForEnv(wranglerConfig, envName);
2776
2551
  if (!nameRes.ok) {
2777
2552
  p4.cancel(nameRes.reason);
@@ -2786,7 +2561,14 @@ var deploy_default = defineCommand8({
2786
2561
  process.exit(1);
2787
2562
  }
2788
2563
  const appName = nameRes.name;
2564
+ let appId = readAppId(appDir, envName);
2565
+ if (!appId) {
2566
+ appId = mintAppId();
2567
+ writeAppId(appDir, appId, { wranglerEnv: envName });
2568
+ p4.log.info(`Minted app id ${appId} \u2014 commit wrangler.toml.`);
2569
+ }
2789
2570
  p4.log.info(envName ? `App: ${appName} (env: ${envName})` : `App: ${appName}`);
2571
+ p4.log.info(`Id: ${appId}`);
2790
2572
  let token;
2791
2573
  let ownerId;
2792
2574
  try {
@@ -2796,41 +2578,33 @@ var deploy_default = defineCommand8({
2796
2578
  p4.cancel(errMessage(err));
2797
2579
  process.exit(1);
2798
2580
  }
2799
- const localSecretsLink = resolveSecretsLink(appDir2, envName);
2800
- if (localSecretsLink.linked) {
2801
- try {
2802
- await setServerSecretsLink(DEPLOY_URL3, token, appName, localSecretsLink.projectName);
2803
- } catch {
2804
- }
2805
- }
2806
2581
  let linkedSecrets = null;
2807
2582
  try {
2808
- linkedSecrets = await pullLinkedSecretsCache(DEPLOY_URL3, token, appDir2, envName);
2809
- if (linkedSecrets) {
2810
- const rendered = renderSecretsCache(linkedSecrets.values, linkedSecrets);
2811
- await writeDevVars(appDir2, ownerId, token, envName, {
2812
- generatedSecretsCache: rendered,
2583
+ const refreshed = await refreshSecretsCache(DEPLOY_URL3, token, appId, envName);
2584
+ if (refreshed) {
2585
+ linkedSecrets = refreshed.pulled;
2586
+ await writeDevVars(appDir, ownerId, token, envName, {
2587
+ appId,
2588
+ generatedSecretsCache: refreshed.rendered,
2813
2589
  sharedDevVarsCache: true
2814
2590
  });
2815
- p4.log.info(
2816
- `Secrets cache: ${linkedSecrets.projectName}/${linkedSecrets.configName} (${Object.keys(linkedSecrets.values).length} secret${Object.keys(linkedSecrets.values).length === 1 ? "" : "s"})`
2817
- );
2591
+ p4.log.info(refreshed.summary);
2818
2592
  }
2819
2593
  } catch (err) {
2820
- p4.cancel(`Failed to refresh linked secrets before deploy: ${errMessage(err)}`);
2594
+ p4.cancel(`Failed to refresh app secrets before deploy: ${errMessage(err)}`);
2821
2595
  process.exit(1);
2822
2596
  }
2823
- const junk = removeMacosJunk(appDir2);
2597
+ const junk = removeMacosJunk(appDir);
2824
2598
  if (junk > 0) p4.log.info(`Removed ${junk} macOS metadata file(s) (._*, .DS_Store)`);
2825
2599
  const s = createSpinner();
2826
2600
  s.start("Building...");
2827
2601
  let preparedWranglerConfig;
2828
2602
  try {
2829
- preparedWranglerConfig = prepareWranglerEnvConfig(appDir2, envName, {
2830
- sharedDevVarsCache: localSecretsLink.linked
2603
+ preparedWranglerConfig = prepareWranglerEnvConfig(appDir, envName, {
2604
+ sharedDevVarsCache: linkedSecrets !== null
2831
2605
  });
2832
2606
  execSync2("npx vite build", {
2833
- cwd: appDir2,
2607
+ cwd: appDir,
2834
2608
  stdio: "pipe",
2835
2609
  env: wranglerViteEnv(process.env, preparedWranglerConfig)
2836
2610
  });
@@ -2844,7 +2618,7 @@ var deploy_default = defineCommand8({
2844
2618
  preparedWranglerConfig?.cleanup();
2845
2619
  }
2846
2620
  s.stop("Built");
2847
- const deployConfigPath = join11(appDir2, ".wrangler", "deploy", "config.json");
2621
+ const deployConfigPath = join11(appDir, ".wrangler", "deploy", "config.json");
2848
2622
  if (!existsSync10(deployConfigPath)) {
2849
2623
  p4.cancel("Build output config not found at .wrangler/deploy/config.json");
2850
2624
  process.exit(1);
@@ -2906,15 +2680,13 @@ var deploy_default = defineCommand8({
2906
2680
  `Custom bindings: ${customBindings.map((b) => `${b.name} (${b.type})`).join(", ")}`
2907
2681
  );
2908
2682
  }
2909
- const devVarsPath = devVarsPathFor(appDir2, envName, {
2910
- sharedDevVarsCache: localSecretsLink.linked
2683
+ const devVarsPath = devVarsPathFor(appDir, envName, {
2684
+ sharedDevVarsCache: linkedSecrets !== null
2911
2685
  });
2912
2686
  let userSecrets = {};
2913
2687
  if (existsSync10(devVarsPath)) {
2914
2688
  const rawDevVars = readFileSync9(devVarsPath, "utf-8");
2915
- const customSection = extractCustomDevVars(
2916
- linkedSecrets ? rawDevVars : stripGeneratedSecretsCache2(rawDevVars)
2917
- );
2689
+ const customSection = extractCustomDevVars(rawDevVars);
2918
2690
  if (customSection) {
2919
2691
  try {
2920
2692
  userSecrets = parseDevVars(customSection);
@@ -2945,10 +2717,8 @@ var deploy_default = defineCommand8({
2945
2717
  }
2946
2718
  if (userSecretNames.length) {
2947
2719
  p4.log.info(`App secrets (from .dev.vars): ${userSecretNames.join(", ")}`);
2948
- if (!localSecretsLink.linked) {
2949
- p4.log.info(
2950
- "Legacy pass-through; move these to managed secrets with `npx deepspace secrets setup`."
2951
- );
2720
+ if (!linkedSecrets) {
2721
+ p4.log.info("Not in the app store yet \u2014 migrating after this deploy.");
2952
2722
  }
2953
2723
  }
2954
2724
  s.start(`Deploying to ${appName}.app.space...`);
@@ -2970,7 +2740,7 @@ var deploy_default = defineCommand8({
2970
2740
  const groupJson = JSON.stringify(assetGroups[i]);
2971
2741
  s.message(`Uploading assets \u2014 group ${i + 1}/${totalGroups}...`);
2972
2742
  const groupRes = await postWithRetry(
2973
- `${DEPLOY_URL3}/api/deploy/${appName}/assets?uploadId=${uploadId}&groupIndex=${i}&totalGroups=${totalGroups}`,
2743
+ `${DEPLOY_URL3}/api/deploy/${appId}/assets?uploadId=${uploadId}&groupIndex=${i}&totalGroups=${totalGroups}`,
2974
2744
  () => ({
2975
2745
  method: "POST",
2976
2746
  headers: {
@@ -2982,7 +2752,7 @@ var deploy_default = defineCommand8({
2982
2752
  );
2983
2753
  if (!groupRes.ok) {
2984
2754
  const errBody = await groupRes.json().catch(() => ({}));
2985
- await abortStagedUpload(appName, uploadId, totalGroups, token);
2755
+ await abortStagedUpload(appId, uploadId, totalGroups, token);
2986
2756
  s.stop("Deploy failed");
2987
2757
  p4.cancel(
2988
2758
  `Asset upload failed on group ${i + 1}/${totalGroups}: ${errBody.error ?? `HTTP ${groupRes.status}`}`
@@ -2991,13 +2761,21 @@ var deploy_default = defineCommand8({
2991
2761
  }
2992
2762
  }
2993
2763
  } catch (err) {
2994
- await abortStagedUpload(appName, uploadId, totalGroups, token);
2764
+ await abortStagedUpload(appId, uploadId, totalGroups, token);
2995
2765
  s.stop("Deploy failed");
2996
2766
  p4.cancel(`Asset upload failed (network): ${errMessage(err)}`);
2997
2767
  process.exit(1);
2998
2768
  }
2999
2769
  s.message(`Deploying to ${appName}.app.space...`);
3000
2770
  }
2771
+ const commitUrl = `${DEPLOY_URL3}/api/deploy/${appId}`;
2772
+ let confirmRename = args.rename === true;
2773
+ const bailDeploy = async (message, stopLabel = "Deploy failed") => {
2774
+ if (uploadId) await abortStagedUpload(appId, uploadId, totalGroups, token);
2775
+ if (stopLabel !== null) s.stop(stopLabel);
2776
+ p4.cancel(message);
2777
+ process.exit(1);
2778
+ };
3001
2779
  const makeForm = () => {
3002
2780
  const form = new FormData();
3003
2781
  form.append("worker", new Blob([workerJs], { type: "application/javascript" }), "worker.js");
@@ -3019,38 +2797,45 @@ var deploy_default = defineCommand8({
3019
2797
  if (extraRoutes.length) {
3020
2798
  form.append("extraRunWorkerFirst", JSON.stringify(extraRoutes));
3021
2799
  }
3022
- if (localSecretsLink.linked) {
3023
- form.append("secretsProjectName", localSecretsLink.projectName);
3024
- }
2800
+ form.append("name", appName);
2801
+ if (confirmRename) form.append("confirmRename", "true");
3025
2802
  return form;
3026
2803
  };
3027
- const commitUrl = `${DEPLOY_URL3}/api/deploy/${appName}`;
2804
+ const postCommit = async () => postWithRetry(
2805
+ commitUrl,
2806
+ () => ({
2807
+ method: "POST",
2808
+ headers: { Authorization: `Bearer ${token}` },
2809
+ body: makeForm()
2810
+ }),
2811
+ // Commit runs a real deploy — don't retry its 5xx (could double-deploy);
2812
+ // only a thrown fetch (dropped connection) is retried, and the staged
2813
+ // groups survive on the worker until this request succeeds.
2814
+ { retryServerErrors: false }
2815
+ );
3028
2816
  let res;
3029
2817
  try {
3030
- res = await postWithRetry(
3031
- commitUrl,
3032
- () => ({
3033
- method: "POST",
3034
- headers: { Authorization: `Bearer ${token}` },
3035
- body: makeForm()
3036
- }),
3037
- // Commit runs a real deploy — don't retry its 5xx (could double-deploy);
3038
- // only a thrown fetch (dropped connection) is retried, and the staged
3039
- // groups survive on the worker until this request succeeds.
3040
- { retryServerErrors: false }
3041
- );
2818
+ res = await postCommit();
3042
2819
  } catch (err) {
3043
- if (uploadId) await abortStagedUpload(appName, uploadId, totalGroups, token);
3044
- s.stop("Deploy failed");
3045
- p4.cancel(`Deploy request failed: ${errMessage(err)}`);
3046
- process.exit(1);
2820
+ await bailDeploy(`Deploy request failed: ${errMessage(err)}`);
2821
+ throw err;
2822
+ }
2823
+ let body = await res.json().catch(() => ({}));
2824
+ if (res.status === 409 && body.code === "rename_required" && !confirmRename) {
2825
+ s.stop("Rename confirmation needed");
2826
+ const yes = await p4.confirm({
2827
+ 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?`
2828
+ });
2829
+ if (p4.isCancel(yes) || !yes) {
2830
+ await bailDeploy("Deploy cancelled.", null);
2831
+ }
2832
+ confirmRename = true;
2833
+ s.start(`Deploying to ${appName}.app.space...`);
2834
+ res = await postCommit();
2835
+ body = await res.json().catch(() => ({}));
3047
2836
  }
3048
- const body = await res.json().catch(() => ({}));
3049
2837
  if (!res.ok || !body.success) {
3050
- if (uploadId) await abortStagedUpload(appName, uploadId, totalGroups, token);
3051
- s.stop("Deploy failed");
3052
- p4.cancel(formatDeployWorkerError(res.status, body.error));
3053
- process.exit(1);
2838
+ await bailDeploy(formatDeployWorkerError(res.status, body.error));
3054
2839
  }
3055
2840
  if (body.onBehalfOfOwner) {
3056
2841
  p4.log.warn(`Deployed on behalf of owner ${body.onBehalfOfOwner}`);
@@ -3064,26 +2849,66 @@ var deploy_default = defineCommand8({
3064
2849
  "First requests may briefly hit the assets transitional page; retry in a minute."
3065
2850
  );
3066
2851
  p4.log.success(`Live at: ${body.url}`);
3067
- await syncSubscriptionPlans(appDir2, appName, token);
3068
- await syncOneTimeProducts(appDir2, appName, token);
2852
+ await migrateLegacyDevVarsSecrets({
2853
+ deployUrl: DEPLOY_URL3,
2854
+ token,
2855
+ appId,
2856
+ envName,
2857
+ devVarsPath,
2858
+ userSecrets,
2859
+ userSecretNames,
2860
+ hadStore: linkedSecrets !== null
2861
+ });
2862
+ await syncSubscriptionPlans(appDir, appId, token);
2863
+ await syncOneTimeProducts(appDir, appId, token);
3069
2864
  p4.outro("Done");
3070
2865
  return;
3071
2866
  }
3072
2867
  }
3073
2868
  s.stop("Deployed!");
3074
2869
  p4.log.success(`Live at: ${body.url}`);
3075
- await syncSubscriptionPlans(appDir2, appName, token);
3076
- await syncOneTimeProducts(appDir2, appName, token);
2870
+ await migrateLegacyDevVarsSecrets({
2871
+ deployUrl: DEPLOY_URL3,
2872
+ token,
2873
+ appId,
2874
+ envName,
2875
+ devVarsPath,
2876
+ userSecrets,
2877
+ userSecretNames,
2878
+ hadStore: linkedSecrets !== null
2879
+ });
2880
+ await syncSubscriptionPlans(appDir, appId, token);
2881
+ await syncOneTimeProducts(appDir, appId, token);
3077
2882
  p4.outro("Done");
3078
2883
  }
3079
2884
  });
3080
- async function syncSubscriptionPlans(appDir2, appName, token) {
3081
- const subsPath = join11(appDir2, "src", "subscriptions.ts");
2885
+ async function migrateLegacyDevVarsSecrets(opts) {
2886
+ const { deployUrl, token, appId, envName, devVarsPath, userSecrets, userSecretNames, hadStore } = opts;
2887
+ if (hadStore || userSecretNames.length === 0) return;
2888
+ const configName = defaultConfigNameForEnv(envName);
2889
+ try {
2890
+ await uploadSecrets(deployUrl, token, appId, configName, userSecrets, false);
2891
+ if (existsSync10(devVarsPath)) {
2892
+ const cleaned = removeDevVarsKeys(readFileSync9(devVarsPath, "utf-8"), new Set(userSecretNames));
2893
+ writeFileSync9(devVarsPath, cleaned.endsWith("\n") ? cleaned : `${cleaned}
2894
+ `);
2895
+ }
2896
+ p4.log.success(
2897
+ `Migrated ${userSecretNames.length} secret${userSecretNames.length === 1 ? "" : "s"} from .dev.vars into the app store (config ${configName}) \u2014 now managed by \`deepspace secrets\`.`
2898
+ );
2899
+ } catch (err) {
2900
+ p4.log.warn(
2901
+ `Couldn't migrate .dev.vars secrets into the app store (${errMessage(err)}); they deployed as bindings for now and the next deploy will retry.`
2902
+ );
2903
+ }
2904
+ }
2905
+ async function syncSubscriptionPlans(appDir, appName, token) {
2906
+ const subsPath = join11(appDir, "src", "subscriptions.ts");
3082
2907
  if (!existsSync10(subsPath)) return;
3083
2908
  let plans;
3084
2909
  try {
3085
2910
  const esbuild = await import("esbuild");
3086
- const outDir = join11(appDir2, ".wrangler", "deploy");
2911
+ const outDir = join11(appDir, ".wrangler", "deploy");
3087
2912
  const outFile = join11(outDir, "subscriptions.bundle.mjs");
3088
2913
  await esbuild.build({
3089
2914
  entryPoints: [subsPath],
@@ -3116,7 +2941,7 @@ async function syncSubscriptionPlans(appDir2, appName, token) {
3116
2941
  Authorization: `Bearer ${token}`,
3117
2942
  "Content-Type": "application/json"
3118
2943
  },
3119
- body: JSON.stringify({ appName, plans })
2944
+ body: JSON.stringify({ appId: appName, plans })
3120
2945
  });
3121
2946
  } catch (err) {
3122
2947
  s.stop("Plan sync skipped");
@@ -3225,13 +3050,13 @@ current billing period? (Skip to leave them grandfathered.)`
3225
3050
  }
3226
3051
  }
3227
3052
  }
3228
- async function syncOneTimeProducts(appDir2, appName, token) {
3229
- const productsPath = join11(appDir2, "src", "products.ts");
3053
+ async function syncOneTimeProducts(appDir, appName, token) {
3054
+ const productsPath = join11(appDir, "src", "products.ts");
3230
3055
  if (!existsSync10(productsPath)) return;
3231
3056
  let products;
3232
3057
  try {
3233
3058
  const esbuild = await import("esbuild");
3234
- const outDir = join11(appDir2, ".wrangler", "deploy");
3059
+ const outDir = join11(appDir, ".wrangler", "deploy");
3235
3060
  const outFile = join11(outDir, "products.bundle.mjs");
3236
3061
  await esbuild.build({
3237
3062
  entryPoints: [productsPath],
@@ -3264,7 +3089,7 @@ async function syncOneTimeProducts(appDir2, appName, token) {
3264
3089
  Authorization: `Bearer ${token}`,
3265
3090
  "Content-Type": "application/json"
3266
3091
  },
3267
- body: JSON.stringify({ appName, products })
3092
+ body: JSON.stringify({ appId: appName, products })
3268
3093
  });
3269
3094
  } catch (err) {
3270
3095
  s?.stop("Product sync skipped");
@@ -3335,7 +3160,7 @@ var undeploy_default = defineCommand9({
3335
3160
  args: {
3336
3161
  name: {
3337
3162
  type: "positional",
3338
- description: "App name to undeploy (reads from wrangler.toml if omitted)",
3163
+ description: "App id to undeploy (reads DEEPSPACE_APP_ID from wrangler.toml if omitted)",
3339
3164
  required: false
3340
3165
  },
3341
3166
  env: {
@@ -3345,21 +3170,17 @@ var undeploy_default = defineCommand9({
3345
3170
  }
3346
3171
  },
3347
3172
  async run({ args }) {
3348
- let appName = args.name;
3349
3173
  const envName = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
3350
- if (!appName) {
3351
- if (hasWranglerConfig(process.cwd())) {
3352
- const config = readWranglerConfig(process.cwd());
3353
- const nameRes = resolveAppNameForEnv(config, envName);
3354
- if (!nameRes.ok) {
3355
- p5.cancel(nameRes.reason);
3356
- process.exit(1);
3357
- }
3358
- appName = nameRes.name;
3359
- }
3174
+ let appId = args.name;
3175
+ if (appId && !APP_ID_RE.test(appId)) {
3176
+ 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.`);
3177
+ process.exit(1);
3360
3178
  }
3361
- if (!appName) {
3362
- p5.cancel("Provide an app name or run from a DeepSpace app directory.");
3179
+ if (!appId && hasWranglerConfig(process.cwd())) {
3180
+ appId = readAppId(process.cwd(), envName) ?? void 0;
3181
+ }
3182
+ if (!appId) {
3183
+ p5.cancel("Provide an app id or run from a DeepSpace app directory with a DEEPSPACE_APP_ID.");
3363
3184
  process.exit(1);
3364
3185
  }
3365
3186
  let token;
@@ -3369,10 +3190,10 @@ var undeploy_default = defineCommand9({
3369
3190
  p5.cancel(err instanceof Error ? err.message : String(err));
3370
3191
  process.exit(1);
3371
3192
  }
3372
- p5.intro(`Undeploying ${appName}`);
3193
+ p5.intro(`Undeploying ${appId}`);
3373
3194
  const s = createSpinner();
3374
3195
  s.start("Removing...");
3375
- const res = await fetch(`${DEPLOY_URL4}/api/deploy/${appName}`, {
3196
+ const res = await fetch(`${DEPLOY_URL4}/api/deploy/${appId}`, {
3376
3197
  method: "DELETE",
3377
3198
  headers: { Authorization: `Bearer ${token}` }
3378
3199
  });
@@ -3383,7 +3204,10 @@ var undeploy_default = defineCommand9({
3383
3204
  process.exit(1);
3384
3205
  }
3385
3206
  s.stop("Removed");
3386
- p5.outro(`${appName}.app.space has been taken down`);
3207
+ const hosts = body.releasedHosts ?? [];
3208
+ p5.outro(
3209
+ hosts.length ? `${hosts.join(", ")} taken down. The app keeps its id \u2014 redeploy to bring it back.` : `App ${appId} taken down.`
3210
+ );
3387
3211
  }
3388
3212
  });
3389
3213
 
@@ -3484,14 +3308,14 @@ var add_default = defineCommand11({
3484
3308
  }
3485
3309
  },
3486
3310
  run({ args }) {
3487
- const appDir2 = resolve8(args.dir ?? ".");
3488
- ensureInstallReady(appDir2);
3489
- const script = findScript(appDir2);
3311
+ const appDir = resolve8(args.dir ?? ".");
3312
+ ensureInstallReady(appDir);
3313
+ const script = findScript(appDir);
3490
3314
  if (!script) {
3491
3315
  console.error("Could not find the deepspace feature installer.");
3492
3316
  console.error("Looked in:");
3493
- console.error(` ${join12(appDir2, "node_modules", "deepspace", "scripts", "add-feature.cjs")}`);
3494
- console.error(` ${join12(appDir2, ".deepspace", "scripts", "add-feature.cjs")}`);
3317
+ console.error(` ${join12(appDir, "node_modules", "deepspace", "scripts", "add-feature.cjs")}`);
3318
+ console.error(` ${join12(appDir, ".deepspace", "scripts", "add-feature.cjs")}`);
3495
3319
  console.error("Are you in a DeepSpace app directory? Try `npm install` first.");
3496
3320
  process.exit(1);
3497
3321
  }
@@ -3501,13 +3325,13 @@ var add_default = defineCommand11({
3501
3325
  } else if (args.info) {
3502
3326
  passArgs.push("--info", args.info);
3503
3327
  } else if (args.feature) {
3504
- passArgs.push(args.feature, appDir2);
3328
+ passArgs.push(args.feature, appDir);
3505
3329
  } else {
3506
3330
  passArgs.push("--help");
3507
3331
  }
3508
3332
  execSync3(`node ${JSON.stringify(script)} ${passArgs.map((a) => JSON.stringify(a)).join(" ")}`, {
3509
3333
  stdio: "inherit",
3510
- cwd: appDir2
3334
+ cwd: appDir
3511
3335
  });
3512
3336
  }
3513
3337
  });
@@ -3520,17 +3344,17 @@ import { join as join13 } from "path";
3520
3344
  import { homedir as homedir5 } from "os";
3521
3345
  var SKILL_REPO = "deepdotspace/deepspace-skill";
3522
3346
  function runSkillsAdd(extraArgs) {
3523
- return new Promise((resolve11) => {
3347
+ return new Promise((resolve10) => {
3524
3348
  const child = spawn2(
3525
3349
  "npx",
3526
3350
  ["-y", "skills@latest", "add", SKILL_REPO, ...extraArgs],
3527
3351
  { stdio: "inherit" }
3528
3352
  );
3529
- child.on("close", (code) => resolve11(code ?? 1));
3353
+ child.on("close", (code) => resolve10(code ?? 1));
3530
3354
  child.on("error", (err) => {
3531
3355
  console.error(`Failed to invoke npx: ${err.message}`);
3532
3356
  console.error("Is Node.js installed and on PATH?");
3533
- resolve11(1);
3357
+ resolve10(1);
3534
3358
  });
3535
3359
  });
3536
3360
  }
@@ -3593,6 +3417,13 @@ var skills_default = defineCommand12({
3593
3417
  import { defineCommand as defineCommand13 } from "citty";
3594
3418
 
3595
3419
  // src/cli/lib/api.ts
3420
+ var ApiError = class extends Error {
3421
+ constructor(message, status3, code) {
3422
+ super(message);
3423
+ this.status = status3;
3424
+ this.code = code;
3425
+ }
3426
+ };
3596
3427
  async function apiFetch(baseUrl, token, path, init) {
3597
3428
  const res = await fetch(`${baseUrl}${path}`, {
3598
3429
  ...init,
@@ -3605,13 +3436,16 @@ async function apiFetch(baseUrl, token, path, init) {
3605
3436
  const text2 = await res.text();
3606
3437
  if (!res.ok) {
3607
3438
  let msg = text2;
3439
+ let code;
3608
3440
  try {
3609
- msg = JSON.parse(text2).error ?? text2;
3441
+ const body = JSON.parse(text2);
3442
+ msg = body.error ?? text2;
3443
+ code = body.code;
3610
3444
  } catch {
3611
3445
  }
3612
- throw new Error(`API ${path} (${res.status}): ${msg}`);
3446
+ throw new ApiError(`API ${path} (${res.status}): ${msg}`, res.status, code);
3613
3447
  }
3614
- return JSON.parse(text2);
3448
+ return text2 ? JSON.parse(text2) : {};
3615
3449
  }
3616
3450
 
3617
3451
  // src/cli/commands/domain.ts
@@ -3642,11 +3476,11 @@ async function confirm3(message) {
3642
3476
  throw new Error(`${message} \u2014 pass --yes to confirm non-interactively`);
3643
3477
  }
3644
3478
  process.stdout.write(`${message} [y/N] `);
3645
- return new Promise((resolve11) => {
3479
+ return new Promise((resolve10) => {
3646
3480
  process.stdin.setEncoding("utf-8");
3647
3481
  process.stdin.once("data", (data) => {
3648
3482
  const answer = data.toString().trim().toLowerCase();
3649
- resolve11(answer === "y" || answer === "yes");
3483
+ resolve10(answer === "y" || answer === "yes");
3650
3484
  });
3651
3485
  });
3652
3486
  }
@@ -3723,7 +3557,7 @@ var buy = defineCommand13({
3723
3557
  }
3724
3558
  const checkout = await api(token, "/api/domains/checkout", {
3725
3559
  method: "POST",
3726
- body: JSON.stringify({ domain: args.domain, appName })
3560
+ body: JSON.stringify({ domain: args.domain, appId: appName })
3727
3561
  });
3728
3562
  if (args.json) {
3729
3563
  process.stdout.write(
@@ -3867,7 +3701,7 @@ var attach = defineCommand13({
3867
3701
  `/api/domains/${found.id}/reattach`,
3868
3702
  {
3869
3703
  method: "POST",
3870
- body: JSON.stringify({ appName: targetApp })
3704
+ body: JSON.stringify({ appId: targetApp })
3871
3705
  }
3872
3706
  );
3873
3707
  if (args.json) {
@@ -3951,35 +3785,18 @@ function isTestAccountEmail(email) {
3951
3785
 
3952
3786
  // src/cli/commands/collaborators.ts
3953
3787
  var API_URL3 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
3954
- async function api2(token, path, init) {
3955
- const res = await fetch(`${API_URL3}${path}`, {
3956
- ...init,
3957
- headers: {
3958
- Authorization: `Bearer ${token}`,
3959
- "Content-Type": "application/json",
3960
- ...init?.headers ?? {}
3961
- }
3962
- });
3963
- const text2 = await res.text();
3964
- if (!res.ok) {
3965
- let msg = text2;
3966
- try {
3967
- msg = JSON.parse(text2).error ?? text2;
3968
- } catch {
3969
- }
3970
- throw new Error(`API ${path} (${res.status}): ${msg}`);
3971
- }
3972
- return text2 ? JSON.parse(text2) : {};
3788
+ function api2(token, path, init) {
3789
+ return apiFetch(API_URL3, token, path, init);
3973
3790
  }
3974
3791
  var list3 = defineCommand14({
3975
3792
  meta: { name: "list", description: "List collaborators on your app" },
3976
3793
  args: {
3977
- app: { type: "string", description: "App name (defaults to ./wrangler.toml)" },
3794
+ app: { type: "string", alias: "a", description: "App id (defaults to ./wrangler.toml)" },
3978
3795
  json: { type: "boolean", description: "Emit JSON" }
3979
3796
  },
3980
3797
  async run({ args }) {
3981
3798
  const token = await ensureToken();
3982
- const app = requireAppName(args.app);
3799
+ const app = requireAppIdArg(args.app);
3983
3800
  const { collaborators } = await api2(
3984
3801
  token,
3985
3802
  `/api/app-collaborators/${encodeURIComponent(app)}`
@@ -4005,12 +3822,12 @@ var add2 = defineCommand14({
4005
3822
  description: "Collaborator email (must already be a DeepSpace user)",
4006
3823
  required: true
4007
3824
  },
4008
- app: { type: "string", description: "App name (defaults to ./wrangler.toml)" },
3825
+ app: { type: "string", alias: "a", description: "App id (defaults to ./wrangler.toml)" },
4009
3826
  json: { type: "boolean", description: "Emit JSON" }
4010
3827
  },
4011
3828
  async run({ args }) {
4012
3829
  const token = await ensureToken();
4013
- const app = requireAppName(args.app);
3830
+ const app = requireAppIdArg(args.app);
4014
3831
  if (isTestAccountEmail(args.email)) {
4015
3832
  console.error(
4016
3833
  "Test accounts (@deepspace.test) cannot be added as collaborators. Use a real DeepSpace account."
@@ -4033,12 +3850,12 @@ var remove = defineCommand14({
4033
3850
  meta: { name: "remove", description: "Remove a collaborator from your app" },
4034
3851
  args: {
4035
3852
  email: { type: "positional", description: "Collaborator email", required: true },
4036
- app: { type: "string", description: "App name (defaults to ./wrangler.toml)" },
3853
+ app: { type: "string", alias: "a", description: "App id (defaults to ./wrangler.toml)" },
4037
3854
  json: { type: "boolean", description: "Emit JSON" }
4038
3855
  },
4039
3856
  async run({ args }) {
4040
3857
  const token = await ensureToken();
4041
- const app = requireAppName(args.app);
3858
+ const app = requireAppIdArg(args.app);
4042
3859
  const { collaborators } = await api2(
4043
3860
  token,
4044
3861
  `/api/app-collaborators/${encodeURIComponent(app)}`
@@ -4066,12 +3883,86 @@ var collaborators_default = defineCommand14({
4066
3883
  subCommands: { list: list3, add: add2, remove }
4067
3884
  });
4068
3885
 
4069
- // src/cli/commands/integrations.ts
3886
+ // src/cli/commands/transfer.ts
4070
3887
  import { defineCommand as defineCommand15 } from "citty";
3888
+ var API_URL4 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
3889
+ var DEPLOY_URL5 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
3890
+ var offer = defineCommand15({
3891
+ meta: { name: "offer", description: "Offer this app to another DeepSpace user" },
3892
+ args: {
3893
+ email: { type: "positional", description: "Recipient email", required: true },
3894
+ app: { type: "string", alias: "a", description: "App id (defaults to ./wrangler.toml)" }
3895
+ },
3896
+ async run({ args }) {
3897
+ const token = await ensureToken();
3898
+ const app = requireAppIdArg(args.app);
3899
+ const { expiresAt } = await apiFetch(
3900
+ API_URL4,
3901
+ token,
3902
+ `/api/app-transfers/${encodeURIComponent(app)}`,
3903
+ { method: "POST", body: JSON.stringify({ email: args.email }) }
3904
+ );
3905
+ console.log(
3906
+ `\u2713 Offered ${app} to ${args.email} (expires ${expiresAt}).
3907
+ They accept with: deepspace transfer accept --app ${app}`
3908
+ );
3909
+ }
3910
+ });
3911
+ var status2 = defineCommand15({
3912
+ meta: { name: "status", description: "Show the pending transfer offer, if any" },
3913
+ args: {
3914
+ app: { type: "string", alias: "a", description: "App id (defaults to ./wrangler.toml)" }
3915
+ },
3916
+ async run({ args }) {
3917
+ const token = await ensureToken();
3918
+ const app = requireAppIdArg(args.app);
3919
+ const { transfer } = await apiFetch(API_URL4, token, `/api/app-transfers/${encodeURIComponent(app)}`);
3920
+ if (!transfer) {
3921
+ console.log(`No pending transfer for ${app}.`);
3922
+ return;
3923
+ }
3924
+ console.log(`Pending: ${app} \u2192 ${transfer.toEmailDisplay} (expires ${transfer.expiresAt}).`);
3925
+ }
3926
+ });
3927
+ var accept = defineCommand15({
3928
+ meta: { name: "accept", description: "Accept a transfer offered to you" },
3929
+ args: {
3930
+ app: { type: "string", alias: "a", description: "App id of the offered app", required: true }
3931
+ },
3932
+ async run({ args }) {
3933
+ const token = await ensureToken();
3934
+ const app = requireAppIdArg(args.app);
3935
+ await apiFetch(DEPLOY_URL5, token, `/api/apps/${encodeURIComponent(app)}/transfer/accept`, {
3936
+ method: "POST"
3937
+ });
3938
+ console.log(
3939
+ `\u2713 You now own ${app}. Run \`deepspace init\` in a fresh clone (or set DEEPSPACE_APP_ID = "${app}" in wrangler.toml) and deploy.`
3940
+ );
3941
+ }
3942
+ });
3943
+ var cancel4 = defineCommand15({
3944
+ meta: { name: "cancel", description: "Cancel/decline the pending offer (either party)" },
3945
+ args: {
3946
+ app: { type: "string", alias: "a", description: "App id (defaults to ./wrangler.toml)" }
3947
+ },
3948
+ async run({ args }) {
3949
+ const token = await ensureToken();
3950
+ const app = requireAppIdArg(args.app);
3951
+ await apiFetch(API_URL4, token, `/api/app-transfers/${encodeURIComponent(app)}`, { method: "DELETE" });
3952
+ console.log(`\u2713 Transfer offer for ${app} cancelled.`);
3953
+ }
3954
+ });
3955
+ var transfer_default = defineCommand15({
3956
+ meta: { name: "transfer", description: "Transfer app ownership to another user" },
3957
+ subCommands: { offer, status: status2, accept, cancel: cancel4 }
3958
+ });
3959
+
3960
+ // src/cli/commands/integrations.ts
3961
+ import { defineCommand as defineCommand16 } from "citty";
4071
3962
 
4072
3963
  // src/cli/commands/_invoke-impl.ts
4073
3964
  import { readFileSync as readFileSync10 } from "fs";
4074
- var API_URL4 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
3965
+ var API_URL5 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
4075
3966
  var DEFAULT_TIMEOUT_MS = 12e4;
4076
3967
  function parseTarget(target) {
4077
3968
  if (!target || typeof target !== "string") {
@@ -4113,7 +4004,7 @@ function flushAndExit(code) {
4113
4004
  });
4114
4005
  }
4115
4006
  async function fetchCatalog(opts = {}) {
4116
- const url = opts.summary ? `${API_URL4}/api/integrations?summary=1` : `${API_URL4}/api/integrations`;
4007
+ const url = opts.summary ? `${API_URL5}/api/integrations?summary=1` : `${API_URL5}/api/integrations`;
4117
4008
  const res = await fetch(url);
4118
4009
  if (!res.ok) {
4119
4010
  throw new Error(`Failed to fetch integration catalog (${res.status})`);
@@ -4201,7 +4092,7 @@ async function runInvoke(args) {
4201
4092
  const startedAt = Date.now();
4202
4093
  let res;
4203
4094
  try {
4204
- res = await fetch(`${API_URL4}/api/integrations/${integration}/${endpoint}`, {
4095
+ res = await fetch(`${API_URL5}/api/integrations/${integration}/${endpoint}`, {
4205
4096
  method: "POST",
4206
4097
  headers: {
4207
4098
  "Content-Type": "application/json",
@@ -4252,7 +4143,7 @@ async function runInvoke(args) {
4252
4143
  }
4253
4144
 
4254
4145
  // src/cli/commands/integrations.ts
4255
- var list4 = defineCommand15({
4146
+ var list4 = defineCommand16({
4256
4147
  meta: {
4257
4148
  name: "list",
4258
4149
  description: "List all available integrations"
@@ -4273,7 +4164,7 @@ var list4 = defineCommand15({
4273
4164
  }
4274
4165
  }
4275
4166
  });
4276
- var info = defineCommand15({
4167
+ var info = defineCommand16({
4277
4168
  meta: {
4278
4169
  name: "info",
4279
4170
  description: "Print schema + example body for an integration endpoint"
@@ -4299,7 +4190,7 @@ var info = defineCommand15({
4299
4190
  }
4300
4191
  }
4301
4192
  });
4302
- var invoke = defineCommand15({
4193
+ var invoke = defineCommand16({
4303
4194
  meta: {
4304
4195
  name: "invoke",
4305
4196
  description: "Invoke a platform integration as the logged-in user"
@@ -4352,7 +4243,7 @@ var invoke = defineCommand15({
4352
4243
  }
4353
4244
  }
4354
4245
  });
4355
- var integrations_default = defineCommand15({
4246
+ var integrations_default = defineCommand16({
4356
4247
  meta: {
4357
4248
  name: "integrations",
4358
4249
  description: "List, inspect, and invoke platform integrations"
@@ -4365,8 +4256,8 @@ var integrations_default = defineCommand15({
4365
4256
  });
4366
4257
 
4367
4258
  // src/cli/commands/invoke.ts
4368
- import { defineCommand as defineCommand16 } from "citty";
4369
- var invoke_default = defineCommand16({
4259
+ import { defineCommand as defineCommand17 } from "citty";
4260
+ var invoke_default = defineCommand17({
4370
4261
  meta: {
4371
4262
  name: "invoke",
4372
4263
  description: "Invoke a platform integration as the logged-in user"
@@ -4439,13 +4330,13 @@ var invoke_default = defineCommand16({
4439
4330
  });
4440
4331
 
4441
4332
  // src/cli/commands/library.ts
4442
- import { defineCommand as defineCommand17 } from "citty";
4333
+ import { defineCommand as defineCommand18 } from "citty";
4443
4334
  import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
4444
4335
  import { join as join14, resolve as resolve9 } from "path";
4445
- import { parse as parseToml3 } from "smol-toml";
4336
+ import { parse as parseToml4 } from "smol-toml";
4446
4337
  import * as p6 from "@clack/prompts";
4447
4338
  var DEFAULT_LIBRARY_APP = "deepdotspace-site";
4448
- var publish = defineCommand17({
4339
+ var publish = defineCommand18({
4449
4340
  meta: {
4450
4341
  name: "publish",
4451
4342
  description: "Publish the current app to the DeepSpace library"
@@ -4518,12 +4409,12 @@ var publish = defineCommand17({
4518
4409
  intro6("Publishing to the DeepSpace library");
4519
4410
  let appName = typeof args.app === "string" && args.app.trim() ? args.app.trim() : void 0;
4520
4411
  if (!appName) {
4521
- const appDir2 = resolve9(typeof args.dir === "string" && args.dir ? args.dir : ".");
4522
- const wranglerPath = join14(appDir2, "wrangler.toml");
4412
+ const appDir = resolve9(typeof args.dir === "string" && args.dir ? args.dir : ".");
4413
+ const wranglerPath = join14(appDir, "wrangler.toml");
4523
4414
  if (!existsSync12(wranglerPath)) {
4524
4415
  return die("No --app provided and no wrangler.toml found. Either pass --app <name> or run from your app's directory.");
4525
4416
  }
4526
- const cfg = parseToml3(readFileSync11(wranglerPath, "utf-8"));
4417
+ const cfg = parseToml4(readFileSync11(wranglerPath, "utf-8"));
4527
4418
  const nameRes = resolveAppName(cfg.name);
4528
4419
  if (!nameRes.ok) return die(`wrangler.toml: ${nameRes.reason}`);
4529
4420
  appName = nameRes.name;
@@ -4584,7 +4475,7 @@ var publish = defineCommand17({
4584
4475
  p6.outro("Done");
4585
4476
  }
4586
4477
  });
4587
- var unpublish = defineCommand17({
4478
+ var unpublish = defineCommand18({
4588
4479
  meta: {
4589
4480
  name: "unpublish",
4590
4481
  description: "Remove a library entry (owner or admin only)"
@@ -4643,7 +4534,7 @@ var unpublish = defineCommand17({
4643
4534
  p6.outro("Done");
4644
4535
  }
4645
4536
  });
4646
- var library_default = defineCommand17({
4537
+ var library_default = defineCommand18({
4647
4538
  meta: {
4648
4539
  name: "library",
4649
4540
  description: "Manage your DeepSpace library entries"
@@ -4655,13 +4546,13 @@ var library_default = defineCommand17({
4655
4546
  });
4656
4547
 
4657
4548
  // src/cli/commands/feedback.ts
4658
- import { defineCommand as defineCommand18 } from "citty";
4549
+ import { defineCommand as defineCommand19 } from "citty";
4659
4550
  import * as p7 from "@clack/prompts";
4660
4551
  import { existsSync as existsSync13, readFileSync as readFileSync12 } from "fs";
4661
4552
  import { dirname as dirname3, join as join15 } from "path";
4662
4553
  import { fileURLToPath } from "url";
4663
4554
  import { release } from "os";
4664
- var API_URL5 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
4555
+ var API_URL6 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
4665
4556
  var TYPES = ["bug", "feature", "other"];
4666
4557
  var TYPE_LABELS = {
4667
4558
  bug: "Bug report",
@@ -4671,7 +4562,7 @@ var TYPE_LABELS = {
4671
4562
  var MAX_TITLE_LEN = 200;
4672
4563
  var MAX_BODY_LEN = 1e4;
4673
4564
  var MAX_CONTEXT_LEN = 200;
4674
- var api3 = (token, path, init) => apiFetch(API_URL5, token, path, init);
4565
+ var api3 = (token, path, init) => apiFetch(API_URL6, token, path, init);
4675
4566
  function normalizeType(raw) {
4676
4567
  const value = (raw ?? "bug").toLowerCase();
4677
4568
  if (TYPES.includes(value)) return value;
@@ -4733,7 +4624,7 @@ function buildFeedbackPayload(input) {
4733
4624
  ...input.context
4734
4625
  };
4735
4626
  }
4736
- var feedback_default = defineCommand18({
4627
+ var feedback_default = defineCommand19({
4737
4628
  meta: {
4738
4629
  name: "feedback",
4739
4630
  description: "Submit a bug report, feature request, or feedback to DeepSpace"
@@ -4769,7 +4660,7 @@ var feedback_default = defineCommand18({
4769
4660
  },
4770
4661
  async run({ args }) {
4771
4662
  const interactive = process.stdin.isTTY && !args.json;
4772
- const fail = (message) => {
4663
+ const fail2 = (message) => {
4773
4664
  if (args.json) {
4774
4665
  process.stdout.write(JSON.stringify({ ok: false, error: message }) + "\n");
4775
4666
  } else {
@@ -4781,12 +4672,12 @@ var feedback_default = defineCommand18({
4781
4672
  try {
4782
4673
  type = normalizeType(args.type ? String(args.type) : void 0);
4783
4674
  } catch (err) {
4784
- return fail(err.message);
4675
+ return fail2(err.message);
4785
4676
  }
4786
4677
  let title = args.title ? String(args.title) : "";
4787
4678
  let body = args.message ? String(args.message) : "";
4788
4679
  if ((!title || !body) && !interactive) {
4789
- return fail(
4680
+ return fail2(
4790
4681
  'Provide a title and --message to submit non-interactively, e.g. `deepspace feedback "Title" --message "Details"`.'
4791
4682
  );
4792
4683
  }
@@ -4848,7 +4739,7 @@ var feedback_default = defineCommand18({
4848
4739
  context: collectContext()
4849
4740
  });
4850
4741
  } catch (err) {
4851
- return fail(err.message);
4742
+ return fail2(err.message);
4852
4743
  }
4853
4744
  if (interactive && !args.yes) {
4854
4745
  p7.note(
@@ -4861,1011 +4752,395 @@ Context: deepspace ${payload.cliVersion} \xB7 ${payload.nodeVersion} \xB7 ${payl
4861
4752
  "Review"
4862
4753
  );
4863
4754
  const ok = await p7.confirm({ message: "Send this to the DeepSpace team?" });
4864
- if (p7.isCancel(ok) || !ok) {
4865
- p7.cancel("Not sent.");
4866
- process.exit(0);
4867
- }
4868
- }
4869
- let token;
4870
- try {
4871
- token = await ensureToken();
4872
- } catch (err) {
4873
- return fail(err.message ?? "Not signed in. Run `deepspace login`.");
4874
- }
4875
- let result;
4876
- try {
4877
- result = await api3(token, "/api/feedback", {
4878
- method: "POST",
4879
- body: JSON.stringify(payload)
4880
- });
4881
- } catch (err) {
4882
- return fail(err.message);
4883
- }
4884
- if (args.json) {
4885
- process.stdout.write(JSON.stringify({ ok: true, report: result.report }) + "\n");
4886
- return;
4887
- }
4888
- if (interactive) {
4889
- p7.outro(`Thanks! Submitted as ${result.report.id}.`);
4890
- } else {
4891
- console.log(`Submitted feedback ${result.report.id} (${result.report.type}).`);
4892
- }
4893
- console.log(`Track triage status at ${DASHBOARD_URL}.`);
4894
- }
4895
- });
4896
-
4897
- // src/cli/commands/secrets.ts
4898
- import { defineCommand as defineCommand19 } from "citty";
4899
- import { existsSync as existsSync14, readFileSync as readFileSync13, rmSync as rmSync4 } from "fs";
4900
- import { join as join16, resolve as resolve10 } from "path";
4901
- var DEPLOY_URL5 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
4902
- function appDir(args) {
4903
- const dir = typeof args.dir === "string" && args.dir ? args.dir : typeof args.scope === "string" && args.scope ? args.scope : ".";
4904
- return resolve10(dir);
4905
- }
4906
- function wranglerEnv(args) {
4907
- return typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
4908
- }
4909
- function targetFromArgs(args) {
4910
- const dir = appDir(args);
4911
- const target = resolveSecretsTarget(dir, {
4912
- project: typeof args.project === "string" && args.project ? args.project : void 0,
4913
- config: typeof args.config === "string" && args.config ? args.config : void 0,
4914
- wranglerEnv: wranglerEnv(args)
4915
- });
4916
- return { appDir: dir, target };
4917
- }
4918
- function scopedArgs() {
4919
- return {
4920
- dir: { type: "string", description: "App directory (default: current directory)", required: false },
4921
- scope: { type: "string", description: "Directory scope; Doppler-compatible alias for --dir", required: false },
4922
- env: { type: "string", alias: "e", description: "Advanced: wrangler.toml [env.<name>] cache/link slot", required: false },
4923
- json: { type: "boolean", description: "Emit JSON", default: false }
4924
- };
4925
- }
4926
- function commonArgs() {
4927
- return {
4928
- ...scopedArgs(),
4929
- project: { type: "string", alias: "p", description: "Secrets project (default: linked project or wrangler name; does not link)", required: false },
4930
- config: { type: "string", alias: "c", description: "Secrets config (Doppler-compatible; default: linked config or prd)", required: false }
4931
- };
4932
- }
4933
- function setupArgs() {
4934
- return {
4935
- ...scopedArgs(),
4936
- project: { type: "string", alias: "p", description: "Secrets project to link (default: linked project or wrangler name)", required: false },
4937
- config: { type: "string", alias: "c", description: "Secrets config to link (default: linked config or prd)", required: false },
4938
- "no-interactive": { type: "boolean", description: "Accepted for Doppler CLI compatibility; DeepSpace setup never prompts", default: false },
4939
- "no-save-token": { type: "boolean", description: "Accepted for Doppler CLI compatibility; DeepSpace does not save a token here", default: false }
4940
- };
4941
- }
4942
- async function refreshCache(token, dir, target) {
4943
- const values = await fetchSecretsValues(
4944
- DEPLOY_URL5,
4945
- token,
4946
- target.projectName,
4947
- target.configName,
4948
- appScopedSecretsApiOptions(dir, target)
4949
- );
4950
- writeSecretsCacheFile(dir, values, target);
4951
- return Object.keys(values).length;
4952
- }
4953
- async function refreshCacheIfLinked(token, dir, target) {
4954
- if (!secretsTargetMatchesLink(dir, target)) return null;
4955
- return refreshCache(token, dir, target);
4956
- }
4957
- function writeLinkForTarget(dir, target, projectOverride) {
4958
- writeWranglerSecretsLink(dir, target, { projectOverride });
4959
- }
4960
- function redeployReminder(target) {
4961
- const envFlag = target.wranglerEnv ? ` --env ${target.wranglerEnv}` : "";
4962
- console.log(`Redeploy for production to receive these secrets: npx deepspace deploy${envFlag}`);
4963
- }
4964
- function unlinkedTargetNotice(target) {
4965
- const envFlag = target.wranglerEnv ? ` --env ${target.wranglerEnv}` : "";
4966
- console.log(
4967
- `Not linked for dev/test/deploy; run npx deepspace secrets setup --project ${target.projectName} --config ${target.configName}${envFlag}.`
4968
- );
4969
- }
4970
- function cacheJson(count) {
4971
- return { linked: count !== null, cachedSecrets: count };
4972
- }
4973
- function updateRequiredSecretsIfTopLevelLinked(dir, target, keys, opts = {}) {
4974
- if (target.wranglerEnv || !secretsTargetMatchesLink(dir, target)) return null;
4975
- return updateWranglerRequiredSecrets(dir, keys, opts);
4976
- }
4977
- function removeRequiredSecretsIfTopLevelLinked(dir, target, keys) {
4978
- if (target.wranglerEnv || !secretsTargetMatchesLink(dir, target)) return null;
4979
- return removeWranglerRequiredSecrets(dir, keys);
4980
- }
4981
- function requiredSecretsJson(keys) {
4982
- return keys ? { requiredSecrets: keys } : {};
4983
- }
4984
- function requiredSecretsNotice(keys) {
4985
- if (!keys) return;
4986
- console.log(`Updated wrangler.toml detected-secret advisory (${keys.length} name${keys.length === 1 ? "" : "s"}).`);
4987
- }
4988
- async function ensureLinkedRemoteTarget(dir, target) {
4989
- let token;
4990
- try {
4991
- token = await ensureToken();
4992
- } catch (err) {
4993
- return {
4994
- status: "unchecked",
4995
- message: err instanceof Error ? err.message : String(err)
4996
- };
4997
- }
4998
- try {
4999
- const configs2 = await listConfigs(
5000
- DEPLOY_URL5,
5001
- token,
5002
- target.projectName,
5003
- appScopedSecretsApiOptions(dir, target)
5004
- );
5005
- if (configs2.some((config) => config.name === target.configName)) {
5006
- return { status: "ok" };
5007
- }
5008
- } catch (err) {
5009
- if (!(err instanceof SecretsApiError && err.status === 404)) {
5010
- return {
5011
- status: "unchecked",
5012
- message: err instanceof Error ? err.message : String(err)
5013
- };
5014
- }
5015
- }
5016
- try {
5017
- await createConfig(
5018
- DEPLOY_URL5,
5019
- token,
5020
- target.projectName,
5021
- target.configName,
5022
- void 0,
5023
- appScopedSecretsApiOptions(dir, target)
5024
- );
5025
- return { status: "created" };
5026
- } catch (err) {
5027
- return {
5028
- status: "unchecked",
5029
- message: `Remote config ${target.projectName}/${target.configName} was not found and could not be created: ${err instanceof Error ? err.message : String(err)}`
5030
- };
5031
- }
5032
- }
5033
- async function bestEffortSetServerSecretsLink(dir, target) {
5034
- try {
5035
- const token = await ensureToken();
5036
- await setServerSecretsLink(
5037
- DEPLOY_URL5,
5038
- token,
5039
- resolveSecretsAppName(dir, target.wranglerEnv),
5040
- target.projectName
5041
- );
5042
- } catch {
5043
- }
5044
- }
5045
- async function bestEffortClearServerSecretsLink(dir, targetEnv) {
5046
- try {
5047
- const token = await ensureToken();
5048
- await clearServerSecretsLink(DEPLOY_URL5, token, resolveSecretsAppName(dir, targetEnv));
5049
- } catch {
5050
- }
5051
- }
5052
- async function importLegacyDevVarsSecrets(dir, target, remoteCheck) {
5053
- if (remoteCheck.status === "unchecked") return null;
5054
- const legacyPath = target.wranglerEnv ? join16(dir, `.dev.vars.${target.wranglerEnv}`) : join16(dir, ".dev.vars");
5055
- if (!existsSync14(legacyPath)) return null;
5056
- try {
5057
- const custom = extractCustomDevVars(
5058
- stripGeneratedSecretsCache2(readFileSync13(legacyPath, "utf-8"))
5059
- );
5060
- if (!custom.trim()) return null;
5061
- const parsed = parseDevVars(custom);
5062
- const secrets = {};
5063
- const skipped = [];
5064
- for (const [key, value] of Object.entries(parsed)) {
5065
- try {
5066
- validateSecretName(key);
5067
- validateSecretValue(key, value);
5068
- secrets[key] = value;
5069
- } catch {
5070
- skipped.push(key);
5071
- }
5072
- }
5073
- const keys = Object.keys(secrets).sort();
5074
- if (keys.length === 0) return null;
5075
- const token = await ensureToken();
5076
- if (remoteCheck.status === "ok") {
5077
- const existing = await listSecrets(
5078
- DEPLOY_URL5,
5079
- token,
5080
- target.projectName,
5081
- target.configName,
5082
- appScopedSecretsApiOptions(dir, target)
5083
- );
5084
- if (existing.secrets.length > 0) {
5085
- return { status: "config-not-empty", existingCount: existing.secrets.length, keys };
5086
- }
5087
- }
5088
- await uploadSecrets(
5089
- DEPLOY_URL5,
5090
- token,
5091
- target.projectName,
5092
- target.configName,
5093
- secrets,
5094
- false,
5095
- appScopedSecretsApiOptions(dir, target)
5096
- );
5097
- let cleanedLegacyFile = false;
5098
- try {
5099
- const cleaned = removeDevVarsKeys(
5100
- stripGeneratedSecretsCache2(readFileSync13(legacyPath, "utf-8")),
5101
- new Set(keys)
5102
- );
5103
- if (target.wranglerEnv && !extractCustomDevVars(cleaned).trim()) {
5104
- rmSync4(legacyPath, { force: true });
5105
- } else {
5106
- writeSecretFileSync(legacyPath, cleaned ? `${cleaned}
5107
- ` : "");
4755
+ if (p7.isCancel(ok) || !ok) {
4756
+ p7.cancel("Not sent.");
4757
+ process.exit(0);
5108
4758
  }
5109
- cleanedLegacyFile = true;
5110
- } catch {
5111
4759
  }
5112
- await refreshCacheIfLinked(token, dir, target);
5113
- const requiredSecrets = updateRequiredSecretsIfTopLevelLinked(dir, target, keys);
5114
- return { status: "imported", keys, skipped: skipped.sort(), requiredSecrets, cleanedLegacyFile };
5115
- } catch (err) {
5116
- return { status: "error", message: err instanceof Error ? err.message : String(err) };
5117
- }
5118
- }
5119
- function printLegacySecretsImport(result, target) {
5120
- if (!result) return;
5121
- const configRef = `${target.projectName}/${target.configName}`;
5122
- if (result.status === "imported") {
5123
- console.log(
5124
- `Imported ${result.keys.length} legacy secret${result.keys.length === 1 ? "" : "s"} from .dev.vars into ${configRef}: ${result.keys.join(", ")}`
5125
- );
5126
- if (result.cleanedLegacyFile) {
5127
- console.log("Removed the migrated lines from the local file; the remote store owns them now.");
4760
+ let token;
4761
+ try {
4762
+ token = await ensureToken();
4763
+ } catch (err) {
4764
+ return fail2(err.message ?? "Not signed in. Run `deepspace login`.");
5128
4765
  }
5129
- if (result.skipped.length > 0) {
5130
- console.log(`Skipped SDK-managed/invalid keys (left in place): ${result.skipped.join(", ")}`);
4766
+ let result;
4767
+ try {
4768
+ result = await api3(token, "/api/feedback", {
4769
+ method: "POST",
4770
+ body: JSON.stringify(payload)
4771
+ });
4772
+ } catch (err) {
4773
+ return fail2(err.message);
5131
4774
  }
5132
- requiredSecretsNotice(result.requiredSecrets);
5133
- return;
5134
- }
5135
- if (result.status === "config-not-empty") {
5136
- console.log(
5137
- `Found ${result.keys.length} legacy secret${result.keys.length === 1 ? "" : "s"} in .dev.vars, but ${configRef} already has ${result.existingCount} \u2014 not imported. Merge manually with npx deepspace secrets upload.`
5138
- );
5139
- return;
5140
- }
5141
- console.warn(`Warning: legacy .dev.vars secrets were not imported: ${result.message}`);
5142
- }
5143
- function printLinkRemoteWarning(check) {
5144
- if (check.status !== "unchecked") return;
5145
- console.warn(`Warning: Linked locally, but the remote config was not checked: ${check.message}`);
5146
- }
5147
- function positionals(args, firstName) {
5148
- const raw = Array.isArray(args._) ? args._ : [];
5149
- if (raw.length > 0) return raw.map(String);
5150
- const first = args[firstName];
5151
- return typeof first === "string" && first ? [first] : [];
5152
- }
5153
- function rawPositionals(rawArgs, argsDef) {
5154
- const camel = (name) => name.replace(/-([a-z0-9])/g, (_, ch) => ch.toUpperCase());
5155
- const stringFlags = /* @__PURE__ */ new Set();
5156
- const booleanFlags = /* @__PURE__ */ new Set();
5157
- for (const [name, def] of Object.entries(argsDef)) {
5158
- if (def.type === "positional") continue;
5159
- const flags = def.type === "boolean" ? booleanFlags : stringFlags;
5160
- flags.add(name);
5161
- flags.add(camel(name));
5162
- for (const alias of Array.isArray(def.alias) ? def.alias : def.alias ? [def.alias] : []) {
5163
- flags.add(alias);
5164
- flags.add(camel(alias));
4775
+ if (args.json) {
4776
+ process.stdout.write(JSON.stringify({ ok: true, report: result.report }) + "\n");
4777
+ return;
5165
4778
  }
5166
- }
5167
- const out = [];
5168
- for (let i = 0; i < rawArgs.length; i++) {
5169
- const token = rawArgs[i];
5170
- if (token === "--") {
5171
- out.push(...rawArgs.slice(i + 1));
5172
- break;
5173
- }
5174
- if (token.length > 1 && token.startsWith("-")) {
5175
- const body = token.startsWith("--") ? token.slice(2) : token.slice(1);
5176
- const eq = body.indexOf("=");
5177
- const name = eq === -1 ? body : body.slice(0, eq);
5178
- if (booleanFlags.has(name) || name.startsWith("no-") && booleanFlags.has(name.slice(3))) {
5179
- continue;
5180
- }
5181
- if (stringFlags.has(name)) {
5182
- if (eq === -1) i++;
5183
- continue;
5184
- }
4779
+ if (interactive) {
4780
+ p7.outro(`Thanks! Submitted as ${result.report.id}.`);
4781
+ } else {
4782
+ console.log(`Submitted feedback ${result.report.id} (${result.report.type}).`);
5185
4783
  }
5186
- out.push(token);
5187
- }
5188
- return out;
5189
- }
5190
- function hasRawFlag(rawArgs, name) {
5191
- if (!Array.isArray(rawArgs)) return false;
5192
- for (const token of rawArgs) {
5193
- if (token === "--") return false;
5194
- if (token === `--${name}`) return true;
4784
+ console.log(`Track triage status at ${DASHBOARD_URL}.`);
5195
4785
  }
5196
- return false;
5197
- }
5198
- function downloadFormat(value) {
5199
- const raw = typeof value === "string" && value ? value : "json";
5200
- if (raw === "json" || raw === "dotnet-json" || raw === "env" || raw === "yaml" || raw === "docker" || raw === "env-no-quotes") {
5201
- return raw;
5202
- }
5203
- throw new Error("Unsupported format. Use one of: json, dotnet-json, env, yaml, docker, env-no-quotes.");
5204
- }
5205
- function resolveSecretsSetupArgs(args) {
5206
- const dir = appDir(args);
5207
- const project = typeof args.project === "string" && args.project ? args.project : void 0;
5208
- const config = typeof args.config === "string" && args.config ? args.config : void 0;
5209
- const target = resolveSecretsTarget(dir, {
5210
- project,
5211
- config,
5212
- wranglerEnv: wranglerEnv(args)
5213
- });
5214
- return {
5215
- appDir: dir,
5216
- target,
5217
- projectOverride: !!project && !!target.wranglerEnv
5218
- };
5219
- }
5220
- function resolveConfigureUnsetArgs(args) {
5221
- const names = positionals(args, "name");
5222
- if (names.length === 0) {
5223
- throw new Error("At least one setting is required. Use `npx deepspace secrets configure unset project config`.");
4786
+ });
4787
+
4788
+ // src/cli/commands/secrets.ts
4789
+ import { defineCommand as defineCommand20 } from "citty";
4790
+ import { readFileSync as readFileSync13 } from "fs";
4791
+ var DEPLOY_URL6 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
4792
+ var COMMON_ARGS = {
4793
+ app: {
4794
+ type: "string",
4795
+ alias: "a",
4796
+ description: "App id (default: DEEPSPACE_APP_ID from the nearest wrangler.toml)",
4797
+ required: false
4798
+ },
4799
+ config: {
4800
+ type: "string",
4801
+ alias: "c",
4802
+ description: 'Config name (default: the --env name, or "prd")',
4803
+ required: false
4804
+ },
4805
+ env: {
4806
+ type: "string",
4807
+ alias: "e",
4808
+ description: "wrangler.toml [env.<name>] slot \u2014 selects that env\u2019s app id and config",
4809
+ required: false
5224
4810
  }
5225
- const unset = [];
5226
- for (const name of names) {
5227
- if (name !== "project" && name !== "config") {
5228
- throw new Error(`Unsupported configure unset option "${name}". Use project and/or config.`);
4811
+ };
4812
+ async function resolveTarget(args) {
4813
+ const wranglerEnv = args.env?.trim() || void 0;
4814
+ let appId = args.app?.trim();
4815
+ if (appId && !APP_ID_RE.test(appId)) {
4816
+ throw new Error(`Invalid app id "${appId}" \u2014 expected app_<26 chars>.`);
4817
+ }
4818
+ if (!appId) {
4819
+ const appDir = findAppDir();
4820
+ appId = appDir && readAppId(appDir, wranglerEnv) || void 0;
4821
+ if (!appId) {
4822
+ throw new Error(
4823
+ "No app id. Run from an app directory whose wrangler.toml carries DEEPSPACE_APP_ID (`deepspace init` mints one), or pass --app <appId>."
4824
+ );
5229
4825
  }
5230
- if (!unset.includes(name)) unset.push(name);
5231
4826
  }
5232
- return {
5233
- appDir: appDir(args),
5234
- unset,
5235
- wranglerEnv: wranglerEnv(args)
5236
- };
4827
+ const configName = validateConfigName(args.config?.trim() || defaultConfigNameForEnv(wranglerEnv));
4828
+ const token = await ensureToken();
4829
+ return { appId, configName, token };
4830
+ }
4831
+ function fail(err) {
4832
+ console.error(err instanceof Error ? err.message : String(err));
4833
+ process.exit(1);
5237
4834
  }
5238
- var list5 = defineCommand19({
4835
+ var list5 = defineCommand20({
5239
4836
  meta: { name: "list", description: "List masked secrets in a config" },
5240
4837
  args: {
5241
- ...commonArgs(),
5242
- "only-names": { type: "boolean", description: "Only print secret names; omit values and metadata", default: false },
5243
- raw: { type: "boolean", description: "Accepted for Doppler CLI compatibility; DeepSpace has no variable interpolation", default: false },
5244
- type: { type: "boolean", description: "Accepted for Doppler CLI compatibility", default: false },
5245
- visibility: { type: "boolean", description: "Accepted for Doppler CLI compatibility", default: false }
4838
+ ...COMMON_ARGS,
4839
+ "only-names": {
4840
+ type: "boolean",
4841
+ description: "Only print secret names; omit values and metadata",
4842
+ default: false
4843
+ },
4844
+ json: { type: "boolean", description: "Machine-readable output", default: false }
5246
4845
  },
5247
4846
  async run({ args }) {
5248
4847
  try {
5249
- const token = await ensureToken();
5250
- const { appDir: dir, target } = targetFromArgs(args);
5251
- const result = await listSecrets(
5252
- DEPLOY_URL5,
5253
- token,
5254
- target.projectName,
5255
- target.configName,
5256
- appScopedSecretsApiOptions(dir, target)
5257
- );
4848
+ const t = await resolveTarget(args);
4849
+ const { secrets } = await listSecrets(DEPLOY_URL6, t.token, t.appId, t.configName);
5258
4850
  if (args.json) {
5259
- process.stdout.write(JSON.stringify(result, null, 2) + "\n");
4851
+ console.log(JSON.stringify({ appId: t.appId, config: t.configName, secrets }, null, 2));
5260
4852
  return;
5261
4853
  }
5262
- if (result.secrets.length === 0) {
5263
- console.log(`No secrets in ${target.projectName}/${target.configName}.`);
4854
+ if (secrets.length === 0) {
4855
+ console.log(`No secrets in ${t.configName}.`);
5264
4856
  return;
5265
4857
  }
5266
- if (args.onlyNames) {
5267
- for (const item of result.secrets) console.log(item.key);
5268
- return;
5269
- }
5270
- for (const item of result.secrets) {
5271
- console.log(`${item.key.padEnd(32)} ${item.masked} v${item.version} ${item.updatedAt}`);
4858
+ for (const s of secrets) {
4859
+ console.log(args["only-names"] ? s.key : `${s.key} (v${s.version}, ${s.valueBytes}B, ${s.updatedAt})`);
5272
4860
  }
5273
4861
  } catch (err) {
5274
- console.error(err instanceof Error ? err.message : String(err));
5275
- process.exit(1);
4862
+ fail(err);
5276
4863
  }
5277
4864
  }
5278
4865
  });
5279
- var SET_ARGS = {
5280
- ...commonArgs(),
5281
- secret: { type: "positional", description: "KEY=value or KEY value", required: true },
5282
- "no-interactive": { type: "boolean", description: "Accepted for Doppler CLI compatibility", default: false },
5283
- raw: { type: "boolean", description: "Accepted for Doppler CLI compatibility; DeepSpace has no variable interpolation", default: false },
5284
- type: { type: "string", description: "Accepted for Doppler CLI compatibility", required: false },
5285
- visibility: { type: "string", description: "Accepted for Doppler CLI compatibility", required: false }
5286
- };
5287
- var set = defineCommand19({
5288
- meta: { name: "set", description: "Set remote secrets from KEY=value or KEY value" },
5289
- args: SET_ARGS,
5290
- async run({ args, rawArgs }) {
4866
+ var set = defineCommand20({
4867
+ meta: { name: "set", description: "Set secrets from KEY=value pairs" },
4868
+ args: {
4869
+ ...COMMON_ARGS,
4870
+ secret: { type: "positional", description: "KEY=value (repeatable)", required: true }
4871
+ },
4872
+ async run({ args }) {
5291
4873
  try {
5292
- const token = await ensureToken();
5293
- const { appDir: dir, target } = targetFromArgs(args);
5294
- const inputs = Array.isArray(rawArgs) ? rawPositionals(rawArgs, SET_ARGS) : positionals(args, "secret");
5295
- const secrets = parseSetInputs(inputs);
5296
- await uploadSecrets(
5297
- DEPLOY_URL5,
5298
- token,
5299
- target.projectName,
5300
- target.configName,
5301
- secrets,
5302
- false,
5303
- appScopedSecretsApiOptions(dir, target)
4874
+ const t = await resolveTarget(args);
4875
+ const pairs = [args.secret, ...args._ ?? []].filter(
4876
+ (v) => typeof v === "string" && v.length > 0
5304
4877
  );
5305
- const count = await refreshCacheIfLinked(token, dir, target);
5306
- const keys = Object.keys(secrets).sort();
5307
- const requiredSecrets = updateRequiredSecretsIfTopLevelLinked(dir, target, keys);
5308
- if (args.json) {
5309
- process.stdout.write(JSON.stringify({ ok: true, keys, target, ...cacheJson(count), ...requiredSecretsJson(requiredSecrets) }) + "\n");
5310
- return;
4878
+ const secrets = {};
4879
+ for (const pair of pairs) {
4880
+ const eq = pair.indexOf("=");
4881
+ if (eq <= 0) throw new Error(`Expected KEY=value, got "${pair}"`);
4882
+ const key = validateSecretName(pair.slice(0, eq));
4883
+ const value = pair.slice(eq + 1);
4884
+ validateSecretValue(key, value);
4885
+ secrets[key] = value;
5311
4886
  }
5312
- console.log(`Set ${keys.length} secret${keys.length === 1 ? "" : "s"} in ${target.projectName}/${target.configName}.`);
5313
- requiredSecretsNotice(requiredSecrets);
5314
- if (count === null) {
5315
- unlinkedTargetNotice(target);
4887
+ if (Object.keys(secrets).length === 1) {
4888
+ const [[k, v]] = Object.entries(secrets);
4889
+ await setSecret(DEPLOY_URL6, t.token, t.appId, t.configName, k, v);
5316
4890
  } else {
5317
- console.log(`Refreshed ${count} cached secret${count === 1 ? "" : "s"}.`);
5318
- redeployReminder(target);
4891
+ await uploadSecrets(DEPLOY_URL6, t.token, t.appId, t.configName, secrets, false);
5319
4892
  }
4893
+ console.log(`Set ${Object.keys(secrets).length} secret${Object.keys(secrets).length === 1 ? "" : "s"} in ${t.configName}.`);
5320
4894
  } catch (err) {
5321
- console.error(err instanceof Error ? err.message : String(err));
5322
- process.exit(1);
4895
+ fail(err);
5323
4896
  }
5324
4897
  }
5325
4898
  });
5326
- var get = defineCommand19({
5327
- meta: { name: "get", description: "Get one or more secrets; pass --plain to print values" },
4899
+ var get = defineCommand20({
4900
+ meta: { name: "get", description: "Get a secret; pass --plain to print the value" },
5328
4901
  args: {
5329
- ...commonArgs(),
5330
- key: { type: "positional", description: "Secret key", required: true },
5331
- plain: { type: "boolean", description: "Print plaintext value", default: false },
5332
- "no-exit-on-missing-secret": { type: "boolean", description: "Do not exit when a requested secret is missing", default: false },
5333
- raw: { type: "boolean", description: "Accepted for Doppler CLI compatibility; DeepSpace has no variable interpolation", default: false },
5334
- copy: { type: "boolean", description: "Accepted for Doppler CLI compatibility; clipboard copy is not implemented", default: false },
5335
- type: { type: "boolean", description: "Accepted for Doppler CLI compatibility", default: false },
5336
- visibility: { type: "boolean", description: "Accepted for Doppler CLI compatibility", default: false }
4902
+ ...COMMON_ARGS,
4903
+ key: { type: "positional", description: "Secret name", required: true },
4904
+ plain: { type: "boolean", description: "Print the plaintext value", default: false }
5337
4905
  },
5338
4906
  async run({ args }) {
5339
4907
  try {
5340
- const token = await ensureToken();
5341
- const { appDir: dir, target } = targetFromArgs(args);
5342
- const keys = positionals(args, "key").map((key) => validateSecretName(key));
4908
+ const t = await resolveTarget(args);
4909
+ const key = validateSecretName(args.key);
5343
4910
  if (args.plain) {
5344
- const values = {};
5345
- const missing2 = [];
5346
- for (const key of keys) {
5347
- try {
5348
- values[key] = await getSecretPlain(
5349
- DEPLOY_URL5,
5350
- token,
5351
- target.projectName,
5352
- target.configName,
5353
- key,
5354
- appScopedSecretsApiOptions(dir, target)
5355
- );
5356
- } catch (err) {
5357
- if (!args.noExitOnMissingSecret) throw err;
5358
- missing2.push(key);
5359
- }
5360
- }
5361
- if (args.json) {
5362
- process.stdout.write(JSON.stringify({ secrets: values, missing: missing2 }, null, 2) + "\n");
5363
- return;
5364
- }
5365
- for (const key of keys) if (Object.prototype.hasOwnProperty.call(values, key)) process.stdout.write(values[key] + "\n");
5366
- return;
5367
- }
5368
- const result = await listSecrets(
5369
- DEPLOY_URL5,
5370
- token,
5371
- target.projectName,
5372
- target.configName,
5373
- appScopedSecretsApiOptions(dir, target)
5374
- );
5375
- const found = result.secrets.filter((item) => keys.includes(item.key));
5376
- const foundKeys = new Set(found.map((item) => item.key));
5377
- const missing = keys.filter((key) => !foundKeys.has(key));
5378
- if (missing.length > 0 && !args.noExitOnMissingSecret) {
5379
- throw new Error(`Secret "${missing[0]}" not found in ${target.projectName}/${target.configName}.`);
5380
- }
5381
- if (args.json) {
5382
- process.stdout.write(JSON.stringify({ secrets: found, missing }, null, 2) + "\n");
4911
+ const { value } = await getSecretPlain(DEPLOY_URL6, t.token, t.appId, t.configName, key);
4912
+ process.stdout.write(value + "\n");
5383
4913
  return;
5384
4914
  }
5385
- for (const item of found) process.stdout.write(`${item.key} ${item.masked} v${item.version}
5386
- `);
4915
+ const { secrets } = await listSecrets(DEPLOY_URL6, t.token, t.appId, t.configName);
4916
+ const item = secrets.find((s) => s.key === key);
4917
+ if (!item) fail(new Error(`Secret "${key}" not found in ${t.configName}`));
4918
+ console.log(`${item.key} (v${item.version}, ${item.valueBytes}B, ${item.updatedAt})`);
5387
4919
  } catch (err) {
5388
- console.error(err instanceof Error ? err.message : String(err));
5389
- process.exit(1);
4920
+ fail(err);
5390
4921
  }
5391
4922
  }
5392
4923
  });
5393
- var del2 = defineCommand19({
5394
- meta: { name: "delete", description: "Delete one or more remote secrets" },
4924
+ var del2 = defineCommand20({
4925
+ meta: { name: "delete", description: "Delete one or more secrets" },
5395
4926
  args: {
5396
- ...commonArgs(),
5397
- key: { type: "positional", description: "Secret key", required: true },
5398
- yes: { type: "boolean", alias: "y", description: "Proceed without confirmation; accepted for Doppler CLI compatibility", default: false },
5399
- raw: { type: "boolean", description: "Accepted for Doppler CLI compatibility; DeepSpace has no variable interpolation", default: false }
4927
+ ...COMMON_ARGS,
4928
+ key: { type: "positional", description: "Secret name (repeatable)", required: true }
5400
4929
  },
5401
4930
  async run({ args }) {
5402
4931
  try {
5403
- const token = await ensureToken();
5404
- const { appDir: dir, target } = targetFromArgs(args);
5405
- const keys = positionals(args, "key").map((key) => validateSecretName(key));
5406
- for (const key of keys) {
5407
- await deleteSecret(
5408
- DEPLOY_URL5,
5409
- token,
5410
- target.projectName,
5411
- target.configName,
5412
- key,
5413
- appScopedSecretsApiOptions(dir, target)
5414
- );
5415
- }
5416
- const count = await refreshCacheIfLinked(token, dir, target);
5417
- const requiredSecrets = removeRequiredSecretsIfTopLevelLinked(dir, target, keys);
5418
- if (args.json) {
5419
- process.stdout.write(JSON.stringify({ ok: true, deleted: keys, target, ...cacheJson(count), ...requiredSecretsJson(requiredSecrets) }) + "\n");
5420
- return;
5421
- }
5422
- console.log(`Deleted ${keys.length} secret${keys.length === 1 ? "" : "s"} from ${target.projectName}/${target.configName}.`);
5423
- requiredSecretsNotice(requiredSecrets);
5424
- if (count === null) {
5425
- unlinkedTargetNotice(target);
5426
- } else {
5427
- console.log(`Refreshed ${count} cached secret${count === 1 ? "" : "s"}.`);
5428
- redeployReminder(target);
5429
- }
5430
- } catch (err) {
5431
- console.error(err instanceof Error ? err.message : String(err));
5432
- process.exit(1);
5433
- }
5434
- }
5435
- });
5436
- var pull = defineCommand19({
5437
- meta: { name: "pull", description: "Refresh .dev.vars* only when the selected config is linked" },
5438
- args: commonArgs(),
5439
- async run({ args }) {
5440
- try {
5441
- const token = await ensureToken();
5442
- const { appDir: dir, target } = targetFromArgs(args);
5443
- const values = await fetchSecretsValues(
5444
- DEPLOY_URL5,
5445
- token,
5446
- target.projectName,
5447
- target.configName,
5448
- appScopedSecretsApiOptions(dir, target)
4932
+ const t = await resolveTarget(args);
4933
+ const keys = [args.key, ...args._ ?? []].filter(
4934
+ (v) => typeof v === "string" && v.length > 0
5449
4935
  );
5450
- const count = Object.keys(values).length;
5451
- const linked = secretsTargetMatchesLink(dir, target);
5452
- if (linked) writeSecretsCacheFile(dir, values, target);
5453
- if (args.json) {
5454
- process.stdout.write(JSON.stringify({ ok: true, target, linked, cachedSecrets: linked ? count : null }) + "\n");
5455
- return;
4936
+ for (const key of keys) {
4937
+ await deleteSecret(DEPLOY_URL6, t.token, t.appId, t.configName, validateSecretName(key));
5456
4938
  }
5457
- console.log(`${linked ? "Pulled" : "Read"} ${count} secret${count === 1 ? "" : "s"} from ${target.projectName}/${target.configName}.`);
5458
- if (!linked) unlinkedTargetNotice(target);
4939
+ console.log(`Deleted ${keys.length} secret${keys.length === 1 ? "" : "s"} from ${t.configName}.`);
5459
4940
  } catch (err) {
5460
- console.error(err instanceof Error ? err.message : String(err));
5461
- process.exit(1);
4941
+ fail(err);
5462
4942
  }
5463
4943
  }
5464
4944
  });
5465
- var download = defineCommand19({
5466
- meta: { name: "download", description: "Download a config's secrets in a Doppler-compatible format" },
4945
+ var upload = defineCommand20({
4946
+ meta: { name: "upload", description: "Upload secrets from a dotenv or JSON file" },
5467
4947
  args: {
5468
- ...commonArgs(),
5469
- file: { type: "positional", description: "Output filepath; omit when using --no-file", required: false },
5470
- format: { type: "string", description: "Output format: json, dotnet-json, env, yaml, docker, env-no-quotes", required: false },
5471
- "no-file": { type: "boolean", description: "Print secrets to stdout instead of writing a file", default: false },
5472
- raw: { type: "boolean", description: "Accepted for Doppler CLI compatibility; DeepSpace has no variable interpolation", default: false },
5473
- "no-cache": { type: "boolean", description: "Accepted for Doppler CLI compatibility; DeepSpace does not use a download fallback cache", default: false },
5474
- "no-fallback": { type: "boolean", description: "Accepted for Doppler CLI compatibility; DeepSpace does not use fallback files", default: false },
5475
- offline: { type: "boolean", description: "Accepted for Doppler CLI compatibility; offline fallback files are not supported", default: false }
5476
- },
5477
- async run({ args, rawArgs }) {
5478
- try {
5479
- const token = await ensureToken();
5480
- const { appDir: dir, target } = targetFromArgs(args);
5481
- const values = await fetchSecretsValues(
5482
- DEPLOY_URL5,
5483
- token,
5484
- target.projectName,
5485
- target.configName,
5486
- appScopedSecretsApiOptions(dir, target)
5487
- );
5488
- const output = formatSecretsDownload(values, downloadFormat(args.format));
5489
- const file = typeof args.file === "string" && args.file ? args.file : void 0;
5490
- const noFile = Boolean(args.noFile) || hasRawFlag(rawArgs, "no-file");
5491
- if (noFile || !file) {
5492
- if (!noFile && !file) {
5493
- throw new Error("Provide an output filepath or pass --no-file to print to stdout.");
5494
- }
5495
- process.stdout.write(output);
5496
- return;
5497
- }
5498
- writeSecretFileSync(resolve10(file), output);
5499
- if (args.json) {
5500
- process.stdout.write(JSON.stringify({ ok: true, path: resolve10(file), target, secretCount: Object.keys(values).length }) + "\n");
5501
- return;
5502
- }
5503
- console.log(`Downloaded ${Object.keys(values).length} secret${Object.keys(values).length === 1 ? "" : "s"} to ${file}.`);
5504
- } catch (err) {
5505
- console.error(err instanceof Error ? err.message : String(err));
5506
- process.exit(1);
4948
+ ...COMMON_ARGS,
4949
+ file: { type: "positional", description: "Path to a dotenv or JSON file (- for stdin)", required: true },
4950
+ replace: {
4951
+ type: "boolean",
4952
+ description: "Replace the whole config (delete keys missing from the file)",
4953
+ default: false
5507
4954
  }
5508
- }
5509
- });
5510
- var upload = defineCommand19({
5511
- meta: { name: "upload", description: "Upload remote secrets from a dotenv or JSON file" },
5512
- args: {
5513
- ...commonArgs(),
5514
- file: { type: "positional", description: "Path to dotenv/JSON file", required: true },
5515
- replace: { type: "boolean", description: "Delete secrets absent from the upload", default: false },
5516
- raw: { type: "boolean", description: "Accepted for Doppler CLI compatibility; DeepSpace has no variable interpolation", default: false }
5517
4955
  },
5518
4956
  async run({ args }) {
5519
4957
  try {
5520
- const token = await ensureToken();
5521
- const { appDir: dir, target } = targetFromArgs(args);
5522
- const file = String(args.file);
5523
- const content = readExistingFile(file);
4958
+ const t = await resolveTarget(args);
4959
+ const content = args.file === "-" ? readFileSync13(0, "utf-8") : readFileSync13(args.file, "utf-8");
5524
4960
  const secrets = parseSecretsUpload(content);
5525
- await uploadSecrets(
5526
- DEPLOY_URL5,
5527
- token,
5528
- target.projectName,
5529
- target.configName,
5530
- secrets,
5531
- !!args.replace,
5532
- appScopedSecretsApiOptions(dir, target)
5533
- );
5534
- const count = await refreshCacheIfLinked(token, dir, target);
5535
- const requiredSecrets = updateRequiredSecretsIfTopLevelLinked(dir, target, Object.keys(secrets), {
5536
- replace: !!args.replace
5537
- });
5538
- if (args.json) {
5539
- process.stdout.write(JSON.stringify({ ok: true, uploaded: Object.keys(secrets), target, ...cacheJson(count), ...requiredSecretsJson(requiredSecrets) }) + "\n");
5540
- return;
5541
- }
5542
- console.log(`Uploaded ${Object.keys(secrets).length} secret${Object.keys(secrets).length === 1 ? "" : "s"} to ${target.projectName}/${target.configName}.`);
5543
- requiredSecretsNotice(requiredSecrets);
5544
- if (count === null) {
5545
- unlinkedTargetNotice(target);
5546
- } else {
5547
- console.log(`Refreshed ${count} cached secret${count === 1 ? "" : "s"}.`);
5548
- redeployReminder(target);
5549
- }
5550
- } catch (err) {
5551
- console.error(err instanceof Error ? err.message : String(err));
5552
- process.exit(1);
5553
- }
5554
- }
5555
- });
5556
- var configsList = defineCommand19({
5557
- meta: { name: "list", description: "List configs in a secrets project" },
5558
- args: commonArgs(),
5559
- async run({ args }) {
5560
- try {
5561
- const token = await ensureToken();
5562
- const { appDir: dir, target } = targetFromArgs(args);
5563
- const configs2 = await listConfigs(
5564
- DEPLOY_URL5,
5565
- token,
5566
- target.projectName,
5567
- appScopedSecretsApiOptions(dir, target)
5568
- );
5569
- if (args.json) {
5570
- process.stdout.write(JSON.stringify({ projectName: target.projectName, configs: configs2 }, null, 2) + "\n");
5571
- return;
5572
- }
5573
- if (configs2.length === 0) {
5574
- console.log(`No configs in ${target.projectName}.`);
5575
- return;
5576
- }
5577
- for (const cfg of configs2) {
5578
- console.log(`${cfg.name.padEnd(24)} ${cfg.secretCount ?? 0} secret${cfg.secretCount === 1 ? "" : "s"} ${cfg.updatedAt}`);
5579
- }
4961
+ if (Object.keys(secrets).length === 0) fail(new Error("No secrets found in the input."));
4962
+ await uploadSecrets(DEPLOY_URL6, t.token, t.appId, t.configName, secrets, args.replace);
4963
+ console.log(`Uploaded ${Object.keys(secrets).length} secrets to ${t.configName}.`);
5580
4964
  } catch (err) {
5581
- console.error(err instanceof Error ? err.message : String(err));
5582
- process.exit(1);
4965
+ fail(err);
5583
4966
  }
5584
4967
  }
5585
4968
  });
5586
- var configsCreate = defineCommand19({
5587
- meta: { name: "create", description: "Create a remote config without linking it" },
4969
+ var download = defineCommand20({
4970
+ meta: { name: "download", description: "Download a config\u2019s secrets (dotenv/json/shell)" },
5588
4971
  args: {
5589
- ...commonArgs(),
5590
- configName: { type: "positional", description: "Config name", required: false },
5591
- name: { type: "string", description: "Config name; Doppler-compatible --name flag", required: false }
4972
+ ...COMMON_ARGS,
4973
+ format: {
4974
+ type: "string",
4975
+ description: "dotenv (default) | json | shell",
4976
+ default: "dotenv"
4977
+ }
5592
4978
  },
5593
4979
  async run({ args }) {
5594
4980
  try {
5595
- if (args.copyFrom !== void 0 || args["copy-from"] !== void 0) {
5596
- throw new Error("Unsupported flag: --copy-from. Use `npx deepspace secrets configs clone <source-config> --name <new-config>`.");
5597
- }
5598
- const token = await ensureToken();
5599
- const dir = appDir(args);
5600
- const projectName = validateProjectName(args.project || resolveSecretsTarget(dir, { wranglerEnv: wranglerEnv(args) }).projectName);
5601
- const rawName = typeof args.config === "string" && args.config ? args.config : typeof args.configName === "string" && args.configName ? args.configName : args.name;
5602
- if (typeof rawName !== "string" || !rawName) throw new Error("Config name required");
5603
- const configName = validateConfigName(rawName);
5604
- const target = { projectName, configName, wranglerEnv: wranglerEnv(args) };
5605
- const result = await createConfig(
5606
- DEPLOY_URL5,
5607
- token,
5608
- projectName,
5609
- configName,
5610
- void 0,
5611
- appScopedSecretsApiOptions(dir, target)
5612
- );
5613
- const count = await refreshCacheIfLinked(token, dir, target);
5614
- if (args.json) {
5615
- process.stdout.write(JSON.stringify({ ...result, target, ...cacheJson(count) }) + "\n");
5616
- return;
5617
- }
5618
- if (result.created) {
5619
- console.log(`Created ${projectName}/${configName}.`);
5620
- } else {
5621
- console.log(`Config ${projectName}/${configName} already exists.`);
5622
- }
5623
- if (count === null) {
5624
- unlinkedTargetNotice(target);
5625
- } else {
5626
- console.log(`Refreshed ${count} cached secret${count === 1 ? "" : "s"}.`);
4981
+ const t = await resolveTarget(args);
4982
+ const format = args.format;
4983
+ if (!["dotenv", "json", "shell"].includes(format)) {
4984
+ fail(new Error(`Unknown format "${args.format}" \u2014 use dotenv, json, or shell.`));
5627
4985
  }
4986
+ const { secrets } = await fetchSecretsValues(DEPLOY_URL6, t.token, t.appId, t.configName);
4987
+ process.stdout.write(formatSecretsDownload(secrets, format));
5628
4988
  } catch (err) {
5629
- console.error(err instanceof Error ? err.message : String(err));
5630
- process.exit(1);
4989
+ fail(err);
5631
4990
  }
5632
4991
  }
5633
4992
  });
5634
- function resolveConfigCloneArgs(args) {
5635
- const dir = appDir(args);
5636
- const baseTarget = resolveSecretsTarget(dir, {
5637
- project: typeof args.project === "string" && args.project ? args.project : void 0,
5638
- wranglerEnv: wranglerEnv(args)
5639
- });
5640
- const sources = positionals(args, "sourceConfig");
5641
- if (sources.length > 1) throw new Error("Only one source config can be provided.");
5642
- const sourceConfigName = validateConfigName(
5643
- sources[0] ?? (typeof args.config === "string" && args.config ? args.config : baseTarget.configName)
5644
- );
5645
- if (typeof args.name !== "string" || !args.name) {
5646
- throw new Error("Destination config name required; pass --name <new-config>.");
5647
- }
5648
- const target = {
5649
- projectName: baseTarget.projectName,
5650
- configName: validateConfigName(args.name),
5651
- wranglerEnv: baseTarget.wranglerEnv
5652
- };
5653
- return { appDir: dir, sourceConfigName, target };
5654
- }
5655
- var configsClone = defineCommand19({
5656
- meta: { name: "clone", description: "Create a remote config by copying another config server-side" },
5657
- args: {
5658
- ...commonArgs(),
5659
- sourceConfig: { type: "positional", description: "Source config name (default: --config or linked/default config)", required: false },
5660
- name: { type: "string", description: "Destination config name", required: true }
5661
- },
4993
+ var pull = defineCommand20({
4994
+ meta: { name: "pull", description: "Refresh the .dev.vars cache from the app store" },
4995
+ args: { ...COMMON_ARGS },
5662
4996
  async run({ args }) {
5663
4997
  try {
5664
- const token = await ensureToken();
5665
- const { appDir: dir, sourceConfigName, target } = resolveConfigCloneArgs(args);
5666
- const result = await createConfig(
5667
- DEPLOY_URL5,
5668
- token,
5669
- target.projectName,
5670
- target.configName,
5671
- sourceConfigName,
5672
- appScopedSecretsApiOptions(dir, target)
5673
- );
5674
- const count = await refreshCacheIfLinked(token, dir, target);
5675
- if (args.json) {
5676
- process.stdout.write(JSON.stringify({ ...result, target, sourceConfigName, ...cacheJson(count) }) + "\n");
4998
+ const wranglerEnv = args.env?.trim() || void 0;
4999
+ const appDir = findAppDir();
5000
+ if (!appDir) fail(new Error("Run from a DeepSpace app directory (one containing wrangler.toml)."));
5001
+ const t = await resolveTarget(args);
5002
+ const ownerId = decodeJwtPayload(t.token).sub;
5003
+ const pulled = await pullAppSecretsCache(DEPLOY_URL6, t.token, t.appId, t.configName);
5004
+ if (!pulled) {
5005
+ console.log(`No secrets in ${t.configName} yet \u2014 nothing to pull.`);
5677
5006
  return;
5678
5007
  }
5679
- if (result.created) {
5680
- const copiedSecrets = result.copiedSecrets ?? 0;
5681
- console.log(
5682
- `Created ${target.projectName}/${target.configName} from ${sourceConfigName} (${copiedSecrets} secret${copiedSecrets === 1 ? "" : "s"}).`
5683
- );
5684
- } else {
5685
- console.log(`Config ${target.projectName}/${target.configName} already exists.`);
5686
- }
5687
- if (count === null) {
5688
- unlinkedTargetNotice(target);
5689
- } else {
5690
- console.log(`Refreshed ${count} cached secret${count === 1 ? "" : "s"}.`);
5691
- }
5008
+ await writeDevVars(appDir, ownerId, t.token, wranglerEnv, {
5009
+ appId: t.appId,
5010
+ generatedSecretsCache: renderSecretsCache(pulled.values, pulled),
5011
+ sharedDevVarsCache: true
5012
+ });
5013
+ console.log(
5014
+ `Pulled ${Object.keys(pulled.values).length} secrets (${t.configName}) into .dev.vars.`
5015
+ );
5692
5016
  } catch (err) {
5693
- console.error(err instanceof Error ? err.message : String(err));
5694
- process.exit(1);
5017
+ fail(err);
5695
5018
  }
5696
5019
  }
5697
5020
  });
5698
- var configsDelete = defineCommand19({
5699
- meta: { name: "delete", description: "Delete a config and all of its secrets" },
5700
- args: {
5701
- ...commonArgs(),
5702
- name: { type: "positional", description: "Config name", required: false }
5703
- },
5021
+ var configsList = defineCommand20({
5022
+ meta: { name: "list", description: "List the app\u2019s configs" },
5023
+ args: { ...COMMON_ARGS },
5704
5024
  async run({ args }) {
5705
5025
  try {
5706
- const token = await ensureToken();
5707
- const dir = appDir(args);
5708
- const { target } = targetFromArgs(args);
5709
- const rawName = typeof args.name === "string" && args.name ? args.name : args.config;
5710
- if (typeof rawName !== "string" || !rawName) throw new Error("Config name required");
5711
- const configName = validateConfigName(rawName);
5712
- const link = resolveSecretsLink(dir, target.wranglerEnv);
5713
- const deletingLinked = link.linked && link.projectName === target.projectName && link.configName === configName;
5714
- await deleteConfig(
5715
- DEPLOY_URL5,
5716
- token,
5717
- target.projectName,
5718
- configName,
5719
- appScopedSecretsApiOptions(dir, target)
5720
- );
5721
- if (deletingLinked) {
5722
- removeWranglerSecretsLink(dir, target.wranglerEnv);
5723
- clearSecretsCacheFile(dir);
5724
- }
5725
- if (args.json) {
5726
- process.stdout.write(JSON.stringify({
5727
- ok: true,
5728
- deleted: configName,
5729
- projectName: target.projectName,
5730
- unlinked: deletingLinked,
5731
- cachedSecrets: deletingLinked ? 0 : null
5732
- }) + "\n");
5026
+ const t = await resolveTarget(args);
5027
+ const { configs: configs2 } = await listConfigs(DEPLOY_URL6, t.token, t.appId);
5028
+ if (configs2.length === 0) {
5029
+ console.log("No configs yet \u2014 the first `secrets set` creates one.");
5733
5030
  return;
5734
5031
  }
5735
- console.log(`Deleted ${target.projectName}/${configName}.`);
5736
- if (deletingLinked) {
5737
- console.log("Deleted config was linked; removed the local link and cleared generated cache.");
5738
- redeployReminder({ ...target, configName });
5032
+ for (const cfg of configs2) {
5033
+ console.log(`${cfg.name} (${cfg.secretCount ?? 0} secrets, updated ${cfg.updatedAt})`);
5739
5034
  }
5740
5035
  } catch (err) {
5741
- console.error(err instanceof Error ? err.message : String(err));
5742
- process.exit(1);
5036
+ fail(err);
5743
5037
  }
5744
5038
  }
5745
5039
  });
5746
- var setup = defineCommand19({
5747
- meta: { name: "setup", description: "Link this app/env to a secrets project config" },
5748
- args: setupArgs(),
5040
+ var configsCreate = defineCommand20({
5041
+ meta: { name: "create", description: "Create a config (optionally copying another)" },
5042
+ args: {
5043
+ ...COMMON_ARGS,
5044
+ name: { type: "positional", description: "Config name", required: true },
5045
+ "copy-from": { type: "string", description: "Copy secrets from this config", required: false }
5046
+ },
5749
5047
  async run({ args }) {
5750
5048
  try {
5751
- const { appDir: dir, target, projectOverride } = resolveSecretsSetupArgs(args);
5752
- writeLinkForTarget(dir, target, projectOverride);
5753
- await bestEffortSetServerSecretsLink(dir, target);
5754
- const remoteCheck = await ensureLinkedRemoteTarget(dir, target);
5755
- const migration = await importLegacyDevVarsSecrets(dir, target, remoteCheck);
5756
- if (args.json) {
5757
- process.stdout.write(
5758
- JSON.stringify({ ok: true, target, remoteCheck, ...migration && { migration } }) + "\n"
5759
- );
5760
- return;
5761
- }
5762
- console.log(`Linked ${target.wranglerEnv ? `[env.${target.wranglerEnv}]` : "top-level app"} to ${target.projectName}/${target.configName}.`);
5763
- if (remoteCheck.status === "created") {
5764
- console.log(`Created remote config ${target.projectName}/${target.configName}.`);
5765
- }
5766
- printLegacySecretsImport(migration, target);
5767
- printLinkRemoteWarning(remoteCheck);
5049
+ const t = await resolveTarget(args);
5050
+ const name = validateConfigName(args.name);
5051
+ await createConfig(DEPLOY_URL6, t.token, t.appId, name, args["copy-from"] || void 0);
5052
+ console.log(`Created ${name}.`);
5768
5053
  } catch (err) {
5769
- console.error(err instanceof Error ? err.message : String(err));
5770
- process.exit(1);
5054
+ fail(err);
5771
5055
  }
5772
5056
  }
5773
5057
  });
5774
- var configureUnset = defineCommand19({
5775
- meta: { name: "unset", description: "Unset linked secrets project/config settings" },
5058
+ var configsDelete = defineCommand20({
5059
+ meta: { name: "delete", description: "Delete a config and all of its secrets" },
5776
5060
  args: {
5777
- ...scopedArgs(),
5778
- name: { type: "positional", description: "Setting names to unset: project and/or config", required: false }
5061
+ ...COMMON_ARGS,
5062
+ name: { type: "positional", description: "Config name", required: true }
5779
5063
  },
5780
5064
  async run({ args }) {
5781
5065
  try {
5782
- const { appDir: dir, unset, wranglerEnv: targetEnv } = resolveConfigureUnsetArgs(args);
5783
- removeWranglerSecretsLinkKeys(dir, targetEnv, unset);
5784
- clearSecretsCacheFile(dir);
5785
- if (unset.includes("project")) {
5786
- await bestEffortClearServerSecretsLink(dir, targetEnv);
5787
- }
5788
- if (args.json) {
5789
- process.stdout.write(JSON.stringify({ ok: true, unset, env: targetEnv ?? null, cachedSecrets: 0 }) + "\n");
5790
- return;
5791
- }
5792
- console.log(`Unset ${unset.join(", ")} for ${targetEnv ? `[env.${targetEnv}]` : "top-level app"} secrets config.`);
5793
- console.log("Cleared generated secrets cache for that target.");
5066
+ const t = await resolveTarget(args);
5067
+ const name = validateConfigName(args.name);
5068
+ await deleteConfig(DEPLOY_URL6, t.token, t.appId, name);
5069
+ console.log(`Deleted ${name}.`);
5794
5070
  } catch (err) {
5795
- console.error(err instanceof Error ? err.message : String(err));
5796
- process.exit(1);
5071
+ fail(err);
5797
5072
  }
5798
5073
  }
5799
5074
  });
5800
- var configure = defineCommand19({
5801
- meta: { name: "configure", description: "Manage local secrets CLI configuration" },
5802
- subCommands: {
5803
- unset: configureUnset
5804
- }
5805
- });
5806
- var configs = defineCommand19({
5807
- meta: { name: "configs", description: "Manage remote secrets configs" },
5808
- subCommands: {
5809
- list: configsList,
5810
- create: configsCreate,
5811
- clone: configsClone,
5812
- delete: configsDelete
5813
- }
5075
+ var configs = defineCommand20({
5076
+ meta: { name: "configs", description: "Manage the app\u2019s secrets configs" },
5077
+ subCommands: { list: configsList, create: configsCreate, delete: configsDelete }
5814
5078
  });
5815
- var secrets_default = defineCommand19({
5816
- meta: { name: "secrets", description: "Manage remote DeepSpace app secrets" },
5079
+ var secrets_default = defineCommand20({
5080
+ meta: { name: "secrets", description: "Manage the app\u2019s secrets store" },
5817
5081
  subCommands: {
5818
5082
  list: list5,
5819
5083
  set,
5820
5084
  get,
5821
5085
  delete: del2,
5822
- pull,
5823
- download,
5824
5086
  upload,
5825
- setup,
5826
- configure,
5087
+ download,
5088
+ pull,
5827
5089
  configs
5828
5090
  }
5829
5091
  });
5830
- function parseSetInputs(inputs) {
5831
- if (inputs.length === 0) throw new Error("At least one secret is required");
5832
- const out = {};
5833
- if (inputs.every((input) => input.includes("="))) {
5834
- for (const raw of inputs) {
5835
- const eq = raw.indexOf("=");
5836
- const key = validateSecretName(raw.slice(0, eq));
5837
- const value = raw.slice(eq + 1);
5838
- validateSecretValue(key, value);
5839
- out[key] = value;
5092
+
5093
+ // src/cli/commands/init.ts
5094
+ import { defineCommand as defineCommand21 } from "citty";
5095
+ var init_default = defineCommand21({
5096
+ meta: {
5097
+ name: "init",
5098
+ description: "Mint this app\u2019s immutable DEEPSPACE_APP_ID into wrangler.toml"
5099
+ },
5100
+ args: {
5101
+ "new-id": {
5102
+ type: "boolean",
5103
+ description: "Replace the existing id \u2014 forks this repo as a SEPARATE app (new data, new secrets, new registration). The original app keeps running.",
5104
+ default: false
5105
+ },
5106
+ env: {
5107
+ type: "string",
5108
+ alias: "e",
5109
+ description: "wrangler.toml [env.<name>] block to stamp (each env is its own app)",
5110
+ required: false
5111
+ }
5112
+ },
5113
+ async run({ args }) {
5114
+ const appDir = findAppDir();
5115
+ if (!appDir) {
5116
+ console.error("Run from a DeepSpace app directory (one containing wrangler.toml).");
5117
+ process.exit(1);
5118
+ }
5119
+ const envName = args.env || void 0;
5120
+ const existing = readAppId(appDir, envName);
5121
+ if (existing && !args["new-id"]) {
5122
+ console.log(`Already initialized: ${existing}${envName ? ` (env: ${envName})` : ""}`);
5123
+ return;
5124
+ }
5125
+ const appId = mintAppId();
5126
+ writeAppId(appDir, appId, { wranglerEnv: envName, force: args["new-id"] });
5127
+ if (existing) {
5128
+ console.log(`Forked: ${existing} \u2192 ${appId}${envName ? ` (env: ${envName})` : ""}`);
5129
+ console.log("The next deploy registers this as a NEW app; the original is untouched.");
5130
+ } else {
5131
+ console.log(`Minted ${appId}${envName ? ` (env: ${envName})` : ""} \u2014 commit wrangler.toml.`);
5132
+ console.log("The first deploy registers it and claims the `name` subdomain.");
5840
5133
  }
5841
- return out;
5842
- }
5843
- if (inputs.length === 1) {
5844
- const raw = inputs[0];
5845
- throw new Error(`Secret value required for "${raw}". Use KEY=value or KEY value.`);
5846
- }
5847
- if (inputs.length === 2 && !inputs[0].includes("=")) {
5848
- const key = validateSecretName(inputs[0]);
5849
- const value = inputs[1];
5850
- validateSecretValue(key, value);
5851
- out[key] = value;
5852
- return out;
5853
5134
  }
5854
- throw new Error("Use KEY=value for multiple secrets, or KEY value for one secret.");
5855
- }
5856
- function readExistingFile(path) {
5857
- const full = resolve10(path);
5858
- if (!existsSync14(full)) throw new Error(`File not found: ${path}`);
5859
- return readFileSync13(full, "utf-8");
5860
- }
5135
+ });
5861
5136
 
5862
5137
  // src/cli/cli.ts
5863
5138
  var __dirname = dirname4(fileURLToPath2(import.meta.url));
5864
- var pkg = JSON.parse(readFileSync14(join17(__dirname, "..", "package.json"), "utf-8"));
5865
- if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync15("/etc/ssl/certs/ca-certificates.crt")) {
5139
+ var pkg = JSON.parse(readFileSync14(join16(__dirname, "..", "package.json"), "utf-8"));
5140
+ if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync14("/etc/ssl/certs/ca-certificates.crt")) {
5866
5141
  process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
5867
5142
  }
5868
- var create2 = defineCommand20({
5143
+ var create2 = defineCommand22({
5869
5144
  meta: {
5870
5145
  name: "create",
5871
5146
  description: "Create a new DeepSpace app"
@@ -5884,13 +5159,14 @@ var create2 = defineCommand20({
5884
5159
  execSync4(`npm create deepspace@latest${name}`, { stdio: "inherit" });
5885
5160
  }
5886
5161
  });
5887
- var main = defineCommand20({
5162
+ var main = defineCommand22({
5888
5163
  meta: {
5889
5164
  name: "deepspace",
5890
5165
  version: pkg.version,
5891
5166
  description: "DeepSpace SDK CLI"
5892
5167
  },
5893
5168
  subCommands: {
5169
+ init: init_default,
5894
5170
  create: create2,
5895
5171
  login: login_default,
5896
5172
  logout: logout_default,
@@ -5908,6 +5184,7 @@ var main = defineCommand20({
5908
5184
  undeploy: undeploy_default,
5909
5185
  domain: domain_default,
5910
5186
  collaborators: collaborators_default,
5187
+ transfer: transfer_default,
5911
5188
  library: library_default,
5912
5189
  feedback: feedback_default,
5913
5190
  secrets: secrets_default