deepspace 0.20.0 → 0.21.0

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/{chunk-A7UFIOWO.js → chunk-KDCDRHBA.js} +7 -2
  3. package/dist/chunk-KDCDRHBA.js.map +1 -0
  4. package/dist/cli.js +193 -62
  5. package/dist/cli.js.map +1 -1
  6. package/dist/documentation-client-core.d.ts +3 -2
  7. package/dist/documentation-client-core.js +1 -1
  8. package/dist/documentation-default-renderer.cjs +48 -48
  9. package/dist/documentation-react.d.ts +2 -1
  10. package/dist/documentation-react.js +1 -1
  11. package/dist/documentation-runtime.js +7 -7
  12. package/dist/documentation-server-core.d.ts +3 -2
  13. package/dist/documentation-server-core.js +1 -1
  14. package/dist/documentation.d.ts +40 -12
  15. package/dist/documentation.js +40 -7
  16. package/dist/documentation.js.map +1 -1
  17. package/dist/index.d.ts +225 -21
  18. package/dist/index.js +239 -100
  19. package/dist/index.js.map +1 -1
  20. package/dist/{public-CFAj1hL2.d.ts → public-DenQ1TsZ.d.ts} +36 -11
  21. package/dist/server.d.ts +21 -0
  22. package/dist/server.js +148 -39
  23. package/dist/server.js.map +1 -1
  24. package/dist/testing-mcp.d.ts +69 -0
  25. package/dist/testing-mcp.js +99 -0
  26. package/dist/testing-mcp.js.map +1 -0
  27. package/dist/testing.d.ts +17 -20
  28. package/dist/testing.js +47 -25
  29. package/dist/testing.js.map +1 -1
  30. package/dist/worker.d.ts +48 -3
  31. package/dist/worker.js +234 -62
  32. package/dist/worker.js.map +1 -1
  33. package/features/ai-chat/feature.json +2 -2
  34. package/features/ai-chat/src/AiChatPage.tsx +56 -21
  35. package/features/ai-chat/src/ChatPanel.tsx +33 -10
  36. package/features/ai-chat/src/__tests__/ChatPanel.lifecycle.test.tsx +156 -0
  37. package/features/cron/src/CronLogPage.tsx +107 -69
  38. package/features/file-attachments/feature.json +2 -1
  39. package/features/file-manager/feature.json +2 -1
  40. package/features/file-manager/src/FileManagerPage.tsx +227 -194
  41. package/features/integration-test/src/IntegrationTestPage.tsx +54 -34
  42. package/package.json +5 -1
  43. package/dist/chunk-A7UFIOWO.js.map +0 -1
package/dist/cli.js CHANGED
@@ -530,7 +530,7 @@ import { readFileSync as readFileSync2, existsSync, mkdirSync } from "fs";
530
530
  import { join } from "path";
531
531
  import { homedir } from "os";
532
532
 
