deepspace 0.5.1 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -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
@@ -13,13 +13,14 @@ var API_ERROR_HINTS = {
13
13
  app_not_found: "App not found. Check the app name (wrangler.toml `name`) and that it has been deployed.",
14
14
  not_app_owner_or_collaborator: "You must be the app owner or a collaborator to do this.",
15
15
  test_account_cannot_be_collaborator: "Test accounts cannot be added as collaborators. Use a real DeepSpace account.",
16
- user_not_found: "No DeepSpace user with that email. New users must sign in and use DeepSpace once (e.g. run `deepspace whoami`) before they can be referenced by email."
16
+ user_not_found: "No DeepSpace user with that email. They need to log in to DeepSpace at least once (`deepspace login`, or sign in to any app) before they can be referenced by email."
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,301 @@ 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
+ async function resolveOwnedAppId(deployUrl, token, appName) {
305
+ try {
306
+ const res = await fetch(`${deployUrl}/api/apps`, {
307
+ headers: { Authorization: `Bearer ${token}` }
308
+ });
309
+ if (!res.ok) return null;
310
+ const body = await res.json();
311
+ const match = body.apps?.find((a) => a.name === appName);
312
+ return match?.appId ?? null;
313
+ } catch {
314
+ return null;
315
+ }
316
+ }
317
+ function readAppId(cwd = process.cwd(), wranglerEnv) {
318
+ const wranglerPath = join(resolve(cwd), "wrangler.toml");
319
+ if (!existsSync(wranglerPath)) return null;
320
+ let cfg;
321
+ try {
322
+ cfg = parseToml(readFileSync(wranglerPath, "utf-8"));
323
+ } catch (err) {
324
+ throw new Error(
325
+ `Could not parse ${wranglerPath}: ${err instanceof Error ? err.message : String(err)}`
326
+ );
327
+ }
328
+ const vars = wranglerEnv ? cfg.env?.[wranglerEnv]?.vars : cfg.vars;
329
+ const id = vars?.DEEPSPACE_APP_ID;
330
+ return typeof id === "string" && APP_ID_RE.test(id) ? id : null;
331
+ }
332
+ function writeAppId(cwd, appId, opts = {}) {
333
+ const wranglerPath = join(resolve(cwd), "wrangler.toml");
334
+ if (!existsSync(wranglerPath)) {
335
+ throw new Error(`No wrangler.toml in ${resolve(cwd)}`);
336
+ }
337
+ const existing = readAppId(cwd, opts.wranglerEnv);
338
+ if (existing && !opts.force) {
339
+ if (existing === appId) return;
340
+ throw new Error(
341
+ `wrangler.toml already carries ${existing}. The app id is immutable \u2014 use --new-id only to fork this repo as a separate app.`
342
+ );
343
+ }
344
+ let src = readFileSync(wranglerPath, "utf-8");
345
+ const header = opts.wranglerEnv ? `[env.${opts.wranglerEnv}.vars]` : "[vars]";
346
+ const line = `DEEPSPACE_APP_ID = "${appId}"`;
347
+ const headerRe = new RegExp(`^\\${header.replace(/\./g, "\\.")}[ \\t]*$`, "m");
348
+ const idLineRe = /^DEEPSPACE_APP_ID\s*=.*$/m;
349
+ const match = headerRe.exec(src);
350
+ if (match) {
351
+ const blockStart = match.index + match[0].length;
352
+ const rest = src.slice(blockStart);
353
+ const nextSection = rest.search(/^\s*\[/m);
354
+ const blockEnd = nextSection === -1 ? src.length : blockStart + nextSection;
355
+ const block = src.slice(blockStart, blockEnd);
356
+ const newBlock = idLineRe.test(block) ? block.replace(idLineRe, line) : `
357
+ ${line}${block}`;
358
+ src = src.slice(0, blockStart) + newBlock + src.slice(blockEnd);
359
+ } else {
360
+ src = src.trimEnd() + `
361
+
362
+ ${header}
363
+ ${line}
364
+ `;
365
+ }
366
+ writeFileSync(wranglerPath, src);
367
+ }
78
368
 
79
369
  // src/cli/lib/secure-file.ts
80
- import { chmodSync, writeFileSync } from "fs";
370
+ import { chmodSync, writeFileSync as writeFileSync2 } from "fs";
81
371
  function writeSecretFileSync(path, content) {
82
- writeFileSync(path, content, { mode: 384 });
372
+ writeFileSync2(path, content, { mode: 384 });
83
373
  try {
84
374
  chmodSync(path, 384);
85
375
  } catch {
@@ -87,9 +377,9 @@ function writeSecretFileSync(path, content) {
87
377
  }
88
378
 
89
379
  // 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";
380
+ import { existsSync as existsSync2, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync3 } from "fs";
381
+ import { join as join2 } from "path";
382
+ import { parse as parseToml2, stringify as stringifyToml } from "smol-toml";
93
383
 
94
384
  // src/server/rooms/app-name.ts
95
385
  var APP_NAME_RULES = {
@@ -187,35 +477,35 @@ function resolveAppNameForEnv(config, envName) {
187
477
  }
188
478
  return resolveAppName(envBlock.name);
189
479
  }
190
- function devVarsPathFor(appDir2, envName, opts = {}) {
191
- if (opts.sharedDevVarsCache || !envName) return join(appDir2, ".dev.vars");
192
- return join(appDir2, `.dev.vars.${envName}`);
480
+ function devVarsPathFor(appDir, envName, opts = {}) {
481
+ if (opts.sharedDevVarsCache || !envName) return join2(appDir, ".dev.vars");
482
+ return join2(appDir, `.dev.vars.${envName}`);
193
483
  }
194
- function legacyEnvDevVarsPathFor(appDir2, envName) {
195
- return envName ? join(appDir2, `.dev.vars.${envName}`) : void 0;
484
+ function legacyEnvDevVarsPathFor(appDir, envName) {
485
+ return envName ? join2(appDir, `.dev.vars.${envName}`) : void 0;
196
486
  }
197
- function warnIfLegacyEnvDevVarsExists(appDir2, envName, warn = console.warn) {
198
- const legacyPath = legacyEnvDevVarsPathFor(appDir2, envName);
199
- if (!legacyPath || !existsSync(legacyPath)) return;
487
+ function warnIfLegacyEnvDevVarsExists(appDir, envName, warn = console.warn) {
488
+ const legacyPath = legacyEnvDevVarsPathFor(appDir, envName);
489
+ if (!legacyPath || !existsSync2(legacyPath)) return;
200
490
  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.`
491
+ `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
492
  );
203
493
  }
204
- function prepareWranglerEnvConfig(appDir2, envName, opts = {}) {
494
+ function prepareWranglerEnvConfig(appDir, envName, opts = {}) {
205
495
  if (!envName) return { cleanup() {
206
496
  } };
207
497
  if (!opts.sharedDevVarsCache) return { wranglerEnv: envName, cleanup() {
208
498
  } };
209
- warnIfLegacyEnvDevVarsExists(appDir2, envName, opts.warn);
210
- const config = readWranglerConfig(appDir2);
499
+ warnIfLegacyEnvDevVarsExists(appDir, envName, opts.warn);
500
+ const config = readWranglerConfig(appDir);
211
501
  const resolved = resolveAppNameForEnv(config, envName);
212
502
  if (!resolved.ok) throw new Error(resolved.reason);
213
503
  const flattened = flattenWranglerEnvConfig(config, envName);
214
- const configPath = join(
215
- appDir2,
504
+ const configPath = join2(
505
+ appDir,
216
506
  `.wrangler.deepspace.${safeGeneratedConfigSegment(envName)}.${process.pid}.${++generatedConfigSeq}.toml`
217
507
  );
218
- writeFileSync2(
508
+ writeFileSync3(
219
509
  configPath,
220
510
  [
221
511
  "# Generated by DeepSpace for this CLI run.",
@@ -271,17 +561,17 @@ var WranglerConfigError = class extends Error {
271
561
  this.name = "WranglerConfigError";
272
562
  }
273
563
  };
274
- function readWranglerConfig(appDir2) {
275
- const path = join(appDir2, "wrangler.toml");
276
- const raw = readFileSync(path, "utf-8");
564
+ function readWranglerConfig(appDir) {
565
+ const path = join2(appDir, "wrangler.toml");
566
+ const raw = readFileSync2(path, "utf-8");
277
567
  try {
278
- return parseToml(raw);
568
+ return parseToml2(raw);
279
569
  } catch (err) {
280
570
  throw new WranglerConfigError(path, err);
281
571
  }
282
572
  }
283
- function hasWranglerConfig(appDir2) {
284
- return existsSync(join(appDir2, "wrangler.toml"));
573
+ function hasWranglerConfig(appDir) {
574
+ return existsSync2(join2(appDir, "wrangler.toml"));
285
575
  }
286
576
 
287
577
  // src/cli/env.ts
@@ -303,21 +593,12 @@ async function fetchPublicKey(authUrl) {
303
593
  }
304
594
  return data.publicKey;
305
595
  }
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;
596
+ function requireAppIdFor(appDir, wranglerEnv) {
597
+ const id = readAppId(appDir, wranglerEnv);
598
+ if (id) return id;
599
+ throw new Error(
600
+ "wrangler.toml has no DEEPSPACE_APP_ID" + (wranglerEnv ? ` for [env.${wranglerEnv}]` : "") + ". Run `deepspace init` (or `deepspace init --env <name>`) and retry."
601
+ );
321
602
  }
322
603
  async function mintAppOwnerJwt(authUrl, callerJwt, appName) {
323
604
  const res = await fetch(`${authUrl}/api/auth/mint-app-token`, {
@@ -326,7 +607,7 @@ async function mintAppOwnerJwt(authUrl, callerJwt, appName) {
326
607
  "Content-Type": "application/json",
327
608
  Authorization: `Bearer ${callerJwt}`
328
609
  },
329
- body: JSON.stringify({ appName })
610
+ body: JSON.stringify({ appId: appName })
330
611
  });
331
612
  if (!res.ok) {
332
613
  const err = await res.text().catch(() => "");
@@ -340,6 +621,7 @@ async function mintAppOwnerJwt(authUrl, callerJwt, appName) {
340
621
  }
341
622
  async function fetchAppIdentityToken(deployUrl, callerJwt, appName) {
342
623
  const res = await fetch(`${deployUrl}/api/apps/${encodeURIComponent(appName)}/identity-token`, {
624
+ // appName carries the appId
343
625
  method: "POST",
344
626
  headers: { Authorization: `Bearer ${callerJwt}` }
345
627
  });
@@ -368,7 +650,6 @@ var SDK_MANAGED_KEYS = /* @__PURE__ */ new Set([
368
650
  "ALLOW_DEBUG_ROUTES"
369
651
  ]);
370
652
  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
653
  function extractCustomDevVars(content) {
373
654
  const lines = content.split("\n");
374
655
  const out = [];
@@ -409,11 +690,6 @@ function extractCustomDevVars(content) {
409
690
  while (out.length && out[out.length - 1].trim() === "") out.pop();
410
691
  return out.join("\n");
411
692
  }
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
693
  function removeDevVarsKeys(content, keys) {
418
694
  const lines = content.split("\n");
419
695
  const out = [];
@@ -529,12 +805,12 @@ function unescapeQuotedDevVar(value) {
529
805
  }
530
806
  return out;
531
807
  }
532
- async function writeDevVars(appDir2, ownerId, callerJwt, wranglerEnv2, opts = {}) {
808
+ async function writeDevVars(appDir, ownerId, callerJwt, wranglerEnv, opts = {}) {
533
809
  const urls = PLATFORM_URLS;
534
810
  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);
811
+ const appId = opts.appId ?? requireAppIdFor(appDir, wranglerEnv);
812
+ const appOwnerJwt = await mintAppOwnerJwt(urls.auth, callerJwt, appId);
813
+ const appIdentityToken = await fetchAppIdentityToken(urls.deploy, callerJwt, appId);
538
814
  const sdkVars = [
539
815
  `AUTH_JWT_PUBLIC_KEY="${publicKey}"`,
540
816
  `AUTH_JWT_ISSUER=${urls.auth}/api/auth`,
@@ -549,20 +825,27 @@ async function writeDevVars(appDir2, ownerId, callerJwt, wranglerEnv2, opts = {}
549
825
  `ALLOW_DEBUG_ROUTES=true`
550
826
  ].join("\n");
551
827
  const useSharedDevVarsCache = opts.sharedDevVarsCache ?? opts.generatedSecretsCache !== void 0;
552
- const devVarsPath = devVarsPathFor(appDir2, wranglerEnv2, {
828
+ const devVarsPath = devVarsPathFor(appDir, wranglerEnv, {
553
829
  sharedDevVarsCache: useSharedDevVarsCache
554
830
  });
555
- const existing = existsSync2(devVarsPath) ? readFileSync2(devVarsPath, "utf-8") : "";
556
- const custom = opts.generatedSecretsCache ?? extractCustomDevVars(stripGeneratedSecretsCache(existing));
557
- const body = custom ? opts.generatedSecretsCache ? `${sdkVars}
831
+ const existing = existsSync3(devVarsPath) ? readFileSync3(devVarsPath, "utf-8") : "";
832
+ const custom = opts.generatedSecretsCache ?? extractCustomDevVars(stripGeneratedSecretsCache(existing).trimEnd());
833
+ let body;
834
+ if (!custom) {
835
+ body = `${sdkVars}
836
+ `;
837
+ } else if (opts.generatedSecretsCache) {
838
+ body = `${sdkVars}
558
839
 
559
840
  ${custom}
560
- ` : `${sdkVars}
841
+ `;
842
+ } else {
843
+ body = `${sdkVars}
561
844
 
562
845
  ${DEV_VARS_DIVIDER}
563
846
  ${custom}
564
- ` : `${sdkVars}
565
847
  `;
848
+ }
566
849
  writeSecretFileSync(devVarsPath, body);
567
850
  }
568
851
 
@@ -592,6 +875,7 @@ function openBrowser(url) {
592
875
 
593
876
  // src/cli/commands/login.ts
594
877
  var AUTH_URL = process.env.DEEPSPACE_AUTH_URL ?? PLATFORM_URLS.auth;
878
+ var API_URL = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
595
879
  var login_default = defineCommand({
596
880
  meta: {
597
881
  name: "login",
@@ -650,6 +934,7 @@ var login_default = defineCommand({
650
934
  process.exit(1);
651
935
  }
652
936
  storeCredentials(result.sessionToken, result.jwt);
937
+ await provisionProfile(result.jwt);
653
938
  s.stop("Authenticated");
654
939
  p2.log.success(`Logged in as ${result.name ?? result.email}`);
655
940
  p2.note(
@@ -688,13 +973,19 @@ async function pollForCompletion(sessionId, codeVerifier) {
688
973
  return null;
689
974
  }
690
975
  function storeCredentials(sessionToken, jwt) {
691
- const dir = join2(homedir(), ".deepspace");
976
+ const dir = join3(homedir(), ".deepspace");
692
977
  mkdirSync(dir, { recursive: true, mode: 448 });
693
- writeFileSync4(join2(dir, "session"), sessionToken, { mode: 384 });
694
- writeFileSync4(join2(dir, "token"), jwt, { mode: 384 });
978
+ writeFileSync5(join3(dir, "session"), sessionToken, { mode: 384 });
979
+ writeFileSync5(join3(dir, "token"), jwt, { mode: 384 });
980
+ }
981
+ async function provisionProfile(jwt) {
982
+ try {
983
+ await fetch(`${API_URL}/api/users/me`, { headers: { Authorization: `Bearer ${jwt}` } });
984
+ } catch {
985
+ }
695
986
  }
696
987
  function sleep(ms) {
697
- return new Promise((resolve11) => setTimeout(resolve11, ms));
988
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
698
989
  }
699
990
  function base64url(bytes) {
700
991
  return bytes.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
@@ -721,24 +1012,25 @@ async function doEmailLogin(email, password) {
721
1012
  throw new Error("JWT issuance failed");
722
1013
  }
723
1014
  storeCredentials(sessionToken, jwt);
1015
+ await provisionProfile(jwt);
724
1016
  }
725
1017
 
726
1018
  // src/cli/commands/logout.ts
727
1019
  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";
1020
+ import { existsSync as existsSync4, readFileSync as readFileSync4, rmSync as rmSync2 } from "fs";
1021
+ import { join as join4 } from "path";
730
1022
  import { homedir as homedir2 } from "os";
731
1023
  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");
1024
+ var DIR = join4(homedir2(), ".deepspace");
1025
+ var SESSION_PATH = join4(DIR, "session");
1026
+ var TOKEN_PATH = join4(DIR, "token");
735
1027
  var logout_default = defineCommand2({
736
1028
  meta: {
737
1029
  name: "logout",
738
1030
  description: "Sign out and remove cached credentials"
739
1031
  },
740
1032
  async run() {
741
- const sessionToken = existsSync3(SESSION_PATH) ? readFileSync3(SESSION_PATH, "utf-8").trim() : null;
1033
+ const sessionToken = existsSync4(SESSION_PATH) ? readFileSync4(SESSION_PATH, "utf-8").trim() : null;
742
1034
  if (sessionToken) {
743
1035
  try {
744
1036
  await fetch(`${AUTH_URL2}/api/auth/sign-out`, {
@@ -753,7 +1045,7 @@ var logout_default = defineCommand2({
753
1045
  }
754
1046
  let removed = 0;
755
1047
  for (const path of [SESSION_PATH, TOKEN_PATH]) {
756
- if (existsSync3(path)) {
1048
+ if (existsSync4(path)) {
757
1049
  rmSync2(path);
758
1050
  removed++;
759
1051
  }
@@ -772,8 +1064,8 @@ import { resolve as resolve4, basename, join as join9 } from "path";
772
1064
  import spawn from "cross-spawn";
773
1065
 
774
1066
  // 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";
1067
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync6, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
1068
+ import { join as join5 } from "path";
777
1069
  import { homedir as homedir3 } from "os";
778
1070
 
779
1071
  // src/cli/jwt.ts
@@ -787,26 +1079,26 @@ function decodeJwtPayload(token) {
787
1079
 
788
1080
  // src/cli/auth.ts
789
1081
  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");
1082
+ var DIR2 = join5(homedir3(), ".deepspace");
1083
+ var SESSION_PATH2 = join5(DIR2, "session");
1084
+ var TOKEN_PATH2 = join5(DIR2, "token");
793
1085
  async function ensureToken() {
794
- if (existsSync4(TOKEN_PATH2)) {
795
- const existing = readFileSync4(TOKEN_PATH2, "utf-8").trim();
1086
+ if (existsSync5(TOKEN_PATH2)) {
1087
+ const existing = readFileSync5(TOKEN_PATH2, "utf-8").trim();
796
1088
  if (isTokenValid(existing)) {
797
1089
  return existing;
798
1090
  }
799
1091
  }
800
- if (!existsSync4(SESSION_PATH2)) {
1092
+ if (!existsSync5(SESSION_PATH2)) {
801
1093
  throw new Error("Not logged in. Run `deepspace login` first.");
802
1094
  }
803
- const sessionToken = readFileSync4(SESSION_PATH2, "utf-8").trim();
1095
+ const sessionToken = readFileSync5(SESSION_PATH2, "utf-8").trim();
804
1096
  const token = await exchangeSession(AUTH_URL3, sessionToken);
805
1097
  if (!token) {
806
1098
  throw new Error("Session expired. Run `deepspace login` to re-authenticate.");
807
1099
  }
808
1100
  mkdirSync2(DIR2, { recursive: true, mode: 448 });
809
- writeFileSync5(TOKEN_PATH2, token, { mode: 384 });
1101
+ writeFileSync6(TOKEN_PATH2, token, { mode: 384 });
810
1102
  return token;
811
1103
  }
812
1104
  function isTokenValid(jwt) {
@@ -820,24 +1112,26 @@ function isTokenValid(jwt) {
820
1112
 
821
1113
  // src/cli/lib/app-context.ts
822
1114
  import {
823
- existsSync as existsSync5,
824
- readFileSync as readFileSync5,
1115
+ existsSync as existsSync6,
1116
+ readFileSync as readFileSync6,
825
1117
  readdirSync,
826
- writeFileSync as writeFileSync6,
1118
+ writeFileSync as writeFileSync7,
827
1119
  mkdirSync as mkdirSync3,
828
1120
  renameSync,
829
1121
  unlinkSync
830
1122
  } from "fs";
831
- import { join as join5, resolve, dirname, isAbsolute, sep } from "path";
832
- import { parse as parseToml2 } from "smol-toml";
1123
+ import { join as join6, resolve as resolve2, dirname, isAbsolute, sep } from "path";
1124
+ import { parse as parseToml3 } from "smol-toml";
833
1125
  function detectAppName(cwd = process.cwd()) {
834
- const wranglerPath = join5(resolve(cwd), "wrangler.toml");
835
- if (!existsSync5(wranglerPath)) return null;
1126
+ const wranglerPath = join6(resolve2(cwd), "wrangler.toml");
1127
+ if (!existsSync6(wranglerPath)) return null;
836
1128
  try {
837
- const cfg = parseToml2(readFileSync5(wranglerPath, "utf-8"));
1129
+ const cfg = parseToml3(readFileSync6(wranglerPath, "utf-8"));
838
1130
  return typeof cfg.name === "string" && cfg.name.length > 0 ? cfg.name : null;
839
- } catch {
840
- return null;
1131
+ } catch (err) {
1132
+ throw new Error(
1133
+ `Could not parse ${wranglerPath}: ${err instanceof Error ? err.message : String(err)}`
1134
+ );
841
1135
  }
842
1136
  }
843
1137
  function requireAppName(explicit, cwd) {
@@ -849,9 +1143,9 @@ function requireAppName(explicit, cwd) {
849
1143
  );
850
1144
  }
851
1145
  function findAppDir(start = process.cwd()) {
852
- let dir = resolve(start);
1146
+ let dir = resolve2(start);
853
1147
  for (; ; ) {
854
- if (existsSync5(join5(dir, "wrangler.toml"))) return dir;
1148
+ if (existsSync6(join6(dir, "wrangler.toml"))) return dir;
855
1149
  const parent = dirname(dir);
856
1150
  if (parent === dir) return null;
857
1151
  dir = parent;
@@ -859,7 +1153,7 @@ function findAppDir(start = process.cwd()) {
859
1153
  }
860
1154
  function findChildApps(dir) {
861
1155
  try {
862
- return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync5(join5(dir, e.name, "wrangler.toml"))).map((e) => e.name).sort();
1156
+ return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync6(join6(dir, e.name, "wrangler.toml"))).map((e) => e.name).sort();
863
1157
  } catch {
864
1158
  return [];
865
1159
  }
@@ -867,7 +1161,7 @@ function findChildApps(dir) {
867
1161
  function writeLaunchFile(launchPath, config) {
868
1162
  mkdirSync3(dirname(launchPath), { recursive: true });
869
1163
  const tmpPath = `${launchPath}.${process.pid}.tmp`;
870
- writeFileSync6(tmpPath, JSON.stringify(config, null, 2) + "\n");
1164
+ writeFileSync7(tmpPath, JSON.stringify(config, null, 2) + "\n");
871
1165
  try {
872
1166
  renameSync(tmpPath, launchPath);
873
1167
  } catch (err) {
@@ -880,15 +1174,15 @@ function writeLaunchFile(launchPath, config) {
880
1174
  }
881
1175
  function readLaunchFile(launchPath) {
882
1176
  const empty = { version: "0.0.1", configurations: [] };
883
- if (!existsSync5(launchPath)) return empty;
884
- const raw = readFileSync5(launchPath, "utf-8").trim();
1177
+ if (!existsSync6(launchPath)) return empty;
1178
+ const raw = readFileSync6(launchPath, "utf-8").trim();
885
1179
  if (!raw) return empty;
886
1180
  const parsed = JSON.parse(raw);
887
1181
  if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.configurations)) return null;
888
1182
  return parsed;
889
1183
  }
890
- function writeLaunchConfigIfMissing(appDir2, appName, port) {
891
- const launchPath = join5(appDir2, ".claude", "launch.json");
1184
+ function writeLaunchConfigIfMissing(appDir, appName, port) {
1185
+ const launchPath = join6(appDir, ".claude", "launch.json");
892
1186
  try {
893
1187
  const config = readLaunchFile(launchPath);
894
1188
  if (!config) return;
@@ -903,8 +1197,8 @@ function writeLaunchConfigIfMissing(appDir2, appName, port) {
903
1197
  } catch {
904
1198
  }
905
1199
  }
906
- function detectClaudeWorktree(appDir2) {
907
- const segments = resolve(appDir2).split(sep);
1200
+ function detectClaudeWorktree(appDir) {
1201
+ const segments = resolve2(appDir).split(sep);
908
1202
  for (let i = segments.length - 3; i >= 1; i--) {
909
1203
  if (segments[i] === ".claude" && segments[i + 1] === "worktrees" && segments[i + 2]) {
910
1204
  return {
@@ -923,16 +1217,16 @@ function deriveWorktreePort(worktreeName) {
923
1217
  for (const ch of worktreeName) hash = hash * 31 + ch.charCodeAt(0) >>> 0;
924
1218
  return WORKTREE_PORT_MIN + hash % WORKTREE_PORT_RANGE;
925
1219
  }
926
- function upsertWorktreeLaunchConfig(mainRepoRoot, worktreeName, appDir2, opts) {
927
- const launchPath = join5(mainRepoRoot, ".claude", "launch.json");
1220
+ function upsertWorktreeLaunchConfig(mainRepoRoot, worktreeName, appDir, opts) {
1221
+ const launchPath = join6(mainRepoRoot, ".claude", "launch.json");
928
1222
  const entryName = WORKTREE_ENTRY_PREFIX + worktreeName;
929
1223
  try {
930
1224
  const config = readLaunchFile(launchPath);
931
1225
  if (!config) return null;
932
- const worktreesRoot = join5(mainRepoRoot, ".claude", "worktrees") + sep;
1226
+ const worktreesRoot = join6(mainRepoRoot, ".claude", "worktrees") + sep;
933
1227
  config.configurations = config.configurations.filter((c) => {
934
1228
  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);
1229
+ 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
1230
  return !isStaleWorktreeEntry;
937
1231
  });
938
1232
  let port = opts.port;
@@ -953,7 +1247,7 @@ function upsertWorktreeLaunchConfig(mainRepoRoot, worktreeName, appDir2, opts) {
953
1247
  // the Claude Code desktop launch.json reference alongside `port`) and
954
1248
  // verified end-to-end: with it set, the preview server builds from the
955
1249
  // worktree instead of the session's main repo.
956
- cwd: resolve(appDir2)
1250
+ cwd: resolve2(appDir)
957
1251
  });
958
1252
  writeLaunchFile(launchPath, config);
959
1253
  return { entryName, port };
@@ -961,11 +1255,11 @@ function upsertWorktreeLaunchConfig(mainRepoRoot, worktreeName, appDir2, opts) {
961
1255
  return null;
962
1256
  }
963
1257
  }
964
- function resolveWorktreePort(appDir2) {
965
- const worktree = detectClaudeWorktree(appDir2);
1258
+ function resolveWorktreePort(appDir) {
1259
+ const worktree = detectClaudeWorktree(appDir);
966
1260
  if (!worktree) return null;
967
1261
  try {
968
- const config = readLaunchFile(join5(worktree.mainRepoRoot, ".claude", "launch.json"));
1262
+ const config = readLaunchFile(join6(worktree.mainRepoRoot, ".claude", "launch.json"));
969
1263
  const entry = config?.configurations.find(
970
1264
  (c) => c?.name === WORKTREE_ENTRY_PREFIX + worktree.worktreeName
971
1265
  );
@@ -975,28 +1269,44 @@ function resolveWorktreePort(appDir2) {
975
1269
  }
976
1270
  return deriveWorktreePort(worktree.worktreeName);
977
1271
  }
1272
+ function requireAppIdArg(explicit) {
1273
+ if (explicit) {
1274
+ if (!APP_ID_RE.test(explicit)) {
1275
+ throw new Error(`"${explicit}" is not an app id (app_\u2026). Pass --app <appId>.`);
1276
+ }
1277
+ return explicit;
1278
+ }
1279
+ const appDir = findAppDir();
1280
+ const id = appDir ? readAppId(appDir) : null;
1281
+ if (!id) {
1282
+ throw new Error(
1283
+ "No app id. Run from an app directory whose wrangler.toml carries DEEPSPACE_APP_ID, or pass --app <appId>."
1284
+ );
1285
+ }
1286
+ return id;
1287
+ }
978
1288
 
979
1289
  // 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)) {
1290
+ import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
1291
+ import { join as join7 } from "path";
1292
+ function ensureInstallReady(appDir) {
1293
+ if (existsSync7(join7(appDir, "node_modules", "deepspace", "package.json"))) return;
1294
+ const sentinelDir = join7(appDir, ".deepspace");
1295
+ const errPath = join7(sentinelDir, "install.err");
1296
+ const startedPath = join7(sentinelDir, "install.started");
1297
+ const donePath = join7(sentinelDir, "install.done");
1298
+ const logPath = join7(sentinelDir, "install.log");
1299
+ if (existsSync7(errPath)) {
990
1300
  console.error("Background install failed:");
991
- console.error(readFileSync6(errPath, "utf-8").trimEnd());
992
- if (existsSync6(logPath)) console.error(`
1301
+ console.error(readFileSync7(errPath, "utf-8").trimEnd());
1302
+ if (existsSync7(logPath)) console.error(`
993
1303
  Full log: ${logPath}`);
994
1304
  console.error("\nRun `npm install` (or `bun install`) manually, then retry.");
995
1305
  process.exit(1);
996
1306
  }
997
- if (existsSync6(startedPath) && !existsSync6(donePath)) {
1307
+ if (existsSync7(startedPath) && !existsSync7(donePath)) {
998
1308
  console.error("Dependencies are still installing in the background.");
999
- if (existsSync6(logPath)) console.error(`Tail progress: tail -f ${logPath}`);
1309
+ if (existsSync7(logPath)) console.error(`Tail progress: tail -f ${logPath}`);
1000
1310
  console.error("Retry once it finishes.");
1001
1311
  process.exit(1);
1002
1312
  }
@@ -1006,9 +1316,9 @@ Full log: ${logPath}`);
1006
1316
 
1007
1317
  // src/cli/lib/preflight.ts
1008
1318
  import { execFileSync } from "child_process";
1009
- import { existsSync as existsSync7 } from "fs";
1319
+ import { existsSync as existsSync8 } from "fs";
1010
1320
  import * as nodeModule from "module";
1011
- import { resolve as resolve2 } from "path";
1321
+ import { resolve as resolve3 } from "path";
1012
1322
  function preflightNodeVersion(command, hasRegisterHooks = typeof nodeModule.registerHooks === "function") {
1013
1323
  if (hasRegisterHooks) return;
1014
1324
  console.error(
@@ -1018,23 +1328,23 @@ function preflightNodeVersion(command, hasRegisterHooks = typeof nodeModule.regi
1018
1328
  process.exit(1);
1019
1329
  }
1020
1330
  var DLL_NOT_FOUND = -1073741515;
1021
- function preflightWindowsWorkerd(appDir2) {
1331
+ function preflightWindowsWorkerd(appDir) {
1022
1332
  if (process.platform !== "win32") return;
1023
- const workerdExe = resolve2(
1024
- appDir2,
1333
+ const workerdExe = resolve3(
1334
+ appDir,
1025
1335
  "node_modules",
1026
1336
  "@cloudflare",
1027
1337
  "workerd-windows-64",
1028
1338
  "bin",
1029
1339
  "workerd.exe"
1030
1340
  );
1031
- if (!existsSync7(workerdExe)) return;
1341
+ if (!existsSync8(workerdExe)) return;
1032
1342
  try {
1033
1343
  execFileSync(workerdExe, ["--help"], { stdio: "pipe", timeout: 1e4 });
1034
1344
  return;
1035
1345
  } catch (err) {
1036
- const status2 = err.status;
1037
- if (status2 !== DLL_NOT_FOUND) return;
1346
+ const status3 = err.status;
1347
+ if (status3 !== DLL_NOT_FOUND) return;
1038
1348
  console.error("");
1039
1349
  console.error(" workerd (Cloudflare Workers runtime) cannot start on this system.");
1040
1350
  console.error(" It needs the Microsoft Visual C++ Redistributable, which is not installed.");
@@ -1052,7 +1362,7 @@ function preflightWindowsWorkerd(appDir2) {
1052
1362
 
1053
1363
  // src/cli/lib/macos-junk.ts
1054
1364
  import { readdirSync as readdirSync2, rmSync as rmSync3 } from "fs";
1055
- import { join as join7 } from "path";
1365
+ import { join as join8 } from "path";
1056
1366
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".wrangler", ".vite"]);
1057
1367
  function removeMacosJunk(dir) {
1058
1368
  let removed = 0;
@@ -1063,7 +1373,7 @@ function removeMacosJunk(dir) {
1063
1373
  return removed;
1064
1374
  }
1065
1375
  for (const entry of entries) {
1066
- const path = join7(dir, entry.name);
1376
+ const path = join8(dir, entry.name);
1067
1377
  if (entry.isDirectory()) {
1068
1378
  if (!SKIP_DIRS.has(entry.name)) removed += removeMacosJunk(path);
1069
1379
  continue;
@@ -1079,741 +1389,17 @@ function removeMacosJunk(dir) {
1079
1389
  return removed;
1080
1390
  }
1081
1391
 
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 });
1392
+ // src/cli/lib/port.ts
1393
+ var DEFAULT_PORT = 5173;
1394
+ function resolvePort(arg) {
1395
+ const raw = arg ?? process.env.DEEPSPACE_PORT;
1396
+ if (!raw) return DEFAULT_PORT;
1397
+ const n = Number(raw);
1398
+ if (!Number.isInteger(n) || n < 1 || n > 65535) {
1399
+ console.error(`Invalid port: ${raw}. Must be an integer between 1 and 65535.`);
1400
+ process.exit(1);
1149
1401
  }
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;
1402
+ return n;
1817
1403
  }
1818
1404
 
1819
1405
  // src/cli/commands/dev.ts
@@ -1842,10 +1428,10 @@ var dev_default = defineCommand3({
1842
1428
  },
1843
1429
  async run({ args }) {
1844
1430
  preflightNodeVersion("dev");
1845
- const wranglerEnv2 = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
1431
+ const wranglerEnv = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
1846
1432
  const start = resolve4(args.dir ?? ".");
1847
- const appDir2 = findAppDir(start);
1848
- if (!appDir2) {
1433
+ const appDir = findAppDir(start);
1434
+ if (!appDir) {
1849
1435
  console.error(`No wrangler.toml found at or above ${start}.`);
1850
1436
  const children = findChildApps(start);
1851
1437
  if (children.length > 0) {
@@ -1856,20 +1442,20 @@ var dev_default = defineCommand3({
1856
1442
  }
1857
1443
  process.exit(1);
1858
1444
  }
1859
- const junk = removeMacosJunk(appDir2);
1445
+ const junk = removeMacosJunk(appDir);
1860
1446
  if (junk > 0) console.log(`Removed ${junk} macOS metadata file(s) (._*, .DS_Store)`);
1861
- const worktree = detectClaudeWorktree(appDir2);
1447
+ const worktree = detectClaudeWorktree(appDir);
1862
1448
  const explicitPort = Boolean(args.port);
1863
1449
  let port = worktree && !explicitPort ? deriveWorktreePort(worktree.worktreeName) : resolvePort(args.port);
1864
1450
  if (worktree) {
1865
1451
  const upserted = upsertWorktreeLaunchConfig(
1866
1452
  worktree.mainRepoRoot,
1867
1453
  worktree.worktreeName,
1868
- appDir2,
1454
+ appDir,
1869
1455
  {
1870
1456
  port,
1871
1457
  probePort: !explicitPort,
1872
- extraArgs: [...wranglerEnv2 ? ["--env", wranglerEnv2] : []]
1458
+ extraArgs: [...wranglerEnv ? ["--env", wranglerEnv] : []]
1873
1459
  }
1874
1460
  );
1875
1461
  if (upserted) {
@@ -1888,8 +1474,8 @@ var dev_default = defineCommand3({
1888
1474
  );
1889
1475
  }
1890
1476
  }
1891
- writeLaunchConfigIfMissing(appDir2, detectAppName(appDir2) ?? basename(appDir2), port);
1892
- ensureInstallReady(appDir2);
1477
+ writeLaunchConfigIfMissing(appDir, detectAppName(appDir) ?? basename(appDir), port);
1478
+ ensureInstallReady(appDir);
1893
1479
  let token;
1894
1480
  try {
1895
1481
  token = await ensureToken();
@@ -1905,39 +1491,40 @@ var dev_default = defineCommand3({
1905
1491
  process.exit(1);
1906
1492
  }
1907
1493
  console.log(`Logged in as ${payload.name ?? payload.email}`);
1908
- if (wranglerEnv2) console.log(`Wrangler env: ${wranglerEnv2}`);
1494
+ if (wranglerEnv) console.log(`Wrangler env: ${wranglerEnv}`);
1909
1495
  console.log(`Port: ${port}`);
1910
1496
  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})`
1497
+ const appIdForSecrets = readAppId(appDir, wranglerEnv);
1498
+ if (appIdForSecrets) {
1499
+ try {
1500
+ const refreshed = await refreshSecretsCache(DEPLOY_URL, token, appIdForSecrets, wranglerEnv);
1501
+ if (refreshed) {
1502
+ generatedSecretsCache = refreshed.rendered;
1503
+ console.log(refreshed.summary);
1504
+ }
1505
+ } catch (err) {
1506
+ console.error(
1507
+ `Failed to refresh app secrets: ${err instanceof Error ? err.message : String(err)}`
1917
1508
  );
1509
+ process.exit(1);
1918
1510
  }
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
1511
  }
1925
1512
  const sharedDevVarsCache = generatedSecretsCache !== void 0;
1926
- await writeDevVars(appDir2, payload.sub, token, wranglerEnv2, {
1513
+ await writeDevVars(appDir, payload.sub, token, wranglerEnv, {
1927
1514
  generatedSecretsCache,
1928
1515
  sharedDevVarsCache
1929
1516
  });
1930
1517
  let wranglerConfig;
1931
1518
  try {
1932
- wranglerConfig = prepareWranglerEnvConfig(appDir2, wranglerEnv2, { sharedDevVarsCache });
1519
+ wranglerConfig = prepareWranglerEnvConfig(appDir, wranglerEnv, { sharedDevVarsCache });
1933
1520
  } catch (err) {
1934
1521
  console.error(err instanceof Error ? err.message : String(err));
1935
1522
  process.exit(1);
1936
1523
  }
1937
- preflightWindowsWorkerd(appDir2);
1524
+ preflightWindowsWorkerd(appDir);
1938
1525
  console.log("Starting dev server...\n");
1939
1526
  const vite = spawn("npx", ["vite", "--port", String(port), "--strictPort", "--host"], {
1940
- cwd: appDir2,
1527
+ cwd: appDir,
1941
1528
  stdio: "inherit",
1942
1529
  env: wranglerViteEnv(process.env, wranglerConfig, { DEEPSPACE_PORT: String(port) })
1943
1530
  });
@@ -2110,18 +1697,18 @@ import { sync as spawnSync3 } from "cross-spawn";
2110
1697
  // src/cli/lib/playwright.ts
2111
1698
  import { execSync } from "child_process";
2112
1699
  import { sync as spawnSync2 } from "cross-spawn";
2113
- function ensurePlaywright(appDir2) {
1700
+ function ensurePlaywright(appDir) {
2114
1701
  try {
2115
- execSync("npx playwright --version", { cwd: appDir2, stdio: "pipe" });
1702
+ execSync("npx playwright --version", { cwd: appDir, stdio: "pipe" });
2116
1703
  } catch {
2117
1704
  console.log("Installing Playwright...");
2118
- execSync("npm install -D @playwright/test", { cwd: appDir2, stdio: "pipe" });
1705
+ execSync("npm install -D @playwright/test", { cwd: appDir, stdio: "pipe" });
2119
1706
  }
2120
1707
  const isLinux = process.platform === "linux";
2121
1708
  const isRoot = isLinux && typeof process.getuid === "function" && process.getuid() === 0;
2122
1709
  const args = isRoot ? ["install", "--with-deps", "chromium"] : ["install", "chromium"];
2123
1710
  const result = spawnSync2("npx", ["playwright", ...args], {
2124
- cwd: appDir2,
1711
+ cwd: appDir,
2125
1712
  stdio: "inherit"
2126
1713
  });
2127
1714
  if (result.status !== 0) {
@@ -2158,10 +1745,10 @@ var test_default = defineCommand5({
2158
1745
  async run({ args }) {
2159
1746
  preflightNodeVersion("test");
2160
1747
  const suite = args.suite ?? "default";
2161
- const wranglerEnv2 = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
1748
+ const wranglerEnv = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
2162
1749
  const start = resolve5(".");
2163
- const appDir2 = findAppDir(start);
2164
- if (!appDir2) {
1750
+ const appDir = findAppDir(start);
1751
+ if (!appDir) {
2165
1752
  console.error(`No wrangler.toml found at or above ${start}.`);
2166
1753
  const children = findChildApps(start);
2167
1754
  if (children.length > 0) {
@@ -2172,14 +1759,14 @@ var test_default = defineCommand5({
2172
1759
  }
2173
1760
  process.exit(1);
2174
1761
  }
2175
- const worktreePort = args.port ? null : resolveWorktreePort(appDir2);
1762
+ const worktreePort = args.port ? null : resolveWorktreePort(appDir);
2176
1763
  const port = worktreePort ?? resolvePort(args.port);
2177
1764
  if (worktreePort && process.env.DEEPSPACE_PORT) {
2178
1765
  console.log(
2179
1766
  `Ignoring DEEPSPACE_PORT=${process.env.DEEPSPACE_PORT} inside a worktree \u2014 targeting per-worktree port ${port}. Pass --port to override.`
2180
1767
  );
2181
1768
  }
2182
- ensureInstallReady(appDir2);
1769
+ ensureInstallReady(appDir);
2183
1770
  let token;
2184
1771
  let ownerId;
2185
1772
  try {
@@ -2191,72 +1778,73 @@ var test_default = defineCommand5({
2191
1778
  process.exit(1);
2192
1779
  }
2193
1780
  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})`
1781
+ const appIdForSecrets = readAppId(appDir, wranglerEnv);
1782
+ if (appIdForSecrets) {
1783
+ try {
1784
+ const refreshed = await refreshSecretsCache(DEPLOY_URL2, token, appIdForSecrets, wranglerEnv);
1785
+ if (refreshed) {
1786
+ generatedSecretsCache = refreshed.rendered;
1787
+ console.log(refreshed.summary);
1788
+ }
1789
+ } catch (err) {
1790
+ console.error(
1791
+ `Failed to refresh app secrets: ${err instanceof Error ? err.message : String(err)}`
2200
1792
  );
1793
+ process.exit(1);
2201
1794
  }
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
1795
  }
2208
1796
  const sharedDevVarsCache = generatedSecretsCache !== void 0;
2209
- await writeDevVars(appDir2, ownerId, token, wranglerEnv2, {
1797
+ await writeDevVars(appDir, ownerId, token, wranglerEnv, {
2210
1798
  generatedSecretsCache,
2211
1799
  sharedDevVarsCache
2212
1800
  });
2213
1801
  if (suite !== "unit") {
2214
- preflightWindowsWorkerd(appDir2);
2215
- ensurePlaywright(appDir2);
1802
+ preflightWindowsWorkerd(appDir);
1803
+ ensurePlaywright(appDir);
2216
1804
  }
2217
1805
  let exitCode = 0;
2218
1806
  switch (suite) {
2219
1807
  case "smoke":
2220
1808
  exitCode = runPlaywright(
2221
- appDir2,
1809
+ appDir,
2222
1810
  ["tests/smoke.spec.ts"],
2223
1811
  port,
2224
- wranglerEnv2,
1812
+ wranglerEnv,
2225
1813
  sharedDevVarsCache
2226
1814
  );
2227
1815
  break;
2228
1816
  case "api":
2229
1817
  exitCode = runPlaywright(
2230
- appDir2,
1818
+ appDir,
2231
1819
  ["tests/api.spec.ts"],
2232
1820
  port,
2233
- wranglerEnv2,
1821
+ wranglerEnv,
2234
1822
  sharedDevVarsCache
2235
1823
  );
2236
1824
  break;
2237
1825
  case "e2e":
2238
- exitCode = runPlaywright(appDir2, [], port, wranglerEnv2, sharedDevVarsCache);
1826
+ exitCode = runPlaywright(appDir, [], port, wranglerEnv, sharedDevVarsCache);
2239
1827
  break;
2240
1828
  case "unit":
2241
- exitCode = runVitest(appDir2);
1829
+ exitCode = runVitest(appDir);
2242
1830
  break;
2243
1831
  case "all":
2244
- exitCode = runVitest(appDir2);
1832
+ exitCode = runVitest(appDir);
2245
1833
  if (exitCode === 0)
2246
- exitCode = runPlaywright(appDir2, [], port, wranglerEnv2, sharedDevVarsCache);
1834
+ exitCode = runPlaywright(appDir, [], port, wranglerEnv, sharedDevVarsCache);
2247
1835
  break;
2248
1836
  case "default":
2249
1837
  exitCode = runPlaywright(
2250
- appDir2,
1838
+ appDir,
2251
1839
  ["tests/smoke.spec.ts", "tests/api.spec.ts"],
2252
1840
  port,
2253
- wranglerEnv2,
1841
+ wranglerEnv,
2254
1842
  sharedDevVarsCache
2255
1843
  );
2256
1844
  break;
2257
1845
  default:
2258
1846
  if (suite.endsWith(".spec.ts")) {
2259
- exitCode = runPlaywright(appDir2, [suite], port, wranglerEnv2, sharedDevVarsCache);
1847
+ exitCode = runPlaywright(appDir, [suite], port, wranglerEnv, sharedDevVarsCache);
2260
1848
  } else {
2261
1849
  console.error(`Unknown test suite: ${suite}`);
2262
1850
  console.error("Available: smoke, api, e2e, unit, all");
@@ -2266,10 +1854,10 @@ var test_default = defineCommand5({
2266
1854
  process.exit(exitCode);
2267
1855
  }
2268
1856
  });
2269
- function runPlaywright(appDir2, testFiles, port, wranglerEnv2, sharedDevVarsCache = false) {
1857
+ function runPlaywright(appDir, testFiles, port, wranglerEnv, sharedDevVarsCache = false) {
2270
1858
  let wranglerConfig;
2271
1859
  try {
2272
- wranglerConfig = prepareWranglerEnvConfig(appDir2, wranglerEnv2, { sharedDevVarsCache });
1860
+ wranglerConfig = prepareWranglerEnvConfig(appDir, wranglerEnv, { sharedDevVarsCache });
2273
1861
  } catch (err) {
2274
1862
  console.error(err instanceof Error ? err.message : String(err));
2275
1863
  return 1;
@@ -2279,7 +1867,7 @@ function runPlaywright(appDir2, testFiles, port, wranglerEnv2, sharedDevVarsCach
2279
1867
  "npx",
2280
1868
  ["playwright", "test", "--config", "tests/playwright.config.ts", ...testFiles],
2281
1869
  {
2282
- cwd: appDir2,
1870
+ cwd: appDir,
2283
1871
  stdio: "inherit",
2284
1872
  env: wranglerViteEnv(process.env, wranglerConfig, { DEEPSPACE_PORT: String(port) })
2285
1873
  }
@@ -2289,9 +1877,9 @@ function runPlaywright(appDir2, testFiles, port, wranglerEnv2, sharedDevVarsCach
2289
1877
  wranglerConfig.cleanup();
2290
1878
  }
2291
1879
  }
2292
- function runVitest(appDir2) {
1880
+ function runVitest(appDir) {
2293
1881
  const result = spawnSync3("npx", ["vitest", "run", "--passWithNoTests"], {
2294
- cwd: appDir2,
1882
+ cwd: appDir,
2295
1883
  stdio: "inherit"
2296
1884
  });
2297
1885
  return result.status ?? 1;
@@ -2329,15 +1917,15 @@ var screenshot_default = defineCommand6({
2329
1917
  }
2330
1918
  },
2331
1919
  run({ args }) {
2332
- const appDir2 = resolve6(".");
2333
- ensurePlaywright(appDir2);
1920
+ const appDir = resolve6(".");
1921
+ ensurePlaywright(appDir);
2334
1922
  const playwrightArgs = ["playwright", "screenshot", args.url, args.output];
2335
1923
  if (args["full-page"]) playwrightArgs.push("--full-page");
2336
1924
  if (args["wait-for-timeout"]) {
2337
1925
  playwrightArgs.push("--wait-for-timeout", String(args["wait-for-timeout"]));
2338
1926
  }
2339
1927
  const result = spawnSync4("npx", playwrightArgs, {
2340
- cwd: appDir2,
1928
+ cwd: appDir,
2341
1929
  stdio: "inherit"
2342
1930
  });
2343
1931
  process.exit(result.status ?? 1);
@@ -2631,18 +2219,222 @@ var test_accounts_default = defineCommand7({
2631
2219
  delete: del,
2632
2220
  clear
2633
2221
  }
2634
- });
2222
+ });
2223
+
2224
+ // src/cli/commands/deploy.ts
2225
+ import { defineCommand as defineCommand8 } from "citty";
2226
+ import { execSync as execSync2 } from "child_process";
2227
+ import { randomUUID } from "crypto";
2228
+ import { readFileSync as readFileSync9, readdirSync as readdirSync3, existsSync as existsSync10, writeFileSync as writeFileSync9 } from "fs";
2229
+ import { join as join11, resolve as resolve7, dirname as dirname2 } from "path";
2230
+ import { pathToFileURL } from "url";
2231
+ import * as p4 from "@clack/prompts";
2232
+
2233
+ // src/server/rooms/binding-manifest.ts
2234
+ var AUTO_PROVISION_SENTINEL = "auto";
2235
+ var VECTORIZE_METRICS = /* @__PURE__ */ new Set(["cosine", "euclidean", "dot-product"]);
2236
+ var ALLOWED_BINDING_TYPES = /* @__PURE__ */ new Set([
2237
+ "vectorize",
2238
+ "ai",
2239
+ "r2_bucket",
2240
+ "kv_namespace",
2241
+ "d1",
2242
+ "queue",
2243
+ "browser_rendering",
2244
+ "analytics_engine",
2245
+ "hyperdrive"
2246
+ ]);
2247
+ var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
2248
+ "ASSETS",
2249
+ "PLATFORM_WORKER",
2250
+ "API_WORKER",
2251
+ "APP_NAME",
2252
+ "OWNER_USER_ID",
2253
+ "AUTH_JWT_PUBLIC_KEY",
2254
+ "AUTH_JWT_ISSUER",
2255
+ "AUTH_WORKER_URL",
2256
+ "APP_IDENTITY_TOKEN",
2257
+ "APP_OWNER_JWT",
2258
+ "USAGE_EVENTS"
2259
+ ]);
2260
+ function validateBindingManifest(manifest) {
2261
+ if (!Array.isArray(manifest)) {
2262
+ return { valid: false, errors: [{ reason: "Manifest must be an array" }] };
2263
+ }
2264
+ const errors = [];
2265
+ const seenNames = /* @__PURE__ */ new Set();
2266
+ for (const entry of manifest) {
2267
+ if (!entry || typeof entry !== "object") {
2268
+ errors.push({ reason: `Entry must be an object (got ${entry === null ? "null" : typeof entry})` });
2269
+ continue;
2270
+ }
2271
+ const e = entry;
2272
+ const type = e.type;
2273
+ const name = e.name;
2274
+ if (typeof type !== "string" || !ALLOWED_BINDING_TYPES.has(type)) {
2275
+ errors.push({
2276
+ reason: `Disallowed binding type: ${typeof type === "string" ? type : `(${typeof type})`}. Allowed: ${[...ALLOWED_BINDING_TYPES].sort().join(", ")}`
2277
+ });
2278
+ continue;
2279
+ }
2280
+ if (typeof name !== "string" || !name) {
2281
+ errors.push({ reason: `Binding of type "${type}" missing 'name' (string)` });
2282
+ continue;
2283
+ }
2284
+ if (RESERVED_BINDING_NAMES.has(name)) {
2285
+ errors.push({ reason: `Binding name "${name}" is reserved by the SDK` });
2286
+ continue;
2287
+ }
2288
+ if (seenNames.has(name)) {
2289
+ errors.push({ reason: `Duplicate binding name "${name}"` });
2290
+ continue;
2291
+ }
2292
+ seenNames.add(name);
2293
+ const binding = entry;
2294
+ const fieldErr = requiredFieldError(binding);
2295
+ if (fieldErr) errors.push({ binding, reason: fieldErr });
2296
+ }
2297
+ if (errors.length) return { valid: false, errors };
2298
+ return { valid: true, bindings: manifest };
2299
+ }
2300
+ function requiredFieldError(b) {
2301
+ switch (b.type) {
2302
+ case "vectorize": {
2303
+ if (!b.index_name) return `vectorize binding "${b.name}" missing index_name (or "auto")`;
2304
+ if (b.index_name === AUTO_PROVISION_SENTINEL) {
2305
+ if (typeof b.dimensions !== "number" || b.dimensions <= 0) {
2306
+ return `vectorize binding "${b.name}" with index_name="auto" requires "dimensions" (positive number)`;
2307
+ }
2308
+ if (!b.metric || !VECTORIZE_METRICS.has(b.metric)) {
2309
+ return `vectorize binding "${b.name}" with index_name="auto" requires "metric" (one of: ${[...VECTORIZE_METRICS].join(", ")})`;
2310
+ }
2311
+ }
2312
+ return null;
2313
+ }
2314
+ case "r2_bucket":
2315
+ return b.bucket_name ? null : `r2_bucket binding "${b.name}" missing bucket_name (or "auto")`;
2316
+ case "kv_namespace": {
2317
+ if (!b.namespace_id) return `kv_namespace binding "${b.name}" missing namespace_id (or "auto")`;
2318
+ if (b.namespace_id === AUTO_PROVISION_SENTINEL && !b.title) {
2319
+ return `kv_namespace binding "${b.name}" with namespace_id="auto" requires "title"`;
2320
+ }
2321
+ return null;
2322
+ }
2323
+ case "d1": {
2324
+ if (!b.id) return `d1 binding "${b.name}" missing id (or "auto")`;
2325
+ if (b.id === AUTO_PROVISION_SENTINEL && !b.database_name) {
2326
+ return `d1 binding "${b.name}" with id="auto" requires "database_name"`;
2327
+ }
2328
+ return null;
2329
+ }
2330
+ case "queue":
2331
+ return b.queue_name ? null : `queue binding "${b.name}" missing queue_name (or "auto")`;
2332
+ case "hyperdrive":
2333
+ if (!b.id) return `hyperdrive binding "${b.name}" missing id`;
2334
+ if (b.id === AUTO_PROVISION_SENTINEL) {
2335
+ return `hyperdrive binding "${b.name}": auto-provisioning not yet supported (provide a real id)`;
2336
+ }
2337
+ return null;
2338
+ case "ai":
2339
+ case "browser_rendering":
2340
+ case "analytics_engine":
2341
+ return null;
2342
+ // no required fields beyond `name`
2343
+ default: {
2344
+ const _exhaustive = b;
2345
+ void _exhaustive;
2346
+ return null;
2347
+ }
2348
+ }
2349
+ }
2350
+ function bindingManifestFromOutputConfig(outputConfig) {
2351
+ const out = [];
2352
+ const aiBinding = pluckString(outputConfig.ai, "binding");
2353
+ if (aiBinding) out.push({ type: "ai", name: aiBinding });
2354
+ for (const v of asObjectArray(outputConfig.vectorize)) {
2355
+ const name = pluckString(v, "binding");
2356
+ const index_name = pluckString(v, "index_name");
2357
+ if (!name || !index_name) continue;
2358
+ const dimensions = pluckNumber(v, "dimensions");
2359
+ const metric = pluckString(v, "metric");
2360
+ out.push({
2361
+ type: "vectorize",
2362
+ name,
2363
+ index_name,
2364
+ ...dimensions != null && { dimensions },
2365
+ ...metric && (metric === "cosine" || metric === "euclidean" || metric === "dot-product") && { metric }
2366
+ });
2367
+ }
2368
+ for (const r of asObjectArray(outputConfig.r2_buckets)) {
2369
+ const name = pluckString(r, "binding");
2370
+ const bucket_name = pluckString(r, "bucket_name");
2371
+ if (name && bucket_name) out.push({ type: "r2_bucket", name, bucket_name });
2372
+ }
2373
+ for (const k of asObjectArray(outputConfig.kv_namespaces)) {
2374
+ const name = pluckString(k, "binding");
2375
+ const namespace_id = pluckString(k, "id");
2376
+ if (!name || !namespace_id) continue;
2377
+ const title = pluckString(k, "title");
2378
+ out.push({
2379
+ type: "kv_namespace",
2380
+ name,
2381
+ namespace_id,
2382
+ ...title && { title }
2383
+ });
2384
+ }
2385
+ for (const d of asObjectArray(outputConfig.d1_databases)) {
2386
+ const name = pluckString(d, "binding");
2387
+ const id = pluckString(d, "database_id");
2388
+ if (!name || !id) continue;
2389
+ const database_name = pluckString(d, "database_name");
2390
+ out.push({
2391
+ type: "d1",
2392
+ name,
2393
+ id,
2394
+ ...database_name && { database_name }
2395
+ });
2396
+ }
2397
+ const queues = isObject(outputConfig.queues) ? outputConfig.queues : null;
2398
+ for (const q of asObjectArray(queues?.producers)) {
2399
+ const name = pluckString(q, "binding");
2400
+ const queue_name = pluckString(q, "queue");
2401
+ if (name && queue_name) out.push({ type: "queue", name, queue_name });
2402
+ }
2403
+ const browserBinding = pluckString(outputConfig.browser, "binding");
2404
+ if (browserBinding) out.push({ type: "browser_rendering", name: browserBinding });
2405
+ for (const a of asObjectArray(outputConfig.analytics_engine_datasets)) {
2406
+ const name = pluckString(a, "binding");
2407
+ const dataset = pluckString(a, "dataset");
2408
+ if (name) out.push({ type: "analytics_engine", name, dataset: dataset ?? void 0 });
2409
+ }
2410
+ for (const h of asObjectArray(outputConfig.hyperdrive)) {
2411
+ const name = pluckString(h, "binding");
2412
+ const id = pluckString(h, "id");
2413
+ if (name && id) out.push({ type: "hyperdrive", name, id });
2414
+ }
2415
+ return out;
2416
+ }
2417
+ function isObject(v) {
2418
+ return v !== null && typeof v === "object" && !Array.isArray(v);
2419
+ }
2420
+ function pluckString(v, key) {
2421
+ if (!isObject(v)) return null;
2422
+ const val = v[key];
2423
+ return typeof val === "string" && val.length > 0 ? val : null;
2424
+ }
2425
+ function pluckNumber(v, key) {
2426
+ if (!isObject(v)) return null;
2427
+ const val = v[key];
2428
+ return typeof val === "number" && Number.isFinite(val) ? val : null;
2429
+ }
2430
+ function asObjectArray(v) {
2431
+ if (!Array.isArray(v)) return [];
2432
+ return v.filter(isObject);
2433
+ }
2635
2434
 
2636
2435
  // 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
2436
  var DEPLOY_URL3 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
2645
- var API_URL = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
2437
+ var API_URL2 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
2646
2438
  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.";
2647
2439
  var RESERVED_RUN_WORKER_FIRST = /* @__PURE__ */ new Set([
2648
2440
  "/api/*",
@@ -2670,8 +2462,8 @@ function extractRunWorkerFirst(cfg) {
2670
2462
  function errMessage(err) {
2671
2463
  return err instanceof Error ? err.message : String(err);
2672
2464
  }
2673
- function isLikelyCloudflareDeployFailure(status2, error) {
2674
- if (status2 >= 500) return true;
2465
+ function isLikelyCloudflareDeployFailure(status3, error) {
2466
+ if (status3 >= 500) return true;
2675
2467
  if (!error) return false;
2676
2468
  return [
2677
2469
  "Upload session failed",
@@ -2681,9 +2473,9 @@ function isLikelyCloudflareDeployFailure(status2, error) {
2681
2473
  "Internal Server Error"
2682
2474
  ].some((needle) => error.includes(needle));
2683
2475
  }
2684
- function formatDeployWorkerError(status2, error) {
2685
- const detail = error ?? `Deployment error (${status2})`;
2686
- if (!isLikelyCloudflareDeployFailure(status2, error)) return detail;
2476
+ function formatDeployWorkerError(status3, error) {
2477
+ const detail = error ?? `Deployment error (${status3})`;
2478
+ if (!isLikelyCloudflareDeployFailure(status3, error)) return detail;
2687
2479
  return `${CLOUDFLARE_DEPLOY_ERROR_HINT}
2688
2480
 
2689
2481
  Underlying error: ${detail}`;
@@ -2735,10 +2527,10 @@ async function postWithRetry(url, makeInit, {
2735
2527
  if (lastRes) return lastRes;
2736
2528
  throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
2737
2529
  }
2738
- async function abortStagedUpload(appName, uploadId, totalGroups, token) {
2530
+ async function abortStagedUpload(appId, uploadId, totalGroups, token) {
2739
2531
  try {
2740
2532
  await fetch(
2741
- `${DEPLOY_URL3}/api/deploy/${appName}/assets?uploadId=${uploadId}&totalGroups=${totalGroups}`,
2533
+ `${DEPLOY_URL3}/api/deploy/${appId}/assets?uploadId=${uploadId}&totalGroups=${totalGroups}`,
2742
2534
  { method: "DELETE", headers: { Authorization: `Bearer ${token}` } }
2743
2535
  );
2744
2536
  } catch {
@@ -2759,19 +2551,24 @@ var deploy_default = defineCommand8({
2759
2551
  type: "string",
2760
2552
  description: "wrangler.toml [env.<name>] block to deploy (e.g. --env staging). Omit to deploy the top-level config.",
2761
2553
  required: false
2554
+ },
2555
+ rename: {
2556
+ type: "boolean",
2557
+ 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.",
2558
+ default: false
2762
2559
  }
2763
2560
  },
2764
2561
  async run({ args }) {
2765
2562
  preflightNodeVersion("deploy");
2766
- const appDir2 = resolve7(args.dir ?? ".");
2563
+ const appDir = resolve7(args.dir ?? ".");
2767
2564
  const envName = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
2768
- if (!hasWranglerConfig(appDir2)) {
2565
+ if (!hasWranglerConfig(appDir)) {
2769
2566
  console.error("No wrangler.toml found. Are you in a DeepSpace app directory?");
2770
2567
  process.exit(1);
2771
2568
  }
2772
- ensureInstallReady(appDir2);
2569
+ ensureInstallReady(appDir);
2773
2570
  p4.intro("Deploying DeepSpace app");
2774
- const wranglerConfig = readWranglerConfig(appDir2);
2571
+ const wranglerConfig = readWranglerConfig(appDir);
2775
2572
  const nameRes = resolveAppNameForEnv(wranglerConfig, envName);
2776
2573
  if (!nameRes.ok) {
2777
2574
  p4.cancel(nameRes.reason);
@@ -2786,6 +2583,7 @@ var deploy_default = defineCommand8({
2786
2583
  process.exit(1);
2787
2584
  }
2788
2585
  const appName = nameRes.name;
2586
+ let appId = readAppId(appDir, envName);
2789
2587
  p4.log.info(envName ? `App: ${appName} (env: ${envName})` : `App: ${appName}`);
2790
2588
  let token;
2791
2589
  let ownerId;
@@ -2796,41 +2594,42 @@ var deploy_default = defineCommand8({
2796
2594
  p4.cancel(errMessage(err));
2797
2595
  process.exit(1);
2798
2596
  }
2799
- const localSecretsLink = resolveSecretsLink(appDir2, envName);
2800
- if (localSecretsLink.linked) {
2801
- try {
2802
- await setServerSecretsLink(DEPLOY_URL3, token, appName, localSecretsLink.projectName);
2803
- } catch {
2804
- }
2597
+ if (!appId) {
2598
+ const adopted = await resolveOwnedAppId(DEPLOY_URL3, token, appName);
2599
+ appId = adopted ?? mintAppId();
2600
+ writeAppId(appDir, appId, { wranglerEnv: envName });
2601
+ p4.log.info(
2602
+ adopted ? `Adopted existing app id ${appId} for ${appName} \u2014 commit wrangler.toml.` : `Minted app id ${appId} \u2014 commit wrangler.toml.`
2603
+ );
2805
2604
  }
2605
+ p4.log.info(`Id: ${appId}`);
2806
2606
  let linkedSecrets = null;
2807
2607
  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,
2608
+ const refreshed = await refreshSecretsCache(DEPLOY_URL3, token, appId, envName);
2609
+ if (refreshed) {
2610
+ linkedSecrets = refreshed.pulled;
2611
+ await writeDevVars(appDir, ownerId, token, envName, {
2612
+ appId,
2613
+ generatedSecretsCache: refreshed.rendered,
2813
2614
  sharedDevVarsCache: true
2814
2615
  });
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
- );
2616
+ p4.log.info(refreshed.summary);
2818
2617
  }
2819
2618
  } catch (err) {
2820
- p4.cancel(`Failed to refresh linked secrets before deploy: ${errMessage(err)}`);
2619
+ p4.cancel(`Failed to refresh app secrets before deploy: ${errMessage(err)}`);
2821
2620
  process.exit(1);
2822
2621
  }
2823
- const junk = removeMacosJunk(appDir2);
2622
+ const junk = removeMacosJunk(appDir);
2824
2623
  if (junk > 0) p4.log.info(`Removed ${junk} macOS metadata file(s) (._*, .DS_Store)`);
2825
2624
  const s = createSpinner();
2826
2625
  s.start("Building...");
2827
2626
  let preparedWranglerConfig;
2828
2627
  try {
2829
- preparedWranglerConfig = prepareWranglerEnvConfig(appDir2, envName, {
2830
- sharedDevVarsCache: localSecretsLink.linked
2628
+ preparedWranglerConfig = prepareWranglerEnvConfig(appDir, envName, {
2629
+ sharedDevVarsCache: linkedSecrets !== null
2831
2630
  });
2832
2631
  execSync2("npx vite build", {
2833
- cwd: appDir2,
2632
+ cwd: appDir,
2834
2633
  stdio: "pipe",
2835
2634
  env: wranglerViteEnv(process.env, preparedWranglerConfig)
2836
2635
  });
@@ -2844,7 +2643,7 @@ var deploy_default = defineCommand8({
2844
2643
  preparedWranglerConfig?.cleanup();
2845
2644
  }
2846
2645
  s.stop("Built");
2847
- const deployConfigPath = join11(appDir2, ".wrangler", "deploy", "config.json");
2646
+ const deployConfigPath = join11(appDir, ".wrangler", "deploy", "config.json");
2848
2647
  if (!existsSync10(deployConfigPath)) {
2849
2648
  p4.cancel("Build output config not found at .wrangler/deploy/config.json");
2850
2649
  process.exit(1);
@@ -2906,15 +2705,13 @@ var deploy_default = defineCommand8({
2906
2705
  `Custom bindings: ${customBindings.map((b) => `${b.name} (${b.type})`).join(", ")}`
2907
2706
  );
2908
2707
  }
2909
- const devVarsPath = devVarsPathFor(appDir2, envName, {
2910
- sharedDevVarsCache: localSecretsLink.linked
2708
+ const devVarsPath = devVarsPathFor(appDir, envName, {
2709
+ sharedDevVarsCache: linkedSecrets !== null
2911
2710
  });
2912
2711
  let userSecrets = {};
2913
2712
  if (existsSync10(devVarsPath)) {
2914
2713
  const rawDevVars = readFileSync9(devVarsPath, "utf-8");
2915
- const customSection = extractCustomDevVars(
2916
- linkedSecrets ? rawDevVars : stripGeneratedSecretsCache2(rawDevVars)
2917
- );
2714
+ const customSection = extractCustomDevVars(rawDevVars);
2918
2715
  if (customSection) {
2919
2716
  try {
2920
2717
  userSecrets = parseDevVars(customSection);
@@ -2945,10 +2742,8 @@ var deploy_default = defineCommand8({
2945
2742
  }
2946
2743
  if (userSecretNames.length) {
2947
2744
  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
- );
2745
+ if (!linkedSecrets) {
2746
+ p4.log.info("Not in the app store yet \u2014 migrating after this deploy.");
2952
2747
  }
2953
2748
  }
2954
2749
  s.start(`Deploying to ${appName}.app.space...`);
@@ -2970,7 +2765,7 @@ var deploy_default = defineCommand8({
2970
2765
  const groupJson = JSON.stringify(assetGroups[i]);
2971
2766
  s.message(`Uploading assets \u2014 group ${i + 1}/${totalGroups}...`);
2972
2767
  const groupRes = await postWithRetry(
2973
- `${DEPLOY_URL3}/api/deploy/${appName}/assets?uploadId=${uploadId}&groupIndex=${i}&totalGroups=${totalGroups}`,
2768
+ `${DEPLOY_URL3}/api/deploy/${appId}/assets?uploadId=${uploadId}&groupIndex=${i}&totalGroups=${totalGroups}`,
2974
2769
  () => ({
2975
2770
  method: "POST",
2976
2771
  headers: {
@@ -2982,7 +2777,7 @@ var deploy_default = defineCommand8({
2982
2777
  );
2983
2778
  if (!groupRes.ok) {
2984
2779
  const errBody = await groupRes.json().catch(() => ({}));
2985
- await abortStagedUpload(appName, uploadId, totalGroups, token);
2780
+ await abortStagedUpload(appId, uploadId, totalGroups, token);
2986
2781
  s.stop("Deploy failed");
2987
2782
  p4.cancel(
2988
2783
  `Asset upload failed on group ${i + 1}/${totalGroups}: ${errBody.error ?? `HTTP ${groupRes.status}`}`
@@ -2991,13 +2786,21 @@ var deploy_default = defineCommand8({
2991
2786
  }
2992
2787
  }
2993
2788
  } catch (err) {
2994
- await abortStagedUpload(appName, uploadId, totalGroups, token);
2789
+ await abortStagedUpload(appId, uploadId, totalGroups, token);
2995
2790
  s.stop("Deploy failed");
2996
2791
  p4.cancel(`Asset upload failed (network): ${errMessage(err)}`);
2997
2792
  process.exit(1);
2998
2793
  }
2999
2794
  s.message(`Deploying to ${appName}.app.space...`);
3000
2795
  }
2796
+ const commitUrl = `${DEPLOY_URL3}/api/deploy/${appId}`;
2797
+ let confirmRename = args.rename === true;
2798
+ const bailDeploy = async (message, stopLabel = "Deploy failed") => {
2799
+ if (uploadId) await abortStagedUpload(appId, uploadId, totalGroups, token);
2800
+ if (stopLabel !== null) s.stop(stopLabel);
2801
+ p4.cancel(message);
2802
+ process.exit(1);
2803
+ };
3001
2804
  const makeForm = () => {
3002
2805
  const form = new FormData();
3003
2806
  form.append("worker", new Blob([workerJs], { type: "application/javascript" }), "worker.js");
@@ -3019,38 +2822,45 @@ var deploy_default = defineCommand8({
3019
2822
  if (extraRoutes.length) {
3020
2823
  form.append("extraRunWorkerFirst", JSON.stringify(extraRoutes));
3021
2824
  }
3022
- if (localSecretsLink.linked) {
3023
- form.append("secretsProjectName", localSecretsLink.projectName);
3024
- }
2825
+ form.append("name", appName);
2826
+ if (confirmRename) form.append("confirmRename", "true");
3025
2827
  return form;
3026
2828
  };
3027
- const commitUrl = `${DEPLOY_URL3}/api/deploy/${appName}`;
2829
+ const postCommit = async () => postWithRetry(
2830
+ commitUrl,
2831
+ () => ({
2832
+ method: "POST",
2833
+ headers: { Authorization: `Bearer ${token}` },
2834
+ body: makeForm()
2835
+ }),
2836
+ // Commit runs a real deploy — don't retry its 5xx (could double-deploy);
2837
+ // only a thrown fetch (dropped connection) is retried, and the staged
2838
+ // groups survive on the worker until this request succeeds.
2839
+ { retryServerErrors: false }
2840
+ );
3028
2841
  let res;
3029
2842
  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
- );
2843
+ res = await postCommit();
3042
2844
  } 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);
2845
+ await bailDeploy(`Deploy request failed: ${errMessage(err)}`);
2846
+ throw err;
2847
+ }
2848
+ let body = await res.json().catch(() => ({}));
2849
+ if (res.status === 409 && body.code === "rename_required" && !confirmRename) {
2850
+ s.stop("Rename confirmation needed");
2851
+ const yes = await p4.confirm({
2852
+ 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?`
2853
+ });
2854
+ if (p4.isCancel(yes) || !yes) {
2855
+ await bailDeploy("Deploy cancelled.", null);
2856
+ }
2857
+ confirmRename = true;
2858
+ s.start(`Deploying to ${appName}.app.space...`);
2859
+ res = await postCommit();
2860
+ body = await res.json().catch(() => ({}));
3047
2861
  }
3048
- const body = await res.json().catch(() => ({}));
3049
2862
  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);
2863
+ await bailDeploy(formatDeployWorkerError(res.status, body.error));
3054
2864
  }
3055
2865
  if (body.onBehalfOfOwner) {
3056
2866
  p4.log.warn(`Deployed on behalf of owner ${body.onBehalfOfOwner}`);
@@ -3064,26 +2874,66 @@ var deploy_default = defineCommand8({
3064
2874
  "First requests may briefly hit the assets transitional page; retry in a minute."
3065
2875
  );
3066
2876
  p4.log.success(`Live at: ${body.url}`);
3067
- await syncSubscriptionPlans(appDir2, appName, token);
3068
- await syncOneTimeProducts(appDir2, appName, token);
2877
+ await migrateLegacyDevVarsSecrets({
2878
+ deployUrl: DEPLOY_URL3,
2879
+ token,
2880
+ appId,
2881
+ envName,
2882
+ devVarsPath,
2883
+ userSecrets,
2884
+ userSecretNames,
2885
+ hadStore: linkedSecrets !== null
2886
+ });
2887
+ await syncSubscriptionPlans(appDir, appId, token);
2888
+ await syncOneTimeProducts(appDir, appId, token);
3069
2889
  p4.outro("Done");
3070
2890
  return;
3071
2891
  }
3072
2892
  }
3073
2893
  s.stop("Deployed!");
3074
2894
  p4.log.success(`Live at: ${body.url}`);
3075
- await syncSubscriptionPlans(appDir2, appName, token);
3076
- await syncOneTimeProducts(appDir2, appName, token);
2895
+ await migrateLegacyDevVarsSecrets({
2896
+ deployUrl: DEPLOY_URL3,
2897
+ token,
2898
+ appId,
2899
+ envName,
2900
+ devVarsPath,
2901
+ userSecrets,
2902
+ userSecretNames,
2903
+ hadStore: linkedSecrets !== null
2904
+ });
2905
+ await syncSubscriptionPlans(appDir, appId, token);
2906
+ await syncOneTimeProducts(appDir, appId, token);
3077
2907
  p4.outro("Done");
3078
2908
  }
3079
2909
  });
3080
- async function syncSubscriptionPlans(appDir2, appName, token) {
3081
- const subsPath = join11(appDir2, "src", "subscriptions.ts");
2910
+ async function migrateLegacyDevVarsSecrets(opts) {
2911
+ const { deployUrl, token, appId, envName, devVarsPath, userSecrets, userSecretNames, hadStore } = opts;
2912
+ if (hadStore || userSecretNames.length === 0) return;
2913
+ const configName = defaultConfigNameForEnv(envName);
2914
+ try {
2915
+ await uploadSecrets(deployUrl, token, appId, configName, userSecrets, false);
2916
+ if (existsSync10(devVarsPath)) {
2917
+ const cleaned = removeDevVarsKeys(readFileSync9(devVarsPath, "utf-8"), new Set(userSecretNames));
2918
+ writeFileSync9(devVarsPath, cleaned.endsWith("\n") ? cleaned : `${cleaned}
2919
+ `);
2920
+ }
2921
+ p4.log.success(
2922
+ `Migrated ${userSecretNames.length} secret${userSecretNames.length === 1 ? "" : "s"} from .dev.vars into the app store (config ${configName}) \u2014 now managed by \`deepspace secrets\`.`
2923
+ );
2924
+ } catch (err) {
2925
+ p4.log.warn(
2926
+ `Couldn't migrate .dev.vars secrets into the app store (${errMessage(err)}); they deployed as bindings for now and the next deploy will retry.`
2927
+ );
2928
+ }
2929
+ }
2930
+ async function syncSubscriptionPlans(appDir, appName, token) {
2931
+ const subsPath = join11(appDir, "src", "subscriptions.ts");
3082
2932
  if (!existsSync10(subsPath)) return;
3083
2933
  let plans;
3084
2934
  try {
3085
2935
  const esbuild = await import("esbuild");
3086
- const outDir = join11(appDir2, ".wrangler", "deploy");
2936
+ const outDir = join11(appDir, ".wrangler", "deploy");
3087
2937
  const outFile = join11(outDir, "subscriptions.bundle.mjs");
3088
2938
  await esbuild.build({
3089
2939
  entryPoints: [subsPath],
@@ -3110,13 +2960,13 @@ async function syncSubscriptionPlans(appDir2, appName, token) {
3110
2960
  s.start("Syncing subscription plans...");
3111
2961
  let res;
3112
2962
  try {
3113
- res = await fetch(`${API_URL}/api/subscriptions/sync`, {
2963
+ res = await fetch(`${API_URL2}/api/subscriptions/sync`, {
3114
2964
  method: "POST",
3115
2965
  headers: {
3116
2966
  Authorization: `Bearer ${token}`,
3117
2967
  "Content-Type": "application/json"
3118
2968
  },
3119
- body: JSON.stringify({ appName, plans })
2969
+ body: JSON.stringify({ appId: appName, plans })
3120
2970
  });
3121
2971
  } catch (err) {
3122
2972
  s.stop("Plan sync skipped");
@@ -3184,7 +3034,7 @@ current billing period? (Skip to leave them grandfathered.)`
3184
3034
  let stopped = false;
3185
3035
  try {
3186
3036
  while (iteration < MAX_ITERATIONS) {
3187
- const res = await fetch(`${API_URL}/api/subscriptions/admin-cancel`, {
3037
+ const res = await fetch(`${API_URL2}/api/subscriptions/admin-cancel`, {
3188
3038
  method: "POST",
3189
3039
  headers: {
3190
3040
  Authorization: `Bearer ${token}`,
@@ -3225,13 +3075,13 @@ current billing period? (Skip to leave them grandfathered.)`
3225
3075
  }
3226
3076
  }
3227
3077
  }
3228
- async function syncOneTimeProducts(appDir2, appName, token) {
3229
- const productsPath = join11(appDir2, "src", "products.ts");
3078
+ async function syncOneTimeProducts(appDir, appName, token) {
3079
+ const productsPath = join11(appDir, "src", "products.ts");
3230
3080
  if (!existsSync10(productsPath)) return;
3231
3081
  let products;
3232
3082
  try {
3233
3083
  const esbuild = await import("esbuild");
3234
- const outDir = join11(appDir2, ".wrangler", "deploy");
3084
+ const outDir = join11(appDir, ".wrangler", "deploy");
3235
3085
  const outFile = join11(outDir, "products.bundle.mjs");
3236
3086
  await esbuild.build({
3237
3087
  entryPoints: [productsPath],
@@ -3258,13 +3108,13 @@ async function syncOneTimeProducts(appDir2, appName, token) {
3258
3108
  s?.start("Syncing one-time products...");
3259
3109
  let res;
3260
3110
  try {
3261
- res = await fetch(`${API_URL}/api/charges/products/sync`, {
3111
+ res = await fetch(`${API_URL2}/api/charges/products/sync`, {
3262
3112
  method: "POST",
3263
3113
  headers: {
3264
3114
  Authorization: `Bearer ${token}`,
3265
3115
  "Content-Type": "application/json"
3266
3116
  },
3267
- body: JSON.stringify({ appName, products })
3117
+ body: JSON.stringify({ appId: appName, products })
3268
3118
  });
3269
3119
  } catch (err) {
3270
3120
  s?.stop("Product sync skipped");
@@ -3335,7 +3185,7 @@ var undeploy_default = defineCommand9({
3335
3185
  args: {
3336
3186
  name: {
3337
3187
  type: "positional",
3338
- description: "App name to undeploy (reads from wrangler.toml if omitted)",
3188
+ description: "App id to undeploy (reads DEEPSPACE_APP_ID from wrangler.toml if omitted)",
3339
3189
  required: false
3340
3190
  },
3341
3191
  env: {
@@ -3345,21 +3195,17 @@ var undeploy_default = defineCommand9({
3345
3195
  }
3346
3196
  },
3347
3197
  async run({ args }) {
3348
- let appName = args.name;
3349
3198
  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
- }
3199
+ let appId = args.name;
3200
+ if (appId && !APP_ID_RE.test(appId)) {
3201
+ 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.`);
3202
+ process.exit(1);
3360
3203
  }
3361
- if (!appName) {
3362
- p5.cancel("Provide an app name or run from a DeepSpace app directory.");
3204
+ if (!appId && hasWranglerConfig(process.cwd())) {
3205
+ appId = readAppId(process.cwd(), envName) ?? void 0;
3206
+ }
3207
+ if (!appId) {
3208
+ p5.cancel("Provide an app id or run from a DeepSpace app directory with a DEEPSPACE_APP_ID.");
3363
3209
  process.exit(1);
3364
3210
  }
3365
3211
  let token;
@@ -3369,10 +3215,10 @@ var undeploy_default = defineCommand9({
3369
3215
  p5.cancel(err instanceof Error ? err.message : String(err));
3370
3216
  process.exit(1);
3371
3217
  }
3372
- p5.intro(`Undeploying ${appName}`);
3218
+ p5.intro(`Undeploying ${appId}`);
3373
3219
  const s = createSpinner();
3374
3220
  s.start("Removing...");
3375
- const res = await fetch(`${DEPLOY_URL4}/api/deploy/${appName}`, {
3221
+ const res = await fetch(`${DEPLOY_URL4}/api/deploy/${appId}`, {
3376
3222
  method: "DELETE",
3377
3223
  headers: { Authorization: `Bearer ${token}` }
3378
3224
  });
@@ -3383,7 +3229,10 @@ var undeploy_default = defineCommand9({
3383
3229
  process.exit(1);
3384
3230
  }
3385
3231
  s.stop("Removed");
3386
- p5.outro(`${appName}.app.space has been taken down`);
3232
+ const hosts = body.releasedHosts ?? [];
3233
+ p5.outro(
3234
+ hosts.length ? `${hosts.join(", ")} taken down. The app keeps its id \u2014 redeploy to bring it back.` : `App ${appId} taken down.`
3235
+ );
3387
3236
  }
3388
3237
  });
3389
3238
 
@@ -3484,14 +3333,14 @@ var add_default = defineCommand11({
3484
3333
  }
3485
3334
  },
3486
3335
  run({ args }) {
3487
- const appDir2 = resolve8(args.dir ?? ".");
3488
- ensureInstallReady(appDir2);
3489
- const script = findScript(appDir2);
3336
+ const appDir = resolve8(args.dir ?? ".");
3337
+ ensureInstallReady(appDir);
3338
+ const script = findScript(appDir);
3490
3339
  if (!script) {
3491
3340
  console.error("Could not find the deepspace feature installer.");
3492
3341
  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")}`);
3342
+ console.error(` ${join12(appDir, "node_modules", "deepspace", "scripts", "add-feature.cjs")}`);
3343
+ console.error(` ${join12(appDir, ".deepspace", "scripts", "add-feature.cjs")}`);
3495
3344
  console.error("Are you in a DeepSpace app directory? Try `npm install` first.");
3496
3345
  process.exit(1);
3497
3346
  }
@@ -3501,13 +3350,13 @@ var add_default = defineCommand11({
3501
3350
  } else if (args.info) {
3502
3351
  passArgs.push("--info", args.info);
3503
3352
  } else if (args.feature) {
3504
- passArgs.push(args.feature, appDir2);
3353
+ passArgs.push(args.feature, appDir);
3505
3354
  } else {
3506
3355
  passArgs.push("--help");
3507
3356
  }
3508
3357
  execSync3(`node ${JSON.stringify(script)} ${passArgs.map((a) => JSON.stringify(a)).join(" ")}`, {
3509
3358
  stdio: "inherit",
3510
- cwd: appDir2
3359
+ cwd: appDir
3511
3360
  });
3512
3361
  }
3513
3362
  });
@@ -3520,17 +3369,17 @@ import { join as join13 } from "path";
3520
3369
  import { homedir as homedir5 } from "os";
3521
3370
  var SKILL_REPO = "deepdotspace/deepspace-skill";
3522
3371
  function runSkillsAdd(extraArgs) {
3523
- return new Promise((resolve11) => {
3372
+ return new Promise((resolve10) => {
3524
3373
  const child = spawn2(
3525
3374
  "npx",
3526
3375
  ["-y", "skills@latest", "add", SKILL_REPO, ...extraArgs],
3527
3376
  { stdio: "inherit" }
3528
3377
  );
3529
- child.on("close", (code) => resolve11(code ?? 1));
3378
+ child.on("close", (code) => resolve10(code ?? 1));
3530
3379
  child.on("error", (err) => {
3531
3380
  console.error(`Failed to invoke npx: ${err.message}`);
3532
3381
  console.error("Is Node.js installed and on PATH?");
3533
- resolve11(1);
3382
+ resolve10(1);
3534
3383
  });
3535
3384
  });
3536
3385
  }
@@ -3593,6 +3442,13 @@ var skills_default = defineCommand12({
3593
3442
  import { defineCommand as defineCommand13 } from "citty";
3594
3443
 
3595
3444
  // src/cli/lib/api.ts
3445
+ var ApiError = class extends Error {
3446
+ constructor(message, status3, code) {
3447
+ super(message);
3448
+ this.status = status3;
3449
+ this.code = code;
3450
+ }
3451
+ };
3596
3452
  async function apiFetch(baseUrl, token, path, init) {
3597
3453
  const res = await fetch(`${baseUrl}${path}`, {
3598
3454
  ...init,
@@ -3605,18 +3461,21 @@ async function apiFetch(baseUrl, token, path, init) {
3605
3461
  const text2 = await res.text();
3606
3462
  if (!res.ok) {
3607
3463
  let msg = text2;
3464
+ let code;
3608
3465
  try {
3609
- msg = JSON.parse(text2).error ?? text2;
3466
+ const body = JSON.parse(text2);
3467
+ msg = body.error ?? text2;
3468
+ code = body.code;
3610
3469
  } catch {
3611
3470
  }
3612
- throw new Error(`API ${path} (${res.status}): ${msg}`);
3471
+ throw new ApiError(`API ${path} (${res.status}): ${msg}`, res.status, code);
3613
3472
  }
3614
- return JSON.parse(text2);
3473
+ return text2 ? JSON.parse(text2) : {};
3615
3474
  }
3616
3475
 
3617
3476
  // src/cli/commands/domain.ts
3618
- var API_URL2 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
3619
- var api = (token, path, init) => apiFetch(API_URL2, token, path, init);
3477
+ var API_URL3 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
3478
+ var api = (token, path, init) => apiFetch(API_URL3, token, path, init);
3620
3479
  function scrubInternal(value) {
3621
3480
  if (Array.isArray(value)) return value.map(scrubInternal);
3622
3481
  if (value && typeof value === "object") {
@@ -3642,11 +3501,11 @@ async function confirm3(message) {
3642
3501
  throw new Error(`${message} \u2014 pass --yes to confirm non-interactively`);
3643
3502
  }
3644
3503
  process.stdout.write(`${message} [y/N] `);
3645
- return new Promise((resolve11) => {
3504
+ return new Promise((resolve10) => {
3646
3505
  process.stdin.setEncoding("utf-8");
3647
3506
  process.stdin.once("data", (data) => {
3648
3507
  const answer = data.toString().trim().toLowerCase();
3649
- resolve11(answer === "y" || answer === "yes");
3508
+ resolve10(answer === "y" || answer === "yes");
3650
3509
  });
3651
3510
  });
3652
3511
  }
@@ -3723,7 +3582,7 @@ var buy = defineCommand13({
3723
3582
  }
3724
3583
  const checkout = await api(token, "/api/domains/checkout", {
3725
3584
  method: "POST",
3726
- body: JSON.stringify({ domain: args.domain, appName })
3585
+ body: JSON.stringify({ domain: args.domain, appId: appName })
3727
3586
  });
3728
3587
  if (args.json) {
3729
3588
  process.stdout.write(
@@ -3867,7 +3726,7 @@ var attach = defineCommand13({
3867
3726
  `/api/domains/${found.id}/reattach`,
3868
3727
  {
3869
3728
  method: "POST",
3870
- body: JSON.stringify({ appName: targetApp })
3729
+ body: JSON.stringify({ appId: targetApp })
3871
3730
  }
3872
3731
  );
3873
3732
  if (args.json) {
@@ -3950,36 +3809,19 @@ function isTestAccountEmail(email) {
3950
3809
  }
3951
3810
 
3952
3811
  // src/cli/commands/collaborators.ts
3953
- 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) : {};
3812
+ var API_URL4 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
3813
+ function api2(token, path, init) {
3814
+ return apiFetch(API_URL4, token, path, init);
3973
3815
  }
3974
3816
  var list3 = defineCommand14({
3975
3817
  meta: { name: "list", description: "List collaborators on your app" },
3976
3818
  args: {
3977
- app: { type: "string", description: "App name (defaults to ./wrangler.toml)" },
3819
+ app: { type: "string", alias: "a", description: "App id (defaults to ./wrangler.toml)" },
3978
3820
  json: { type: "boolean", description: "Emit JSON" }
3979
3821
  },
3980
3822
  async run({ args }) {
3981
3823
  const token = await ensureToken();
3982
- const app = requireAppName(args.app);
3824
+ const app = requireAppIdArg(args.app);
3983
3825
  const { collaborators } = await api2(
3984
3826
  token,
3985
3827
  `/api/app-collaborators/${encodeURIComponent(app)}`
@@ -4005,12 +3847,12 @@ var add2 = defineCommand14({
4005
3847
  description: "Collaborator email (must already be a DeepSpace user)",
4006
3848
  required: true
4007
3849
  },
4008
- app: { type: "string", description: "App name (defaults to ./wrangler.toml)" },
3850
+ app: { type: "string", alias: "a", description: "App id (defaults to ./wrangler.toml)" },
4009
3851
  json: { type: "boolean", description: "Emit JSON" }
4010
3852
  },
4011
3853
  async run({ args }) {
4012
3854
  const token = await ensureToken();
4013
- const app = requireAppName(args.app);
3855
+ const app = requireAppIdArg(args.app);
4014
3856
  if (isTestAccountEmail(args.email)) {
4015
3857
  console.error(
4016
3858
  "Test accounts (@deepspace.test) cannot be added as collaborators. Use a real DeepSpace account."
@@ -4033,12 +3875,12 @@ var remove = defineCommand14({
4033
3875
  meta: { name: "remove", description: "Remove a collaborator from your app" },
4034
3876
  args: {
4035
3877
  email: { type: "positional", description: "Collaborator email", required: true },
4036
- app: { type: "string", description: "App name (defaults to ./wrangler.toml)" },
3878
+ app: { type: "string", alias: "a", description: "App id (defaults to ./wrangler.toml)" },
4037
3879
  json: { type: "boolean", description: "Emit JSON" }
4038
3880
  },
4039
3881
  async run({ args }) {
4040
3882
  const token = await ensureToken();
4041
- const app = requireAppName(args.app);
3883
+ const app = requireAppIdArg(args.app);
4042
3884
  const { collaborators } = await api2(
4043
3885
  token,
4044
3886
  `/api/app-collaborators/${encodeURIComponent(app)}`
@@ -4066,12 +3908,86 @@ var collaborators_default = defineCommand14({
4066
3908
  subCommands: { list: list3, add: add2, remove }
4067
3909
  });
4068
3910
 
4069
- // src/cli/commands/integrations.ts
3911
+ // src/cli/commands/transfer.ts
4070
3912
  import { defineCommand as defineCommand15 } from "citty";
3913
+ var API_URL5 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
3914
+ var DEPLOY_URL5 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
3915
+ var offer = defineCommand15({
3916
+ meta: { name: "offer", description: "Offer this app to another DeepSpace user" },
3917
+ args: {
3918
+ email: { type: "positional", description: "Recipient email", required: true },
3919
+ app: { type: "string", alias: "a", description: "App id (defaults to ./wrangler.toml)" }
3920
+ },
3921
+ async run({ args }) {
3922
+ const token = await ensureToken();
3923
+ const app = requireAppIdArg(args.app);
3924
+ const { expiresAt } = await apiFetch(
3925
+ API_URL5,
3926
+ token,
3927
+ `/api/app-transfers/${encodeURIComponent(app)}`,
3928
+ { method: "POST", body: JSON.stringify({ email: args.email }) }
3929
+ );
3930
+ console.log(
3931
+ `\u2713 Offered ${app} to ${args.email} (expires ${expiresAt}).
3932
+ They accept with: deepspace transfer accept --app ${app}`
3933
+ );
3934
+ }
3935
+ });
3936
+ var status2 = defineCommand15({
3937
+ meta: { name: "status", description: "Show the pending transfer offer, if any" },
3938
+ args: {
3939
+ app: { type: "string", alias: "a", description: "App id (defaults to ./wrangler.toml)" }
3940
+ },
3941
+ async run({ args }) {
3942
+ const token = await ensureToken();
3943
+ const app = requireAppIdArg(args.app);
3944
+ const { transfer } = await apiFetch(API_URL5, token, `/api/app-transfers/${encodeURIComponent(app)}`);
3945
+ if (!transfer) {
3946
+ console.log(`No pending transfer for ${app}.`);
3947
+ return;
3948
+ }
3949
+ console.log(`Pending: ${app} \u2192 ${transfer.toEmailDisplay} (expires ${transfer.expiresAt}).`);
3950
+ }
3951
+ });
3952
+ var accept = defineCommand15({
3953
+ meta: { name: "accept", description: "Accept a transfer offered to you" },
3954
+ args: {
3955
+ app: { type: "string", alias: "a", description: "App id of the offered app", required: true }
3956
+ },
3957
+ async run({ args }) {
3958
+ const token = await ensureToken();
3959
+ const app = requireAppIdArg(args.app);
3960
+ await apiFetch(DEPLOY_URL5, token, `/api/apps/${encodeURIComponent(app)}/transfer/accept`, {
3961
+ method: "POST"
3962
+ });
3963
+ console.log(
3964
+ `\u2713 You now own ${app}. Run \`deepspace init\` in a fresh clone (or set DEEPSPACE_APP_ID = "${app}" in wrangler.toml) and deploy.`
3965
+ );
3966
+ }
3967
+ });
3968
+ var cancel4 = defineCommand15({
3969
+ meta: { name: "cancel", description: "Cancel/decline the pending offer (either party)" },
3970
+ args: {
3971
+ app: { type: "string", alias: "a", description: "App id (defaults to ./wrangler.toml)" }
3972
+ },
3973
+ async run({ args }) {
3974
+ const token = await ensureToken();
3975
+ const app = requireAppIdArg(args.app);
3976
+ await apiFetch(API_URL5, token, `/api/app-transfers/${encodeURIComponent(app)}`, { method: "DELETE" });
3977
+ console.log(`\u2713 Transfer offer for ${app} cancelled.`);
3978
+ }
3979
+ });
3980
+ var transfer_default = defineCommand15({
3981
+ meta: { name: "transfer", description: "Transfer app ownership to another user" },
3982
+ subCommands: { offer, status: status2, accept, cancel: cancel4 }
3983
+ });
3984
+
3985
+ // src/cli/commands/integrations.ts
3986
+ import { defineCommand as defineCommand16 } from "citty";
4071
3987
 
4072
3988
  // src/cli/commands/_invoke-impl.ts
4073
3989
  import { readFileSync as readFileSync10 } from "fs";
4074
- var API_URL4 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
3990
+ var API_URL6 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
4075
3991
  var DEFAULT_TIMEOUT_MS = 12e4;
4076
3992
  function parseTarget(target) {
4077
3993
  if (!target || typeof target !== "string") {
@@ -4113,7 +4029,7 @@ function flushAndExit(code) {
4113
4029
  });
4114
4030
  }
4115
4031
  async function fetchCatalog(opts = {}) {
4116
- const url = opts.summary ? `${API_URL4}/api/integrations?summary=1` : `${API_URL4}/api/integrations`;
4032
+ const url = opts.summary ? `${API_URL6}/api/integrations?summary=1` : `${API_URL6}/api/integrations`;
4117
4033
  const res = await fetch(url);
4118
4034
  if (!res.ok) {
4119
4035
  throw new Error(`Failed to fetch integration catalog (${res.status})`);
@@ -4201,7 +4117,7 @@ async function runInvoke(args) {
4201
4117
  const startedAt = Date.now();
4202
4118
  let res;
4203
4119
  try {
4204
- res = await fetch(`${API_URL4}/api/integrations/${integration}/${endpoint}`, {
4120
+ res = await fetch(`${API_URL6}/api/integrations/${integration}/${endpoint}`, {
4205
4121
  method: "POST",
4206
4122
  headers: {
4207
4123
  "Content-Type": "application/json",
@@ -4252,7 +4168,7 @@ async function runInvoke(args) {
4252
4168
  }
4253
4169
 
4254
4170
  // src/cli/commands/integrations.ts
4255
- var list4 = defineCommand15({
4171
+ var list4 = defineCommand16({
4256
4172
  meta: {
4257
4173
  name: "list",
4258
4174
  description: "List all available integrations"
@@ -4273,7 +4189,7 @@ var list4 = defineCommand15({
4273
4189
  }
4274
4190
  }
4275
4191
  });
4276
- var info = defineCommand15({
4192
+ var info = defineCommand16({
4277
4193
  meta: {
4278
4194
  name: "info",
4279
4195
  description: "Print schema + example body for an integration endpoint"
@@ -4299,7 +4215,7 @@ var info = defineCommand15({
4299
4215
  }
4300
4216
  }
4301
4217
  });
4302
- var invoke = defineCommand15({
4218
+ var invoke = defineCommand16({
4303
4219
  meta: {
4304
4220
  name: "invoke",
4305
4221
  description: "Invoke a platform integration as the logged-in user"
@@ -4352,7 +4268,7 @@ var invoke = defineCommand15({
4352
4268
  }
4353
4269
  }
4354
4270
  });
4355
- var integrations_default = defineCommand15({
4271
+ var integrations_default = defineCommand16({
4356
4272
  meta: {
4357
4273
  name: "integrations",
4358
4274
  description: "List, inspect, and invoke platform integrations"
@@ -4365,8 +4281,8 @@ var integrations_default = defineCommand15({
4365
4281
  });
4366
4282
 
4367
4283
  // src/cli/commands/invoke.ts
4368
- import { defineCommand as defineCommand16 } from "citty";
4369
- var invoke_default = defineCommand16({
4284
+ import { defineCommand as defineCommand17 } from "citty";
4285
+ var invoke_default = defineCommand17({
4370
4286
  meta: {
4371
4287
  name: "invoke",
4372
4288
  description: "Invoke a platform integration as the logged-in user"
@@ -4439,13 +4355,13 @@ var invoke_default = defineCommand16({
4439
4355
  });
4440
4356
 
4441
4357
  // src/cli/commands/library.ts
4442
- import { defineCommand as defineCommand17 } from "citty";
4358
+ import { defineCommand as defineCommand18 } from "citty";
4443
4359
  import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
4444
4360
  import { join as join14, resolve as resolve9 } from "path";
4445
- import { parse as parseToml3 } from "smol-toml";
4361
+ import { parse as parseToml4 } from "smol-toml";
4446
4362
  import * as p6 from "@clack/prompts";
4447
4363
  var DEFAULT_LIBRARY_APP = "deepdotspace-site";
4448
- var publish = defineCommand17({
4364
+ var publish = defineCommand18({
4449
4365
  meta: {
4450
4366
  name: "publish",
4451
4367
  description: "Publish the current app to the DeepSpace library"
@@ -4518,12 +4434,12 @@ var publish = defineCommand17({
4518
4434
  intro6("Publishing to the DeepSpace library");
4519
4435
  let appName = typeof args.app === "string" && args.app.trim() ? args.app.trim() : void 0;
4520
4436
  if (!appName) {
4521
- const appDir2 = resolve9(typeof args.dir === "string" && args.dir ? args.dir : ".");
4522
- const wranglerPath = join14(appDir2, "wrangler.toml");
4437
+ const appDir = resolve9(typeof args.dir === "string" && args.dir ? args.dir : ".");
4438
+ const wranglerPath = join14(appDir, "wrangler.toml");
4523
4439
  if (!existsSync12(wranglerPath)) {
4524
4440
  return die("No --app provided and no wrangler.toml found. Either pass --app <name> or run from your app's directory.");
4525
4441
  }
4526
- const cfg = parseToml3(readFileSync11(wranglerPath, "utf-8"));
4442
+ const cfg = parseToml4(readFileSync11(wranglerPath, "utf-8"));
4527
4443
  const nameRes = resolveAppName(cfg.name);
4528
4444
  if (!nameRes.ok) return die(`wrangler.toml: ${nameRes.reason}`);
4529
4445
  appName = nameRes.name;
@@ -4584,7 +4500,7 @@ var publish = defineCommand17({
4584
4500
  p6.outro("Done");
4585
4501
  }
4586
4502
  });
4587
- var unpublish = defineCommand17({
4503
+ var unpublish = defineCommand18({
4588
4504
  meta: {
4589
4505
  name: "unpublish",
4590
4506
  description: "Remove a library entry (owner or admin only)"
@@ -4643,7 +4559,7 @@ var unpublish = defineCommand17({
4643
4559
  p6.outro("Done");
4644
4560
  }
4645
4561
  });
4646
- var library_default = defineCommand17({
4562
+ var library_default = defineCommand18({
4647
4563
  meta: {
4648
4564
  name: "library",
4649
4565
  description: "Manage your DeepSpace library entries"
@@ -4655,13 +4571,13 @@ var library_default = defineCommand17({
4655
4571
  });
4656
4572
 
4657
4573
  // src/cli/commands/feedback.ts
4658
- import { defineCommand as defineCommand18 } from "citty";
4574
+ import { defineCommand as defineCommand19 } from "citty";
4659
4575
  import * as p7 from "@clack/prompts";
4660
4576
  import { existsSync as existsSync13, readFileSync as readFileSync12 } from "fs";
4661
4577
  import { dirname as dirname3, join as join15 } from "path";
4662
4578
  import { fileURLToPath } from "url";
4663
4579
  import { release } from "os";
4664
- var API_URL5 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
4580
+ var API_URL7 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
4665
4581
  var TYPES = ["bug", "feature", "other"];
4666
4582
  var TYPE_LABELS = {
4667
4583
  bug: "Bug report",
@@ -4671,7 +4587,7 @@ var TYPE_LABELS = {
4671
4587
  var MAX_TITLE_LEN = 200;
4672
4588
  var MAX_BODY_LEN = 1e4;
4673
4589
  var MAX_CONTEXT_LEN = 200;
4674
- var api3 = (token, path, init) => apiFetch(API_URL5, token, path, init);
4590
+ var api3 = (token, path, init) => apiFetch(API_URL7, token, path, init);
4675
4591
  function normalizeType(raw) {
4676
4592
  const value = (raw ?? "bug").toLowerCase();
4677
4593
  if (TYPES.includes(value)) return value;
@@ -4733,7 +4649,7 @@ function buildFeedbackPayload(input) {
4733
4649
  ...input.context
4734
4650
  };
4735
4651
  }
4736
- var feedback_default = defineCommand18({
4652
+ var feedback_default = defineCommand19({
4737
4653
  meta: {
4738
4654
  name: "feedback",
4739
4655
  description: "Submit a bug report, feature request, or feedback to DeepSpace"
@@ -4769,7 +4685,7 @@ var feedback_default = defineCommand18({
4769
4685
  },
4770
4686
  async run({ args }) {
4771
4687
  const interactive = process.stdin.isTTY && !args.json;
4772
- const fail = (message) => {
4688
+ const fail2 = (message) => {
4773
4689
  if (args.json) {
4774
4690
  process.stdout.write(JSON.stringify({ ok: false, error: message }) + "\n");
4775
4691
  } else {
@@ -4781,12 +4697,12 @@ var feedback_default = defineCommand18({
4781
4697
  try {
4782
4698
  type = normalizeType(args.type ? String(args.type) : void 0);
4783
4699
  } catch (err) {
4784
- return fail(err.message);
4700
+ return fail2(err.message);
4785
4701
  }
4786
4702
  let title = args.title ? String(args.title) : "";
4787
4703
  let body = args.message ? String(args.message) : "";
4788
4704
  if ((!title || !body) && !interactive) {
4789
- return fail(
4705
+ return fail2(
4790
4706
  'Provide a title and --message to submit non-interactively, e.g. `deepspace feedback "Title" --message "Details"`.'
4791
4707
  );
4792
4708
  }
@@ -4848,7 +4764,7 @@ var feedback_default = defineCommand18({
4848
4764
  context: collectContext()
4849
4765
  });
4850
4766
  } catch (err) {
4851
- return fail(err.message);
4767
+ return fail2(err.message);
4852
4768
  }
4853
4769
  if (interactive && !args.yes) {
4854
4770
  p7.note(
@@ -4861,1011 +4777,395 @@ Context: deepspace ${payload.cliVersion} \xB7 ${payload.nodeVersion} \xB7 ${payl
4861
4777
  "Review"
4862
4778
  );
4863
4779
  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
- ` : "");
4780
+ if (p7.isCancel(ok) || !ok) {
4781
+ p7.cancel("Not sent.");
4782
+ process.exit(0);
5108
4783
  }
5109
- cleanedLegacyFile = true;
5110
- } catch {
5111
4784
  }
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.");
4785
+ let token;
4786
+ try {
4787
+ token = await ensureToken();
4788
+ } catch (err) {
4789
+ return fail2(err.message ?? "Not signed in. Run `deepspace login`.");
5128
4790
  }
5129
- if (result.skipped.length > 0) {
5130
- console.log(`Skipped SDK-managed/invalid keys (left in place): ${result.skipped.join(", ")}`);
4791
+ let result;
4792
+ try {
4793
+ result = await api3(token, "/api/feedback", {
4794
+ method: "POST",
4795
+ body: JSON.stringify(payload)
4796
+ });
4797
+ } catch (err) {
4798
+ return fail2(err.message);
5131
4799
  }
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));
4800
+ if (args.json) {
4801
+ process.stdout.write(JSON.stringify({ ok: true, report: result.report }) + "\n");
4802
+ return;
5165
4803
  }
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
- }
4804
+ if (interactive) {
4805
+ p7.outro(`Thanks! Submitted as ${result.report.id}.`);
4806
+ } else {
4807
+ console.log(`Submitted feedback ${result.report.id} (${result.report.type}).`);
5185
4808
  }
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;
4809
+ console.log(`Track triage status at ${DASHBOARD_URL}.`);
5195
4810
  }
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`.");
4811
+ });
4812
+
4813
+ // src/cli/commands/secrets.ts
4814
+ import { defineCommand as defineCommand20 } from "citty";
4815
+ import { readFileSync as readFileSync13 } from "fs";
4816
+ var DEPLOY_URL6 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
4817
+ var COMMON_ARGS = {
4818
+ app: {
4819
+ type: "string",
4820
+ alias: "a",
4821
+ description: "App id (default: DEEPSPACE_APP_ID from the nearest wrangler.toml)",
4822
+ required: false
4823
+ },
4824
+ config: {
4825
+ type: "string",
4826
+ alias: "c",
4827
+ description: 'Config name (default: the --env name, or "prd")',
4828
+ required: false
4829
+ },
4830
+ env: {
4831
+ type: "string",
4832
+ alias: "e",
4833
+ description: "wrangler.toml [env.<name>] slot \u2014 selects that env\u2019s app id and config",
4834
+ required: false
5224
4835
  }
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.`);
4836
+ };
4837
+ async function resolveTarget(args) {
4838
+ const wranglerEnv = args.env?.trim() || void 0;
4839
+ let appId = args.app?.trim();
4840
+ if (appId && !APP_ID_RE.test(appId)) {
4841
+ throw new Error(`Invalid app id "${appId}" \u2014 expected app_<26 chars>.`);
4842
+ }
4843
+ if (!appId) {
4844
+ const appDir = findAppDir();
4845
+ appId = appDir && readAppId(appDir, wranglerEnv) || void 0;
4846
+ if (!appId) {
4847
+ throw new Error(
4848
+ "No app id. Run from an app directory whose wrangler.toml carries DEEPSPACE_APP_ID (`deepspace init` mints one), or pass --app <appId>."
4849
+ );
5229
4850
  }
5230
- if (!unset.includes(name)) unset.push(name);
5231
4851
  }
5232
- return {
5233
- appDir: appDir(args),
5234
- unset,
5235
- wranglerEnv: wranglerEnv(args)
5236
- };
4852
+ const configName = validateConfigName(args.config?.trim() || defaultConfigNameForEnv(wranglerEnv));
4853
+ const token = await ensureToken();
4854
+ return { appId, configName, token };
4855
+ }
4856
+ function fail(err) {
4857
+ console.error(err instanceof Error ? err.message : String(err));
4858
+ process.exit(1);
5237
4859
  }
5238
- var list5 = defineCommand19({
4860
+ var list5 = defineCommand20({
5239
4861
  meta: { name: "list", description: "List masked secrets in a config" },
5240
4862
  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 }
4863
+ ...COMMON_ARGS,
4864
+ "only-names": {
4865
+ type: "boolean",
4866
+ description: "Only print secret names; omit values and metadata",
4867
+ default: false
4868
+ },
4869
+ json: { type: "boolean", description: "Machine-readable output", default: false }
5246
4870
  },
5247
4871
  async run({ args }) {
5248
4872
  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
- );
4873
+ const t = await resolveTarget(args);
4874
+ const { secrets } = await listSecrets(DEPLOY_URL6, t.token, t.appId, t.configName);
5258
4875
  if (args.json) {
5259
- process.stdout.write(JSON.stringify(result, null, 2) + "\n");
4876
+ console.log(JSON.stringify({ appId: t.appId, config: t.configName, secrets }, null, 2));
5260
4877
  return;
5261
4878
  }
5262
- if (result.secrets.length === 0) {
5263
- console.log(`No secrets in ${target.projectName}/${target.configName}.`);
4879
+ if (secrets.length === 0) {
4880
+ console.log(`No secrets in ${t.configName}.`);
5264
4881
  return;
5265
4882
  }
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}`);
4883
+ for (const s of secrets) {
4884
+ console.log(args["only-names"] ? s.key : `${s.key} (v${s.version}, ${s.valueBytes}B, ${s.updatedAt})`);
5272
4885
  }
5273
4886
  } catch (err) {
5274
- console.error(err instanceof Error ? err.message : String(err));
5275
- process.exit(1);
4887
+ fail(err);
5276
4888
  }
5277
4889
  }
5278
4890
  });
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 }) {
4891
+ var set = defineCommand20({
4892
+ meta: { name: "set", description: "Set secrets from KEY=value pairs" },
4893
+ args: {
4894
+ ...COMMON_ARGS,
4895
+ secret: { type: "positional", description: "KEY=value (repeatable)", required: true }
4896
+ },
4897
+ async run({ args }) {
5291
4898
  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)
4899
+ const t = await resolveTarget(args);
4900
+ const pairs = [args.secret, ...args._ ?? []].filter(
4901
+ (v) => typeof v === "string" && v.length > 0
5304
4902
  );
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;
4903
+ const secrets = {};
4904
+ for (const pair of pairs) {
4905
+ const eq = pair.indexOf("=");
4906
+ if (eq <= 0) throw new Error(`Expected KEY=value, got "${pair}"`);
4907
+ const key = validateSecretName(pair.slice(0, eq));
4908
+ const value = pair.slice(eq + 1);
4909
+ validateSecretValue(key, value);
4910
+ secrets[key] = value;
5311
4911
  }
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);
4912
+ if (Object.keys(secrets).length === 1) {
4913
+ const [[k, v]] = Object.entries(secrets);
4914
+ await setSecret(DEPLOY_URL6, t.token, t.appId, t.configName, k, v);
5316
4915
  } else {
5317
- console.log(`Refreshed ${count} cached secret${count === 1 ? "" : "s"}.`);
5318
- redeployReminder(target);
4916
+ await uploadSecrets(DEPLOY_URL6, t.token, t.appId, t.configName, secrets, false);
5319
4917
  }
4918
+ console.log(`Set ${Object.keys(secrets).length} secret${Object.keys(secrets).length === 1 ? "" : "s"} in ${t.configName}.`);
5320
4919
  } catch (err) {
5321
- console.error(err instanceof Error ? err.message : String(err));
5322
- process.exit(1);
4920
+ fail(err);
5323
4921
  }
5324
4922
  }
5325
4923
  });
5326
- var get = defineCommand19({
5327
- meta: { name: "get", description: "Get one or more secrets; pass --plain to print values" },
4924
+ var get = defineCommand20({
4925
+ meta: { name: "get", description: "Get a secret; pass --plain to print the value" },
5328
4926
  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 }
4927
+ ...COMMON_ARGS,
4928
+ key: { type: "positional", description: "Secret name", required: true },
4929
+ plain: { type: "boolean", description: "Print the plaintext value", default: false }
5337
4930
  },
5338
4931
  async run({ args }) {
5339
4932
  try {
5340
- const token = await ensureToken();
5341
- const { appDir: dir, target } = targetFromArgs(args);
5342
- const keys = positionals(args, "key").map((key) => validateSecretName(key));
4933
+ const t = await resolveTarget(args);
4934
+ const key = validateSecretName(args.key);
5343
4935
  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");
4936
+ const { value } = await getSecretPlain(DEPLOY_URL6, t.token, t.appId, t.configName, key);
4937
+ process.stdout.write(value + "\n");
5383
4938
  return;
5384
4939
  }
5385
- for (const item of found) process.stdout.write(`${item.key} ${item.masked} v${item.version}
5386
- `);
4940
+ const { secrets } = await listSecrets(DEPLOY_URL6, t.token, t.appId, t.configName);
4941
+ const item = secrets.find((s) => s.key === key);
4942
+ if (!item) fail(new Error(`Secret "${key}" not found in ${t.configName}`));
4943
+ console.log(`${item.key} (v${item.version}, ${item.valueBytes}B, ${item.updatedAt})`);
5387
4944
  } catch (err) {
5388
- console.error(err instanceof Error ? err.message : String(err));
5389
- process.exit(1);
4945
+ fail(err);
5390
4946
  }
5391
4947
  }
5392
4948
  });
5393
- var del2 = defineCommand19({
5394
- meta: { name: "delete", description: "Delete one or more remote secrets" },
4949
+ var del2 = defineCommand20({
4950
+ meta: { name: "delete", description: "Delete one or more secrets" },
5395
4951
  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 }
4952
+ ...COMMON_ARGS,
4953
+ key: { type: "positional", description: "Secret name (repeatable)", required: true }
5400
4954
  },
5401
4955
  async run({ args }) {
5402
4956
  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)
4957
+ const t = await resolveTarget(args);
4958
+ const keys = [args.key, ...args._ ?? []].filter(
4959
+ (v) => typeof v === "string" && v.length > 0
5449
4960
  );
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;
4961
+ for (const key of keys) {
4962
+ await deleteSecret(DEPLOY_URL6, t.token, t.appId, t.configName, validateSecretName(key));
5456
4963
  }
5457
- console.log(`${linked ? "Pulled" : "Read"} ${count} secret${count === 1 ? "" : "s"} from ${target.projectName}/${target.configName}.`);
5458
- if (!linked) unlinkedTargetNotice(target);
4964
+ console.log(`Deleted ${keys.length} secret${keys.length === 1 ? "" : "s"} from ${t.configName}.`);
5459
4965
  } catch (err) {
5460
- console.error(err instanceof Error ? err.message : String(err));
5461
- process.exit(1);
4966
+ fail(err);
5462
4967
  }
5463
4968
  }
5464
4969
  });
5465
- var download = defineCommand19({
5466
- meta: { name: "download", description: "Download a config's secrets in a Doppler-compatible format" },
4970
+ var upload = defineCommand20({
4971
+ meta: { name: "upload", description: "Upload secrets from a dotenv or JSON file" },
5467
4972
  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);
4973
+ ...COMMON_ARGS,
4974
+ file: { type: "positional", description: "Path to a dotenv or JSON file (- for stdin)", required: true },
4975
+ replace: {
4976
+ type: "boolean",
4977
+ description: "Replace the whole config (delete keys missing from the file)",
4978
+ default: false
5507
4979
  }
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
4980
  },
5518
4981
  async run({ args }) {
5519
4982
  try {
5520
- const token = await ensureToken();
5521
- const { appDir: dir, target } = targetFromArgs(args);
5522
- const file = String(args.file);
5523
- const content = readExistingFile(file);
4983
+ const t = await resolveTarget(args);
4984
+ const content = args.file === "-" ? readFileSync13(0, "utf-8") : readFileSync13(args.file, "utf-8");
5524
4985
  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
- }
4986
+ if (Object.keys(secrets).length === 0) fail(new Error("No secrets found in the input."));
4987
+ await uploadSecrets(DEPLOY_URL6, t.token, t.appId, t.configName, secrets, args.replace);
4988
+ console.log(`Uploaded ${Object.keys(secrets).length} secrets to ${t.configName}.`);
5580
4989
  } catch (err) {
5581
- console.error(err instanceof Error ? err.message : String(err));
5582
- process.exit(1);
4990
+ fail(err);
5583
4991
  }
5584
4992
  }
5585
4993
  });
5586
- var configsCreate = defineCommand19({
5587
- meta: { name: "create", description: "Create a remote config without linking it" },
4994
+ var download = defineCommand20({
4995
+ meta: { name: "download", description: "Download a config\u2019s secrets (dotenv/json/shell)" },
5588
4996
  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 }
4997
+ ...COMMON_ARGS,
4998
+ format: {
4999
+ type: "string",
5000
+ description: "dotenv (default) | json | shell",
5001
+ default: "dotenv"
5002
+ }
5592
5003
  },
5593
5004
  async run({ args }) {
5594
5005
  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"}.`);
5006
+ const t = await resolveTarget(args);
5007
+ const format = args.format;
5008
+ if (!["dotenv", "json", "shell"].includes(format)) {
5009
+ fail(new Error(`Unknown format "${args.format}" \u2014 use dotenv, json, or shell.`));
5627
5010
  }
5011
+ const { secrets } = await fetchSecretsValues(DEPLOY_URL6, t.token, t.appId, t.configName);
5012
+ process.stdout.write(formatSecretsDownload(secrets, format));
5628
5013
  } catch (err) {
5629
- console.error(err instanceof Error ? err.message : String(err));
5630
- process.exit(1);
5014
+ fail(err);
5631
5015
  }
5632
5016
  }
5633
5017
  });
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
- },
5018
+ var pull = defineCommand20({
5019
+ meta: { name: "pull", description: "Refresh the .dev.vars cache from the app store" },
5020
+ args: { ...COMMON_ARGS },
5662
5021
  async run({ args }) {
5663
5022
  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");
5023
+ const wranglerEnv = args.env?.trim() || void 0;
5024
+ const appDir = findAppDir();
5025
+ if (!appDir) fail(new Error("Run from a DeepSpace app directory (one containing wrangler.toml)."));
5026
+ const t = await resolveTarget(args);
5027
+ const ownerId = decodeJwtPayload(t.token).sub;
5028
+ const pulled = await pullAppSecretsCache(DEPLOY_URL6, t.token, t.appId, t.configName);
5029
+ if (!pulled) {
5030
+ console.log(`No secrets in ${t.configName} yet \u2014 nothing to pull.`);
5677
5031
  return;
5678
5032
  }
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
- }
5033
+ await writeDevVars(appDir, ownerId, t.token, wranglerEnv, {
5034
+ appId: t.appId,
5035
+ generatedSecretsCache: renderSecretsCache(pulled.values, pulled),
5036
+ sharedDevVarsCache: true
5037
+ });
5038
+ console.log(
5039
+ `Pulled ${Object.keys(pulled.values).length} secrets (${t.configName}) into .dev.vars.`
5040
+ );
5692
5041
  } catch (err) {
5693
- console.error(err instanceof Error ? err.message : String(err));
5694
- process.exit(1);
5042
+ fail(err);
5695
5043
  }
5696
5044
  }
5697
5045
  });
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
- },
5046
+ var configsList = defineCommand20({
5047
+ meta: { name: "list", description: "List the app\u2019s configs" },
5048
+ args: { ...COMMON_ARGS },
5704
5049
  async run({ args }) {
5705
5050
  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");
5051
+ const t = await resolveTarget(args);
5052
+ const { configs: configs2 } = await listConfigs(DEPLOY_URL6, t.token, t.appId);
5053
+ if (configs2.length === 0) {
5054
+ console.log("No configs yet \u2014 the first `secrets set` creates one.");
5733
5055
  return;
5734
5056
  }
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 });
5057
+ for (const cfg of configs2) {
5058
+ console.log(`${cfg.name} (${cfg.secretCount ?? 0} secrets, updated ${cfg.updatedAt})`);
5739
5059
  }
5740
5060
  } catch (err) {
5741
- console.error(err instanceof Error ? err.message : String(err));
5742
- process.exit(1);
5061
+ fail(err);
5743
5062
  }
5744
5063
  }
5745
5064
  });
5746
- var setup = defineCommand19({
5747
- meta: { name: "setup", description: "Link this app/env to a secrets project config" },
5748
- args: setupArgs(),
5065
+ var configsCreate = defineCommand20({
5066
+ meta: { name: "create", description: "Create a config (optionally copying another)" },
5067
+ args: {
5068
+ ...COMMON_ARGS,
5069
+ name: { type: "positional", description: "Config name", required: true },
5070
+ "copy-from": { type: "string", description: "Copy secrets from this config", required: false }
5071
+ },
5749
5072
  async run({ args }) {
5750
5073
  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);
5074
+ const t = await resolveTarget(args);
5075
+ const name = validateConfigName(args.name);
5076
+ await createConfig(DEPLOY_URL6, t.token, t.appId, name, args["copy-from"] || void 0);
5077
+ console.log(`Created ${name}.`);
5768
5078
  } catch (err) {
5769
- console.error(err instanceof Error ? err.message : String(err));
5770
- process.exit(1);
5079
+ fail(err);
5771
5080
  }
5772
5081
  }
5773
5082
  });
5774
- var configureUnset = defineCommand19({
5775
- meta: { name: "unset", description: "Unset linked secrets project/config settings" },
5083
+ var configsDelete = defineCommand20({
5084
+ meta: { name: "delete", description: "Delete a config and all of its secrets" },
5776
5085
  args: {
5777
- ...scopedArgs(),
5778
- name: { type: "positional", description: "Setting names to unset: project and/or config", required: false }
5086
+ ...COMMON_ARGS,
5087
+ name: { type: "positional", description: "Config name", required: true }
5779
5088
  },
5780
5089
  async run({ args }) {
5781
5090
  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.");
5091
+ const t = await resolveTarget(args);
5092
+ const name = validateConfigName(args.name);
5093
+ await deleteConfig(DEPLOY_URL6, t.token, t.appId, name);
5094
+ console.log(`Deleted ${name}.`);
5794
5095
  } catch (err) {
5795
- console.error(err instanceof Error ? err.message : String(err));
5796
- process.exit(1);
5096
+ fail(err);
5797
5097
  }
5798
5098
  }
5799
5099
  });
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
- }
5100
+ var configs = defineCommand20({
5101
+ meta: { name: "configs", description: "Manage the app\u2019s secrets configs" },
5102
+ subCommands: { list: configsList, create: configsCreate, delete: configsDelete }
5814
5103
  });
5815
- var secrets_default = defineCommand19({
5816
- meta: { name: "secrets", description: "Manage remote DeepSpace app secrets" },
5104
+ var secrets_default = defineCommand20({
5105
+ meta: { name: "secrets", description: "Manage the app\u2019s secrets store" },
5817
5106
  subCommands: {
5818
5107
  list: list5,
5819
5108
  set,
5820
5109
  get,
5821
5110
  delete: del2,
5822
- pull,
5823
- download,
5824
5111
  upload,
5825
- setup,
5826
- configure,
5112
+ download,
5113
+ pull,
5827
5114
  configs
5828
5115
  }
5829
5116
  });
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;
5117
+
5118
+ // src/cli/commands/init.ts
5119
+ import { defineCommand as defineCommand21 } from "citty";
5120
+ var init_default = defineCommand21({
5121
+ meta: {
5122
+ name: "init",
5123
+ description: "Mint this app\u2019s immutable DEEPSPACE_APP_ID into wrangler.toml"
5124
+ },
5125
+ args: {
5126
+ "new-id": {
5127
+ type: "boolean",
5128
+ description: "Replace the existing id \u2014 forks this repo as a SEPARATE app (new data, new secrets, new registration). The original app keeps running.",
5129
+ default: false
5130
+ },
5131
+ env: {
5132
+ type: "string",
5133
+ alias: "e",
5134
+ description: "wrangler.toml [env.<name>] block to stamp (each env is its own app)",
5135
+ required: false
5136
+ }
5137
+ },
5138
+ async run({ args }) {
5139
+ const appDir = findAppDir();
5140
+ if (!appDir) {
5141
+ console.error("Run from a DeepSpace app directory (one containing wrangler.toml).");
5142
+ process.exit(1);
5143
+ }
5144
+ const envName = args.env || void 0;
5145
+ const existing = readAppId(appDir, envName);
5146
+ if (existing && !args["new-id"]) {
5147
+ console.log(`Already initialized: ${existing}${envName ? ` (env: ${envName})` : ""}`);
5148
+ return;
5149
+ }
5150
+ const appId = mintAppId();
5151
+ writeAppId(appDir, appId, { wranglerEnv: envName, force: args["new-id"] });
5152
+ if (existing) {
5153
+ console.log(`Forked: ${existing} \u2192 ${appId}${envName ? ` (env: ${envName})` : ""}`);
5154
+ console.log("The next deploy registers this as a NEW app; the original is untouched.");
5155
+ } else {
5156
+ console.log(`Minted ${appId}${envName ? ` (env: ${envName})` : ""} \u2014 commit wrangler.toml.`);
5157
+ console.log("The first deploy registers it and claims the `name` subdomain.");
5840
5158
  }
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
5159
  }
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
- }
5160
+ });
5861
5161
 
5862
5162
  // src/cli/cli.ts
5863
5163
  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")) {
5164
+ var pkg = JSON.parse(readFileSync14(join16(__dirname, "..", "package.json"), "utf-8"));
5165
+ if (process.platform === "linux" && !process.env.SSL_CERT_FILE && existsSync14("/etc/ssl/certs/ca-certificates.crt")) {
5866
5166
  process.env.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
5867
5167
  }
5868
- var create2 = defineCommand20({
5168
+ var create2 = defineCommand22({
5869
5169
  meta: {
5870
5170
  name: "create",
5871
5171
  description: "Create a new DeepSpace app"
@@ -5884,13 +5184,14 @@ var create2 = defineCommand20({
5884
5184
  execSync4(`npm create deepspace@latest${name}`, { stdio: "inherit" });
5885
5185
  }
5886
5186
  });
5887
- var main = defineCommand20({
5187
+ var main = defineCommand22({
5888
5188
  meta: {
5889
5189
  name: "deepspace",
5890
5190
  version: pkg.version,
5891
5191
  description: "DeepSpace SDK CLI"
5892
5192
  },
5893
5193
  subCommands: {
5194
+ init: init_default,
5894
5195
  create: create2,
5895
5196
  login: login_default,
5896
5197
  logout: logout_default,
@@ -5908,6 +5209,7 @@ var main = defineCommand20({
5908
5209
  undeploy: undeploy_default,
5909
5210
  domain: domain_default,
5910
5211
  collaborators: collaborators_default,
5212
+ transfer: transfer_default,
5911
5213
  library: library_default,
5912
5214
  feedback: feedback_default,
5913
5215
  secrets: secrets_default