533
- // src/cli/jwt.ts
533
+ // src/shared/jwt.ts
534
534
  function decodeJwtPayload(token) {
535
535
  const payloadSegment = token.split(".")[1];
536
536
  if (!payloadSegment) {
@@ -2677,11 +2677,15 @@ Free it with \`${killCmd}\`, or start on another port: \`deepspace dev start --p
2677
2677
  }
2678
2678
  const url = `http://${host === DEFAULT_DEV_HOST ? "localhost" : host}:${port}`;
2679
2679
  say("Starting dev server...\n");
2680
- const vite = spawn2("npx", ["vite", "--port", String(port), "--strictPort", "--host", host], {
2681
- cwd: appDir,
2682
- stdio: "inherit",
2683
- env: wranglerViteEnv(process.env, wranglerConfig, { DEEPSPACE_PORT: String(port) })
2684
- });
2680
+ const vite = spawn2(
2681
+ "npx",
2682
+ ["vite", "--port", String(port), "--strictPort", "--host", host, "--clearScreen", "false"],
2683
+ {
2684
+ cwd: appDir,
2685
+ stdio: "inherit",
2686
+ env: wranglerViteEnv(process.env, wranglerConfig, { DEEPSPACE_PORT: String(port) })
2687
+ }
2688
+ );
2685
2689
  const readiness = new AbortController();
2686
2690
  if (args.json) {
2687
2691
  void waitForPortListening(port, host, READINESS_TIMEOUT_MS, readiness.signal).then((ready) => {
@@ -3195,7 +3199,7 @@ async function syncTestAccountStore() {
3195
3199
  // src/cli/commands/test.ts
3196
3200
  var DEPLOY_URL2 = process.env.DEEPSPACE_DEPLOY_URL ?? PLATFORM_URLS.deploy;
3197
3201
  var PLAYWRIGHT_OUTPUT_DIR = ".deepspace/test-results";
3198
- function playwrightTestArgs(testFiles) {
3202
+ function playwrightTestArgs(testFiles, flags = {}) {
3199
3203
  return [
3200
3204
  "playwright",
3201
3205
  "test",
@@ -3203,6 +3207,9 @@ function playwrightTestArgs(testFiles) {
3203
3207
  "tests/playwright.config.ts",
3204
3208
  "--output",
3205
3209
  PLAYWRIGHT_OUTPUT_DIR,
3210
+ ...flags.grep ? ["--grep", flags.grep] : [],
3211
+ ...flags.project ? ["--project", flags.project] : [],
3212
+ ...flags.headed ? ["--headed"] : [],
3206
3213
  ...testFiles
3207
3214
  ];
3208
3215
  }
@@ -3223,6 +3230,21 @@ var test_default = defineDeepspaceCommand({
3223
3230
  description: `Port for vite/webServer (default ${DEFAULT_PORT}, or $DEEPSPACE_PORT)`,
3224
3231
  required: false
3225
3232
  },
3233
+ grep: {
3234
+ type: "string",
3235
+ description: "Only run Playwright tests matching this pattern (forwarded to --grep)",
3236
+ required: false
3237
+ },
3238
+ project: {
3239
+ type: "string",
3240
+ description: "Playwright project to run (forwarded to --project)",
3241
+ required: false
3242
+ },
3243
+ headed: {
3244
+ type: "boolean",
3245
+ description: "Run Playwright browsers headed (forwarded to --headed)",
3246
+ default: false
3247
+ },
3226
3248
  env: {
3227
3249
  type: "string",
3228
3250
  alias: "e",
@@ -3237,6 +3259,11 @@ var test_default = defineDeepspaceCommand({
3237
3259
  preflightNodeVersion("test run");
3238
3260
  const suite = args.suite ?? "default";
3239
3261
  const wranglerEnv = typeof args.env === "string" && args.env.trim() ? args.env.trim() : void 0;
3262
+ const forwarded = {
3263
+ grep: typeof args.grep === "string" && args.grep ? args.grep : void 0,
3264
+ project: typeof args.project === "string" && args.project ? args.project : void 0,
3265
+ headed: Boolean(args.headed)
3266
+ };
3240
3267
  const start = resolve9(".");
3241
3268
  const appDir = findAppDir(start);
3242
3269
  if (!appDir) {
@@ -3293,32 +3320,36 @@ var test_default = defineDeepspaceCommand({
3293
3320
  let exitCode = 0;
3294
3321
  switch (suite) {
3295
3322
  case "smoke":
3296
- exitCode = runPlaywright(appDir, ["tests/smoke.spec.ts"], port, wranglerEnv);
3323
+ exitCode = runPlaywright(appDir, ["tests/smoke.spec.ts"], port, wranglerEnv, forwarded);
3297
3324
  break;
3298
3325
  case "api":
3299
- exitCode = runPlaywright(appDir, ["tests/api.spec.ts"], port, wranglerEnv);
3326
+ exitCode = runPlaywright(appDir, ["tests/api.spec.ts"], port, wranglerEnv, forwarded);
3300
3327
  break;
3301
3328
  case "e2e":
3302
- exitCode = runPlaywright(appDir, [], port, wranglerEnv);
3329
+ exitCode = runPlaywright(appDir, [], port, wranglerEnv, forwarded);
3303
3330
  break;
3304
3331
  case "unit":
3332
+ if (forwarded.grep || forwarded.project || forwarded.headed) {
3333
+ console.error("note: --grep/--project/--headed apply to Playwright suites and are ignored by `unit`");
3334
+ }
3305
3335
  exitCode = runVitest(appDir);
3306
3336
  break;
3307
3337
  case "all":
3308
3338
  exitCode = runVitest(appDir);
3309
- if (exitCode === 0) exitCode = runPlaywright(appDir, [], port, wranglerEnv);
3339
+ if (exitCode === 0) exitCode = runPlaywright(appDir, [], port, wranglerEnv, forwarded);
3310
3340
  break;
3311
3341
  case "default":
3312
3342
  exitCode = runPlaywright(
3313
3343
  appDir,
3314
3344
  ["tests/smoke.spec.ts", "tests/api.spec.ts"],
3315
3345
  port,
3316
- wranglerEnv
3346
+ wranglerEnv,
3347
+ forwarded
3317
3348
  );
3318
3349
  break;
3319
3350
  default:
3320
3351
  if (suite.endsWith(".spec.ts")) {
3321
- exitCode = runPlaywright(appDir, [suite], port, wranglerEnv);
3352
+ exitCode = runPlaywright(appDir, [suite], port, wranglerEnv, forwarded);
3322
3353
  } else {
3323
3354
  throw new Refusal(
3324
3355
  `Unknown test suite: ${suite}
@@ -3335,7 +3366,7 @@ Available: smoke, api, e2e, unit, all`,
3335
3366
  return { data: { suite, port, appDir, wranglerEnv: wranglerEnv ?? null } };
3336
3367
  }
3337
3368
  });
3338
- function runPlaywright(appDir, testFiles, port, wranglerEnv) {
3369
+ function runPlaywright(appDir, testFiles, port, wranglerEnv, flags) {
3339
3370
  let wranglerConfig;
3340
3371
  try {
3341
3372
  wranglerConfig = prepareWranglerEnvConfig(appDir, wranglerEnv);
@@ -3344,7 +3375,7 @@ function runPlaywright(appDir, testFiles, port, wranglerEnv) {
3344
3375
  return 1;
3345
3376
  }
3346
3377
  try {
3347
- const result = spawnSync4("npx", playwrightTestArgs(testFiles), {
3378
+ const result = spawnSync4("npx", playwrightTestArgs(testFiles, flags), {
3348
3379
  cwd: appDir,
3349
3380
  stdio: "inherit",
3350
3381
  env: wranglerViteEnv(process.env, wranglerConfig, {
@@ -3453,7 +3484,7 @@ var create = defineDeepspaceCommand({
3453
3484
  await ensureToken();
3454
3485
  const email = args.email;
3455
3486
  const password = args.password;
3456
- const name = args.name;
3487
+ const name = args.name ?? email.split("@")[0];
3457
3488
  const label = args.label;
3458
3489
  let account;
3459
3490
  try {
@@ -3477,6 +3508,7 @@ var create = defineDeepspaceCommand({
3477
3508
  console.log(`Created test account:`);
3478
3509
  console.log(` ID: ${account.id}`);
3479
3510
  console.log(` Email: ${account.email}`);
3511
+ console.log(` Selector: ${name}`);
3480
3512
  console.log(` Password: ${password}`);
3481
3513
  console.log(` UserID: ${account.userId}`);
3482
3514
  if (account.label) console.log(` Label: ${account.label}`);
@@ -3487,6 +3519,7 @@ Saved to ${TEST_ACCOUNTS_PATH}`);
3487
3519
  data: {
3488
3520
  id: account.id,
3489
3521
  email: account.email,
3522
+ name,
3490
3523
  // The password is already echoed on the human path and stored 0600 in
3491
3524
  // ACCOUNTS_PATH — withholding it from --json would only force scripts
3492
3525
  // to parse the file themselves.
@@ -3504,6 +3537,18 @@ var list = defineDeepspaceCommand({
3504
3537
  name: "list",
3505
3538
  description: "List your test accounts"
3506
3539
  },
3540
+ args: {
3541
+ usable: {
3542
+ type: "boolean",
3543
+ description: "Only accounts with locally saved credentials (usable by the users() fixture)",
3544
+ default: false
3545
+ },
3546
+ reveal: {
3547
+ type: "boolean",
3548
+ description: "Print locally saved passwords instead of masking them",
3549
+ default: false
3550
+ }
3551
+ },
3507
3552
  async run({ args }) {
3508
3553
  await ensureToken();
3509
3554
  let remote;
@@ -3512,42 +3557,47 @@ var list = defineDeepspaceCommand({
3512
3557
  } catch (err) {
3513
3558
  throw new Refusal(`Failed: ${err.message}`, "test_accounts_list_failed");
3514
3559
  }
3515
- const local = loadAllTestAccounts();
3516
- const localByEmail = new Map(local.map((a) => [a.email, a]));
3517
- if (remote.length === 0) {
3560
+ const localByEmail = new Map(loadAllTestAccounts().map((a) => [a.email, a]));
3561
+ const accounts = remote.map((a) => {
3562
+ const stored = localByEmail.get(a.email);
3563
+ return {
3564
+ id: a.id,
3565
+ email: a.email,
3566
+ userId: a.userId,
3567
+ label: a.label,
3568
+ createdAt: a.createdAt,
3569
+ name: stored?.name ?? null,
3570
+ password: stored?.password ?? null,
3571
+ usableByFixture: Boolean(stored?.password)
3572
+ };
3573
+ }).filter((a) => !args.usable || a.usableByFixture);
3574
+ if (accounts.length === 0) {
3518
3575
  if (!args.json) {
3519
3576
  console.log(
3520
- "No test accounts. Create one with: deepspace test accounts create --email <email> --password <password>"
3577
+ remote.length > 0 ? `No test accounts with locally saved credentials (${remote.length} remote-only). Recreate them with: deepspace test accounts create --email <email> --password <password>` : "No test accounts. Create one with: deepspace test accounts create --email <email> --password <password>"
3521
3578
  );
3522
3579
  }
3523
3580
  return { data: { accounts: [], count: 0, limit: 10 } };
3524
3581
  }
3525
3582
  if (!args.json) {
3526
- console.log(`Test accounts (${remote.length}/10):
3583
+ console.log(`Test accounts (${remote.length}/10${args.usable ? ", usable only" : ""}):
3527
3584
  `);
3528
- for (const a of remote) {
3529
- const stored = localByEmail.get(a.email);
3585
+ for (const a of accounts) {
3530
3586
  const date = new Date(a.createdAt).toLocaleDateString();
3531
3587
  console.log(` ${a.email}${a.label ? ` (${a.label})` : ""}`);
3532
3588
  console.log(` ID: ${a.id} UserID: ${a.userId} Created: ${date}`);
3533
- if (stored?.password) {
3534
- console.log(` Password: ${stored.password}`);
3589
+ console.log(` Selector: ${a.name ?? "(none)"}`);
3590
+ if (a.usableByFixture) {
3591
+ console.log(` Password: ${args.reveal ? a.password : "(saved locally)"}`);
3535
3592
  } else {
3536
- console.log(` Password: (not saved locally)`);
3593
+ console.log(` Password: (not saved locally \u2014 not usable by the users() fixture)`);
3537
3594
  }
3538
3595
  }
3539
3596
  }
3540
3597
  return {
3541
3598
  data: {
3542
- accounts: remote.map((a) => ({
3543
- id: a.id,
3544
- email: a.email,
3545
- userId: a.userId,
3546
- label: a.label,
3547
- createdAt: a.createdAt,
3548
- password: localByEmail.get(a.email)?.password ?? null
3549
- })),
3550
- count: remote.length,
3599
+ accounts,
3600
+ count: accounts.length,
3551
3601
  limit: 10
3552
3602
  }
3553
3603
  };
@@ -4417,6 +4467,7 @@ async function buildDeployBundle(options) {
4417
4467
  if (lintFindings && lintFindings.length > 0) {
4418
4468
  p6.log.warn(formatSchemaLintFindings(lintFindings).join("\n"));
4419
4469
  }
4470
+ const buildStarted = Date.now();
4420
4471
  spinner2.start("Building...");
4421
4472
  let preparedWranglerConfig;
4422
4473
  try {
@@ -4439,7 +4490,7 @@ async function buildDeployBundle(options) {
4439
4490
  } finally {
4440
4491
  preparedWranglerConfig?.cleanup();
4441
4492
  }
4442
- spinner2.stop("Built");
4493
+ spinner2.stop(`Built (${Math.round((Date.now() - buildStarted) / 1e3)}s)`);
4443
4494
  const deployConfigPath = join15(appDir, ".wrangler", "deploy", "config.json");
4444
4495
  if (!existsSync14(deployConfigPath)) {
4445
4496
  output.die(
@@ -4601,6 +4652,51 @@ import * as p7 from "@clack/prompts";
4601
4652
  import { existsSync as existsSync15 } from "fs";
4602
4653
  import { join as join16 } from "path";
4603
4654
  import { pathToFileURL as pathToFileURL2 } from "url";
4655
+
4656
+ // src/shared/api-error.ts
4657
+ var SLUG_RE = /^[a-z0-9]+(?:[_.-][a-z0-9]+)*$/;
4658
+ var HUMAN_MESSAGES = {
4659
+ owner_connect_not_ready: "This app isn't ready to accept payments yet \u2014 the app owner needs to finish payment setup.",
4660
+ invalid_json: "The request body was not valid JSON.",
4661
+ invalid_body: "The request body is invalid.",
4662
+ invalid_product_id: "Invalid product id.",
4663
+ duplicate_product_id: "Duplicate product id.",
4664
+ unknown_product: "Unknown product.",
4665
+ too_many_products: "Too many products declared.",
4666
+ invalid_amount: "Invalid amount.",
4667
+ invalid_name: "Invalid product name.",
4668
+ invalid_description: "Invalid product description.",
4669
+ unsupported_currency: "Unsupported currency.",
4670
+ unknown_app: "Unknown app.",
4671
+ not_app_owner: "Only the app owner (or a collaborator) can do this.",
4672
+ invalid_return_url: "Invalid return URL.",
4673
+ invalid_cancel_url: "Invalid cancel URL.",
4674
+ unknown_plan: "Unknown subscription plan.",
4675
+ unknown_price: "No price is configured for that billing interval.",
4676
+ no_subscription: "No subscription found for this account."
4677
+ };
4678
+ function normalizeApiError(status3, body) {
4679
+ const payload = body !== null && typeof body === "object" ? body : {};
4680
+ const rawError = typeof payload.error === "string" ? payload.error : void 0;
4681
+ const message = typeof payload.message === "string" ? payload.message : void 0;
4682
+ const humanError = rawError !== void 0 && !SLUG_RE.test(rawError) ? rawError : void 0;
4683
+ const code = rawError !== void 0 && SLUG_RE.test(rawError) ? rawError : typeof payload.code === "string" && SLUG_RE.test(payload.code) ? payload.code : void 0;
4684
+ const details = {};
4685
+ for (const [key, value] of Object.entries(payload)) {
4686
+ if (key === "success" || key === "error" || key === "message" || key === "issues" || key === "code")
4687
+ continue;
4688
+ details[key] = value;
4689
+ }
4690
+ return {
4691
+ error: message ?? humanError ?? (code !== void 0 ? HUMAN_MESSAGES[code] : void 0) ?? rawError ?? `Request failed (${status3})`,
4692
+ ...code !== void 0 ? { code } : {},
4693
+ status: status3,
4694
+ ...Object.keys(details).length > 0 ? { details } : {},
4695
+ ...Array.isArray(payload.issues) ? { issues: payload.issues } : {}
4696
+ };
4697
+ }
4698
+
4699
+ // src/cli/commands/deploy/commerce.ts
4604
4700
  var API_URL2 = process.env.DEEPSPACE_API_URL ?? PLATFORM_URLS.api;
4605
4701
  function errorMessage2(error) {
4606
4702
  return error instanceof Error ? error.message : String(error);
@@ -4674,7 +4770,7 @@ Stripe will keep charging them until you cancel. Cancel at the end of their curr
4674
4770
  });
4675
4771
  const body = await response.json().catch(() => ({}));
4676
4772
  if (!response.ok || !body.success) {
4677
- lastError = body.error ?? `HTTP ${response.status}`;
4773
+ lastError = normalizeApiError(response.status, body).error;
4678
4774
  spinner2.stop(`Cancel failed for ${change.slug}: ${lastError}`);
4679
4775
  stopped = true;
4680
4776
  break;
@@ -4732,7 +4828,7 @@ async function syncSubscriptionPlans(appDir, appName, token, nonInteractive) {
4732
4828
  if (!response.ok || !body.ok) {
4733
4829
  spinner2.stop("Plan sync skipped");
4734
4830
  p7.log.warn(
4735
- `Plan sync failed: ${body.error ?? `HTTP ${response.status}`}${body.hint ? ` \u2014 ${body.hint}` : ""}`
4831
+ `Plan sync failed: ${normalizeApiError(response.status, body).error}${body.hint ? ` \u2014 ${body.hint}` : ""}`
4736
4832
  );
4737
4833
  return;
4738
4834
  }
@@ -4790,7 +4886,7 @@ async function syncOneTimeProducts(appDir, appName, token) {
4790
4886
  }
4791
4887
  spinner2?.stop("Product sync skipped");
4792
4888
  p7.log.warn(
4793
- `Product sync failed: ${body.error ?? `HTTP ${response.status}`}${body.details ? ` \u2014 ${JSON.stringify(body.details)}` : ""}`
4889
+ `Product sync failed: ${normalizeApiError(response.status, body).error}${body.details ? ` \u2014 ${JSON.stringify(body.details)}` : ""}`
4794
4890
  );
4795
4891
  } catch (error) {
4796
4892
  spinner2?.stop("Product sync skipped");
@@ -5658,7 +5754,7 @@ function detachedHeadRefusal() {
5658
5754
  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.";
5659
5755
  var DEPLOY_SERVICE_LIMIT_HINT = "The DeepSpace deploy service hit a resource limit processing this deploy (the deploy worker was terminated before it could answer). This is a DeepSpace-side failure, not a Cloudflare incident \u2014 your app was not changed. Retry; if it keeps happening, report the app id and asset count.";
5660
5756
  var REQUIRED_ASSET_TRANSPORT = "content-addressed-v1";
5661
- var UPLOAD_CONCURRENCY = 4;
5757
+ var UPLOAD_CONCURRENCY = 16;
5662
5758
  async function deployBuiltBundle(options) {
5663
5759
  const {
5664
5760
  deployUrl,
@@ -5747,15 +5843,19 @@ async function deployBuiltBundle(options) {
5747
5843
  form.append("deployKey", repository.deployKey);
5748
5844
  return form;
5749
5845
  };
5750
- const postCommit = async () => postWithRetry(
5751
- `${deployUrl}/api/deploy/${appId}`,
5752
- () => ({
5753
- method: "POST",
5754
- headers: { Authorization: `Bearer ${token}` },
5755
- body: makeForm()
5756
- }),
5757
- { retryServerErrors: false }
5758
- );
5846
+ let commitStarted = Date.now();
5847
+ const postCommit = async () => {
5848
+ commitStarted = Date.now();
5849
+ return postWithRetry(
5850
+ `${deployUrl}/api/deploy/${appId}`,
5851
+ () => ({
5852
+ method: "POST",
5853
+ headers: { Authorization: `Bearer ${token}` },
5854
+ body: makeForm()
5855
+ }),
5856
+ { retryServerErrors: false, attempts: 2, timeoutMs: 24e4 }
5857
+ );
5858
+ };
5759
5859
  let response;
5760
5860
  try {
5761
5861
  response = await postCommit();
@@ -5802,6 +5902,7 @@ async function deployBuiltBundle(options) {
5802
5902
  body.code
5803
5903
  );
5804
5904
  }
5905
+ p10.log.info(`Platform commit: ${Math.round((Date.now() - commitStarted) / 1e3)}s`);
5805
5906
  if (body.onBehalfOfOwner) {
5806
5907
  p10.log.warn("This release is attributed to the app's owner, not to your account.");
5807
5908
  }
@@ -5920,8 +6021,12 @@ async function uploadDeployAssets(options) {
5920
6021
  spinner2.message(`Uploading assets \u2014 ${uploaded}/${total}...`);
5921
6022
  }
5922
6023
  };
6024
+ const uploadStarted = Date.now();
5923
6025
  try {
5924
6026
  await Promise.all(Array.from({ length: Math.min(UPLOAD_CONCURRENCY, missing.length) }, worker));
6027
+ p10.log.info(
6028
+ `Uploaded ${total} file(s) in ${Math.round((Date.now() - uploadStarted) / 1e3)}s`
6029
+ );
5925
6030
  } catch (error) {
5926
6031
  spinner2.stop("Deploy failed");
5927
6032
  if (error instanceof AssetUploadError) {
@@ -5967,13 +6072,14 @@ function errorMessage4(error) {
5967
6072
  }
5968
6073
  async function postWithRetry(url, makeInit, {
5969
6074
  attempts = 4,
5970
- retryServerErrors = true
6075
+ retryServerErrors = true,
6076
+ timeoutMs = 6e4
5971
6077
  } = {}) {
5972
6078
  let lastResponse;
5973
6079
  let lastError;
5974
6080
  for (let attempt = 1; attempt <= attempts; attempt++) {
5975
6081
  try {
5976
- const response = await fetch(url, makeInit());
6082
+ const response = await fetch(url, { ...makeInit(), signal: AbortSignal.timeout(timeoutMs) });
5977
6083
  const transient = retryServerErrors && (response.status >= 500 || response.status === 408 || response.status === 429);
5978
6084
  if (!transient) return response;
5979
6085
  lastResponse = response;
@@ -6218,6 +6324,7 @@ var deploy_default = defineCommand3({
6218
6324
  spinner: spinner2
6219
6325
  });
6220
6326
  let serving = "unverifiable";
6327
+ const edgeWaitStarted = Date.now();
6221
6328
  if (body.url) {
6222
6329
  spinner2.message("Waiting for the edge to serve this release...");
6223
6330
  serving = await waitForLiveRelease(body.url, body.releaseStamp, 9e4);
@@ -6247,7 +6354,9 @@ var deploy_default = defineCommand3({
6247
6354
  return;
6248
6355
  }
6249
6356
  }
6250
- spinner2.stop("Deployed!");
6357
+ spinner2.stop(
6358
+ `Deployed! (edge confirmed in ${Math.round((Date.now() - edgeWaitStarted) / 1e3)}s)`
6359
+ );
6251
6360
  p12.log.success(`Live at: ${body.url}`);
6252
6361
  await syncCommerce(appDir, appId, token, output.nonInteractive);
6253
6362
  if (output.json) {
@@ -6508,7 +6617,10 @@ var undeploy_default = defineDeepspaceCommand({
6508
6617
  }
6509
6618
  );
6510
6619
  }
6511
- throw new Refusal(body.error ?? `Undeploy error (${res.status})`, "undeploy_failed");
6620
+ throw new Refusal(
6621
+ body.error ?? `Undeploy error (${res.status})`,
6622
+ body.code ?? "undeploy_failed"
6623
+ );
6512
6624
  }
6513
6625
  s?.stop("Removed");
6514
6626
  const hosts = body.releasedHosts ?? [];
@@ -9205,7 +9317,9 @@ async function runList(args) {
9205
9317
  for (const ep of endpoints) {
9206
9318
  const pad = ep.endpoint.padEnd(widest);
9207
9319
  const cost = formatCurrency(ep.billing.baseCost, ep.billing.currency);
9208
- console.log(` ${pad} ${ep.billing.model.padEnd(12)} ${cost}`);
9320
+ const oauth = ep.requiresOAuth ? " [oauth]" : "";
9321
+ console.log(` ${pad} ${ep.billing.model.padEnd(12)} ${cost}${oauth}`);
9322
+ if (ep.description) console.log(` ${ep.description}`);
9209
9323
  }
9210
9324
  console.log();
9211
9325
  }
@@ -9234,9 +9348,15 @@ async function runInfo(args) {
9234
9348
  }
9235
9349
  if (!args.json) {
9236
9350
  console.log(`${integration}/${endpoint}`);
9351
+ if (info2.description) console.log(` ${info2.description}`);
9237
9352
  console.log(
9238
9353
  ` billing: ${formatCurrency(info2.billing.baseCost, info2.billing.currency)} ${billingUnit(info2.billing.model)}`
9239
9354
  );
9355
+ if (info2.requiresOAuth) {
9356
+ console.log(
9357
+ " Requires OAuth: without a connected account, a call succeeds with data { requiresOAuth: true, authUrl } \u2014 open the authUrl, then retry."
9358
+ );
9359
+ }
9240
9360
  console.log();
9241
9361
  console.log("Input schema:");
9242
9362
  console.log(
@@ -9245,6 +9365,11 @@ async function runInfo(args) {
9245
9365
  console.log();
9246
9366
  console.log("Example body:");
9247
9367
  console.log(info2.example ? JSON.stringify(info2.example, null, 2) : " (no example available)");
9368
+ if (info2.outputSchema) {
9369
+ console.log();
9370
+ console.log("Output schema:");
9371
+ console.log(JSON.stringify(info2.outputSchema, null, 2));
9372
+ }
9248
9373
  }
9249
9374
  return { data: info2 };
9250
9375
  }
@@ -9325,17 +9450,19 @@ async function runInvoke(args) {
9325
9450
  }
9326
9451
  return { data: payload };
9327
9452
  }
9328
- const errMsg = payload.error ?? `Request failed (${res.status})`;
9329
- const lines = [`${integration}/${endpoint} (${res.status}, ${elapsed}ms): ${errMsg}`];
9330
- if (Array.isArray(payload.issues)) {
9331
- for (const issue of payload.issues) {
9453
+ const norm = normalizeApiError(res.status, payload);
9454
+ const lines = [`${integration}/${endpoint} (${res.status}, ${elapsed}ms): ${norm.error}`];
9455
+ if (norm.issues) {
9456
+ for (const issue of norm.issues) {
9332
9457
  const path = issue.path?.length ? issue.path.join(".") : "(root)";
9333
9458
  lines.push(` - ${path}: ${issue.message}`);
9334
9459
  }
9335
9460
  }
9336
- const code = typeof payload.code === "string" ? payload.code : "integration_call_failed";
9461
+ const code = norm.code ?? "integration_call_failed";
9337
9462
  const rest = { ...payload };
9338
9463
  delete rest.error;
9464
+ delete rest.message;
9465
+ delete rest.code;
9339
9466
  throw new Refusal(lines.join("\n"), code, {
9340
9467
  ...res.status === 401 ? { action: cliAction("deepspace", "auth", "login") } : {},
9341
9468
  extra: rest
@@ -12581,7 +12708,7 @@ function conflictMarkerFiles(diffCheckOutput) {
12581
12708
  function pullAfterLandAction(primaryDir, intoBranch, landedOid) {
12582
12709
  if (currentBranch(primaryDir) !== intoBranch) return null;
12583
12710
  if (resolveCommit(primaryDir, "HEAD") === landedOid) return null;
12584
- return { cwd: primaryDir, argv: ["deepspace", "pull"] };
12711
+ return executableAction({ cwd: primaryDir, argv: ["deepspace", "pull"] });
12585
12712
  }
12586
12713
  function landResumeArgv(args) {
12587
12714
  const argv = ["deepspace", "workspace", "land"];
@@ -12862,6 +12989,7 @@ var landWorkspaceCommand = defineDeepspaceCommand({
12862
12989
  }
12863
12990
  spinner2?.stop(`Landed ${id} into ${intoBranch} at ${landedOid.slice(0, 10)}.`);
12864
12991
  const primaryDir = primaryAppDir(appDir);
12992
+ const action = pullAfterLandAction(primaryDir, intoBranch, landedOid);
12865
12993
  const cleanup = keepWorktree ? null : cleanupWorkspaceLocal(appDir, id, intoBranch, { expectedBranchOid: landedOid });
12866
12994
  const inOwnWorktree = !cleanup && inOwnLinkedWorktree(appDir, id);
12867
12995
  const retainedWorktree = cleanup?.worktreeRetained ?? (keepWorktree || inOwnWorktree ? appDir : null);
@@ -12895,7 +13023,6 @@ var landWorkspaceCommand = defineDeepspaceCommand({
12895
13023
  { actionRequired: true, action: action2, extra: data }
12896
13024
  );
12897
13025
  }
12898
- const action = pullAfterLandAction(primaryDir, intoBranch, landedOid);
12899
13026
  return { data, ...action ? { action } : {} };
12900
13027
  }
12901
13028
  });
@@ -13622,7 +13749,7 @@ import { join as join23, resolve as resolve20 } from "path";
13622
13749
  // package.json
13623
13750
  var package_default = {
13624
13751
  name: "deepspace",
13625
- version: "0.20.0",
13752
+ version: "0.21.0",
13626
13753
  engines: {
13627
13754
  node: ">=22.15.0 <23 || >=24 <25 || >=26 <27"
13628
13755
  },
@@ -13655,6 +13782,10 @@ var package_default = {
13655
13782
  types: "./dist/testing.d.ts",
13656
13783
  import: "./dist/testing.js"
13657
13784
  },
13785
+ "./testing/mcp": {
13786
+ types: "./dist/testing-mcp.d.ts",
13787
+ import: "./dist/testing-mcp.js"
13788
+ },
13658
13789
  "./documentation": {
13659
13790
  types: "./dist/documentation.d.ts",
13660
13791
  import: "./dist/documentation.js"