@amerged/ohmyhost-cli 0.1.22 → 0.1.24

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 (78) hide show
  1. package/README.md +1 -1
  2. package/dist/bin.js +3 -2
  3. package/dist/{chunk-DKXDKUAX.js → chunk-QIJJFWW6.js} +993 -301
  4. package/dist/index.js +13 -1
  5. package/package.json +1 -1
  6. package/src/apps/product-cli/package.json +45 -0
  7. package/src/apps/product-cli/{bin.ts → src/bin.ts} +3 -0
  8. package/src/apps/product-cli/{cli.ts → src/cli.ts} +363 -87
  9. package/src/apps/product-cli/{command.ts → src/command.ts} +58 -11
  10. package/src/apps/product-cli/{composition.ts → src/composition.ts} +98 -21
  11. package/src/apps/product-cli/{index.ts → src/index.ts} +1 -0
  12. package/src/apps/product-cli/{product-api.ts → src/product-api.ts} +65 -19
  13. package/src/apps/product-cli/{repository-init.ts → src/repository-init.ts} +79 -29
  14. package/src/apps/product-cli/{runtime-contract.ts → src/runtime-contract.ts} +16 -309
  15. package/src/apps/product-cli/{user-token-file.ts → src/user-token-file.ts} +46 -35
  16. package/src/apps/product-cli/{workspace.ts → src/workspace.ts} +6 -0
  17. package/src/packages/contracts/package.json +137 -0
  18. package/src/packages/contracts/src/browser-capabilities.ts +133 -0
  19. package/src/packages/contracts/src/customer-auth-admission.ts +36 -0
  20. package/src/packages/contracts/{database-access.ts → src/database-access.ts} +10 -2
  21. package/src/packages/contracts/{framework-admission.ts → src/framework-admission.ts} +88 -13
  22. package/src/packages/contracts/src/framework-build-script.mjs +22 -0
  23. package/src/packages/contracts/src/framework-build-script.ts +1 -0
  24. package/src/packages/contracts/{index.ts → src/index.ts} +17 -16
  25. package/src/packages/contracts/src/operation-data-change-result.ts +86 -0
  26. package/src/packages/contracts/{operation-deployment-result.ts → src/operation-deployment-result.ts} +2 -2
  27. package/src/packages/contracts/{operation-failure.ts → src/operation-failure.ts} +24 -12
  28. package/src/packages/contracts/src/problem-policy.ts +498 -0
  29. package/src/packages/contracts/src/project-data-changes.ts +90 -0
  30. package/src/packages/contracts/src/secret-names.ts +27 -0
  31. package/src/packages/sdk-ts/package.json +21 -0
  32. package/src/packages/sdk-ts/{generated → src/generated}/index.ts +15 -0
  33. package/src/packages/sdk-ts/{generated → src/generated}/sdk.gen.ts +114 -5
  34. package/src/packages/sdk-ts/{generated → src/generated}/types.gen.ts +209 -10
  35. package/src/packages/workos-auth-contracts/package.json +39 -0
  36. /package/src/apps/product-cli/{credential-store.ts → src/credential-store.ts} +0 -0
  37. /package/src/apps/product-cli/{device-token-expiry.ts → src/device-token-expiry.ts} +0 -0
  38. /package/src/apps/product-cli/{file-credential-store.ts → src/file-credential-store.ts} +0 -0
  39. /package/src/apps/product-cli/{profile-store.ts → src/profile-store.ts} +0 -0
  40. /package/src/apps/product-cli/{project-link-store.ts → src/project-link-store.ts} +0 -0
  41. /package/src/apps/product-cli/{psql-session.ts → src/psql-session.ts} +0 -0
  42. /package/src/packages/contracts/{application-root.ts → src/application-root.ts} +0 -0
  43. /package/src/packages/contracts/{billing.ts → src/billing.ts} +0 -0
  44. /package/src/packages/contracts/{credit-pricing.ts → src/credit-pricing.ts} +0 -0
  45. /package/src/packages/contracts/{database-compute.ts → src/database-compute.ts} +0 -0
  46. /package/src/packages/contracts/{database-migration-admission.ts → src/database-migration-admission.ts} +0 -0
  47. /package/src/packages/contracts/{feedback.ts → src/feedback.ts} +0 -0
  48. /package/src/packages/contracts/{framework-family.ts → src/framework-family.ts} +0 -0
  49. /package/src/packages/contracts/{functions.ts → src/functions.ts} +0 -0
  50. /package/src/packages/contracts/{platform-origins.ts → src/platform-origins.ts} +0 -0
  51. /package/src/packages/contracts/{project-context.ts → src/project-context.ts} +0 -0
  52. /package/src/packages/contracts/{project-exports.ts → src/project-exports.ts} +0 -0
  53. /package/src/packages/contracts/{user-api-keys.ts → src/user-api-keys.ts} +0 -0
  54. /package/src/packages/sdk-ts/{generated → src/generated}/client/client.gen.ts +0 -0
  55. /package/src/packages/sdk-ts/{generated → src/generated}/client/index.ts +0 -0
  56. /package/src/packages/sdk-ts/{generated → src/generated}/client/types.gen.ts +0 -0
  57. /package/src/packages/sdk-ts/{generated → src/generated}/client/utils.gen.ts +0 -0
  58. /package/src/packages/sdk-ts/{generated → src/generated}/client.gen.ts +0 -0
  59. /package/src/packages/sdk-ts/{generated → src/generated}/core/auth.gen.ts +0 -0
  60. /package/src/packages/sdk-ts/{generated → src/generated}/core/bodySerializer.gen.ts +0 -0
  61. /package/src/packages/sdk-ts/{generated → src/generated}/core/params.gen.ts +0 -0
  62. /package/src/packages/sdk-ts/{generated → src/generated}/core/pathSerializer.gen.ts +0 -0
  63. /package/src/packages/sdk-ts/{generated → src/generated}/core/queryKeySerializer.gen.ts +0 -0
  64. /package/src/packages/sdk-ts/{generated → src/generated}/core/serverSentEvents.gen.ts +0 -0
  65. /package/src/packages/sdk-ts/{generated → src/generated}/core/types.gen.ts +0 -0
  66. /package/src/packages/sdk-ts/{generated → src/generated}/core/utils.gen.ts +0 -0
  67. /package/src/packages/sdk-ts/{index.ts → src/index.ts} +0 -0
  68. /package/src/packages/sdk-ts/{managed-mail-response.ts → src/managed-mail-response.ts} +0 -0
  69. /package/src/packages/sdk-ts/{managed-mail.ts → src/managed-mail.ts} +0 -0
  70. /package/src/packages/workos-auth-contracts/{device-flow.ts → src/device-flow.ts} +0 -0
  71. /package/src/packages/workos-auth-contracts/{index.ts → src/index.ts} +0 -0
  72. /package/src/packages/workos-auth-contracts/{management-rpc.ts → src/management-rpc.ts} +0 -0
  73. /package/src/packages/workos-auth-contracts/{organizations.ts → src/organizations.ts} +0 -0
  74. /package/src/packages/workos-auth-contracts/{parsing.ts → src/parsing.ts} +0 -0
  75. /package/src/packages/workos-auth-contracts/{session-status.ts → src/session-status.ts} +0 -0
  76. /package/src/packages/workos-auth-contracts/{tokens.ts → src/tokens.ts} +0 -0
  77. /package/src/packages/workos-auth-contracts/{user-api-keys.ts → src/user-api-keys.ts} +0 -0
  78. /package/src/packages/workos-auth-contracts/{webhooks.ts → src/webhooks.ts} +0 -0
@@ -5019,8 +5019,8 @@ var require_multipleOf = __commonJS({
5019
5019
  const { gen, data, schemaCode, it } = cxt;
5020
5020
  const prec = it.opts.multipleOfPrecision;
5021
5021
  const res = gen.let("res");
5022
- const invalid4 = prec ? (0, codegen_1._)`Math.abs(Math.round(${res}) - ${res}) > 1e-${prec}` : (0, codegen_1._)`${res} !== parseInt(${res})`;
5023
- cxt.fail$data((0, codegen_1._)`(${schemaCode} === 0 || (${res} = ${data}/${schemaCode}, ${invalid4}))`);
5022
+ const invalid5 = prec ? (0, codegen_1._)`Math.abs(Math.round(${res}) - ${res}) > 1e-${prec}` : (0, codegen_1._)`${res} !== parseInt(${res})`;
5023
+ cxt.fail$data((0, codegen_1._)`(${schemaCode} === 0 || (${res} = ${data}/${schemaCode}, ${invalid5}))`);
5024
5024
  }
5025
5025
  };
5026
5026
  exports.default = def;
@@ -14769,17 +14769,25 @@ var UserTokenFileError = class extends Error {
14769
14769
  code;
14770
14770
  keyId;
14771
14771
  constructor(code, keyId) {
14772
- super({
14773
- token_file_not_ignored: "Add the selected env file to .gitignore or choose a location outside the repository. No token was created or written.",
14774
- token_file_unsafe: "Choose a private .env file owned by you. Symlinks, shared files and files larger than 64 KiB are not accepted.",
14775
- token_file_has_token: "The selected file already contains OHMYHOST_TOKEN. Use that token or choose a new file; existing credentials were preserved.",
14776
- token_file_changed: "The environment file changed during creation. It was not overwritten. List tokens to inspect the issued key before creating another.",
14777
- token_value_unavailable: "This token was already issued and its full value cannot be retrieved. Use the previously saved file, or explicitly revoke the listed key before creating a replacement."
14778
- }[code] + (keyId ? ` Token ID: ${keyId}.` : ""));
14772
+ super(tokenFileAction(code, keyId));
14779
14773
  this.code = code;
14780
14774
  this.keyId = keyId;
14781
14775
  }
14782
14776
  };
14777
+ var tokenFileAction = (code, keyId) => {
14778
+ switch (code) {
14779
+ case "token_file_not_ignored":
14780
+ return "Add the selected env file to .gitignore or choose a location outside the repository. No token was created or written.";
14781
+ case "token_file_unsafe":
14782
+ return "Choose an env file whose name starts or ends with .env, for example .env.local: a regular file you own, not a symlink or hard link, at most 64 KiB.";
14783
+ case "token_file_has_token":
14784
+ return "The selected file already contains OHMYHOST_TOKEN. Use that token or choose a new file; existing credentials were preserved.";
14785
+ case "token_file_changed":
14786
+ return keyId === void 0 ? "The env file changed while it was being read; no key was issued. Run the same command again." : `The env file changed while the token was being saved, so its value may not be in the file; key ${keyId} was issued. Revoke it with 'ohmyhost token revoke --organization ULID --key ${keyId} --yes --json' (MCP token_revoke), then create a new one with a new idempotency key.`;
14787
+ case "token_value_unavailable":
14788
+ return `A key was already issued for this idempotency key, and its value is shown only once. Use the file it was saved to, or revoke key ${String(keyId)} (ohmyhost token revoke, MCP token_revoke) and create a replacement with a new idempotency key.`;
14789
+ }
14790
+ };
14783
14791
  async function prepareUserTokenFile(output) {
14784
14792
  const path = resolve(output);
14785
14793
  if (!basename(path).startsWith(".env") && !basename(path).endsWith(".env"))
@@ -14812,18 +14820,22 @@ OHMYHOST_TOKEN=${preview}
14812
14820
  const result = parseUserApiKeyCreation(value);
14813
14821
  if (result.value === null)
14814
14822
  throw new UserTokenFileError("token_value_unavailable", result.key.id);
14815
- const current = await lstat(path);
14816
- if (!current.isFile() || current.isSymbolicLink() || current.nlink !== 1 || current.ino !== identity.ino || current.dev !== identity.dev || await tokenFileText(handle) !== initial)
14817
- throw new UserTokenFileError("token_file_changed");
14818
- await handle.appendFile(`
14823
+ try {
14824
+ const current = await lstat(path);
14825
+ if (!current.isFile() || current.isSymbolicLink() || current.nlink !== 1 || current.ino !== identity.ino || current.dev !== identity.dev || await tokenFileText(handle) !== initial)
14826
+ throw new UserTokenFileError("token_file_changed");
14827
+ await handle.appendFile(`
14819
14828
  OHMYHOST_TOKEN=${result.value}
14820
14829
  `, "utf8");
14821
- await handle.sync();
14822
- if (parseEnv(await tokenFileText(handle))["OHMYHOST_TOKEN"] !== result.value)
14823
- throw new UserTokenFileError("token_file_changed");
14824
- const saved = await lstat(path);
14825
- if (!saved.isFile() || saved.isSymbolicLink() || saved.ino !== identity.ino || saved.dev !== identity.dev)
14830
+ await handle.sync();
14831
+ if (parseEnv(await tokenFileText(handle))["OHMYHOST_TOKEN"] !== result.value)
14832
+ throw new UserTokenFileError("token_file_changed");
14833
+ const saved = await lstat(path);
14834
+ if (!saved.isFile() || saved.isSymbolicLink() || saved.ino !== identity.ino || saved.dev !== identity.dev)
14835
+ throw new UserTokenFileError("token_file_changed");
14836
+ } catch {
14826
14837
  throw new UserTokenFileError("token_file_changed", result.key.id);
14838
+ }
14827
14839
  return { request_id: result.request_id, key: result.key, token_file: path };
14828
14840
  },
14829
14841
  close: () => handle.close()
@@ -15271,6 +15283,63 @@ var describeRequest = (request) => {
15271
15283
  };
15272
15284
  var describeProfile = (profile) => `profile ${profile.name} (user ${profile.userId}, ${profile.organizationId === null ? "no organization" : `organization ${profile.organizationName === null ? "" : `${profile.organizationName} `}${profile.organizationId}`})`;
15273
15285
 
15286
+ // packages/contracts/dist/secret-names.js
15287
+ var USER_SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]{0,127}$/u;
15288
+ var FIXED_PLATFORM_NAMES = /* @__PURE__ */ new Set([
15289
+ "BETTER_AUTH_SECRET",
15290
+ "BETTER_AUTH_URL",
15291
+ "DATABASE_URL",
15292
+ "HYPERDRIVE",
15293
+ "ASSETS",
15294
+ "FILES",
15295
+ "IMAGES",
15296
+ "STORAGE"
15297
+ ]);
15298
+ var INSTALLED_PLATFORM_KEYS = /* @__PURE__ */ new Set([
15299
+ "OHMYHOST_STORAGE_KEY",
15300
+ "OHMYHOST_MAIL_KEY",
15301
+ "BETTER_AUTH_SECRET"
15302
+ ]);
15303
+ function isUserSecretName(value, action = "set") {
15304
+ if (typeof value !== "string" || !USER_SECRET_NAME_PATTERN.test(value))
15305
+ return false;
15306
+ return action === "delete" ? !INSTALLED_PLATFORM_KEYS.has(value) : !value.startsWith("OHMYHOST_") && !FIXED_PLATFORM_NAMES.has(value);
15307
+ }
15308
+
15309
+ // packages/contracts/dist/project-data-changes.js
15310
+ var PROJECT_DATA_CHANGE_KINDS = [
15311
+ "isolate_prod_keeps_data",
15312
+ "isolate_dev_keeps_data",
15313
+ "share_prod_keeps_data",
15314
+ "reset_dev"
15315
+ ];
15316
+ function parseProjectDataChangePlan(value) {
15317
+ if (value === null || typeof value !== "object" || Array.isArray(value))
15318
+ invalid();
15319
+ const input = value;
15320
+ if (Object.keys(input).sort().join(",") !== "action,change,confirmation_token,created_at,data_mode_after,data_mode_before,destructive_effects,effects,expires_at,project_id,redeploy_environments,resource_etag,risks" || input["action"] !== "data_change" || typeof input["project_id"] !== "string" || !/^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u.test(input["project_id"]) || !isProjectDataChangeKind(input["change"]) || !["shared", "isolated"].includes(String(input["data_mode_before"])) || input["data_mode_after"] !== (input["change"] === "share_prod_keeps_data" ? "shared" : "isolated") || input["data_mode_before"] !== (input["change"].startsWith("isolate_") ? "shared" : "isolated") || !Array.isArray(input["redeploy_environments"]) || input["redeploy_environments"].length !== 1 || input["redeploy_environments"][0] !== (input["change"] === "isolate_dev_keeps_data" ? "prod" : "dev") || !planStrings(input["effects"]) || !planStrings(input["destructive_effects"]) || !planStrings(input["risks"]) || typeof input["resource_etag"] !== "string" || !/^"sha256-[a-f0-9]{64}"$/u.test(input["resource_etag"]) || typeof input["confirmation_token"] !== "string" || !/^[A-Za-z0-9_-]{43}$/u.test(input["confirmation_token"]) || !instant(input["created_at"]) || !instant(input["expires_at"]) || Date.parse(input["expires_at"]) - Date.parse(input["created_at"]) !== 6e5)
15321
+ invalid();
15322
+ return Object.freeze({
15323
+ ...input,
15324
+ effects: Object.freeze([...input["effects"]]),
15325
+ destructive_effects: Object.freeze([...input["destructive_effects"]]),
15326
+ risks: Object.freeze([...input["risks"]]),
15327
+ redeploy_environments: Object.freeze([...input["redeploy_environments"]])
15328
+ });
15329
+ }
15330
+ function isProjectDataChangeKind(value) {
15331
+ return typeof value === "string" && PROJECT_DATA_CHANGE_KINDS.some((kind) => kind === value);
15332
+ }
15333
+ function planStrings(value) {
15334
+ return Array.isArray(value) && value.length <= 32 && value.every((text) => typeof text === "string" && text.length > 0 && text.length <= 2e3 && !/[\p{Cc}\p{Cf}]/u.test(text));
15335
+ }
15336
+ function instant(value) {
15337
+ return typeof value === "string" && Number.isFinite(Date.parse(value)) && new Date(value).toISOString() === value;
15338
+ }
15339
+ function invalid() {
15340
+ throw new TypeError("Project data change plan is invalid");
15341
+ }
15342
+
15274
15343
  // packages/workos-auth-contracts/dist/parsing.js
15275
15344
  var WorkosContractError = class extends Error {
15276
15345
  code;
@@ -15390,6 +15459,10 @@ var recognizedCommandLabel = (argv) => {
15390
15459
  return "feedback status";
15391
15460
  if (value === "project" && values[0] === "create")
15392
15461
  return "project create";
15462
+ if (value === "project" && values[0] === "data" && values[1] === "plan")
15463
+ return "project data plan";
15464
+ if (value === "project" && values[0] === "data" && values[1] === "change")
15465
+ return "project data change";
15393
15466
  if (value === "organization" && values[0] === "create")
15394
15467
  return "organization create";
15395
15468
  if (value === "organization" && values[0] === "list")
@@ -15906,6 +15979,27 @@ var parseProductCliCommand = (argv) => {
15906
15979
  }
15907
15980
  if (first === "project") {
15908
15981
  const subcommand = values.shift();
15982
+ if (subcommand === "data") {
15983
+ const action = values.shift();
15984
+ if (follow || stdin || action !== "plan" && action !== "change")
15985
+ throw new InvalidCommandError();
15986
+ if (action === "plan" && (yes || wait) || action === "change" && !yes)
15987
+ throw new InvalidCommandError();
15988
+ const options2 = parseOptions(values, action === "plan" ? ["--project", "--change"] : ["--project", "--change", "--if-match", "--confirmation-token", "--idempotency-key"]);
15989
+ const change = options2["--change"];
15990
+ if (!isProjectDataChangeKind(change))
15991
+ throw new InvalidCommandError();
15992
+ const common = { projectId: options2["--project"], change, credentialStore };
15993
+ return action === "plan" ? { kind: "project-data-plan", ...common } : {
15994
+ kind: "project-data-change",
15995
+ ...common,
15996
+ ifMatch: options2["--if-match"],
15997
+ confirmationToken: options2["--confirmation-token"],
15998
+ idempotencyKey: options2["--idempotency-key"],
15999
+ yes: true,
16000
+ wait
16001
+ };
16002
+ }
15909
16003
  if (subcommand === "handle" && values[0] === "set") {
15910
16004
  values.shift();
15911
16005
  if (yes || wait || follow || stdin)
@@ -16565,7 +16659,7 @@ var parseProductCliCommand = (argv) => {
16565
16659
  throw new InvalidCommandError();
16566
16660
  values.shift();
16567
16661
  const name = values.shift();
16568
- if (!isUserSecretName(name))
16662
+ if (!isUserSecretName(name, "delete"))
16569
16663
  throw new InvalidCommandError();
16570
16664
  const options = parseOptions(values, ["--project", "--environment", "--idempotency-key"]);
16571
16665
  return {
@@ -16763,6 +16857,8 @@ var parseOptions = (values, allowed) => {
16763
16857
  if (option === void 0 || !allowed.includes(option) || !isValue(value)) {
16764
16858
  throw new InvalidCommandError();
16765
16859
  }
16860
+ if (option === "--idempotency-key" && value.length > 128)
16861
+ throw new InvalidCommandError();
16766
16862
  if (option in result)
16767
16863
  throw new InvalidCommandError();
16768
16864
  result[option] = value;
@@ -16786,6 +16882,8 @@ var parseOptionalOptions = (values, allowed) => {
16786
16882
  if (option === void 0 || !allowed.includes(option) || !isValue(value)) {
16787
16883
  throw new InvalidCommandError();
16788
16884
  }
16885
+ if (option === "--idempotency-key" && value.length > 128)
16886
+ throw new InvalidCommandError();
16789
16887
  if (option in result)
16790
16888
  throw new InvalidCommandError();
16791
16889
  result[option] = value;
@@ -16818,7 +16916,6 @@ var validGithubBranchName = (value) => !(value.length < 1 || value.length > 1024
16818
16916
  return code <= 32 || code === 127 || "\\~^:?*[]".includes(character);
16819
16917
  }) || value.split("/").some((component) => component.startsWith(".") || component.endsWith(".lock")));
16820
16918
  var validPaidHostname = (value) => /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/u.test(value) && !value.endsWith(".poc.aixyte.com") && value !== "omh.st" && !value.endsWith(".omh.st") && value !== "ohmyho.st" && !value.endsWith(".ohmyho.st");
16821
- var isUserSecretName = (value) => isValue(value) && /^[A-Z][A-Z0-9_]{0,127}$/u.test(value) && value !== "DATABASE_URL" && value !== "HYPERDRIVE" && value !== "BETTER_AUTH_SECRET" && value !== "BETTER_AUTH_URL" && value !== "OHMYHOST_MAIL_KEY" && value !== "OHMYHOST_MAIL_GATEWAY_URL";
16822
16919
 
16823
16920
  // packages/contracts/dist/feedback.js
16824
16921
  var FEEDBACK_HISTORY_PAGE_SIZE = 25;
@@ -16892,6 +16989,49 @@ function hasControlCharacter(value, multiline) {
16892
16989
  });
16893
16990
  }
16894
16991
 
16992
+ // packages/contracts/dist/framework-build-script.mjs
16993
+ var PINNED_BUN_VERSION = "1.2.22";
16994
+ var TYPESCRIPT_BUILD_STAGE = /^tsc(?:[ \t]+(?:--noEmit|-b|--build))?$/u;
16995
+ function isAdmittedFrameworkBuildScript(framework, script) {
16996
+ if (typeof script !== "string")
16997
+ return false;
16998
+ if (framework === "nextjs")
16999
+ return script === "next build" || script === "next build --webpack";
17000
+ if (framework !== "vite" && framework !== "tanstack-start")
17001
+ return false;
17002
+ const stages = script.trim().split(/[ \t]*&&[ \t]*/u);
17003
+ if (stages.length === 1)
17004
+ return stages[0] === "vite build";
17005
+ if (stages.length !== 2)
17006
+ return false;
17007
+ return stages[0] === "vite build" && TYPESCRIPT_BUILD_STAGE.test(stages[1] ?? "") || TYPESCRIPT_BUILD_STAGE.test(stages[0] ?? "") && stages[1] === "vite build";
17008
+ }
17009
+
17010
+ // packages/contracts/dist/customer-auth-admission.js
17011
+ function managedCustomerAuthMailMessage(path) {
17012
+ return `"${path}" selects the managed Better Auth bridge, which sends verification and password-reset email through ohmyhost Mail. Set mail.enabled: true and set up a verified mail sender, or set auth.provider: none and keep the application-owned Better Auth configuration and sender. Commit and push the change, then plan the new commit.`;
17013
+ }
17014
+ function managedCustomerAuthDatabaseMessage(path) {
17015
+ return `"${path}" selects the managed Better Auth bridge, which requires the managed PostgreSQL database. Set database.enabled: true with an admitted migrations path, or set auth.provider: none and keep the application-owned authentication configuration. Commit and push the change, then plan the new commit.`;
17016
+ }
17017
+ function managedCustomerAuthAdmissionIssue(capabilities) {
17018
+ if (capabilities?.auth?.provider !== "better-auth") {
17019
+ return null;
17020
+ }
17021
+ if (capabilities.database?.enabled !== true) {
17022
+ return Object.freeze({
17023
+ code: "managed_auth_database_required",
17024
+ message: managedCustomerAuthDatabaseMessage("ohmyhost.yaml")
17025
+ });
17026
+ }
17027
+ if (capabilities.mail?.enabled === true)
17028
+ return null;
17029
+ return Object.freeze({
17030
+ code: "managed_auth_mail_required",
17031
+ message: managedCustomerAuthMailMessage("ohmyhost.yaml")
17032
+ });
17033
+ }
17034
+
16895
17035
  // packages/contracts/dist/framework-admission.js
16896
17036
  var FrameworkAdmissionError = class extends Error {
16897
17037
  reason;
@@ -16946,6 +17086,9 @@ function resolvePackageManager(input) {
16946
17086
  if (!contract.lockfiles.includes(lockfile)) {
16947
17087
  throw new FrameworkAdmissionError("package_manager_lockfile_mismatch");
16948
17088
  }
17089
+ if (manager === "bun" && version2 !== PINNED_BUN_VERSION) {
17090
+ throw new FrameworkAdmissionError("package_manager_version_unsupported");
17091
+ }
16949
17092
  return Object.freeze({
16950
17093
  manager,
16951
17094
  version: version2,
@@ -17006,26 +17149,51 @@ function compareVersions(left, right) {
17006
17149
  function versionAdmission(classification, reason) {
17007
17150
  return Object.freeze({ classification, reason });
17008
17151
  }
17152
+ var PUSH_AND_PLAN = "Commit and push the change, then plan the new commit.";
17009
17153
  var FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
17154
+ managed_auth_database_required: Object.freeze({
17155
+ render: managedCustomerAuthDatabaseMessage,
17156
+ pattern: /^"([^"]+)" selects the managed Better Auth bridge, which requires the managed PostgreSQL database\. Set database\.enabled: true with an admitted migrations path, or set auth\.provider: none and keep the application-owned authentication configuration\. Commit and push the change, then plan the new commit\.$/u
17157
+ }),
17158
+ managed_auth_mail_required: Object.freeze({
17159
+ render: managedCustomerAuthMailMessage,
17160
+ pattern: /^"([^"]+)" selects the managed Better Auth bridge, which sends verification and password-reset email through ohmyhost Mail\. Set mail\.enabled: true and set up a verified mail sender, or set auth\.provider: none and keep the application-owned Better Auth configuration and sender\. Commit and push the change, then plan the new commit\.$/u
17161
+ }),
17010
17162
  database_binding_private: Object.freeze({
17011
- render: (path) => `"${path}" reads a managed database connection string. HYPERDRIVE is retired; DATABASE_URL and postgres:// URLs never reach a customer Worker. Call the database with createPrivateDatabaseClient from "@ohmyhost/customer-runtime/database". Commit the change, then plan again.`,
17012
- pattern: /^"([^"]+)" reads a managed database connection string\. HYPERDRIVE is retired; DATABASE_URL and postgres:\/\/ URLs never reach a customer Worker\. Call the database with createPrivateDatabaseClient from "@ohmyhost\/customer-runtime\/database"\. Commit the change, then plan again\.$/u
17163
+ render: (path) => `"${path}" reads a database connection string (DATABASE_URL, HYPERDRIVE, connectionString or a postgres:// URL); none reaches a customer Worker. Call the database with createPrivateDatabaseClient from "@ohmyhost/customer-runtime/database". ${PUSH_AND_PLAN}`,
17164
+ pattern: /^"([^"]+)" reads a database connection string \(DATABASE_URL, HYPERDRIVE, connectionString or a postgres:\/\/ URL\); none reaches a customer Worker\. Call the database with createPrivateDatabaseClient from "@ohmyhost\/customer-runtime\/database"\. Commit and push the change, then plan the new commit\.$/u
17013
17165
  }),
17014
17166
  database_driver_unsupported: Object.freeze({
17015
- render: (path) => `"${path}" imports a PostgreSQL socket driver. Outbound connect() is disabled for customer Workers, so it can never open a connection; call the database with createPrivateDatabaseClient from "@ohmyhost/customer-runtime/database". Commit the change, then plan again.`,
17016
- pattern: /^"([^"]+)" imports a PostgreSQL socket driver\. Outbound connect\(\) is disabled for customer Workers, so it can never open a connection; call the database with createPrivateDatabaseClient from "@ohmyhost\/customer-runtime\/database"\. Commit the change, then plan again\.$/u
17167
+ render: (path) => `"${path}" imports a database socket driver (pg, postgres, pg-native or mysql2); customer Workers cannot open sockets. Call the database with createPrivateDatabaseClient from "@ohmyhost/customer-runtime/database". ${PUSH_AND_PLAN}`,
17168
+ pattern: /^"([^"]+)" imports a database socket driver \(pg, postgres, pg-native or mysql2\); customer Workers cannot open sockets\. Call the database with createPrivateDatabaseClient from "@ohmyhost\/customer-runtime\/database"\. Commit and push the change, then plan the new commit\.$/u
17017
17169
  }),
17018
17170
  source_syntax_invalid: Object.freeze({
17019
- render: (path) => `Invalid JavaScript or TypeScript in "${path}". Fix the file, commit the change, then plan again.`,
17020
- pattern: /^Invalid JavaScript or TypeScript in "([^"]+)"\. Fix the file, commit the change, then plan again\.$/u
17171
+ render: (path) => `Invalid JavaScript or TypeScript in "${path}". Fix the file, commit and push the change, then plan the new commit.`,
17172
+ pattern: /^Invalid JavaScript or TypeScript in "([^"]+)"\. Fix the file, commit and push the change, then plan the new commit\.$/u
17021
17173
  }),
17022
17174
  worker_module_default_export_required: Object.freeze({
17023
- render: (path) => `Worker module "${path}" has no default export. Export default { fetch, scheduled } with the handlers ohmyho.st invokes; named exports are never called. Commit the change, then plan again.`,
17024
- pattern: /^Worker module "([^"]+)" has no default export\. Export default \{ fetch, scheduled \} with the handlers ohmyho\.st invokes; named exports are never called\. Commit the change, then plan again\.$/u
17175
+ render: (path) => `Worker module "${path}" has no default export. Export default { fetch, scheduled } with the handlers ohmyho.st invokes; named exports are never called. ${PUSH_AND_PLAN}`,
17176
+ pattern: /^Worker module "([^"]+)" has no default export\. Export default \{ fetch, scheduled \} with the handlers ohmyho\.st invokes; named exports are never called\. Commit and push the change, then plan the new commit\.$/u
17025
17177
  }),
17026
17178
  scheduled_handler_required: Object.freeze({
17027
- render: (path) => `functions.crons is declared but "${path}" exports no scheduled handler. Add scheduled(controller, env, ctx) to its default export, commit the change, then plan again.`,
17028
- pattern: /^functions\.crons is declared but "([^"]+)" exports no scheduled handler\. Add scheduled\(controller, env, ctx\) to its default export, commit the change, then plan again\.$/u
17179
+ render: (path) => `functions.crons is declared but "${path}" exports no scheduled handler. Add scheduled(controller, env, ctx) to its default export, commit and push the change, then plan the new commit.`,
17180
+ pattern: /^functions\.crons is declared but "([^"]+)" exports no scheduled handler\. Add scheduled\(controller, env, ctx\) to its default export, commit and push the change, then plan the new commit\.$/u
17181
+ }),
17182
+ framework_base_path_unsupported: Object.freeze({
17183
+ render: (path) => `"${path}" sets a Vite base. ohmyho.st serves the app at the root origin: remove base or set base: "/". ${PUSH_AND_PLAN}`,
17184
+ pattern: /^"([^"]+)" sets a Vite base\. ohmyho\.st serves the app at the root origin: remove base or set base: "\/"\. Commit and push the change, then plan the new commit\.$/u
17185
+ }),
17186
+ tanstack_prerender_required: Object.freeze({
17187
+ render: (path) => `"${path}" does not call tanstackStart({ prerender: { enabled: true } }), which the static TanStack Start runtime needs; enable prerender, or set runtime.mode: edge with the Cloudflare Vite plugin. ${PUSH_AND_PLAN}`,
17188
+ pattern: /^"([^"]+)" does not call tanstackStart\(\{ prerender: \{ enabled: true \} \}\), which the static TanStack Start runtime needs; enable prerender, or set runtime\.mode: edge with the Cloudflare Vite plugin\. Commit and push the change, then plan the new commit\.$/u
17189
+ }),
17190
+ tanstack_vite_plugins_required: Object.freeze({
17191
+ render: (path) => `"${path}" does not follow the TanStack Start plugin contract: import tanstackStart from "@tanstack/react-start/plugin/vite"; on runtime edge also import cloudflare from "@cloudflare/vite-plugin" and call cloudflare({ viteEnvironment: { name: "ssr" } }) before tanstackStart(); on runtime static do not import the Cloudflare plugin. ${PUSH_AND_PLAN}`,
17192
+ pattern: /^"([^"]+)" does not follow the TanStack Start plugin contract: import tanstackStart from "@tanstack\/react-start\/plugin\/vite"; on runtime edge also import cloudflare from "@cloudflare\/vite-plugin" and call cloudflare\(\{ viteEnvironment: \{ name: "ssr" \} \}\) before tanstackStart\(\); on runtime static do not import the Cloudflare plugin\. Commit and push the change, then plan the new commit\.$/u
17193
+ }),
17194
+ next_cache_components_unsupported: Object.freeze({
17195
+ render: (path) => `"${path}" sets cacheComponents: true, which ohmyho.st does not support; remove it or set it to false. ${PUSH_AND_PLAN}`,
17196
+ pattern: /^"([^"]+)" sets cacheComponents: true, which ohmyho\.st does not support; remove it or set it to false\. Commit and push the change, then plan the new commit\.$/u
17029
17197
  })
17030
17198
  });
17031
17199
  function isFrameworkConversionDiagnosticCode(value) {
@@ -17037,7 +17205,7 @@ function parseFrameworkConversionDiagnostic(value) {
17037
17205
  const record5 = value;
17038
17206
  const path = record5["path"];
17039
17207
  const code = record5["code"];
17040
- if (Object.keys(record5).length !== 2 || !isFrameworkConversionDiagnosticCode(code) || typeof path !== "string" || path.length > 240 || !/^[A-Za-z0-9_@()[\]./-]+\.(?:[cm]?[jt]sx?)$/u.test(path) || path.split("/").some((segment) => segment === "" || segment === "." || segment === ".."))
17208
+ if (Object.keys(record5).length !== 2 || !isFrameworkConversionDiagnosticCode(code) || typeof path !== "string" || path.length > 240 || (code === "managed_auth_mail_required" || code === "managed_auth_database_required" ? path !== "ohmyhost.yaml" : !/^[A-Za-z0-9_@()[\]./-]+\.(?:[cm]?[jt]sx?)$/u.test(path)) || path.split("/").some((segment) => segment === "" || segment === "." || segment === ".."))
17041
17209
  return null;
17042
17210
  return Object.freeze({ code, path });
17043
17211
  }
@@ -17059,36 +17227,36 @@ function parseFrameworkConversionDetail(value) {
17059
17227
  // packages/contracts/dist/operation-deployment-result.js
17060
17228
  function exactRecord(value, keys) {
17061
17229
  if (typeof value !== "object" || value === null || Array.isArray(value))
17062
- invalid();
17230
+ invalid2();
17063
17231
  const actual = Object.keys(value).sort();
17064
17232
  const expected = [...keys].sort();
17065
17233
  if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index])) {
17066
- invalid();
17234
+ invalid2();
17067
17235
  }
17068
17236
  return value;
17069
17237
  }
17070
17238
  function ulid2(value) {
17071
17239
  if (typeof value !== "string" || !/^[0-9A-HJKMNP-TV-Z]{26}$/u.test(value))
17072
- invalid();
17240
+ invalid2();
17073
17241
  return value;
17074
17242
  }
17075
- function invalid() {
17243
+ function invalid2() {
17076
17244
  throw new TypeError("Operation deployment result is invalid");
17077
17245
  }
17078
17246
  var deploymentProgressActions = {
17079
17247
  queued: "The deployment is queued. Poll this same operation; do not submit another deployment.",
17080
17248
  building: "The build has not yet completed. Poll this same operation and read deployment logs if it fails.",
17081
17249
  publishing: "The build has completed. Runtime preparation or publication is still running; poll this same operation.",
17082
- waiting_for_mail: "Read 'ohmyhost mail domain status --project PROJECT --json' now and follow its verification issue and DNS records. Keep this operation; do not rebuild or repeat correct DNS changes.",
17083
- mail_status_unavailable: "Mail readiness could not be observed. Read 'ohmyhost mail domain status --project PROJECT --json' and report a persistent error; keep this operation."
17250
+ waiting_for_mail: "Read 'ohmyhost mail status --project PROJECT --environment PROD_ENVIRONMENT_ULID --json' (MCP mail_status) now, with the Prod environment ID from 'ohmyhost project status', and follow its verification issue and DNS records. Keep this operation; do not rebuild or repeat correct DNS changes.",
17251
+ mail_status_unavailable: "Mail readiness could not be observed. Read 'ohmyhost mail status --project PROJECT --environment PROD_ENVIRONMENT_ULID --json' (MCP mail_status) with the Prod environment ID from 'ohmyhost project status', and report a persistent error; keep this operation."
17084
17252
  };
17085
17253
  function createOperationDeploymentProgress(input) {
17086
17254
  const project = ulid2(input.project_id);
17087
17255
  ulid2(input.deployment_id);
17088
17256
  if (!Object.hasOwn(deploymentProgressActions, input.phase) || !Number.isFinite(Date.parse(input.observed_at)) || new Date(input.observed_at).toISOString() !== input.observed_at)
17089
- invalid();
17257
+ invalid2();
17090
17258
  if (input.build_completed_at !== void 0 && (!Number.isFinite(Date.parse(input.build_completed_at)) || new Date(input.build_completed_at).toISOString() !== input.build_completed_at))
17091
- invalid();
17259
+ invalid2();
17092
17260
  return Object.freeze({
17093
17261
  ...input.build_completed_at === void 0 ? {} : { build_completed_at: input.build_completed_at },
17094
17262
  phase: input.phase,
@@ -17119,7 +17287,7 @@ function parseOperationDeploymentProgress(value) {
17119
17287
  observed_at: row["observed_at"]
17120
17288
  });
17121
17289
  if (row["next_poll_after_seconds"] !== 60 || row["suggested_action"] !== parsed.suggested_action)
17122
- invalid();
17290
+ invalid2();
17123
17291
  return parsed;
17124
17292
  }
17125
17293
 
@@ -17223,12 +17391,12 @@ function assertOrganizationAccount(value) {
17223
17391
  // packages/contracts/dist/operation-failure.js
17224
17392
  var failures = {
17225
17393
  operation_abandoned: [
17226
- "The operation stopped renewing its execution lease and was ended.",
17227
- "Inspect project status and, when this operation names a deployment_id, its diagnostics with 'ohmyhost deployment logs --project PROJECT_ULID --deployment DEPLOYMENT_ULID --follow --json' or MCP deployment_logs before creating a fresh plan. Keep this operation ID; the expired execution cannot resume."
17394
+ "ohmyho.st stopped this operation before it finished and ended it.",
17395
+ "Repeating this operation cannot resume it. Read project status for what is live; if this operation names a deployment_id, read 'ohmyhost deployment logs --project PROJECT_ULID --deployment DEPLOYMENT_ULID --follow --json' or MCP deployment_logs. Then submit the request once more with a new idempotency key: a deployment, promotion, rollback or deletion from a fresh plan, a rename with the ETag from project status. If it ends the same way, report both operation IDs through feedback."
17228
17396
  ],
17229
17397
  database_write_rejected: [
17230
17398
  "The database rejected this write; it was not committed.",
17231
- "Inspect the schema and parameter types with database query, then correct the statement and use a new key. Do not reset the database."
17399
+ "The statement failed on PostgreSQL, ran or waited for a lock longer than 5 seconds, affected more than 1,000 rows, or was not one INSERT, UPDATE or DELETE. Check table, column and parameter types with database query, split a large change into batches of at most 1,000 rows, then submit with a new key. Nothing was committed; do not reset the database."
17232
17400
  ],
17233
17401
  database_write_unavailable: [
17234
17402
  "The database write could not start.",
@@ -17280,7 +17448,7 @@ var failures = {
17280
17448
  ],
17281
17449
  operation_failed: [
17282
17450
  "The operation failed.",
17283
- "Inspect the operation events and deployment diagnostics. Include the operation ID when reporting the issue."
17451
+ "Read project status and, if this operation names a deployment_id, its diagnostics with 'ohmyhost deployment logs --project PROJECT_ULID --deployment DEPLOYMENT_ULID --follow --json'. Without one (a source link or project creation), check project status or project list, then repeat that command once with a new idempotency key. If it fails again, report the operation ID through feedback."
17284
17452
  ],
17285
17453
  build_not_started: [
17286
17454
  "The deployment ended without starting a build.",
@@ -17288,7 +17456,7 @@ var failures = {
17288
17456
  ],
17289
17457
  build_failed: [
17290
17458
  "The build did not produce a verified deployable artifact.",
17291
- "Read the BUILD_FAILED diagnostic of this operation's deployment_id with 'ohmyhost deployment logs --project PROJECT_ULID --deployment DEPLOYMENT_ULID --follow --json' or MCP deployment_logs; its excerpt is the tail of the install and build output. Correct that failure and verify the commands for the admitted commit before planning a new deployment."
17459
+ "Read the BUILD_FAILED item of this operation's deployment_id with 'ohmyhost deployment logs --project PROJECT_ULID --deployment DEPLOYMENT_ULID --follow --json' or MCP deployment_logs. Its excerpt is the tail of your install and build output: fix that error, push and plan the new commit. Without an excerpt the build printed nothing, passed the 8-minute limit or failed inside ohmyho.st: plan the same commit once more, and report the operation ID through feedback if it fails again."
17292
17460
  ],
17293
17461
  insufficient_organization_credits: [
17294
17462
  "The organization does not have sufficient available credits for this operation.",
@@ -17296,27 +17464,39 @@ var failures = {
17296
17464
  ],
17297
17465
  paid_plan_required: [
17298
17466
  "Transactional mail requires an active Paid plan for this organization.",
17299
- "Activate or renew Paid for the organization, then create a new deployment plan. Replaying this terminal operation cannot enable mail. Do not change application code or reset its database."
17467
+ "Ask the organization Owner to activate Paid through billing checkout; top-ups and signup credits do not activate it. Then plan the same deployment or promotion again. If the app should send no mail, instead set mail.enabled to false in ohmyhost.yaml, commit and push, and plan the new commit. Replaying this operation cannot enable mail. Do not reset the database."
17300
17468
  ],
17301
17469
  database_migration_failed: [
17302
- "The database migrations could not be applied.",
17303
- "Inspect the migration diagnostics and verify the canonical SQL migrations on PostgreSQL. Preserve production data; do not reset the database to retry a deployment."
17470
+ "The database migrations were not applied; none of this deployment's new migration files took effect.",
17471
+ "Read the DATABASE_MIGRATION_FAILED item in 'ohmyhost deployment logs --project PROJECT_ULID --deployment DEPLOYMENT_ULID --follow --json' and fix what it names; run 'ohmyhost init --dry-run --json' for migration_sql_not_admitted. Keep applied files unchanged, give new files later timestamps, and add to existing tables only nullable columns without default and non-unique indexes. Push and plan the new commit; never reset the database."
17304
17472
  ],
17305
17473
  runtime_candidate_failed: [
17306
17474
  "The deployed candidate failed its runtime checks.",
17307
- "Read the HEALTH_CHECK_FAILED diagnostic of this operation's deployment_id with 'ohmyhost deployment logs --project PROJECT_ULID --deployment DEPLOYMENT_ULID --follow --json' or MCP deployment_logs. Its route must answer 2xx to a cookieless GET without a redirect; status_code and, for owners, excerpt show what it answered. Fix that, often a missing secret or a redirect, then plan a new deployment."
17475
+ "Read the HEALTH_CHECK_FAILED item with 'ohmyhost deployment logs --project PROJECT_ULID --deployment DEPLOYMENT_ULID --follow --json' or MCP deployment_logs. ohmyhost.yaml runtime.healthcheck (default /) must answer 2xx to a cookieless GET without redirect; status_code and, for owners, excerpt show the answer. After a promotion or rollback, fix that environment, often a missing secret, and plan it again; else fix the app, push and plan the new commit. No such item: plan once more, then report."
17308
17476
  ],
17309
17477
  runtime_candidate_rejected: [
17310
- "The runtime rejected the built Worker script.",
17311
- "Check that src/ohmyhost/worker.ts exports its handlers as the default export and imports no framework-only modules, then plan the new commit. Replaying this terminal operation cannot stage the same script."
17478
+ "The runtime refused to load the built Worker.",
17479
+ "The built Worker could not be loaded: top-level code throws or calls fetch, timers or random values outside a handler, an import cannot be resolved, a Worker module such as src/ohmyhost/companion.ts has no default export, or the bundle is too large. Fix that, push and plan the new commit; replaying this operation cannot pass. If none of this applies, report the operation ID through feedback."
17312
17480
  ],
17313
17481
  storage_jurisdiction_conflict: [
17314
17482
  "storage.jurisdiction must equal the project's hosting region.",
17315
- "Set storage.jurisdiction in ohmyhost.yaml to the project's hosting region shown by project status and plan again. A project cannot move its files between regions; do not delete or recreate the project to change it."
17483
+ "Set storage.jurisdiction in ohmyhost.yaml to the project's region (us or eu) shown by 'ohmyhost project status', commit and push, then plan the new commit (for a promotion, deploy it to Dev and promote again); the unchanged commit fails again. A project cannot move its files between regions; do not delete or recreate the project to change it."
17316
17484
  ],
17317
17485
  provider_state_absent: [
17318
17486
  "Reconciliation confirmed that the required deployment resources are absent.",
17319
17487
  "Create a fresh deployment plan. The original operation is terminal; do not repeatedly reconcile it."
17488
+ ],
17489
+ auto_deploy_plan_rejected: [
17490
+ "The GitHub push could not be planned for deployment.",
17491
+ "Read project status and the operation's error or deployment diagnostics. Correct the source or configuration named there, push the new commit and plan it before deploying. Replaying this failed operation cannot start a build."
17492
+ ],
17493
+ promotion_source_stale: [
17494
+ "The Dev deployment changed before promotion could start.",
17495
+ "Read the current Dev deployment with 'ohmyhost project status --project ULID --json', then run 'ohmyhost deployment promote plan' for that succeeded deployment and confirm the new plan. Replaying this terminal promotion cannot select another source."
17496
+ ],
17497
+ promotion_target_stale: [
17498
+ "The Prod deployment changed before promotion could start.",
17499
+ "Read current Prod status, run 'ohmyhost deployment promote plan' again and confirm it with the new resource_etag and confirmation_token. Replaying this terminal operation cannot replace its stale target."
17320
17500
  ]
17321
17501
  };
17322
17502
  function publicOperationReconciliation(observation) {
@@ -17336,280 +17516,436 @@ function publicOperationFailure(internalCode) {
17336
17516
  return Object.freeze({ code, message, retryable: false, suggested_action });
17337
17517
  }
17338
17518
 
17339
- // apps/product-cli/dist/runtime-contract.js
17519
+ // packages/contracts/dist/problem-policy.js
17340
17520
  var PROBLEM_POLICY = {
17341
17521
  cloudflare_zone_not_bound: {
17342
17522
  status: 409,
17343
17523
  retryable: false,
17344
- action: "Configure the project domain first with 'ohmyhost domain paid plan' then 'ohmyhost domain paid apply', or configure its sender domain with 'ohmyhost mail setup'. Then request Cloudflare authorization for that domain's zone."
17524
+ action: "Configure the project domain first with 'ohmyhost domain paid plan' then 'ohmyhost domain paid apply', or configure its sender domain with 'ohmyhost mail setup'. Then request Cloudflare authorization for that domain's zone.",
17525
+ title: "Cloudflare zone is not bound to this project"
17345
17526
  },
17346
17527
  github_connection_required: {
17347
17528
  status: 409,
17348
17529
  retryable: false,
17349
- action: "This workspace has no GitHub connection yet. Run 'ohmyhost github connect --organization ULID --idempotency-key KEY --json' for it, complete its browser link, then repeat the original source link. If the project belongs to another account, use that account's login instead; error.acting_as names the login this ran as."
17530
+ action: "This workspace has no GitHub connection yet. Run 'ohmyhost github connect --organization ULID --idempotency-key KEY --json' for it, complete its browser link, then repeat the original source link. If the project belongs to another account, use that account's login instead; error.acting_as in the CLI and MCP names the login this ran as.",
17531
+ title: "GitHub connection required"
17350
17532
  },
17351
17533
  github_connection_revoked: {
17352
17534
  status: 409,
17353
17535
  retryable: false,
17354
- action: "Run 'ohmyhost github connect --organization ULID --idempotency-key KEY --json' to restore this workspace's GitHub connection, then repeat the original source link."
17536
+ action: "The workspace's GitHub App installation was removed or can no longer be used. Run 'ohmyhost github connect --organization ULID --idempotency-key NEW_KEY --json' with a new key (an earlier key only replays the old connection), send the customer its authorization_url, repeat that command with the same new key until status is connected, then repeat the original source link.",
17537
+ title: "GitHub connection revoked",
17538
+ restAction: "The workspace's GitHub App installation was removed or can no longer be used. Run 'ohmyhost github connect --organization ULID --idempotency-key NEW_KEY --json' with a new key (an earlier key only replays the old connection), send the customer its authorization_url, repeat that command with the same new key until status is connected, then repeat the original source link."
17355
17539
  },
17356
17540
  repository_not_installed: {
17357
17541
  status: 403,
17358
17542
  retryable: false,
17359
- action: "Read 'ohmyhost github status --organization ULID --json', open connection.settings_url and add this repository to the installation. Then repeat the original source link and key."
17543
+ action: "Read 'ohmyhost github status --organization ULID --json', open connection.settings_url and add this repository to the installation. Then repeat the original source link and key.",
17544
+ title: "Repository access required"
17360
17545
  },
17361
17546
  database_write_pending: {
17362
17547
  status: 409,
17363
17548
  retryable: false,
17364
- action: "Poll the original database-write operation before transfer or deletion."
17549
+ action: "A database write of this project is still running, so the project cannot be deleted yet. Read latest_operation from 'ohmyhost project status --project ULID --json' until it is no longer running; writes are short. Then repeat the deletion, planning it again if its confirmation expired.",
17550
+ title: "Database write in progress",
17551
+ restAction: "A database write of this project is still running, so the project cannot be deleted yet. Read latest_operation from 'ohmyhost project status --project ULID --json' until it is no longer running; writes are short. Then repeat the deletion, planning it again if its confirmation expired."
17365
17552
  },
17366
17553
  cloudflare_authorization_closed: {
17367
17554
  status: 409,
17368
17555
  retryable: false,
17369
- action: "Read Cloudflare DNS status. Reuse a valid grant for the requested zone; otherwise request fresh authorization with a new idempotency key. Do not replay the callback."
17556
+ action: "Read Cloudflare DNS status. Reuse a valid grant for the requested zone; otherwise request fresh authorization with a new idempotency key. Do not replay the callback.",
17557
+ title: "Cloudflare authorization closed"
17370
17558
  },
17371
17559
  billing_recharge_conflict: {
17372
17560
  status: 409,
17373
17561
  retryable: false,
17374
- action: "Read billing recharge get and the current revision. Preserve the original key after uncertainty; do not start a checkout or automatically re-enable charges."
17562
+ action: "The auto-recharge settings changed since you read them, or this key was used with other settings. Run 'ohmyhost billing recharge get --organization ULID --json'. If it already shows the requested change, stop. Otherwise, only if the Owner still wants it, send the change again with the returned revision and a new idempotency key. Never re-enable charges without the Owner's approval.",
17563
+ title: "Auto-recharge settings conflict",
17564
+ restAction: "The auto-recharge settings changed since you read them, or this key was used with other settings. Run 'ohmyhost billing recharge get --organization ULID --json'. If it already shows the requested change, stop. Otherwise, only if the Owner still wants it, send the change again with the returned revision and a new idempotency key. Never re-enable charges without the Owner's approval."
17375
17565
  },
17376
17566
  billing_purchase_conflict: {
17377
17567
  status: 409,
17378
17568
  retryable: false,
17379
- action: "Read the original checkout and use billing portal to manage the existing subscription. Do not create another purchase after uncertainty."
17569
+ action: "This workspace already has Paid or an open Paid checkout, this key belongs to a checkout with a different offer or pack count, or, for 'billing portal', the workspace has never started a checkout. Read the original checkout with 'ohmyhost billing status --organization ULID --checkout CHECKOUT_ULID --json'; manage an existing Paid subscription with 'ohmyhost billing portal'. Never start a second purchase after an uncertain one.",
17570
+ title: "Billing purchase conflict",
17571
+ restAction: "This workspace already has Paid or an open Paid checkout, this key belongs to a checkout with a different offer or pack count, or, for 'billing portal', the workspace has never started a checkout. Read the original checkout with 'ohmyhost billing status --organization ULID --checkout CHECKOUT_ULID --json'; manage an existing Paid subscription with 'ohmyhost billing portal'. Never start a second purchase after an uncertain one."
17380
17572
  },
17381
17573
  compute_performance_paid_required: {
17382
17574
  status: 403,
17383
17575
  retryable: false,
17384
- action: "Check Paid service and usable credits or grace, or choose standard compute."
17576
+ action: "Performance compute needs active Paid and usable credits or grace; repeating unchanged does not help. Read 'ohmyhost credits account --organization ULID --json'. On Free, the Owner may start Paid with 'ohmyhost billing checkout --offer paid'; without usable credits, they may add credits. Until then, keep standard compute.",
17577
+ title: "Performance requires Paid access",
17578
+ restAction: "Performance compute needs active Paid and usable credits or grace; repeating unchanged does not help. Read 'ohmyhost credits account --organization ULID --json'. On Free, the Owner may start Paid with 'ohmyhost billing checkout --offer paid'; without usable credits, they may add credits. Until then, keep standard compute."
17385
17579
  },
17386
17580
  compute_performance_unavailable: {
17387
17581
  status: 503,
17388
17582
  retryable: false,
17389
- action: "Choose standard compute or report inactive performance pricing through feedback. Do not repeatedly resubmit."
17583
+ action: "Choose standard compute or report inactive performance pricing through feedback. Do not repeatedly resubmit.",
17584
+ title: "Performance pricing unavailable"
17390
17585
  },
17391
17586
  compute_change_pending: {
17392
17587
  status: 409,
17393
17588
  retryable: false,
17394
- action: "Poll the existing operation after 60 seconds. Do not submit another size change, deletion or transfer while it runs."
17589
+ action: "A database size change of this project is still running. Every 60 seconds read latest_operation from 'ohmyhost project status --project ULID --json', or 'ohmyhost operation get OPERATION_ID --json' for the change you started, until it ends, then repeat this request. Do not submit another size change or a deletion meanwhile.",
17590
+ title: "Database size change running",
17591
+ restAction: "A database size change of this project is still running. Every 60 seconds read latest_operation from 'ohmyhost project status --project ULID --json', or 'ohmyhost operation get OPERATION_ID --json' for the change you started, until it ends, then repeat this request. Do not submit another size change or a deletion meanwhile."
17395
17592
  },
17396
17593
  compute_change_conflict: {
17397
17594
  status: 409,
17398
17595
  retryable: false,
17399
- action: "Read the original operation and current database compute. Preserve data and report unresolved conflicts through feedback with the operation ID."
17596
+ action: "Read the original operation and current database compute. Preserve data and report unresolved conflicts through feedback with the operation ID.",
17597
+ title: "Database size change conflict"
17400
17598
  },
17401
17599
  database_access_limit: {
17402
17600
  status: 409,
17403
17601
  retryable: false,
17404
- action: "List the project's database credentials and revoke one you no longer need, then request access again."
17602
+ action: "List the project's database credentials and revoke one you no longer need, then request access again.",
17603
+ title: "Database access limit reached"
17405
17604
  },
17406
17605
  project_notes_conflict: {
17407
17606
  status: 409,
17408
17607
  retryable: false,
17409
- action: "Read project context, merge the current notes and retry with its version and a new idempotency key."
17608
+ action: "Read project context, merge the current notes and retry with its version and a new idempotency key.",
17609
+ title: "Project notes changed"
17610
+ },
17611
+ invalid_request: {
17612
+ status: 400,
17613
+ retryable: false,
17614
+ action: "The server rejected this request; repeating it unchanged fails again. Check every argument against the command usage or the tool schema. For 'mail webhook verify' and mail receiving, the app's webhook route must answer the signed test with 2xx using the installed signing secret; receiving needs a verified webhook. For 'database query', send one read-only SELECT or WITH statement that runs on the current schema.",
17615
+ title: "Invalid request",
17616
+ restAction: "Read detail for the rejected field or condition, correct it and send the corrected request; repeating it unchanged fails again."
17410
17617
  },
17411
- invalid_request: { status: 400, retryable: false, action: "Check the request and retry." },
17412
17618
  unauthenticated: {
17413
17619
  status: 401,
17414
17620
  retryable: false,
17415
- action: "Check OHMYHOST_TOKEN and the selected environment. Replace invalid tokens, or unset OHMYHOST_TOKEN and run 'ohmyhost login'."
17621
+ action: "The login or token was not accepted; repeating unchanged does not help. If OHMYHOST_TOKEN is set, it was revoked, mistyped or belongs to a different platform than OHMYHOST_ENVIRONMENT selects (a platform setting, not the project's dev or prod): ask the customer for a current token from the portal's API Tokens page, or unset it and run 'ohmyhost login --json'. Otherwise run 'ohmyhost login --json', then repeat the request.",
17622
+ title: "Authentication required",
17623
+ restAction: "The login or token was not accepted; repeating unchanged does not help. If OHMYHOST_TOKEN is set, it was revoked, mistyped or belongs to a different platform than OHMYHOST_ENVIRONMENT selects (a platform setting, not the project's dev or prod): ask the customer for a current token from the portal's API Tokens page, or unset it and run 'ohmyhost login --json'. Otherwise run 'ohmyhost login --json', then repeat the request."
17624
+ },
17625
+ forbidden: {
17626
+ status: 403,
17627
+ retryable: false,
17628
+ action: "This login may not do this; repeating unchanged does not help. error.acting_as names the login used: use a workspace this login belongs to ('ohmyhost whoami --json'), or the login of the account that owns it. For 'organization create', pass --source exactly as the Signup source on the portal Profile page, or omit it when that page shows Direct.",
17629
+ title: "Forbidden",
17630
+ restAction: "This identity may not do this; repeating unchanged does not help. Use a workspace this identity belongs to. A new workspace must preserve the signup source already recorded for this user; a scoped API token may create another workspace through the ordinary authorized flow."
17416
17631
  },
17417
- forbidden: { status: 403, retryable: false, action: "Check organization access." },
17418
17632
  organization_required: {
17419
17633
  status: 409,
17420
17634
  retryable: false,
17421
- action: "Run 'ohmyhost organization list --json' and select one with 'ohmyhost organization use --organization ULID --json', or create the first workspace."
17635
+ action: "Run 'ohmyhost organization list --json' and select one with 'ohmyhost organization use --organization ULID --json', or create the first workspace.",
17636
+ title: "Workspace selection required"
17422
17637
  },
17423
17638
  resource_not_found: {
17424
17639
  status: 404,
17425
17640
  retryable: false,
17426
- action: "Check the resource identifier and the account: a project is visible only to its own organization, and error.acting_as names the login this ran as. Use the login of the account that owns it."
17641
+ action: "Check the resource identifier and the account: a project is visible only to its own organization, and error.acting_as in the CLI and MCP names the login this ran as. Use the login of the account that owns it.",
17642
+ title: "Resource not found"
17427
17643
  },
17428
17644
  idempotency_key_reused: {
17429
17645
  status: 409,
17430
17646
  retryable: false,
17431
- action: "Use a new idempotency key for a different request."
17647
+ action: "This key was already used for a request with different arguments or from another login. To repeat that original request, send exactly its original arguments with this key from the same login; for a new request, use a new key. Never pair a new key with a request whose first attempt had an uncertain outcome: read its result first.",
17648
+ title: "Idempotency key reused",
17649
+ restAction: "This key was already used for a request with different arguments or from another login. To repeat that original request, send exactly its original arguments with this key from the same login; for a new request, use a new key. Never pair a new key with a request whose first attempt had an uncertain outcome: read its result first."
17432
17650
  },
17433
17651
  project_handle_unavailable: {
17434
17652
  status: 503,
17435
17653
  retryable: true,
17436
- action: "Retry project creation with the same idempotency key."
17654
+ action: "Retry project creation with the same idempotency key.",
17655
+ title: "Project handle unavailable"
17437
17656
  },
17438
17657
  project_handle_taken: {
17439
17658
  status: 409,
17440
17659
  retryable: false,
17441
- action: "Run 'ohmyhost project handle check' for a free address, then rename to one of the alternatives it returns."
17660
+ action: "Another project uses this address. Run 'ohmyhost project handle check --handle NAME --json' and offer the customer its alternatives; rename only to the one they choose.",
17661
+ title: "Project address is taken",
17662
+ restAction: "Another project uses this address. Run 'ohmyhost project handle check --handle NAME --json' and offer the customer its alternatives; rename only to the one they choose."
17442
17663
  },
17443
17664
  project_handle_invalid: {
17444
17665
  status: 409,
17445
17666
  retryable: false,
17446
- action: "Choose one to five lowercase words of letters and digits, three to fifty-nine characters, avoiding reserved words."
17667
+ action: "Choose one to five lowercase words of letters and digits, three to fifty-nine characters, avoiding reserved words.",
17668
+ title: "Project address is invalid"
17447
17669
  },
17448
17670
  project_handle_unchanged: {
17449
17671
  status: 409,
17450
17672
  retryable: false,
17451
- action: "The project already answers on this address; no rename is needed."
17673
+ action: "The project already answers on this address; no rename is needed.",
17674
+ title: "Project address is unchanged"
17452
17675
  },
17453
17676
  project_rename_blocked: {
17454
17677
  status: 409,
17455
17678
  retryable: true,
17456
- action: "Wait for the running operation to finish, read the project again, then rename with the new ETag."
17679
+ action: "Wait for the running operation to finish, read the project again, then rename with the new ETag.",
17680
+ title: "Project address change blocked"
17457
17681
  },
17458
17682
  project_identity_unavailable: {
17459
17683
  status: 503,
17460
17684
  retryable: true,
17461
- action: "Retry project creation with the same idempotency key."
17685
+ action: "Retry project creation with the same idempotency key.",
17686
+ title: "Project identity unavailable"
17462
17687
  },
17463
17688
  deployment_plan_expired: {
17464
17689
  status: 409,
17465
17690
  retryable: false,
17466
- action: "Run 'ohmyhost plan' again, then deploy the new plan."
17691
+ action: "Run 'ohmyhost plan' again, then deploy the new plan.",
17692
+ title: "Deployment plan expired"
17467
17693
  },
17468
17694
  deployment_plan_incompatible: {
17469
17695
  status: 409,
17470
17696
  retryable: false,
17471
- action: "Run 'ohmyhost plan' again with the current source revision."
17697
+ action: "Run 'ohmyhost plan' again with the current source revision.",
17698
+ title: "Deployment plan incompatible"
17472
17699
  },
17473
17700
  confirmation_expired: {
17474
17701
  status: 409,
17475
17702
  retryable: false,
17476
- action: "Plan the destructive action again before confirming it."
17703
+ action: "Plan the destructive action again before confirming it.",
17704
+ title: "Confirmation expired"
17477
17705
  },
17478
17706
  confirmation_invalid: {
17479
17707
  status: 409,
17480
17708
  retryable: false,
17481
- action: "Use the confirmation token from a fresh matching plan."
17709
+ action: "The confirmation token does not match this action or the project's current state. A deletion is also refused while a SQL export of the project is being captured, for up to about ten minutes ('ohmyhost export get'). Then create a new plan and confirm with its confirmation_token and resource_etag before the token expires.",
17710
+ title: "Confirmation invalid",
17711
+ restAction: "The confirmation token does not match this action or the project's current state. A deletion is also refused while a SQL export of the project is being captured, for up to about ten minutes ('ohmyhost export get'). Then create a new plan and confirm with its confirmation_token and resource_etag before the token expires."
17482
17712
  },
17483
17713
  etag_mismatch: {
17484
17714
  status: 409,
17485
17715
  retryable: false,
17486
- action: "Refresh the resource, plan the action again, and retry."
17716
+ action: "The project changed after you read its ETag. For a rename, read etag from 'ohmyhost project status --project ULID --json' and repeat 'ohmyhost project handle set' with it. For a rollback, promotion or deletion, create a new plan and confirm with its resource_etag and confirmation_token.",
17717
+ title: "Resource changed",
17718
+ restAction: "The project changed after you read its ETag. For a rename, read etag from 'ohmyhost project status --project ULID --json' and repeat 'ohmyhost project handle set' with it. For a rollback, promotion or deletion, create a new plan and confirm with its resource_etag and confirmation_token."
17487
17719
  },
17488
17720
  promotion_source_stale: {
17489
17721
  status: 409,
17490
17722
  retryable: false,
17491
- action: "Read the current dev deployment, then plan promotion again."
17723
+ action: "Read the current dev deployment, then plan promotion again.",
17724
+ title: "Promotion source changed"
17492
17725
  },
17493
17726
  promotion_target_stale: {
17494
17727
  status: 409,
17495
17728
  retryable: false,
17496
- action: "Read the current prod status, then plan promotion again."
17729
+ action: "Read the current prod status, then plan promotion again.",
17730
+ title: "Promotion target changed"
17497
17731
  },
17498
17732
  promotion_invalid_target: {
17499
17733
  status: 409,
17500
17734
  retryable: false,
17501
- action: "Select the current succeeded dev deployment and target prod."
17735
+ action: "Select the current succeeded dev deployment and target prod.",
17736
+ title: "Promotion target invalid"
17502
17737
  },
17503
17738
  mail_capacity_unavailable: {
17504
17739
  status: 503,
17505
17740
  retryable: true,
17506
- action: "Report this request ID through feedback. The stored mail setup continues automatically once ohmyho.st has capacity; follow mail status and keep the original idempotency key."
17741
+ action: "Report this request ID through feedback. The stored mail setup continues automatically once ohmyho.st has capacity; follow mail status and keep the original idempotency key.",
17742
+ title: "Mail capacity unavailable"
17507
17743
  },
17508
17744
  mail_domain_conflict: {
17509
17745
  status: 409,
17510
17746
  retryable: false,
17511
- action: "Read and use the sender domain already configured for this project."
17747
+ action: "Use the sender domain already configured for this project, or retire it with 'ohmyhost mail domain delete' (repeat while the removal is pending) and then set up the new domain; a domain registered to another project cannot be set up.",
17748
+ title: "Mail domain conflict"
17512
17749
  },
17513
17750
  mail_domain_required: {
17514
17751
  status: 409,
17515
17752
  retryable: false,
17516
- action: "This app declares mail.enabled. Configure the project's own sender domain with 'ohmyhost mail setup' using its Prod environment ID, or set mail.enabled to false if the app sends no mail, then plan again."
17753
+ action: "This app declares mail.enabled. Configure the project's own sender domain with 'ohmyhost mail setup' using its Prod environment ID, or set mail.enabled to false if the app sends no mail, then plan again.",
17754
+ title: "Mail domain required"
17517
17755
  },
17518
17756
  storage_jurisdiction_conflict: {
17519
17757
  status: 409,
17520
17758
  retryable: false,
17521
- action: "Set storage.jurisdiction in ohmyhost.yaml to the project's hosting region shown by project status, then plan again."
17759
+ action: "Set storage.jurisdiction in ohmyhost.yaml to the project's region (us or eu) shown by 'ohmyhost project status', commit and push, then plan the new commit (for a promotion, deploy it to Dev and promote again); the unchanged commit fails again. A project cannot move its files between regions; do not delete or recreate the project to change it.",
17760
+ title: "Storage jurisdiction conflict",
17761
+ restAction: "Set storage.jurisdiction in ohmyhost.yaml to the project's region (us or eu) shown by 'ohmyhost project status', commit and push, then plan the new commit (for a promotion, deploy it to Dev and promote again); the unchanged commit fails again. A project cannot move its files between regions; do not delete or recreate the project to change it."
17522
17762
  },
17523
17763
  shared_data_requires_promotion: {
17524
17764
  status: 409,
17525
17765
  retryable: false,
17526
- action: "Plan and deploy this commit to Dev, then run 'ohmyhost deployment promote plan' to reach Prod."
17766
+ action: "Plan and deploy this commit to Dev, then run 'ohmyhost deployment promote plan' to reach Prod.",
17767
+ title: "Shared data requires promotion"
17527
17768
  },
17528
17769
  production_deployment_required: {
17529
17770
  status: 409,
17530
17771
  retryable: false,
17531
- action: "Deploy to Prod first: run 'ohmyhost plan --environment prod' and deploy that plan, or promote the current Dev deployment with 'ohmyhost deployment promote plan'. Then plan the domain again."
17772
+ action: "Deploy to Prod first: run 'ohmyhost plan --environment prod' and deploy that plan, or promote the current Dev deployment with 'ohmyhost deployment promote plan'. Then plan the domain again.",
17773
+ title: "Production deployment required"
17532
17774
  },
17533
17775
  framework_conversion_required: {
17534
17776
  status: 409,
17535
17777
  retryable: false,
17536
- action: "Run 'ohmyhost init', apply the matching ohmyhost framework skill, then plan the new commit."
17778
+ action: "Read detail for the file, reason and required change. Run ohmyhost init --dry-run --json at that commit, apply its blockers and the matching ohmyho.st framework Skill, then commit and push. Plan the new commit; repeating the unchanged source cannot fix an admission error.",
17779
+ title: "Framework conversion required",
17780
+ restAction: "Read detail for the file, reason and required change. Run ohmyhost init --dry-run --json at that commit, apply its blockers and the matching ohmyho.st framework Skill, then commit and push. Plan the new commit; repeating the unchanged source cannot fix an admission error."
17537
17781
  },
17538
17782
  repository_configuration_missing: {
17539
17783
  status: 409,
17540
17784
  retryable: false,
17541
- action: "Place ohmyhost.yaml at the Git repository root and set applicationRoot to the application directory (for example website). Commit and push the change, then plan the new commit."
17785
+ action: "The planned commit has no ohmyhost.yaml at the Git repository root. If the repository has none, run 'ohmyhost init --json' at the root (--root DIR when the app lives in a subdirectory, --region eu for an EU project). Otherwise move the existing file to the root and set applicationRoot to the app directory, for example website. Commit and push, then plan the new commit.",
17786
+ title: "Repository configuration missing",
17787
+ restAction: "The planned commit has no ohmyhost.yaml at the Git repository root. If the repository has none, run 'ohmyhost init --json' at the root (--root DIR when the app lives in a subdirectory, --region eu for an EU project). Otherwise move the existing file to the root and set applicationRoot to the app directory, for example website. Commit and push, then plan the new commit."
17542
17788
  },
17543
17789
  migration_filename_noncanonical: {
17544
17790
  status: 409,
17545
17791
  retryable: false,
17546
- action: "Rename every migration to YYYYMMDDHHMMSS_name.sql, commit the change, then plan again."
17792
+ action: "Every file in the migrations directory (database.migrations in ohmyhost.yaml) must be named YYYYMMDDHHMMSS_name.sql: 14 digits, an underscore, then lowercase letters, digits, _ or -. Rename each file that does not match and move any other file, such as README.md or .gitkeep, out of that directory. Commit and push, then plan the new commit. Never rename a migration a deployment already applied.",
17793
+ title: "Migration filename noncanonical",
17794
+ restAction: "Every file in the migrations directory (database.migrations in ohmyhost.yaml) must be named YYYYMMDDHHMMSS_name.sql: 14 digits, an underscore, then lowercase letters, digits, _ or -. Rename each file that does not match and move any other file, such as README.md or .gitkeep, out of that directory. Commit and push, then plan the new commit. Never rename a migration a deployment already applied."
17547
17795
  },
17548
17796
  environment_secret_mutation_blocked: {
17549
17797
  status: 409,
17550
17798
  retryable: true,
17551
- action: "Wait for the active deployment or rollback to finish, then retry unchanged."
17799
+ action: "Wait for the active deployment or rollback to finish, then retry unchanged.",
17800
+ title: "Environment secret mutation blocked"
17552
17801
  },
17553
17802
  workers_runtime_incompatible: {
17554
17803
  status: 409,
17555
17804
  retryable: false,
17556
- action: "Remove or replace the reported module, commit the change, then create a new plan."
17805
+ action: "A Next.js file under app/, pages/, middleware or proxy imports a native addon (.node) or a Node.js module that does not run on Workers: child_process, cluster, dgram, domain, http2, inspector, readline, repl, sqlite, trace_events, tty, v8, vm, wasi, worker_threads or _stream_wrap, with or without node:. Remove or replace that import, commit and push, then plan the new commit.",
17806
+ title: "Workers runtime incompatible",
17807
+ restAction: "A Next.js file under app/, pages/, middleware or proxy imports a native addon (.node) or a Node.js module that does not run on Workers: child_process, cluster, dgram, domain, http2, inspector, readline, repl, sqlite, trace_events, tty, v8, vm, wasi, worker_threads or _stream_wrap, with or without node:. Remove or replace that import, commit and push, then plan the new commit."
17808
+ },
17809
+ payload_too_large: {
17810
+ status: 413,
17811
+ retryable: false,
17812
+ action: "Reduce the request size.",
17813
+ title: "Payload too large"
17557
17814
  },
17558
- payload_too_large: { status: 413, retryable: false, action: "Reduce the request size." },
17559
17815
  project_export_not_ready: {
17560
17816
  status: 409,
17561
17817
  retryable: false,
17562
- action: "Finish database provisioning, then request the export again. No backup was accepted."
17818
+ action: "No database of this project is ready, so no export was accepted. A database is created when a commit whose ohmyhost.yaml sets database.enabled: true is deployed; request the export again after that deployment succeeds ('ohmyhost operation get OPERATION_ID --json'). A project without a database has no SQL export.",
17819
+ title: "Project database not ready for export",
17820
+ restAction: "No database of this project is ready, so no export was accepted. A database is created when a commit whose ohmyhost.yaml sets database.enabled: true is deployed; request the export again after that deployment succeeds ('ohmyhost operation get OPERATION_ID --json'). A project without a database has no SQL export."
17563
17821
  },
17564
17822
  powered_by_flag_required: {
17565
17823
  status: 409,
17566
17824
  retryable: false,
17567
- action: "Keep the flag on, remove the custom domain with 'ohmyhost domain paid delete', or buy Paid, then switch the flag off."
17825
+ action: "Keep the flag on, remove the custom domain with 'ohmyhost domain paid delete', or buy Paid, then switch the flag off.",
17826
+ title: "Powered-by flag required"
17827
+ },
17828
+ rate_limited: {
17829
+ status: 429,
17830
+ retryable: true,
17831
+ action: "Too many requests. Wait retry_after_seconds, or 60 seconds when none is shown, then repeat the same request with the same idempotency key. SQL exports allow one request per project per rolling 24 hours: poll the export already requested with 'ohmyhost export get EXPORT_ID --project ULID --json' instead of creating another.",
17832
+ title: "Rate limited",
17833
+ restAction: "Wait the Retry-After interval (also retry_after_seconds), then repeat the same request with the same idempotency key."
17568
17834
  },
17569
- rate_limited: { status: 429, retryable: true, action: "Retry after the server delay." },
17570
17835
  insufficient_organization_credits: {
17571
17836
  status: 402,
17572
17837
  retryable: false,
17573
- action: "Add organization credits or wait for reservations to settle, then retry the same request."
17838
+ action: "Add organization credits or wait for reservations to settle, then retry the same request.",
17839
+ title: "Insufficient organization credits"
17574
17840
  },
17575
17841
  project_budget_exceeded: {
17576
17842
  status: 402,
17577
17843
  retryable: false,
17578
- action: "Raise the project's stop budget or select continue, then retry the same request."
17844
+ action: "This project's Stop budget for the current UTC month is reached; nothing was started. Show the customer 'ohmyhost budget get --project ULID --json'. Only on their request raise the limit or switch the mode to continue with 'ohmyhost budget set', then retry the same request; otherwise wait for the next UTC month.",
17845
+ title: "Project budget exceeded",
17846
+ restAction: "This project's Stop budget for the current UTC month is reached; nothing was started. Show the customer 'ohmyhost budget get --project ULID --json'. Only on their request raise the limit or switch the mode to continue with 'ohmyhost budget set', then retry the same request; otherwise wait for the next UTC month."
17579
17847
  },
17580
17848
  paid_plan_required: {
17581
17849
  status: 402,
17582
17850
  retryable: false,
17583
- action: "Ask the organization Owner to activate Paid through billing checkout, then retry. Top-ups and signup credits do not activate Paid. For a website domain alone, a Free workspace may instead show the powered-by flag on the project."
17851
+ action: "Ask the organization Owner to activate Paid through billing checkout, then retry. Top-ups and signup credits do not activate Paid. For a website domain alone, a Free workspace may instead show the powered-by flag on the project.",
17852
+ title: "Paid plan required"
17584
17853
  },
17585
17854
  interactive_login_required: {
17586
17855
  status: 403,
17587
17856
  retryable: false,
17588
- action: "Unset OHMYHOST_TOKEN and run ohmyhost login before managing tokens. Existing token files remain unchanged."
17857
+ action: "Unset OHMYHOST_TOKEN and run ohmyhost login before managing tokens. Existing token files remain unchanged.",
17858
+ title: "Interactive login required"
17589
17859
  },
17590
17860
  api_key_creation_uncertain: {
17591
17861
  status: 503,
17592
17862
  retryable: true,
17593
- action: "Repeat the original token name and Idempotency-Key to observe its result; do not blindly create another key."
17863
+ action: "Repeat the original token name and Idempotency-Key to observe its result; do not blindly create another key.",
17864
+ title: "Token creation unconfirmed"
17594
17865
  },
17595
17866
  api_key_permissions_unavailable: {
17596
17867
  status: 503,
17597
17868
  retryable: false,
17598
- action: "Report the platform user-key permission configuration through feedback. Repeated login or creation will not fix it."
17869
+ action: "Report the platform user-key permission configuration through feedback. Repeated login or creation will not fix it.",
17870
+ title: "Token permissions unavailable"
17599
17871
  },
17600
17872
  reconciliation_exhausted: {
17601
17873
  status: 409,
17602
17874
  retryable: false,
17603
- action: "Stop retries and submit feedback with this operation ID. A platform recovery review is required."
17875
+ action: "Stop retries and submit feedback with this operation ID. A platform recovery review is required.",
17876
+ title: "Recovery attempts exhausted"
17604
17877
  },
17605
- service_unavailable: { status: 503, retryable: true, action: "Retry later." }
17878
+ service_unavailable: {
17879
+ status: 503,
17880
+ retryable: true,
17881
+ action: "The service could not complete or confirm this request; it may already have taken effect. Wait, then repeat exactly the same request with the same idempotency key, waiting longer each time; never switch to a new key. If 'ohmyhost plan' keeps failing, check that the commit is pushed to the linked repository and that 'ohmyhost init --dry-run --json' at that commit reports no blockers. If it still fails, report the request_id through feedback.",
17882
+ title: "Service unavailable",
17883
+ restAction: "The service could not complete or confirm this request; it may already have taken effect. Wait, then repeat exactly the same request with the same idempotency key, waiting longer each time; never switch to a new key. If 'ohmyhost plan' keeps failing, check that the commit is pushed to the linked repository and that 'ohmyhost init --dry-run --json' at that commit reports no blockers. If it still fails, report the request_id through feedback."
17884
+ },
17885
+ source_commit_not_found: {
17886
+ status: 409,
17887
+ retryable: false,
17888
+ action: "The requested commit is not in the linked repository. Push that exact commit to the repository named by project status, or select a commit already there, then run 'ohmyhost plan' with its full SHA. Repeating the unchanged unavailable commit cannot work.",
17889
+ restAction: "The requested commit is not in the linked repository. Push that exact commit to the repository named by project status, or select a commit already there, then run 'ohmyhost plan' with its full SHA. Repeating the unchanged unavailable commit cannot work.",
17890
+ title: "Source commit not found"
17891
+ },
17892
+ domain_hostname_taken: {
17893
+ status: 409,
17894
+ retryable: false,
17895
+ action: "A live project connection already holds this hostname. Remove it from the project you manage with 'ohmyhost domain paid delete --project ULID --hostname HOST --yes --json', then apply it to the new project. Repeating while that connection remains cannot work; report the request_id if you cannot manage its owning project.",
17896
+ restAction: "A live project connection already holds this hostname. Remove it from the project you manage with 'ohmyhost domain paid delete --project ULID --hostname HOST --yes --json', then apply it to the new project. Repeating while that connection remains cannot work; report the request_id if you cannot manage its owning project.",
17897
+ title: "Domain hostname is already connected"
17898
+ },
17899
+ data_change_blocked: {
17900
+ status: 409,
17901
+ retryable: true,
17902
+ action: "An operation of this project is still running. Wait until 'ohmyhost operation get OPERATION_ULID --json' shows it finished, plan the data change again and confirm it with the new ETag and token.",
17903
+ restAction: "An operation of this project is still running. Wait until 'ohmyhost operation get OPERATION_ULID --json' shows it finished, plan the data change again and confirm it with the new ETag and token.",
17904
+ title: "Data change blocked"
17905
+ },
17906
+ data_change_in_progress: {
17907
+ status: 409,
17908
+ retryable: true,
17909
+ action: "A data change of this project is running. Wait until its operation has finished ('ohmyhost project status --project ULID --json'), then repeat the request unchanged.",
17910
+ restAction: "A data change of this project is running. Wait until its operation has finished ('ohmyhost project status --project ULID --json'), then repeat the request unchanged.",
17911
+ title: "Data change in progress"
17912
+ },
17913
+ data_change_not_applicable: {
17914
+ status: 409,
17915
+ retryable: false,
17916
+ action: "Read 'ohmyhost project status --project ULID --json'. Isolation applies to shared projects; returning to shared needs a deployed Prod database, and reset_dev applies to isolated projects. If detail names an old database or storage binding, deploy the affected environment again before planning the change. Repeating unchanged does not help.",
17917
+ restAction: "Read 'ohmyhost project status --project ULID --json'. Isolation applies to shared projects; returning to shared needs a deployed Prod database, and reset_dev applies to isolated projects. If detail names an old database or storage binding, deploy the affected environment again before planning the change. Repeating unchanged does not help.",
17918
+ title: "Data change is not applicable"
17919
+ },
17920
+ rollback_target_data_changed: {
17921
+ status: 409,
17922
+ retryable: false,
17923
+ action: "This deployment used a data area its environment no longer has. Deploy that commit again with a fresh plan instead of rolling back to its old bindings; repeating this rollback cannot work.",
17924
+ restAction: "This deployment used a data area its environment no longer has. Deploy that commit again with a fresh plan instead of rolling back to its old bindings; repeating this rollback cannot work.",
17925
+ title: "Rollback data area changed"
17926
+ },
17927
+ project_domain_delete_required: {
17928
+ status: 409,
17929
+ retryable: false,
17930
+ title: "Remove the project domain first",
17931
+ action: "This project still has a live or pending custom-domain connection. Read 'ohmyhost domain paid status --project ULID --json', remove that connection with 'ohmyhost domain paid delete --project ULID --hostname HOST --yes --json', then create a fresh project deletion plan. Repeating while it remains connected cannot work."
17932
+ }
17606
17933
  };
17934
+
17935
+ // apps/product-cli/dist/runtime-contract.js
17607
17936
  var ResponseContractError = class extends Error {
17608
17937
  constructor() {
17609
17938
  super("The server response does not match the public contract.");
17610
17939
  this.name = "ResponseContractError";
17611
17940
  }
17612
17941
  };
17942
+ function parseProjectDataChangePlan2(value) {
17943
+ try {
17944
+ return parseProjectDataChangePlan(value);
17945
+ } catch {
17946
+ throw new ResponseContractError();
17947
+ }
17948
+ }
17613
17949
  var parsePaidDomainPlan = (value) => {
17614
17950
  const input = exactRecord2(value, [
17615
17951
  "cname_target",
@@ -18259,6 +18595,7 @@ var parseSafeProblem = (value) => {
18259
18595
  const docsUrl = String(input["type"]);
18260
18596
  return {
18261
18597
  requestId: input["request_id"],
18598
+ ...input["detail"] === void 0 ? {} : { message: String(input["detail"]) },
18262
18599
  ...docsUrl.startsWith("https://docs.ohmyho.st/errors/") ? { docsUrl } : {},
18263
18600
  code,
18264
18601
  retryable: policy.retryable,
@@ -18439,8 +18776,10 @@ var selectWorkspace = async (session, organization, signal, identity) => {
18439
18776
  throw new InteractiveSessionRequiredError();
18440
18777
  const refreshed = await session.refresher.refresh(stored.refreshToken, signal, organization.workos_id);
18441
18778
  assertVerifiedLogin(await identity(refreshed.accessToken), session.profile.userId, organization.id);
18779
+ const existing = (await session.profiles.list()).find((saved) => saved.userId === session.profile.userId && saved.organizationId === organization.id);
18442
18780
  const bound = {
18443
18781
  ...session.profile,
18782
+ name: existing?.name ?? session.profile.name,
18444
18783
  organizationId: organization.id,
18445
18784
  organizationName: organization.name
18446
18785
  };
@@ -18537,7 +18876,7 @@ function assertProjectDatabaseAccessPage(value) {
18537
18876
  }
18538
18877
  function assertAccessFields(value) {
18539
18878
  const revoked = value["revoked_at"], reason = value["revocation_reason"];
18540
- if (typeof value["access_id"] !== "string" || !/^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u.test(value["access_id"]) || !["dev", "prod"].includes(String(value["environment"])) || !["read", "write"].includes(String(value["mode"])) || typeof value["role_name"] !== "string" || !ACCESS_ROLE_PATTERN.test(value["role_name"]) || typeof value["host"] !== "string" || !/^[a-z0-9]([a-z0-9.-]{0,251}[a-z0-9])?$/u.test(value["host"]) || value["database"] !== "neondb" || value["label"] !== null && accessLabel(value["label"]) !== value["label"] || !["active", "expired", "revoked"].includes(String(value["state"])) || !timestamp(value["issued_at"]) || !timestamp(value["expires_at"]) || Date.parse(String(value["expires_at"])) <= Date.parse(String(value["issued_at"])) || revoked !== null && !timestamp(revoked) || revoked === null !== (reason === null) || reason !== null && !["principal", "expired", "budget", "creation_failed"].includes(String(reason)) || value["state"] === "revoked" !== (revoked !== null))
18879
+ if (typeof value["access_id"] !== "string" || !/^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u.test(value["access_id"]) || !["dev", "prod"].includes(String(value["environment"])) || !["read", "write"].includes(String(value["mode"])) || typeof value["role_name"] !== "string" || !ACCESS_ROLE_PATTERN.test(value["role_name"]) || typeof value["host"] !== "string" || !/^[a-z0-9]([a-z0-9.-]{0,251}[a-z0-9])?$/u.test(value["host"]) || value["database"] !== "neondb" || value["label"] !== null && accessLabel(value["label"]) !== value["label"] || !["active", "expired", "revoked"].includes(String(value["state"])) || !timestamp(value["issued_at"]) || !timestamp(value["expires_at"]) || Date.parse(String(value["expires_at"])) <= Date.parse(String(value["issued_at"])) || revoked !== null && !timestamp(revoked) || revoked === null !== (reason === null) || reason !== null && !["principal", "expired", "budget", "creation_failed", "data_change"].includes(String(reason)) || value["state"] === "revoked" !== (revoked !== null))
18541
18880
  throw new TypeError("Invalid database access");
18542
18881
  }
18543
18882
  function accessLabel(value) {
@@ -18609,7 +18948,7 @@ function exact2(v, keys) {
18609
18948
  // apps/product-cli/package.json
18610
18949
  var package_default = {
18611
18950
  name: "@ohmyhost/product-cli",
18612
- version: "0.1.22",
18951
+ version: "0.1.24",
18613
18952
  private: true,
18614
18953
  ohmyhost: {
18615
18954
  deployment: "production",
@@ -18672,43 +19011,43 @@ function assertProjectContext(v) {
18672
19011
  // packages/contracts/dist/project-exports.js
18673
19012
  function assertProjectExport(value) {
18674
19013
  if (!record4(value) || Object.keys(value).sort().join(",") !== "archive,error,id,next_poll_after_seconds,next_request_at,project_id,requested_at,state" || typeof value["id"] !== "string" || !id.test(value["id"]) || typeof value["project_id"] !== "string" || !id.test(value["project_id"]) || !["queued", "running", "succeeded", "failed", "cancelled"].includes(String(value["state"])) || !timestamp3(value["requested_at"]) || !timestamp3(value["next_request_at"]))
18675
- invalid2();
19014
+ invalid3();
18676
19015
  const active = value["state"] === "queued" || value["state"] === "running";
18677
19016
  if (value["next_poll_after_seconds"] !== (active ? 5 : null))
18678
- invalid2();
19017
+ invalid3();
18679
19018
  if (value["state"] === "failed") {
18680
19019
  const error = value["error"];
18681
19020
  if (!record4(error) || Object.keys(error).sort().join(",") !== "code,message,retryable,suggested_action" || typeof error["code"] !== "string")
18682
- invalid2();
19021
+ invalid3();
18683
19022
  const expected = publicOperationFailure(error["code"]);
18684
19023
  for (const key of Object.keys(expected))
18685
19024
  if (error[key] !== expected[key])
18686
- invalid2();
19025
+ invalid3();
18687
19026
  } else if (value["error"] !== null)
18688
- invalid2();
19027
+ invalid3();
18689
19028
  const archive = value["archive"];
18690
19029
  if (archive === null) {
18691
19030
  if (value["state"] === "succeeded")
18692
- invalid2();
19031
+ invalid3();
18693
19032
  return;
18694
19033
  }
18695
19034
  if (value["state"] !== "succeeded" || !record4(archive) || Object.keys(archive).sort().join(",") !== "bytes,captured_at,download_expires_at,download_url,expires_at,sha256,sql_files" || !Number.isSafeInteger(archive["bytes"]) || Number(archive["bytes"]) < 1 || Number(archive["bytes"]) > 536870912 || typeof archive["sha256"] !== "string" || !/^sha256:[a-f0-9]{64}$/u.test(archive["sha256"]) || !['["dev.sql"]', '["prod.sql"]', '["dev.sql","prod.sql"]', '["shared.sql"]'].includes(JSON.stringify(archive["sql_files"])) || !timestamp3(archive["captured_at"]) || !timestamp3(archive["expires_at"]))
18696
- invalid2();
19035
+ invalid3();
18697
19036
  if (archive["download_url"] === null) {
18698
19037
  if (archive["download_expires_at"] !== null)
18699
- invalid2();
19038
+ invalid3();
18700
19039
  return;
18701
19040
  }
18702
19041
  if (typeof archive["download_url"] !== "string" || !timestamp3(archive["download_expires_at"]) || Date.parse(archive["download_expires_at"]) > Date.parse(archive["expires_at"]))
18703
- invalid2();
19042
+ invalid3();
18704
19043
  let url;
18705
19044
  try {
18706
19045
  url = new URL(archive["download_url"]);
18707
19046
  } catch {
18708
- invalid2();
19047
+ invalid3();
18709
19048
  }
18710
19049
  if (url.protocol !== "https:" || url.username || url.password || url.hash)
18711
- invalid2();
19050
+ invalid3();
18712
19051
  }
18713
19052
  var id = /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u;
18714
19053
  function record4(value) {
@@ -18717,7 +19056,7 @@ function record4(value) {
18717
19056
  function timestamp3(value) {
18718
19057
  return typeof value === "string" && Number.isFinite(Date.parse(value)) && new Date(value).toISOString() === value;
18719
19058
  }
18720
- function invalid2() {
19059
+ function invalid3() {
18721
19060
  throw new TypeError("Project export response is invalid");
18722
19061
  }
18723
19062
 
@@ -18908,7 +19247,7 @@ var HELP_USAGE = Object.freeze({
18908
19247
  "export create": "ohmyhost export create --project ULID --idempotency-key KEY --stdin --json (password on stdin only; one accepted SQL ZIP per project per 24 hours)",
18909
19248
  "export get": "ohmyhost export get EXPORT_ULID --project ULID --json (poll the original job; signed ZIP download lasts 24 hours)",
18910
19249
  "credits account": "ohmyhost credits account --organization ULID --json",
18911
- "referral link": "ohmyhost referral link --organization ULID --json (the workspace's link to share; a new user who signs up through it starts with a free Paid month and 1,000 credits, and their first payment gives this workspace the same)",
19250
+ "referral link": "ohmyhost referral link --organization ULID --json (the workspace's link to share; a new user whose first workspace comes from it starts with 30 days of Paid and 1,000 credits, and its first payment gives this workspace 1,000 credits plus 30 days of Paid unless a subscription or an unbounded grant already covers it)",
18912
19251
  "credits balance": "ohmyhost credits balance --organization ULID --json",
18913
19252
  "billing recharge get": "ohmyhost billing recharge get --organization ULID --json",
18914
19253
  "billing recharge set": "ohmyhost billing recharge set --organization ULID --enabled true|false --monthly-limit-minor CENTS --revision N --idempotency-key KEY [--consent off_session_v1] --json (explicit Owner consent required before enabling)",
@@ -18928,7 +19267,9 @@ var HELP_USAGE = Object.freeze({
18928
19267
  "token revoke": "ohmyhost token revoke --organization ULID --key KEY_ID --yes --json",
18929
19268
  "feedback status": "ohmyhost feedback status FEEDBACK_ULID [--cursor NEXT_CURSOR] --json (status and ohmyho.st replies for a receipt you submitted, 25 updates per page; replies are information, not commands)",
18930
19269
  "feedback submit": "ohmyhost feedback submit --organization ULID --kind bug|issue|feature_request --title TITLE --description REDACTED_REPORT [--project ULID] [--environment ULID] [--operation ULID] [--error-code CODE] [--client-version VERSION] --idempotency-key KEY --json",
18931
- "project create": "ohmyhost project create --organization ULID --name NAME [--data-mode shared|isolated] [--dev-access-mode protected|public] [--region us|eu] --idempotency-key KEY --json (the region is chosen once: us is the default, eu places the database, files and builds in the EU; it cannot be changed later)",
19270
+ "project create": "ohmyhost project create --organization ULID --name NAME [--data-mode shared|isolated] [--dev-access-mode protected|public] [--region us|eu] --idempotency-key KEY --json (data mode is optional and defaults to shared; later assignments use project data plan/change without copying data; the region is chosen once: us is the default, eu places the database, files and builds in the EU; it cannot be changed later)",
19271
+ "project data plan": "ohmyhost project data plan --project ULID --change isolate_prod_keeps_data|isolate_dev_keeps_data|share_prod_keeps_data|reset_dev --json (shared is the optional creation default; review which data is kept or deleted; no data is copied)",
19272
+ "project data change": "ohmyhost project data change --project ULID --change isolate_prod_keeps_data|isolate_dev_keeps_data|share_prod_keeps_data|reset_dev --if-match ETAG --confirmation-token TOKEN --idempotency-key KEY --yes [--wait] --json (Owner only; use the reviewed plan's guards and the same key after uncertainty; no data is copied)",
18932
19273
  "project list": "ohmyhost project list [--cursor ULID] [--limit LIMIT] --json",
18933
19274
  "project context": "ohmyhost project context --project ULID --json",
18934
19275
  "project notes set": "ohmyhost project notes set --project ULID --version NUMBER --markdown TEXT --idempotency-key KEY --json (no credentials or signed URLs)",
@@ -18939,7 +19280,7 @@ var HELP_USAGE = Object.freeze({
18939
19280
  "project dev-share revoke": "ohmyhost project dev-share revoke --project ULID --idempotency-key KEY --yes --json",
18940
19281
  "project dev-access mode": "ohmyhost project dev-access mode --project ULID --mode protected|public --idempotency-key KEY --yes --json",
18941
19282
  "project flag status": "ohmyhost project flag status --project ULID --json",
18942
- "project flag set": "ohmyhost project flag set --project ULID --enabled true|false --idempotency-key KEY --json (shows the small Powered by ohmyho.st flag on the production site; while it shows, a Free workspace may connect its own domain without the domain fee and each Paid period adds 250 credits)",
19283
+ "project flag set": "ohmyhost project flag set --project ULID --enabled true|false --idempotency-key KEY --json (shows the small Powered by ohmyho.st flag on the production site; while it shows, this project's custom domain uses no domain credits and also works on Free, and each Paid period bought through Stripe adds 250 credits to the workspace, however many projects show the flag)",
18943
19284
  "project handle check": "ohmyhost project handle check --handle HANDLE --json (is this address free? answers with a reason and free alternatives; the address becomes HANDLE.check.omh.st)",
18944
19285
  "project handle set": "ohmyhost project handle set --project ULID --handle HANDLE --if-match ETAG --idempotency-key KEY --json (moves the project to a free address; the old one stops working and anyone may claim it)",
18945
19286
  "database compute set": "ohmyhost database compute set --project ULID --environment dev|prod --profile standard|performance --idempotency-key KEY --yes [--wait] --json\n standard: Free 0.25 CU/1 GB/60s idle, Paid 0.5 CU/2 GB/60s idle. performance: Paid 1 CU/4 GB/300s idle, 2.5x database compute credits per equal active minute; longer idle time also consumes credits.",
@@ -18994,6 +19335,30 @@ var cliMetadataResult = (kind, topic) => kind === "help" ? success("help", {
18994
19335
  environment: "OHMYHOST_ENVIRONMENT=development|production (default: production); OHMYHOST_PROFILE=NAME binds this process to one saved login",
18995
19336
  commands: HELP_COMMANDS.filter((entry) => !topic || entry.command === topic)
18996
19337
  }) : success("version", { status: "succeeded", cli_version: CLI_VERSION });
19338
+ var invalidCommandAction = (argv, commandPrefix) => {
19339
+ const label = recognizedCommandLabel(argv);
19340
+ if (label === "secret set" || label === "secret delete") {
19341
+ const action = label === "secret set" ? "set" : "delete";
19342
+ const at = argv.findIndex((value, index) => value === "secret" && argv[index + 1] === action);
19343
+ const name = at < 0 ? void 0 : argv[at + 2];
19344
+ if (name !== void 0 && !isUserSecretName(name, action))
19345
+ return action === "set" ? "Choose a customer secret name of at most 128 uppercase letters, digits and underscores, starting with a letter. OHMYHOST_* and DATABASE_URL, HYPERDRIVE, BETTER_AUTH_SECRET, BETTER_AUTH_URL, ASSETS, FILES, IMAGES and STORAGE are reserved for the platform. Rename the application's own variable and its source references; platform bindings are installed automatically. Nothing was sent; changing only the idempotency key will not help." : "Choose an existing customer secret name of at most 128 uppercase letters, digits and underscores, starting with a letter. The installed platform keys OHMYHOST_STORAGE_KEY, OHMYHOST_MAIL_KEY and BETTER_AUTH_SECRET cannot be deleted. Older reserved-name collisions can be removed with secret delete; deployed platform keys stay managed by ohmyho.st. Nothing was sent; changing only the idempotency key will not help.";
19346
+ }
19347
+ const usage = Object.hasOwn(HELP_USAGE, label) ? HELP_USAGE[label].split("\n")[0] : void 0;
19348
+ if (usage === void 0)
19349
+ return "Unknown command; nothing was sent. 'ohmyhost --help --json' lists every command with its flags. If a documented command is missing, this CLI is older than https://ohmyho.st/client-release.json: install the published version.";
19350
+ const linkable = /(?:^|\s)--project ULID/u.test(usage) && !argv.includes("--project");
19351
+ return `The arguments of '${label}' are invalid or incomplete; nothing was sent. Usage: ${usage}.${linkable ? ` --project may be omitted only inside a directory linked with '${commandPrefix} link' for this OHMYHOST_ENVIRONMENT; otherwise pass --project ULID.` : ""}`;
19352
+ };
19353
+ var INTERNAL_ERROR_ACTION = "The ohmyhost client stopped on an unexpected error; the stderr line starting with [ohmyhost: identifies it. Compare 'ohmyhost --version' with https://ohmyho.st/client-release.json and install the published client if it is older, then run the same command again with the same --idempotency-key. If the current client fails the same way, report it with feedback submit (kind bug, error code internal_error) and quote that stderr line.";
19354
+ var CREDENTIAL_STORE_UNAVAILABLE_ACTION = "This computer's credential store (for example macOS Keychain or Windows Credential Manager) or the saved-login list in ~/.ohmyhost/profiles could not be read or written; nothing was changed. If the agent runs in a sandbox, run the command with access to them before asking the customer to sign in again. If the store is locked, ask the customer to unlock it, then run the command again. On a server, container or CI runner without a credential store, do not run login: load OHMYHOST_TOKEN from a private env file with a token from the portal's API Tokens page.";
19355
+ var RESPONSE_CONTRACT_INVALID_ACTION = "The ohmyho.st API answered in a form this client version does not accept, usually because the client is older than the service. Compare 'ohmyhost --version' with https://ohmyho.st/client-release.json; if it is older, install the published CLI and MCP and reload MCP. Then run the same command again with the same --idempotency-key: the change may already have been applied. If the current version answers this too, report it with feedback submit (kind bug, error code response_contract_invalid).";
19356
+ var REQUEST_TIMEOUT_ACTION = "The ohmyho.st API did not answer within 30 seconds. Run the same command again with the same --idempotency-key; the change may already be under way, and a new key could apply it twice.";
19357
+ var LOGOUT_UNCONFIRMED_ACTION = "The saved login was removed from this computer, but ohmyho.st did not confirm that its session ended. Running logout again cannot retry this, because the login is gone; do not sign in again just to revoke it. Tell the customer the session end was not confirmed.";
19358
+ var STATEMENT_FILE_INVALID_ACTION = "Put one INSERT, UPDATE or DELETE statement that starts with that keyword (no leading comment or WITH), UTF-8, at most 64 KiB, in the file named by --statement-file, then rerun with the same --idempotency-key.";
19359
+ var signInCommand = (commandPrefix, profile) => profile === void 0 ? `${commandPrefix} login --json` : `${commandPrefix} login${profile.organizationId === null ? "" : ` --organization ${profile.organizationId}`} --user ${profile.userId} --profile-name ${profile.name} --json`;
19360
+ var endedLoginAction = (commandPrefix, profile) => `The sign-in of ${profile === void 0 ? "this computer's saved login" : `saved login ${profile.name} (user ${profile.userId}${profile.organizationId === null ? "" : `, organization ${profile.organizationId}`})`} ended and it was removed. Run '${signInCommand(commandPrefix, profile)}' to sign it in again.`;
19361
+ var refreshFailedAction = (commandPrefix, profile) => `Renewing the saved login${profile === void 0 ? "" : ` ${profile.name}`} at the sign-in service failed. Run the same command again in a minute, with the same --idempotency-key if it has one. If it fails again, run '${signInCommand(commandPrefix, profile)}' and send the customer the link and code.`;
18997
19362
  var ProductCliFailure = class extends Error {
18998
19363
  exitCode;
18999
19364
  code;
@@ -19024,14 +19389,14 @@ var ProductCli = class {
19024
19389
  command = parseProductCliCommand(argv);
19025
19390
  } catch (error) {
19026
19391
  if (error instanceof InvalidCommandError) {
19027
- return failure(recognizedCommandLabel(argv), 2, "invalid_command", false, "Check 'ohmyhost --help'.");
19392
+ return failure(recognizedCommandLabel(argv), 2, "invalid_command", false, invalidCommandAction(argv, this.dependencies.commandPrefix));
19028
19393
  }
19029
19394
  this.recordDiagnostic({
19030
19395
  category: "internal",
19031
19396
  subsystem: "parser",
19032
19397
  errorKind: "unexpected"
19033
19398
  });
19034
- return failure("unknown", 1, "internal_error", false, "Retry the command.");
19399
+ return failure(recognizedCommandLabel(argv), 1, "internal_error", false, INTERNAL_ERROR_ACTION);
19035
19400
  }
19036
19401
  return this.runCommand(command, signal);
19037
19402
  }
@@ -19251,6 +19616,10 @@ var ProductCli = class {
19251
19616
  }
19252
19617
  if (command.kind === "project-create")
19253
19618
  return await this.createProject(command, signal);
19619
+ if (command.kind === "project-data-plan")
19620
+ return await this.planProjectData(command, signal);
19621
+ if (command.kind === "project-data-change")
19622
+ return await this.changeProjectData(command, signal);
19254
19623
  if (command.kind === "project-list")
19255
19624
  return await this.listProjects(command, signal);
19256
19625
  if (command.kind === "project-handle-check")
@@ -19299,25 +19668,15 @@ var ProductCli = class {
19299
19668
  return success(command.kind === "project-dev-share-link" ? "project dev-share link" : command.kind === "project-dev-share-rotate" ? "project dev-share rotate" : command.kind === "project-dev-share-revoke" ? "project dev-share revoke" : "project dev-access mode", { status: "succeeded", state });
19300
19669
  }
19301
19670
  if (command.kind === "database-write") {
19302
- const file = await open3(command.statementFile, "r"), buffer = Buffer.alloc(65537);
19303
- let statement;
19671
+ const statement = await readStatementFile(command.statementFile);
19304
19672
  try {
19305
- const stat3 = await file.stat();
19306
- if (!stat3.isFile() || stat3.size > 65536)
19307
- throw new InvalidCommandError();
19308
- let length = 0;
19309
- while (length < buffer.length) {
19310
- const read = await file.read(buffer, length, buffer.length - length, null);
19311
- if (read.bytesRead === 0)
19312
- break;
19313
- length += read.bytesRead;
19314
- }
19315
- if (length > 65536)
19316
- throw new InvalidCommandError();
19317
- statement = new TextDecoder("utf-8", { fatal: true }).decode(buffer.subarray(0, length));
19318
- } finally {
19319
- buffer.fill(0);
19320
- await file.close();
19673
+ parseProjectDatabaseWrite({
19674
+ environment: command.environment,
19675
+ statement,
19676
+ parameters: []
19677
+ });
19678
+ } catch {
19679
+ throw new ProductCliFailure(2, "statement_file_invalid", false, STATEMENT_FILE_INVALID_ACTION);
19321
19680
  }
19322
19681
  const body = parseProjectDatabaseWrite({
19323
19682
  environment: command.environment,
@@ -19544,8 +19903,13 @@ var ProductCli = class {
19544
19903
  expiresAt
19545
19904
  };
19546
19905
  const account = await this.dependencies.accountProfile(async () => signedIn.accessToken);
19906
+ const loginFlags = {
19907
+ organizationId,
19908
+ userId: expectedUserId,
19909
+ profileName: this.dependencies.profileName
19910
+ };
19547
19911
  if (!isProfileUserId(account.userId))
19548
- throw this.unverifiedLogin();
19912
+ throw this.unverifiedLogin(loginFlags);
19549
19913
  if (expectedUserId !== void 0 && account.userId !== expectedUserId)
19550
19914
  throw new ProductCliFailure(4, "login_account_mismatch", false, `The browser signed in as user ${account.userId}, not ${expectedUserId}. Nothing was saved and every saved login is unchanged. Sign out of ohmyho.st in that browser or use the browser profile of ${expectedUserId}, then run '${this.dependencies.commandPrefix} login${organizationId === void 0 ? "" : ` --organization ${organizationId}`} --user ${expectedUserId} --json' again.`);
19551
19915
  const selection = chooseWorkspace(account.organizations, organizationId);
@@ -19556,12 +19920,12 @@ var ProductCli = class {
19556
19920
  const scopedId = selection.outcome === "selected" ? selection.organization.id : parseCurrentIdentity(identity).organization_ids[0] ?? null;
19557
19921
  const organization = scopedId === null ? null : account.organizations.find((candidate) => candidate.id === scopedId);
19558
19922
  if (organization === void 0)
19559
- throw this.unverifiedLogin();
19923
+ throw this.unverifiedLogin(loginFlags);
19560
19924
  try {
19561
19925
  assertVerifiedLogin(identity, account.userId, scopedId);
19562
19926
  } catch (error) {
19563
19927
  if (error instanceof AccountVerificationError)
19564
- throw this.unverifiedLogin();
19928
+ throw this.unverifiedLogin(loginFlags);
19565
19929
  throw error;
19566
19930
  }
19567
19931
  const login = {
@@ -19605,8 +19969,8 @@ var ProductCli = class {
19605
19969
  }
19606
19970
  }
19607
19971
  /** The provider answered with another account than the one this login requested. */
19608
- unverifiedLogin() {
19609
- return new ProductCliFailure(4, "login_verification_failed", true, `The signed-in account could not be verified; nothing was saved. Run '${this.dependencies.commandPrefix} login --json' again.`);
19972
+ unverifiedLogin(flags) {
19973
+ return new ProductCliFailure(4, "login_verification_failed", true, `The signed-in account could not be verified; nothing was saved. Run '${this.dependencies.commandPrefix} login${flags.organizationId === void 0 ? "" : ` --organization ${flags.organizationId}`}${flags.userId === void 0 ? "" : ` --user ${flags.userId}`}${flags.profileName === void 0 ? "" : ` --profile-name ${flags.profileName}`} --json' again with the same flags and send the customer the new link and code.`);
19610
19974
  }
19611
19975
  /**
19612
19976
  * Bind the login of this run, which has no organization yet, to one of its user's organizations.
@@ -19627,7 +19991,11 @@ var ProductCli = class {
19627
19991
  }, organization, bounded2, (accessToken) => this.dependencies.productApi(async () => accessToken).getCurrentIdentity()), signal, this.dependencies.lifecycleTimeoutMs);
19628
19992
  } catch (error) {
19629
19993
  if (error instanceof AccountVerificationError)
19630
- throw this.unverifiedLogin();
19994
+ throw this.unverifiedLogin({
19995
+ organizationId: organization.id,
19996
+ userId: profile.userId,
19997
+ profileName: profile.name
19998
+ });
19631
19999
  if (!(error instanceof ProfileOrganizationFixedError))
19632
20000
  throw error;
19633
20001
  throw new ProductCliFailure(2, "profile_organization_fixed", false, `Profile ${profile.name} stays signed in to organization ${String(profile.organizationId)}. Run '${this.dependencies.commandPrefix} login --organization ${organization.id} --user ${profile.userId} --json' to add a separate login for ${organization.id} as the same user; a saved login never switches organizations.`);
@@ -19663,10 +20031,10 @@ var ProductCli = class {
19663
20031
  throw new ProductCliFailure(9, "blocked_prerequisite", false, "Remote token revocation is not available in this release.");
19664
20032
  }
19665
20033
  if (isTimeout(revocationError)) {
19666
- throw new ProductCliFailure(8, "request_timeout", true, "Remote revocation timed out; local credentials were removed.");
20034
+ throw new ProductCliFailure(8, "request_timeout", false, LOGOUT_UNCONFIRMED_ACTION);
19667
20035
  }
19668
20036
  if (revocationError instanceof LifecycleTransportError) {
19669
- throw new ProductCliFailure(6, "token_revocation_failed", true, "Remote revocation failed; the local credential was removed.");
20037
+ throw new ProductCliFailure(6, "token_revocation_failed", false, LOGOUT_UNCONFIRMED_ACTION);
19670
20038
  }
19671
20039
  throw revocationError;
19672
20040
  }
@@ -19707,6 +20075,29 @@ var ProductCli = class {
19707
20075
  }));
19708
20076
  return success("operation reconcile", { status: "accepted", reconciliation });
19709
20077
  }
20078
+ async planProjectData(command, signal) {
20079
+ const api = await this.authenticatedApi(command.credentialStore, signal);
20080
+ return success("project data plan", {
20081
+ status: "planned",
20082
+ plan: parseProjectDataChangePlan2(await api.planProjectDataChange({ projectId: command.projectId, change: command.change }))
20083
+ });
20084
+ }
20085
+ async changeProjectData(command, signal) {
20086
+ const api = await this.authenticatedApi(command.credentialStore, signal);
20087
+ let operation = parseOperation(await api.createProjectDataChange({
20088
+ projectId: command.projectId,
20089
+ change: command.change,
20090
+ ifMatch: command.ifMatch,
20091
+ confirmationToken: command.confirmationToken,
20092
+ idempotencyKey: command.idempotencyKey
20093
+ }));
20094
+ if (command.wait)
20095
+ operation = await this.waitForOperation(api, operation, signal);
20096
+ return success("project data change", {
20097
+ status: command.wait ? "completed" : "accepted",
20098
+ operation
20099
+ });
20100
+ }
19710
20101
  async createProject(command, signal) {
19711
20102
  const api = await this.authenticatedApi(command.credentialStore, signal);
19712
20103
  const operation = parseOperation(await api.createProject({
@@ -19960,11 +20351,20 @@ var ProductCli = class {
19960
20351
  commitSha: command.commitSha,
19961
20352
  environment: command.environment
19962
20353
  }));
19963
- let operation = parseOperation(await api.createDeployment({
19964
- projectId: command.projectId,
19965
- planId: plan === null ? String(command.planId) : String(plan["id"]),
19966
- idempotencyKey: command.idempotencyKey
19967
- }));
20354
+ let accepted;
20355
+ try {
20356
+ accepted = await api.createDeployment({
20357
+ projectId: command.projectId,
20358
+ planId: plan === null ? String(command.planId) : String(plan["id"]),
20359
+ idempotencyKey: command.idempotencyKey
20360
+ });
20361
+ } catch (error) {
20362
+ const reused = plan === null ? void 0 : reusedKeyProblem(error);
20363
+ if (reused === void 0)
20364
+ throw error;
20365
+ return failure("deploy", 7, reused.code, false, `This key already started a deployment from an earlier run of this command; a new key would build the same commit again. Run 'ohmyhost project status --project ${command.projectId} --json' and follow latest_operation with 'ohmyhost operation get OPERATION_ULID --json'.`, void 0, void 0, reused.requestId, reused.docsUrl, void 0, reused.message);
20366
+ }
20367
+ let operation = parseOperation(accepted);
19968
20368
  if (command.wait)
19969
20369
  operation = await this.waitForOperation(api, operation, signal);
19970
20370
  const notes = operation.state === "succeeded" ? [
@@ -19979,27 +20379,40 @@ var ProductCli = class {
19979
20379
  }
19980
20380
  async logs(command, signal) {
19981
20381
  const api = await this.authenticatedApi(command.credentialStore, signal);
20382
+ let operation = parseOperation(await api.getOperation(command.operationId));
19982
20383
  let eventCount = 0;
19983
- for await (const event of await api.streamOperationEvents(command.operationId, signal)) {
19984
- const parsed = parseOperationEvent(event);
19985
- this.dependencies.operationEventSink?.write(`${JSON.stringify(parsed)}
20384
+ let lastEventId;
20385
+ for (let connection = 0; connection < OPERATION_STREAM_CONNECTIONS; connection += 1) {
20386
+ if (connection > 0)
20387
+ await this.dependencies.waitUntil(new Date(this.dependencies.now().getTime() + 1e3), signal);
20388
+ const events = lastEventId === void 0 ? await api.streamOperationEvents(command.operationId, signal) : await api.streamOperationEvents(command.operationId, signal, lastEventId);
20389
+ for await (const event of events) {
20390
+ const parsed = parseOperationEvent(event);
20391
+ this.dependencies.operationEventSink?.write(`${JSON.stringify(parsed)}
19986
20392
  `);
19987
- eventCount += 1;
19988
- if (parsed["type"] === "OperationSucceeded" || parsed["type"] === "OperationFailed" || parsed["type"] === "OperationCancelled") {
19989
- break;
20393
+ eventCount += 1;
20394
+ lastEventId = String(parsed["event_id"]);
20395
+ if (parsed["type"] === "OperationSucceeded" || parsed["type"] === "OperationFailed" || parsed["type"] === "OperationCancelled")
20396
+ return success("logs", { status: "completed", event_count: eventCount });
19990
20397
  }
20398
+ throwIfAborted(signal);
20399
+ operation = parseOperation(await api.getOperation(command.operationId));
20400
+ if (operation.state !== "queued" && operation.state !== "running")
20401
+ return success("logs", { status: "completed", event_count: eventCount, operation });
19991
20402
  }
19992
- return success("logs", { status: "completed", event_count: eventCount });
20403
+ throw new ProductCliFailure(8, "operation_stream_ended", true, `The event stream ended before operation ${command.operationId} finished, and it is still running; nothing was cancelled. Poll 'ohmyhost operation get ${command.operationId} --json' about every 60 seconds until it is succeeded, failed or cancelled.`, operation);
19993
20404
  }
19994
20405
  async deploymentLogs(command, signal) {
19995
20406
  const api = await this.authenticatedApi(command.credentialStore, signal);
19996
- let count = 0;
19997
- for await (const event of await api.streamDeploymentLogs({ projectId: command.projectId, deploymentId: command.deploymentId }, signal)) {
19998
- this.dependencies.operationEventSink?.write(`${JSON.stringify(event)}
20407
+ const page = await api.getDeploymentLogs({
20408
+ projectId: command.projectId,
20409
+ deploymentId: command.deploymentId,
20410
+ limit: 100
20411
+ });
20412
+ for (const item of page.items)
20413
+ this.dependencies.operationEventSink?.write(`${JSON.stringify(item)}
19999
20414
  `);
20000
- count += 1;
20001
- }
20002
- return success("deployment logs", { status: "completed", event_count: count });
20415
+ return success("deployment logs", { status: "completed", event_count: page.items.length });
20003
20416
  }
20004
20417
  async planRollback(command, signal) {
20005
20418
  const api = await this.authenticatedApi(command.credentialStore, signal);
@@ -20077,7 +20490,7 @@ var ProductCli = class {
20077
20490
  }));
20078
20491
  const completed = command.wait ? await this.waitForSecretDelivery(api, command.projectId, command.environmentId, secret, signal) : secret;
20079
20492
  return success("secret set", {
20080
- status: command.wait ? "ready" : "stored",
20493
+ status: command.wait && completed.delivery_state === "ready" ? "ready" : "stored",
20081
20494
  secret: completed
20082
20495
  });
20083
20496
  } finally {
@@ -20094,7 +20507,7 @@ var ProductCli = class {
20094
20507
  }));
20095
20508
  const delivery = command.wait ? await this.waitForSecretDelivery(api, command.projectId, command.environmentId, { desired_generation: deleted.desired_generation }, signal) : deleted;
20096
20509
  return success("secret delete", {
20097
- status: command.wait ? "ready" : deleted.status,
20510
+ status: command.wait && delivery.delivery_state === "ready" ? "ready" : deleted.status,
20098
20511
  secret: { name: deleted.name, ...secretDeliveryProjection(delivery) }
20099
20512
  });
20100
20513
  }
@@ -20103,9 +20516,9 @@ var ProductCli = class {
20103
20516
  for (let attempt = 0; attempt < 120; attempt += 1) {
20104
20517
  const page = parseEnvironmentSecretPage(await api.listEnvironmentSecrets({ projectId, environmentId }));
20105
20518
  if (page.last_error !== null || page.delivery_state === "failed") {
20106
- throw new ProductCliFailure(10, "secret_delivery_failed", true, "Inspect secret metadata and deployment logs, then retry reconciliation.");
20519
+ throw new ProductCliFailure(10, "secret_delivery_failed", true, `The secret is saved, but delivering it to the running app failed (last_error in 'ohmyhost secret list --project ${projectId} --environment ${environmentId} --json'). Do not set the value again: ohmyho.st retries delivery automatically, and the next deployment of this environment also delivers it. Read secret list again in a few minutes; if delivery_state stays failed, report last_error with feedback submit (kind bug, error code secret_delivery_failed).`);
20107
20520
  }
20108
- if (page.delivery_state === "ready" && page.desired_generation >= desiredGeneration && page.applied_generation >= desiredGeneration) {
20521
+ if (page.delivery_state === "not_deployed" || page.delivery_state === "ready" && page.desired_generation >= desiredGeneration && page.applied_generation >= desiredGeneration) {
20109
20522
  if (!("name" in stored))
20110
20523
  return page;
20111
20524
  return page.items.find((secret) => secret.name === stored.name) ?? {
@@ -20117,7 +20530,7 @@ var ProductCli = class {
20117
20530
  }
20118
20531
  await this.dependencies.waitUntil(new Date(this.dependencies.now().getTime() + 1e3), signal);
20119
20532
  }
20120
- throw new ProductCliFailure(8, "secret_delivery_wait_timeout", true, "Run 'ohmyhost secret list --json' to inspect delivery metadata.");
20533
+ throw new ProductCliFailure(8, "secret_delivery_wait_timeout", true, `The secret is saved, but it had not reached the running app after about 2 minutes. Read 'ohmyhost secret list --project ${projectId} --environment ${environmentId} --json': pending means delivery is still under way, and not_deployed means it arrives with the first deployment. Do not set the value again.`);
20121
20534
  }
20122
20535
  async waitForOperation(api, initial, signal) {
20123
20536
  let operation = initial;
@@ -20128,7 +20541,7 @@ var ProductCli = class {
20128
20541
  if (operation.reconciliation?.state === "required")
20129
20542
  throw new ProductCliFailure(10, "operation_reconciliation_required", false, `${operation.reconciliation.suggested_action} Inspect 'ohmyhost operation get ${operation.id} --json' and use 'ohmyhost operation reconcile' after confirmation.`, operation);
20130
20543
  if (operation.state === "queued" || operation.state === "running")
20131
- throw new ProductCliFailure(8, "operation_wait_timeout", true, `Run 'ohmyhost operation get ${operation.id} --json' to inspect the existing operation before resubmitting a mutation.`, operation);
20544
+ throw new ProductCliFailure(8, "operation_wait_timeout", true, `The operation is still running; this command stopped waiting after about 2 minutes and cancelled nothing. Poll 'ohmyhost operation get ${operation.id} --json' about every 60 seconds until it is succeeded, failed or cancelled. Do not submit the same change again; a new key starts a second operation.`, operation);
20132
20545
  if (operation.state === "failed" && operation.error !== void 0)
20133
20546
  throw new ProductCliFailure(10, operation.error.code, operation.error.retryable, operation.error.suggested_action, operation);
20134
20547
  if (operation.state === "cancelled")
@@ -20153,8 +20566,9 @@ var ProductCli = class {
20153
20566
  } catch (error) {
20154
20567
  if (!(error instanceof TokenRefreshRejectedError))
20155
20568
  throw error;
20569
+ const ended = this.#profile;
20156
20570
  await this.forgetLogin(store);
20157
- throw new ProductCliFailure(5, "authentication_required", false, this.loginAction());
20571
+ throw new ProductCliFailure(5, "authentication_required", false, endedLoginAction(this.dependencies.commandPrefix, ended));
20158
20572
  }
20159
20573
  if (refresh.refreshed)
20160
20574
  await store.replace(refresh.credential);
@@ -20187,10 +20601,10 @@ var ProductCli = class {
20187
20601
  throw new ProductCliFailure(9, "blocked_prerequisite", false, "Token refresh is not available in this release; run 'ohmyhost login'.");
20188
20602
  }
20189
20603
  if (isTimeout(error)) {
20190
- throw new ProductCliFailure(8, "request_timeout", true, "Retry the command.");
20604
+ throw new ProductCliFailure(8, "request_timeout", true, REQUEST_TIMEOUT_ACTION);
20191
20605
  }
20192
20606
  if (error instanceof LifecycleTransportError) {
20193
- throw new ProductCliFailure(5, "token_refresh_failed", true, "Run 'ohmyhost login' if retrying does not succeed.");
20607
+ throw new ProductCliFailure(5, "token_refresh_failed", true, refreshFailedAction(this.dependencies.commandPrefix, this.#profile));
20194
20608
  }
20195
20609
  throw error;
20196
20610
  }
@@ -20231,31 +20645,31 @@ var ACCOUNT_SENSITIVE_PROBLEMS = /* @__PURE__ */ new Set([
20231
20645
  var classifyCliFailure = (command, error, signal) => classifyFailure(command, error, signal);
20232
20646
  var classifyFailure = (command, error, signal, actingAs) => {
20233
20647
  if (signal.aborted || isCancellation(error)) {
20234
- return failure(command, 130, "command_cancelled", true, "Run the command again.");
20648
+ return failure(command, 130, "command_cancelled", true, "The command was stopped by an interrupt or termination signal, for example the shell's command timeout, before it finished. Run it again with the same --idempotency-key; a change may already be under way. Give login and commands with --wait or --follow several minutes, or run them in the background.");
20235
20649
  }
20236
20650
  if (isTimeout(error)) {
20237
- return failure(command, 8, "request_timeout", true, "Retry the command.");
20651
+ return failure(command, 8, "request_timeout", true, REQUEST_TIMEOUT_ACTION);
20238
20652
  }
20239
20653
  if (error instanceof ProductCliFailure) {
20240
20654
  return failure(command, error.exitCode, error.code, error.retryable, error.suggestedAction, error.operation);
20241
20655
  }
20242
20656
  if (error instanceof SecretInputError) {
20243
- return failure(command, 2, "secret_input_invalid", false, "Pipe a non-empty UTF-8 value of at most 5120 bytes through standard input.");
20657
+ return failure(command, 2, "secret_input_invalid", false, command === "export create" ? "Pipe the ZIP password through standard input: non-blank UTF-8 of at most 1024 bytes. A final line break becomes part of the password." : "Pipe a non-empty UTF-8 value of at most 5120 bytes through standard input.");
20244
20658
  }
20245
20659
  if (error instanceof WorkosContractError) {
20246
20660
  if (error.code === "access_denied") {
20247
- return failure(command, 4, "device_authorization_denied", false, "Run login again.");
20661
+ return failure(command, 4, "device_authorization_denied", false, "The customer declined the sign-in in the browser; nothing was saved. Ask the customer whether to sign in, then run the same login command again and send them the new link and code.");
20248
20662
  }
20249
20663
  if (error.code === "expired_token") {
20250
- return failure(command, 4, "device_authorization_expired", false, "Run login again.");
20664
+ return failure(command, 4, "device_authorization_expired", false, "Nobody finished the sign-in within 30 minutes; nothing was saved. Run the same login command again and send the customer the new link and code at once.");
20251
20665
  }
20252
- return failure(command, 4, "device_authorization_failed", true, "Run login again.");
20666
+ return failure(command, 4, "device_authorization_failed", true, "The sign-in service answered unexpectedly; nothing was saved. Run the same login command again; if it fails twice more, ask the customer to try again later.");
20253
20667
  }
20254
20668
  if (error instanceof CredentialStoreUnavailableError) {
20255
- return failure(command, 3, "credential_store_unavailable", true, "Unlock or repair the selected credential store and retry.");
20669
+ return failure(command, 3, "credential_store_unavailable", false, CREDENTIAL_STORE_UNAVAILABLE_ACTION);
20256
20670
  }
20257
20671
  if (error instanceof ResponseContractError) {
20258
- return failure(command, 7, "response_contract_invalid", false, "Retry only after the client or server contract is repaired.");
20672
+ return failure(command, 7, "response_contract_invalid", false, RESPONSE_CONTRACT_INVALID_ACTION);
20259
20673
  }
20260
20674
  if (error instanceof PublicResponseTooLargeError) {
20261
20675
  return failure(command, 7, "response_too_large", false, "Retry only after the remote response is repaired.");
@@ -20263,18 +20677,61 @@ var classifyFailure = (command, error, signal, actingAs) => {
20263
20677
  if (looksLikeProblem(error)) {
20264
20678
  try {
20265
20679
  const problem = parseSafeProblem(error);
20266
- return failure(command, 7, problem.code, problem.retryable, problem.suggestedAction, void 0, problem.retryAfterSeconds, problem.requestId, problem.docsUrl, ACCOUNT_SENSITIVE_PROBLEMS.has(problem.code) ? actingAs : void 0);
20680
+ return failure(command, 7, problem.code, problem.retryable, problem.suggestedAction, void 0, problem.retryAfterSeconds, problem.requestId, problem.docsUrl, ACCOUNT_SENSITIVE_PROBLEMS.has(problem.code) ? actingAs : void 0, problem.message);
20267
20681
  } catch {
20268
- return failure(command, 7, "response_contract_invalid", false, "Retry only after the client or server contract is repaired.");
20682
+ return failure(command, 7, "response_contract_invalid", false, RESPONSE_CONTRACT_INVALID_ACTION);
20269
20683
  }
20270
20684
  }
20271
20685
  if (looksLikeRemoteObject(error)) {
20272
- return failure(command, 7, "response_contract_invalid", false, "Retry only after the client or server contract is repaired.");
20686
+ return failure(command, 7, "response_contract_invalid", false, RESPONSE_CONTRACT_INVALID_ACTION);
20273
20687
  }
20274
20688
  if (error instanceof NetworkTransportError) {
20275
- return failure(command, 8, "network_error", true, "Check connectivity and retry.");
20689
+ return failure(command, 8, "network_error", true, "The request did not reach the ohmyho.st API, or its answer was cut off. If the agent runs in a sandbox without network access, run the command with network access. Otherwise run the same command again with the same --idempotency-key; the change may already have been applied, and a new key could apply it twice.");
20690
+ }
20691
+ return failure(command, 1, "internal_error", false, INTERNAL_ERROR_ACTION);
20692
+ };
20693
+ var reusedKeyProblem = (error) => {
20694
+ if (!looksLikeProblem(error))
20695
+ return void 0;
20696
+ try {
20697
+ const problem = parseSafeProblem(error);
20698
+ return problem.code === "idempotency_key_reused" ? problem : void 0;
20699
+ } catch {
20700
+ return void 0;
20701
+ }
20702
+ };
20703
+ var OPERATION_STREAM_CONNECTIONS = 4;
20704
+ var readStatementFile = async (path) => {
20705
+ const invalid5 = () => new ProductCliFailure(2, "statement_file_invalid", false, STATEMENT_FILE_INVALID_ACTION);
20706
+ let file;
20707
+ try {
20708
+ file = await open3(path, "r");
20709
+ } catch {
20710
+ throw invalid5();
20711
+ }
20712
+ const buffer = Buffer.alloc(65537);
20713
+ try {
20714
+ const stat3 = await file.stat();
20715
+ if (!stat3.isFile() || stat3.size > 65536)
20716
+ throw invalid5();
20717
+ let length = 0;
20718
+ while (length < buffer.length) {
20719
+ const read = await file.read(buffer, length, buffer.length - length, null);
20720
+ if (read.bytesRead === 0)
20721
+ break;
20722
+ length += read.bytesRead;
20723
+ }
20724
+ if (length > 65536)
20725
+ throw invalid5();
20726
+ return new TextDecoder("utf-8", { fatal: true }).decode(buffer.subarray(0, length));
20727
+ } catch (error) {
20728
+ if (error instanceof ProductCliFailure)
20729
+ throw error;
20730
+ throw invalid5();
20731
+ } finally {
20732
+ buffer.fill(0);
20733
+ await file.close();
20276
20734
  }
20277
- return failure(command, 1, "internal_error", false, "Retry the command.");
20278
20735
  };
20279
20736
  var psqlUnavailable = () => failure("database psql", 9, "psql_unavailable", false, "Install the PostgreSQL client (psql) and make it available on PATH, or run 'ohmyhost database access create' and connect with your own SQL client.");
20280
20737
  var success = (command, body) => ({
@@ -20283,7 +20740,7 @@ var success = (command, body) => ({
20283
20740
  `,
20284
20741
  stderr: ""
20285
20742
  });
20286
- var failure = (command, exitCode, code, retryable, suggestedAction, operation, retryAfterSeconds, requestId, docsUrl, actingAs) => {
20743
+ var failure = (command, exitCode, code, retryable, suggestedAction, operation, retryAfterSeconds, requestId, docsUrl, actingAs, message) => {
20287
20744
  const document = {
20288
20745
  version: 1,
20289
20746
  command,
@@ -20297,6 +20754,7 @@ var failure = (command, exitCode, code, retryable, suggestedAction, operation, r
20297
20754
  ...requestId === void 0 ? {} : { request_id: requestId },
20298
20755
  ...docsUrl === void 0 ? {} : { docs_url: docsUrl },
20299
20756
  ...operation?.error === void 0 ? {} : { message: operation.error.message },
20757
+ ...message === void 0 ? {} : { message },
20300
20758
  ...actingAs === void 0 ? {} : { acting_as: actingAs }
20301
20759
  }
20302
20760
  };
@@ -20346,6 +20804,10 @@ var parsedCommandName = (command) => {
20346
20804
  return "feedback status";
20347
20805
  if (command.kind === "project-create")
20348
20806
  return "project create";
20807
+ if (command.kind === "project-data-plan")
20808
+ return "project data plan";
20809
+ if (command.kind === "project-data-change")
20810
+ return "project data change";
20349
20811
  if (command.kind === "organization-create")
20350
20812
  return "organization create";
20351
20813
  if (command.kind === "organization-list")
@@ -22459,24 +22921,6 @@ var getDeploymentLogs = (parameters, options) => {
22459
22921
  ...params
22460
22922
  });
22461
22923
  };
22462
- var streamDeploymentLogs = (parameters, options) => {
22463
- const params = buildClientParams([parameters], [
22464
- {
22465
- args: [
22466
- { in: "headers", key: "X-Request-Id" },
22467
- { in: "path", key: "project_id" },
22468
- { in: "path", key: "deployment_id" }
22469
- ]
22470
- }
22471
- ]);
22472
- return (options?.client ?? client).sse.get({
22473
- responseStyle: "data",
22474
- security: [{ scheme: "bearer", type: "http" }],
22475
- url: "/v1/projects/{project_id}/deployments/{deployment_id}/logs/events",
22476
- ...options,
22477
- ...params
22478
- });
22479
- };
22480
22924
  var getDatabaseCompute = (parameters, options) => {
22481
22925
  const params = buildClientParams([parameters], [
22482
22926
  {
@@ -23023,6 +23467,55 @@ var retryManagedMailMessage = (parameters, options) => {
23023
23467
  ...params
23024
23468
  });
23025
23469
  };
23470
+ var planProjectDataChange = (parameters, options) => {
23471
+ const params = buildClientParams([parameters], [
23472
+ {
23473
+ args: [
23474
+ { in: "headers", key: "X-Request-Id" },
23475
+ { in: "path", key: "project_id" },
23476
+ { key: "projectDataChangeRequest", map: "body" }
23477
+ ]
23478
+ }
23479
+ ]);
23480
+ return (options?.client ?? client).post({
23481
+ responseStyle: "data",
23482
+ security: [{ scheme: "bearer", type: "http" }],
23483
+ url: "/v1/projects/{project_id}/data-changes:plan",
23484
+ ...options,
23485
+ ...params,
23486
+ headers: {
23487
+ "Content-Type": "application/json",
23488
+ ...options?.headers,
23489
+ ...params.headers
23490
+ }
23491
+ });
23492
+ };
23493
+ var createProjectDataChange = (parameters, options) => {
23494
+ const params = buildClientParams([parameters], [
23495
+ {
23496
+ args: [
23497
+ { in: "headers", key: "X-Request-Id" },
23498
+ { in: "headers", key: "Idempotency-Key" },
23499
+ { in: "headers", key: "If-Match" },
23500
+ { in: "headers", key: "X-Confirmation-Token" },
23501
+ { in: "path", key: "project_id" },
23502
+ { key: "projectDataChangeRequest", map: "body" }
23503
+ ]
23504
+ }
23505
+ ]);
23506
+ return (options?.client ?? client).post({
23507
+ responseStyle: "data",
23508
+ security: [{ scheme: "bearer", type: "http" }],
23509
+ url: "/v1/projects/{project_id}/data-changes",
23510
+ ...options,
23511
+ ...params,
23512
+ headers: {
23513
+ "Content-Type": "application/json",
23514
+ ...options?.headers,
23515
+ ...params.headers
23516
+ }
23517
+ });
23518
+ };
23026
23519
 
23027
23520
  // packages/sdk-ts/dist/managed-mail-response.js
23028
23521
  var ULID = /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u;
@@ -23045,30 +23538,30 @@ function assertManagedMailResponse(value, input) {
23045
23538
  id2(r["domain_id"]);
23046
23539
  domain(r["domain"]);
23047
23540
  if (r["environment_id"] !== input.environmentId)
23048
- invalid3();
23541
+ invalid4();
23049
23542
  for (const key of ["sending", "receiving"])
23050
23543
  if (!["disabled", "pending", "ready", "failed"].includes(String(r[key])))
23051
- invalid3();
23544
+ invalid4();
23052
23545
  if (!["ready", "verification_pending"].includes(String(r["status"])))
23053
- invalid3();
23546
+ invalid4();
23054
23547
  time(r["configured_at"]);
23055
23548
  if (r["observed_at"] !== null)
23056
23549
  time(r["observed_at"]);
23057
23550
  records(r["dns_records"]);
23058
23551
  if (r["next_check_after_seconds"] !== null && r["next_check_after_seconds"] !== 60)
23059
- invalid3();
23552
+ invalid4();
23060
23553
  if (r["webhook"] !== null) {
23061
23554
  const hook = object2(r["webhook"]);
23062
23555
  exact3(hook, ["url", "verified"]);
23063
23556
  https(hook["url"]);
23064
23557
  if (typeof hook["verified"] !== "boolean")
23065
- invalid3();
23558
+ invalid4();
23066
23559
  }
23067
23560
  if (r["dns"] !== void 0) {
23068
23561
  const dns = object2(r["dns"]);
23069
23562
  exact3(dns, ["status", "records"]);
23070
23563
  if (!["configured", "action_required", "conflict"].includes(String(dns["status"])))
23071
- invalid3();
23564
+ invalid4();
23072
23565
  records(dns["records"]);
23073
23566
  }
23074
23567
  return;
@@ -23076,19 +23569,19 @@ function assertManagedMailResponse(value, input) {
23076
23569
  if (input.action === "webhook_set") {
23077
23570
  exact3(r, ["url", "signing_secret", "status"]);
23078
23571
  if (r["url"] !== input.url || r["status"] !== "verification_required" || typeof r["signing_secret"] !== "string" || !/^whsec_[A-Za-z0-9+/]{43}=$/u.test(r["signing_secret"]))
23079
- invalid3();
23572
+ invalid4();
23080
23573
  return;
23081
23574
  }
23082
23575
  if (input.action === "webhook_disable") {
23083
23576
  exact3(r, ["status"]);
23084
23577
  if (r["status"] !== "disabled")
23085
- invalid3();
23578
+ invalid4();
23086
23579
  return;
23087
23580
  }
23088
23581
  if (input.action === "domain_delete") {
23089
23582
  exact3(r, ["status", "domain", "dns_records"]);
23090
23583
  if (r["status"] !== "deleted")
23091
- invalid3();
23584
+ invalid4();
23092
23585
  domain(r["domain"]);
23093
23586
  records(r["dns_records"]);
23094
23587
  return;
@@ -23096,25 +23589,25 @@ function assertManagedMailResponse(value, input) {
23096
23589
  if (input.action === "messages_list") {
23097
23590
  exact3(r, ["items"]);
23098
23591
  if (!Array.isArray(r["items"]) || r["items"].length > 50)
23099
- invalid3();
23592
+ invalid4();
23100
23593
  for (const value2 of r["items"]) {
23101
23594
  const item = object2(value2);
23102
23595
  exact3(item, ["message_id", "received_at", "state", "attempts"]);
23103
23596
  id2(item["message_id"]);
23104
23597
  time(item["received_at"]);
23105
23598
  if (!["pending", "delivering", "delivered", "delivery_failed", "expired"].includes(String(item["state"])) || !Number.isInteger(item["attempts"]) || Number(item["attempts"]) < 0 || Number(item["attempts"]) > 4)
23106
- invalid3();
23599
+ invalid4();
23107
23600
  }
23108
23601
  return;
23109
23602
  }
23110
23603
  if (input.action === "message_retry") {
23111
23604
  exact3(r, ["attempted"], ["delivered"]);
23112
23605
  if (typeof r["attempted"] !== "boolean" || r["delivered"] !== void 0 && typeof r["delivered"] !== "boolean")
23113
- invalid3();
23606
+ invalid4();
23114
23607
  return;
23115
23608
  }
23116
23609
  if (input.action !== "message_get")
23117
- invalid3();
23610
+ invalid4();
23118
23611
  if (r["status"] === "expired") {
23119
23612
  exact3(r, ["status"]);
23120
23613
  return;
@@ -23133,65 +23626,65 @@ function assertManagedMailResponse(value, input) {
23133
23626
  "attachments"
23134
23627
  ]);
23135
23628
  if (r["id"] !== input.messageId)
23136
- invalid3();
23629
+ invalid4();
23137
23630
  domain(r["domain"]);
23138
23631
  time(r["received_at"]);
23139
23632
  time(r["expires_at"]);
23140
23633
  if (Date.parse(String(r["expires_at"])) - Date.parse(String(r["received_at"])) !== 72 * 36e5)
23141
- invalid3();
23634
+ invalid4();
23142
23635
  for (const key of ["from", "subject", "message_id"])
23143
23636
  if (typeof r[key] !== "string")
23144
- invalid3();
23637
+ invalid4();
23145
23638
  for (const key of ["text", "html"])
23146
23639
  if (r[key] !== null && typeof r[key] !== "string")
23147
- invalid3();
23640
+ invalid4();
23148
23641
  if (!Array.isArray(r["to"]) || r["to"].some((v) => typeof v !== "string" || v.slice(v.lastIndexOf("@") + 1).toLowerCase() !== r["domain"]))
23149
- invalid3();
23642
+ invalid4();
23150
23643
  if (!Array.isArray(r["attachments"]))
23151
- invalid3();
23644
+ invalid4();
23152
23645
  for (const value2 of r["attachments"]) {
23153
23646
  const a = object2(value2);
23154
23647
  exact3(a, ["id", "filename", "content_type", "download_path"]);
23155
23648
  if (typeof a["id"] !== "string" || !/^[A-Za-z0-9_-]{1,128}$/u.test(a["id"]) || a["filename"] !== null && typeof a["filename"] !== "string" || typeof a["content_type"] !== "string" || a["download_path"] !== `/v1/projects/${input.projectId}/environments/${input.environmentId}/mail/messages/${input.messageId}/attachments/${a["id"]}`)
23156
- invalid3();
23649
+ invalid4();
23157
23650
  }
23158
23651
  }
23159
23652
  function object2(v) {
23160
23653
  if (typeof v !== "object" || v === null || Array.isArray(v))
23161
- invalid3();
23654
+ invalid4();
23162
23655
  return v;
23163
23656
  }
23164
23657
  function exact3(v, required, optional = []) {
23165
23658
  if (required.some((k) => !(k in v)) || Object.keys(v).some((k) => !required.includes(k) && !optional.includes(k)))
23166
- invalid3();
23659
+ invalid4();
23167
23660
  }
23168
23661
  function id2(v) {
23169
23662
  if (typeof v !== "string" || !ULID.test(v))
23170
- invalid3();
23663
+ invalid4();
23171
23664
  }
23172
23665
  function domain(v) {
23173
23666
  if (typeof v !== "string" || !/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/u.test(v))
23174
- invalid3();
23667
+ invalid4();
23175
23668
  }
23176
23669
  function time(v) {
23177
23670
  if (typeof v !== "string" || !Number.isFinite(Date.parse(v)) || new Date(v).toISOString() !== v)
23178
- invalid3();
23671
+ invalid4();
23179
23672
  }
23180
23673
  function https(v) {
23181
23674
  if (typeof v !== "string" || !v.startsWith("https://"))
23182
- invalid3();
23675
+ invalid4();
23183
23676
  }
23184
23677
  function records(v) {
23185
23678
  if (!Array.isArray(v))
23186
- invalid3();
23679
+ invalid4();
23187
23680
  for (const value of v) {
23188
23681
  const r = object2(value);
23189
23682
  exact3(r, ["type", "name", "value", "purpose", "status"], ["priority"]);
23190
23683
  if (!["TXT", "CNAME", "MX"].includes(String(r["type"])) || ["name", "value", "purpose", "status"].some((k) => typeof r[k] !== "string") || r["type"] === "MX" && (!Number.isInteger(r["priority"]) || Number(r["priority"]) < 0 || Number(r["priority"]) > 65535))
23191
- invalid3();
23684
+ invalid4();
23192
23685
  }
23193
23686
  }
23194
- function invalid3() {
23687
+ function invalid4() {
23195
23688
  throw new TypeError("Mail response contract invalid");
23196
23689
  }
23197
23690
 
@@ -23489,6 +23982,18 @@ var GeneratedSdkProductApi = class {
23489
23982
  async getFeedback(feedbackId, cursor) {
23490
23983
  return parseFeedbackStatus(await getFeedback({ feedback_id: feedbackId, ...cursor === void 0 ? {} : { cursor } }, { client: this.#client }));
23491
23984
  }
23985
+ async planProjectDataChange(input) {
23986
+ return parseProjectDataChangePlan2(await planProjectDataChange({ project_id: input.projectId, projectDataChangeRequest: { change: input.change } }, { client: this.#client }));
23987
+ }
23988
+ async createProjectDataChange(input) {
23989
+ return parseOperation(await createProjectDataChange({
23990
+ project_id: input.projectId,
23991
+ projectDataChangeRequest: { change: input.change },
23992
+ "If-Match": input.ifMatch,
23993
+ "X-Confirmation-Token": input.confirmationToken,
23994
+ "Idempotency-Key": input.idempotencyKey
23995
+ }, { client: this.#client }));
23996
+ }
23492
23997
  async createProject(input) {
23493
23998
  return createProject({
23494
23999
  "Idempotency-Key": input.idempotencyKey,
@@ -23569,8 +24074,17 @@ var GeneratedSdkProductApi = class {
23569
24074
  async getDeployment(input) {
23570
24075
  return getDeployment({ project_id: input.projectId, deployment_id: input.deploymentId }, { client: this.#client });
23571
24076
  }
23572
- async streamOperationEvents(operationId, signal) {
23573
- return (await streamOperationEvents({ operation_id: operationId }, { client: this.#client, signal, headers: { Accept: "text/event-stream" } })).stream;
24077
+ async streamOperationEvents(operationId, signal, lastEventId) {
24078
+ return (await streamOperationEvents({ operation_id: operationId }, {
24079
+ client: this.#client,
24080
+ signal,
24081
+ // The generated client would otherwise retry a refused stream, such as a 404, forever.
24082
+ sseMaxRetryAttempts: 1,
24083
+ headers: {
24084
+ Accept: "text/event-stream",
24085
+ ...lastEventId === void 0 ? {} : { "Last-Event-ID": lastEventId }
24086
+ }
24087
+ })).stream;
23574
24088
  }
23575
24089
  async getDeploymentLogs(input) {
23576
24090
  return getDeploymentLogs({
@@ -23580,9 +24094,6 @@ var GeneratedSdkProductApi = class {
23580
24094
  ...input.cursor === void 0 ? {} : { cursor: input.cursor }
23581
24095
  }, { client: this.#client });
23582
24096
  }
23583
- async streamDeploymentLogs(input, signal) {
23584
- return (await streamDeploymentLogs({ project_id: input.projectId, deployment_id: input.deploymentId }, { client: this.#client, signal, headers: { Accept: "text/event-stream" } })).stream;
23585
- }
23586
24097
  async planRollback(input) {
23587
24098
  return planDeploymentRollback({ project_id: input.projectId, target_deployment_id: input.deploymentId }, { client: this.#client });
23588
24099
  }
@@ -23951,6 +24462,9 @@ var ohmyhost_schema_default = {
23951
24462
  maxLength: 256,
23952
24463
  pattern: "^/"
23953
24464
  },
24465
+ browser: {
24466
+ $ref: "#/$defs/browser"
24467
+ },
23954
24468
  egress: {
23955
24469
  type: "object",
23956
24470
  additionalProperties: false,
@@ -23958,7 +24472,7 @@ var ohmyhost_schema_default = {
23958
24472
  properties: {
23959
24473
  allow: {
23960
24474
  type: "array",
23961
- maxItems: 16,
24475
+ maxItems: 13,
23962
24476
  uniqueItems: true,
23963
24477
  items: {
23964
24478
  type: "string",
@@ -23971,6 +24485,56 @@ var ohmyhost_schema_default = {
23971
24485
  }
23972
24486
  }
23973
24487
  },
24488
+ browserOrigins: {
24489
+ type: "array",
24490
+ maxItems: 16,
24491
+ uniqueItems: true,
24492
+ items: {
24493
+ type: "string",
24494
+ minLength: 1,
24495
+ maxLength: 256,
24496
+ pattern: "^https://"
24497
+ }
24498
+ },
24499
+ browser: {
24500
+ type: "object",
24501
+ additionalProperties: false,
24502
+ properties: {
24503
+ connect: {
24504
+ type: "array",
24505
+ maxItems: 16,
24506
+ uniqueItems: true,
24507
+ items: {
24508
+ type: "string",
24509
+ minLength: 1,
24510
+ maxLength: 256,
24511
+ pattern: "^(?:https|wss)://"
24512
+ }
24513
+ },
24514
+ scripts: { $ref: "#/$defs/browserOrigins" },
24515
+ styles: { $ref: "#/$defs/browserOrigins" },
24516
+ images: { $ref: "#/$defs/browserOrigins" },
24517
+ fonts: { $ref: "#/$defs/browserOrigins" },
24518
+ frames: { $ref: "#/$defs/browserOrigins" },
24519
+ frameAncestors: { $ref: "#/$defs/browserOrigins" },
24520
+ workers: {
24521
+ type: "object",
24522
+ additionalProperties: false,
24523
+ properties: {
24524
+ self: { type: "boolean" },
24525
+ blob: { type: "boolean" }
24526
+ }
24527
+ },
24528
+ cors: {
24529
+ type: "object",
24530
+ additionalProperties: false,
24531
+ properties: {
24532
+ origins: { $ref: "#/$defs/browserOrigins" },
24533
+ credentials: { type: "boolean" }
24534
+ }
24535
+ }
24536
+ }
24537
+ },
23974
24538
  database: {
23975
24539
  type: "object",
23976
24540
  additionalProperties: false,
@@ -24228,6 +24792,74 @@ function validCronField(value, minimum, maximum) {
24228
24792
  return parsed >= minimum && parsed <= maximum;
24229
24793
  }
24230
24794
 
24795
+ // packages/contracts/dist/browser-capabilities.js
24796
+ var RESOURCE_GROUPS = [
24797
+ "connect",
24798
+ "scripts",
24799
+ "styles",
24800
+ "images",
24801
+ "fonts",
24802
+ "frames",
24803
+ "frameAncestors"
24804
+ ];
24805
+ var POLICY_KEYS = [...RESOURCE_GROUPS, "workers", "cors"];
24806
+ var MAX_ORIGINS = 16;
24807
+ function normalizeBrowserPolicy(value) {
24808
+ const input = value === void 0 ? {} : policyRecord(value, POLICY_KEYS);
24809
+ const workers = input["workers"] === void 0 ? {} : policyRecord(input["workers"], ["self", "blob"]);
24810
+ const cors = input["cors"] === void 0 ? {} : policyRecord(input["cors"], ["origins", "credentials"]);
24811
+ const resources = Object.fromEntries(RESOURCE_GROUPS.map((group) => [group, normalizeOrigins(input[group], group === "connect")]));
24812
+ return Object.freeze({
24813
+ ...resources,
24814
+ workers: Object.freeze({
24815
+ self: normalizeBoolean(workers["self"]),
24816
+ blob: normalizeBoolean(workers["blob"])
24817
+ }),
24818
+ cors: Object.freeze({
24819
+ origins: normalizeOrigins(cors["origins"], false),
24820
+ credentials: normalizeBoolean(cors["credentials"])
24821
+ })
24822
+ });
24823
+ }
24824
+ function policyRecord(value, keys, complete = false) {
24825
+ if (typeof value !== "object" || value === null || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value)) || Object.keys(value).some((key) => !keys.includes(key)) || complete && Object.keys(value).length !== keys.length)
24826
+ throw invalidPolicy();
24827
+ return value;
24828
+ }
24829
+ function normalizeBoolean(value) {
24830
+ if (value === void 0)
24831
+ return false;
24832
+ if (typeof value !== "boolean")
24833
+ throw invalidPolicy();
24834
+ return value;
24835
+ }
24836
+ function normalizeOrigins(value, allowWebSocket) {
24837
+ if (value === void 0)
24838
+ return Object.freeze([]);
24839
+ if (!Array.isArray(value) || value.length > MAX_ORIGINS)
24840
+ throw invalidPolicy();
24841
+ const origins = [];
24842
+ for (const origin of value) {
24843
+ if (!browserOrigin(origin, allowWebSocket) || origins.includes(origin))
24844
+ throw invalidPolicy();
24845
+ origins.push(origin);
24846
+ }
24847
+ return Object.freeze(origins.sort());
24848
+ }
24849
+ function browserOrigin(value, allowWebSocket) {
24850
+ if (typeof value !== "string" || value.length > 256)
24851
+ return false;
24852
+ try {
24853
+ const url = new URL(value);
24854
+ return (url.protocol === "https:" || allowWebSocket && url.protocol === "wss:") && url.origin === value && url.username === "" && url.password === "" && url.pathname === "/" && url.search === "" && url.hash === "" && url.hostname.includes(".") && !url.hostname.endsWith(".local") && !url.hostname.endsWith(".internal") && !/^\d+(?:\.\d+){3}$/u.test(url.hostname) && url.hostname.split(".").every((label) => /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/u.test(label));
24855
+ } catch {
24856
+ return false;
24857
+ }
24858
+ }
24859
+ function invalidPolicy() {
24860
+ return new TypeError("Invalid browser capability policy");
24861
+ }
24862
+
24231
24863
  // packages/contracts/dist/index.js
24232
24864
  var validateOhmyhostConfig = new import__.Ajv2020({
24233
24865
  allErrors: true,
@@ -24252,7 +24884,8 @@ function parseOhmyhostConfig(value) {
24252
24884
  healthcheck: normalizeHealthcheck(input.runtime.healthcheck),
24253
24885
  egress: Object.freeze({
24254
24886
  allow: normalizeEgressOrigins(input.runtime.egress?.allow)
24255
- })
24887
+ }),
24888
+ ...input.runtime.browser === void 0 ? {} : { browser: normalizeBrowserPolicy(input.runtime.browser) }
24256
24889
  }),
24257
24890
  ...input.database === void 0 ? {} : {
24258
24891
  database: Object.freeze({
@@ -24306,7 +24939,7 @@ function normalizeEgressOrigins(value) {
24306
24939
  } catch {
24307
24940
  throw new TypeError("ohmyhost.yaml runtime egress origin is invalid");
24308
24941
  }
24309
- if (parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "" || parsed.origin !== origin || parsed.pathname !== "/" || parsed.search !== "" || parsed.hash !== "" || forbiddenEgressHostname(parsed.hostname)) {
24942
+ if (parsed.protocol !== "https:" || parsed.port !== "" || parsed.username !== "" || parsed.password !== "" || parsed.origin !== origin || parsed.pathname !== "/" || parsed.search !== "" || parsed.hash !== "" || forbiddenEgressHostname(parsed.hostname)) {
24310
24943
  throw new TypeError("ohmyhost.yaml runtime egress origin is invalid");
24311
24944
  }
24312
24945
  }
@@ -24315,16 +24948,7 @@ function normalizeEgressOrigins(value) {
24315
24948
  }
24316
24949
  function forbiddenEgressHostname(hostname) {
24317
24950
  const normalized = hostname.toLowerCase();
24318
- if (normalized === "localhost" || normalized.endsWith(".localhost") || normalized.endsWith(".local") || normalized.endsWith(".internal") || normalized === "0.0.0.0" || normalized === "::1") {
24319
- return true;
24320
- }
24321
- const ipv4 = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/u.exec(normalized);
24322
- if (ipv4 === null)
24323
- return normalized.startsWith("[") || normalized.endsWith("]");
24324
- const octets = ipv4.slice(1).map(Number);
24325
- if (octets.some((octet) => octet > 255))
24326
- return true;
24327
- return octets[0] === 10 || octets[0] === 127 || octets[0] === 169 && octets[1] === 254 || octets[0] === 172 && (octets[1] ?? 0) >= 16 && (octets[1] ?? 0) <= 31 || octets[0] === 192 && octets[1] === 168;
24951
+ return normalized === "localhost" || normalized.endsWith(".localhost") || normalized.endsWith(".local") || normalized.endsWith(".internal") || /^\d{1,3}(?:\.\d{1,3}){3}$/u.test(normalized) || normalized.startsWith("[") || normalized.endsWith("]");
24328
24952
  }
24329
24953
  function parseOhmyhostConfigYaml(source) {
24330
24954
  if (typeof source !== "string") {
@@ -24718,7 +25342,9 @@ async function initializeRepository(input) {
24718
25342
  sourceEntryPoint: WORKER_MODULE_PATH,
24719
25343
  present: applicationFiles.includes(WORKER_MODULE_PATH),
24720
25344
  ...runtimeContract(capabilities)
24721
- }) : null
25345
+ }) : null,
25346
+ // Next.js and TanStack Start have neither a companion nor a Worker module, and read it here.
25347
+ runtime: Object.values(capabilities).some(Boolean) ? runtimeContract(capabilities) : null
24722
25348
  });
24723
25349
  }
24724
25350
  function detectRepositoryFramework(dependencies, files, existingConfiguration) {
@@ -24738,7 +25364,7 @@ async function cronBlockers(framework, config, applicationDirectory, files) {
24738
25364
  return [
24739
25365
  {
24740
25366
  code: "scheduled_functions_runtime_unsupported",
24741
- message: "Declared crons run on the edge runtime of a Vite API companion, Next.js or TanStack Start application, or on the functions runtime."
25367
+ message: "functions.crons needs runtime.mode edge (Next.js, TanStack Start, or Vite with src/ohmyhost/companion.ts) or functions (src/ohmyhost/worker.ts) in ohmyhost.yaml. Change runtime.mode, or remove functions.crons."
24742
25368
  }
24743
25369
  ];
24744
25370
  }
@@ -24815,14 +25441,20 @@ function migrationFilenameBlockers(files, configuredPath, provider) {
24815
25441
  return [];
24816
25442
  const root = configuredPath ?? "postgres/migrations";
24817
25443
  const prefix = `${root}/`;
24818
- const invalid4 = files.filter((path) => path.startsWith(prefix)).map((path) => path.slice(prefix.length)).some((path) => !/^(?:[A-Za-z0-9][A-Za-z0-9._-]*\/)*\d{14}_[a-z0-9][a-z0-9_-]*\.sql$/u.test(path));
24819
- return invalid4 ? [
25444
+ const invalid5 = files.filter((path) => path.startsWith(prefix)).map((path) => path.slice(prefix.length)).find((path) => !/^(?:[A-Za-z0-9][A-Za-z0-9._-]*\/)*\d{14}_[a-z0-9][a-z0-9_-]*\.sql$/u.test(path));
25445
+ return invalid5 === void 0 ? [] : [
24820
25446
  {
24821
25447
  code: "migration_filename_noncanonical",
24822
- message: "Rename every PostgreSQL migration to YYYYMMDDHHMMSS_name.sql before initialization."
25448
+ message: `Migration file '${invalid5}' in ${root} is not named YYYYMMDDHHMMSS_name.sql (14 digits, an underscore, then lowercase letters, digits, _ or -). Every file in that directory must match, README and .gitkeep included: rename or move it, keeping the run order.`
24823
25449
  }
24824
- ] : [];
25450
+ ];
24825
25451
  }
25452
+ var MIGRATION_REJECTION = {
25453
+ destructive: "It contains DROP, TRUNCATE, INSERT, UPDATE, DELETE, BEGIN/COMMIT/ROLLBACK, CREATE OR REPLACE, or ALTER TABLE other than ADD; move row changes to 'ohmyhost database write'.",
25454
+ provider_specific: "It mentions supabase, CREATE POLICY, ROW LEVEL SECURITY, the auth. or storage. schema, or the anon, authenticated or service_role role; comments count.",
25455
+ unsupported: "It has a statement other than CREATE TABLE, TYPE, DOMAIN, SEQUENCE, VIEW, MATERIALIZED VIEW, TRIGGER, INDEX or FUNCTION, ALTER TABLE ... ADD, COMMENT ON, CREATE SCHEMA IF NOT EXISTS auth|extensions|private or CREATE EXTENSION IF NOT EXISTS pgcrypto|btree_gist|unaccent; or a function not in LANGUAGE sql or plpgsql, SECURITY DEFINER without SET search_path, or the word ohmyhost, comments included.",
25456
+ invalid: "It is empty, not UTF-8 with LF line endings, has a BOM or NUL byte or an unterminated quote or comment, or exceeds 256 KiB (at most 128 files and 2 MiB in total)."
25457
+ };
24826
25458
  async function migrationSqlBlockers(applicationDirectory, files, configuredPath, provider) {
24827
25459
  if (provider !== "postgresql")
24828
25460
  return [];
@@ -24845,7 +25477,7 @@ async function migrationSqlBlockers(applicationDirectory, files, configuredPath,
24845
25477
  throw error;
24846
25478
  blockers.push({
24847
25479
  code: "migration_sql_not_admitted",
24848
- message: `PostgreSQL migration '${migration.path}' is not admitted (${error.reason}).`
25480
+ message: `PostgreSQL migration '${migration.path}' is not admitted (${error.reason}): ${MIGRATION_REJECTION[error.reason]} Migrations are expand-only. Fix the file before its first deployment; never edit a migration a deployment already applied.`
24849
25481
  });
24850
25482
  }
24851
25483
  }
@@ -24860,7 +25492,7 @@ async function migrationSqlBlockers(applicationDirectory, files, configuredPath,
24860
25492
  return [
24861
25493
  {
24862
25494
  code: "migration_sql_not_admitted",
24863
- message: `PostgreSQL migration set is not admitted (${error.reason}).`
25495
+ message: `The migration set is not admitted (${error.reason}): ${MIGRATION_REJECTION[error.reason]}`
24864
25496
  }
24865
25497
  ];
24866
25498
  }
@@ -24871,17 +25503,17 @@ async function readExistingConfiguration(repositoryRoot, repositoryFiles, applic
24871
25503
  const configurationPath = resolve2(repositoryRoot, "ohmyhost.yaml");
24872
25504
  const information = await stat2(configurationPath);
24873
25505
  if (!information.isFile() || information.size > MAXIMUM_CONFIGURATION_BYTES) {
24874
- throw new RepositoryInitError("repository_configuration_invalid", "Existing ohmyhost.yaml must be one bounded regular file.");
25506
+ throw new RepositoryInitError("repository_configuration_invalid", "ohmyhost.yaml must be one regular file of at most 64 KiB");
24875
25507
  }
24876
25508
  const configuration = await readFile3(configurationPath, "utf8");
24877
25509
  let parsed;
24878
25510
  try {
24879
25511
  parsed = parseOhmyhostConfigYaml(configuration);
24880
- } catch {
24881
- throw new RepositoryInitError("repository_configuration_invalid", "Existing ohmyhost.yaml is invalid.");
25512
+ } catch (error) {
25513
+ throw new RepositoryInitError("repository_configuration_invalid", error instanceof TypeError ? error.message : "ohmyhost.yaml is invalid");
24882
25514
  }
24883
25515
  if (parsed.applicationRoot !== applicationRoot) {
24884
- throw new RepositoryInitError("repository_configuration_root_mismatch", "Existing ohmyhost.yaml must select the detected application root.");
25516
+ throw new RepositoryInitError("repository_configuration_root_mismatch", `ohmyhost.yaml sets applicationRoot to '${parsed.applicationRoot}', but init selected '${applicationRoot}'. Run 'ohmyhost init --root ${parsed.applicationRoot} --dry-run --json', or correct applicationRoot.`);
24885
25517
  }
24886
25518
  return configuration;
24887
25519
  }
@@ -25003,12 +25635,13 @@ function hasDirectSafeBuildCommand(packageJson, framework) {
25003
25635
  const scripts = packageJson["scripts"];
25004
25636
  if (!isRecord4(scripts) || typeof scripts["build"] !== "string")
25005
25637
  return false;
25006
- const command = scripts["build"];
25007
- if (command.includes("..") || /(?:^|\s)cd(?:\s|$)/u.test(command))
25008
- return false;
25638
+ return (framework === "nextjs" || framework === "vite" || framework === "tanstack-start") && isAdmittedFrameworkBuildScript(framework, scripts["build"]);
25639
+ }
25640
+ function buildScriptMessage(framework, command) {
25641
+ const quoted = command.slice(0, 200);
25009
25642
  if (framework === "nextjs")
25010
- return command === "next build" || command === "next build --webpack";
25011
- return command === "vite build" || command === "vite build && tsc --noEmit";
25643
+ return `package.json scripts.build must be exactly "next build" or "next build --webpack", not "${quoted}". Move other steps into their own scripts, then commit and push.`;
25644
+ return `package.json scripts.build must be "vite build", optionally joined with && to exactly one "tsc", "tsc -b", "tsc --build" or "tsc --noEmit" stage before or after it, not "${quoted}". Move other steps into their own scripts, then commit and push.`;
25012
25645
  }
25013
25646
  async function assertRepositoryRoot(root) {
25014
25647
  let information;
@@ -25157,11 +25790,16 @@ function frameworkConfigurations(files) {
25157
25790
  ambiguous: Object.freeze(ambiguous)
25158
25791
  });
25159
25792
  }
25793
+ var FRAMEWORK_PACKAGE = {
25794
+ nextjs: "next",
25795
+ "tanstack-start": "@tanstack/react-start",
25796
+ vite: "vite"
25797
+ };
25160
25798
  function frameworkCompatibility(packageJson, framework) {
25161
25799
  const targets = framework === "tanstack-start" ? [
25162
- { framework: "tanstack-start", packageName: "@tanstack/react-start" },
25163
- { framework: "vite", packageName: "vite" }
25164
- ] : framework === "nextjs" ? [{ framework: "nextjs", packageName: "next" }] : framework === "vite" ? [{ framework: "vite", packageName: "vite" }] : [];
25800
+ { framework: "tanstack-start", packageName: FRAMEWORK_PACKAGE["tanstack-start"] },
25801
+ { framework: "vite", packageName: FRAMEWORK_PACKAGE.vite }
25802
+ ] : framework === "nextjs" ? [{ framework: "nextjs", packageName: FRAMEWORK_PACKAGE.nextjs }] : framework === "vite" ? [{ framework: "vite", packageName: FRAMEWORK_PACKAGE.vite }] : [];
25165
25803
  const frameworks = targets.map(({ framework: targetFramework, packageName }) => {
25166
25804
  const version2 = dependencyVersion(packageJson, packageName) ?? "";
25167
25805
  return Object.freeze({
@@ -25199,7 +25837,7 @@ function repositoryBlockers(packageJson, framework, nextStandaloneOutput, tanSta
25199
25837
  if (tanStackRuntime === "ambiguous") {
25200
25838
  blockers.push({
25201
25839
  code: "framework_ambiguous",
25202
- message: "TanStack Start must explicitly enable static prerendering or the complete Cloudflare edge contract."
25840
+ message: 'TanStack Start needs one vite.config.js, .mjs or .ts that selects a mode. Static: tanstackStart({ prerender: { enabled: true } }) from "@tanstack/react-start/plugin/vite" and no "@cloudflare/vite-plugin". Server: cloudflare({ viteEnvironment: { name: "ssr" } }) from "@cloudflare/vite-plugin" listed before tanstackStart() in plugins, with @cloudflare/vite-plugin, @tanstack/react-router, @tanstack/react-start, react, react-dom, @vitejs/plugin-react and vite in package.json.'
25203
25841
  });
25204
25842
  }
25205
25843
  const relevantAmbiguousConfigs = frameworkConfigs.ambiguous.filter((config) => framework === "nextjs" ? config === "next" || config === "open-next" : config === "vite");
@@ -25217,19 +25855,19 @@ function repositoryBlockers(packageJson, framework, nextStandaloneOutput, tanSta
25217
25855
  } else if (packageManagerAdmission instanceof FrameworkAdmissionError) {
25218
25856
  blockers.push({
25219
25857
  code: "package_manager_unpinned",
25220
- message: "Declare one exact npm, pnpm, Yarn, or Bun packageManager version and commit its matching lockfile."
25858
+ message: packageManagerAdmission.reason === "package_manager_version_unsupported" ? `This build image runs Bun ${PINNED_BUN_VERSION}; pin packageManager to bun@${PINNED_BUN_VERSION} and commit its matching lockfile. Repeating the unchanged version cannot work.` : "Declare one exact npm, pnpm, Yarn, or Bun packageManager version and commit its matching lockfile."
25221
25859
  });
25222
25860
  } else if (existingConfiguration !== null && (existingConfiguration.build.install !== packageManagerAdmission.installCommand || "command" in existingConfiguration.build && existingConfiguration.build.command !== packageManagerAdmission.buildCommand)) {
25223
25861
  blockers.push({
25224
25862
  code: "build_command_unsupported",
25225
- message: "Use the admitted package manager's secure install and direct build commands."
25863
+ message: `ohmyhost.yaml build.install must be exactly "${packageManagerAdmission.installCommand}"${"command" in existingConfiguration.build ? ` and build.command "${packageManagerAdmission.buildCommand}"` : ""} for the packageManager in package.json. Set them, then commit and push.`
25226
25864
  });
25227
25865
  }
25228
25866
  if (compatibility.classification === "unsupported" && compatibility.frameworks.length > 0) {
25229
- const unsupported = compatibility.frameworks.filter((entry) => entry.classification === "unsupported").map((entry) => `${entry.framework}@${entry.version || "missing"}`).join(", ");
25867
+ const unsupported = compatibility.frameworks.filter((entry) => entry.classification === "unsupported").map((entry) => `${FRAMEWORK_PACKAGE[entry.framework]}@${entry.version || "missing"}`).join(", ");
25230
25868
  blockers.push({
25231
25869
  code: "framework_version_unsupported",
25232
- message: `Framework version is outside the admitted POC window: ${unsupported}.`
25870
+ message: `${unsupported} is outside the versions ohmyho.st builds: next 15.5.x or 16.0.0\u201316.3.4, vite 5.4.0\u20138.2.2, @tanstack/react-start 1.168.26\u20131.168.49, as an exact version or a ^ or ~ range whose base version is inside. Pin a version in that range in package.json, update the lockfile, commit and push.`
25233
25871
  });
25234
25872
  }
25235
25873
  const scripts = packageJson["scripts"];
@@ -25240,10 +25878,13 @@ function repositoryBlockers(packageJson, framework, nextStandaloneOutput, tanSta
25240
25878
  } else if (!hasDirectSafeBuildCommand(packageJson, framework)) {
25241
25879
  blockers.push({
25242
25880
  code: "build_command_unsupported",
25243
- message: "Use a direct framework build command without traversal or directory changes."
25881
+ message: buildScriptMessage(framework, scripts["build"])
25244
25882
  });
25245
25883
  }
25246
25884
  const betterAuthVersion = dependencyVersion(packageJson, "better-auth");
25885
+ const managedMailIssue = managedCustomerAuthAdmissionIssue(existingConfiguration);
25886
+ if (managedMailIssue !== null)
25887
+ blockers.push(managedMailIssue);
25247
25888
  if (existingConfiguration?.auth?.provider === "better-auth" && betterAuthVersion !== null && betterAuthVersion !== SUPPORTED_BETTER_AUTH_VERSION) {
25248
25889
  blockers.push({
25249
25890
  code: "better_auth_version_unsupported",
@@ -25264,7 +25905,7 @@ function repositoryBlockers(packageJson, framework, nextStandaloneOutput, tanSta
25264
25905
  if (!nextConfigured) {
25265
25906
  blockers.push({
25266
25907
  code: "next_adapter_unconfigured",
25267
- message: "Declare direct next build, exact Next/React versions, and one supported Next config; ohmyhost supplies the OpenNext build overlay."
25908
+ message: 'Next.js needs scripts.build exactly "next build" or "next build --webpack", react and react-dom at exact versions such as "19.1.0" (no ^ or ~), next within 15.5.x or 16.0.0\u201316.3.4, and exactly one next.config.js, .mjs or .ts, with at most one open-next.config file. ohmyho.st adds the OpenNext build itself; do not make it the build script.'
25268
25909
  });
25269
25910
  }
25270
25911
  }
@@ -26021,7 +26662,7 @@ var runProductionCli = async (argv, environment, io, signal, overrides = {}) =>
26021
26662
  args = extracted.argv;
26022
26663
  profileName = extracted.profileName;
26023
26664
  } catch {
26024
- return written(invalidCommandFailure(recognizedCommandLabel(argv)));
26665
+ return written(invalidCommandFailure(argv, environment));
26025
26666
  }
26026
26667
  const workingDirectory = overrides.workingDirectory ?? process.cwd();
26027
26668
  let command;
@@ -26053,8 +26694,8 @@ var runProductionCli = async (argv, environment, io, signal, overrides = {}) =>
26053
26694
  if (error instanceof LinkedProjectAmbiguousError)
26054
26695
  return written(errorDocument(recognizedCommandLabel(args), 2, "linked_project_selection_required", false, `This directory is linked to projects of several organizations (${error.organizationIds.join(", ")}). Pass --project ULID, or choose the login with --profile-name NAME; '${resolveProductCliEnvironment(environment).commandPrefix} profile list --json' shows the saved logins.`));
26055
26696
  if (error instanceof CredentialStoreUnavailableError)
26056
- return written(errorDocument(recognizedCommandLabel(args), 3, "credential_store_unavailable", true, "Unlock or repair the selected credential store and retry."));
26057
- const result = error instanceof InvalidCommandError ? invalidCommandFailure(recognizedCommandLabel(args)) : errorDocument("unknown", 1, "internal_error", false, "Retry the command.");
26697
+ return written(errorDocument(recognizedCommandLabel(args), 3, "credential_store_unavailable", false, CREDENTIAL_STORE_UNAVAILABLE_ACTION));
26698
+ const result = error instanceof InvalidCommandError ? invalidCommandFailure(args, environment) : errorDocument(recognizedCommandLabel(args), 1, "internal_error", false, INTERNAL_ERROR_ACTION);
26058
26699
  if (!(error instanceof InvalidCommandError)) {
26059
26700
  diagnostics.record({
26060
26701
  correlationId: randomUUID2(),
@@ -26067,7 +26708,7 @@ var runProductionCli = async (argv, environment, io, signal, overrides = {}) =>
26067
26708
  }
26068
26709
  if (command.kind === "init") {
26069
26710
  if (profileName !== void 0)
26070
- return written(invalidCommandFailure("init"));
26711
+ return written(errorDocument("init", 2, "invalid_command", false, "ohmyhost init runs offline and uses no login: remove --profile-name and run it again."));
26071
26712
  return runOfflineInit(command, io, diagnostics);
26072
26713
  }
26073
26714
  if (command.kind === "help" || command.kind === "version") {
@@ -26101,6 +26742,8 @@ var runProductionCli = async (argv, environment, io, signal, overrides = {}) =>
26101
26742
  const registry = overrides.profiles ?? new NativeKeyringProfileRegistry(void 0, platform.credentialService);
26102
26743
  const linkDirectory = join3(workingDirectory, platform.linkDirectory);
26103
26744
  const projectId = commandProjectId(command);
26745
+ if (projectId !== void 0 && linked === null)
26746
+ links = await readProjectLinks(linkDirectory, platform.apiOrigin);
26104
26747
  const organizationId = handedAccount2?.organizationId ?? commandOrganizationId(command) ?? linked?.organizationId ?? (projectId === void 0 ? void 0 : linkedOrganization(links, projectId));
26105
26748
  const request = profileRequest({
26106
26749
  ...profileName === void 0 ? {} : { name: profileName },
@@ -26179,7 +26822,15 @@ var runProductionCli = async (argv, environment, io, signal, overrides = {}) =>
26179
26822
  }
26180
26823
  }
26181
26824
  } catch (error) {
26182
- return written(compositionFailure(recognizedCommandLabel(args), error, signal, platform.commandPrefix));
26825
+ const result = compositionFailure(recognizedCommandLabel(args), error, signal, platform.commandPrefix, profile);
26826
+ if (result.exitCode === 1)
26827
+ diagnostics.record({
26828
+ correlationId: randomUUID2(),
26829
+ category: "internal",
26830
+ subsystem: "authentication",
26831
+ errorKind: "unexpected"
26832
+ });
26833
+ return written(result);
26183
26834
  }
26184
26835
  const selected = profile;
26185
26836
  const productApiOrigin = platform.apiOrigin;
@@ -26263,13 +26914,13 @@ var adoptLegacyLink = async (input) => {
26263
26914
  });
26264
26915
  await removeLegacyProjectLink(input.directory);
26265
26916
  };
26266
- var compositionFailure = (command, error, signal, commandPrefix) => {
26917
+ var compositionFailure = (command, error, signal, commandPrefix, profile) => {
26267
26918
  if (error instanceof ProfileSelectionError)
26268
26919
  return errorDocument(command, error.exitCode, error.code, false, error.suggestedAction);
26269
26920
  if (error instanceof ProfileLimitError)
26270
26921
  return errorDocument(command, 2, "profile_limit_reached", false, profileLimitAction(commandPrefix));
26271
26922
  if (error instanceof ProductApiAuthenticationError)
26272
- return error.code === "authentication_required" ? errorDocument(command, 5, "authentication_required", false, `Run '${commandPrefix} login --json'.`) : errorDocument(command, 5, "token_refresh_failed", true, `Run '${commandPrefix} login --json' if retrying does not succeed.`);
26923
+ return error.code === "authentication_required" ? errorDocument(command, 5, "authentication_required", false, endedLoginAction(commandPrefix, profile)) : errorDocument(command, 5, "token_refresh_failed", true, refreshFailedAction(commandPrefix, profile));
26273
26924
  return classifyCliFailure(command, error, signal);
26274
26925
  };
26275
26926
  var runOfflineInit = async (command, io, diagnostics) => {
@@ -26301,7 +26952,8 @@ var runOfflineInit = async (command, io, diagnostics) => {
26301
26952
  companion: result.companion,
26302
26953
  // The functions runtime reports its own bindings and packages; dropping it left a
26303
26954
  // storage-enabled functions app with no contract to read at all.
26304
- worker: result.worker
26955
+ worker: result.worker,
26956
+ runtime: result.runtime
26305
26957
  })}
26306
26958
  `);
26307
26959
  return blocked ? 9 : 0;
@@ -26315,11 +26967,38 @@ var runOfflineInit = async (command, io, diagnostics) => {
26315
26967
  });
26316
26968
  const code = error instanceof RepositoryInitBlockedError ? "repository_init_blocked" : error instanceof RepositoryInitConflictError ? "repository_configuration_exists" : known ? error.code : "internal_error";
26317
26969
  const exitCode = error instanceof RepositoryInitBlockedError ? 9 : known ? 2 : 1;
26318
- const result = errorDocument("init", exitCode, code, false, error instanceof RepositoryInitConflictError ? "Keep the existing ohmyhost.yaml or remove it explicitly before retrying." : error instanceof RepositoryInitError && error.code === "repository_root_required" ? "Run ohmyhost init from the Git repository root with --root set to the application directory (for example --root website); commit the root ohmyhost.yaml before planning." : error instanceof RepositoryInitError && error.code === "project_name_invalid" ? "Pass a lowercase deployment slug with '--project', then rerun init." : "Run 'ohmyhost init --dry-run --json' and resolve every reported blocker.");
26970
+ const result = errorDocument("init", exitCode, code, false, known ? initFailureAction(error) : INTERNAL_ERROR_ACTION);
26319
26971
  io.writeStdout(result.stdout);
26320
26972
  return result.exitCode;
26321
26973
  }
26322
26974
  };
26975
+ var initFailureAction = (error) => {
26976
+ if (error instanceof RepositoryInitBlockedError)
26977
+ return "Run 'ohmyhost init --dry-run --json' and resolve every reported blocker.";
26978
+ if (error instanceof RepositoryInitConflictError)
26979
+ return "ohmyhost.yaml already exists and init never overwrites it; that file stays authoritative. Run 'ohmyhost init --dry-run --json' to check it and list blockers, then edit the file by hand. Do not delete it to rerun init.";
26980
+ const message = /[.!?]$/u.test(error.message) ? error.message : `${error.message}.`;
26981
+ switch (error.code) {
26982
+ case "repository_root_required":
26983
+ return "Run ohmyhost init from the Git repository root with --root set to the application directory (for example --root website); commit the root ohmyhost.yaml before planning.";
26984
+ case "project_name_invalid":
26985
+ return "Pass a lowercase deployment slug with '--project', then rerun init.";
26986
+ case "application_root_ambiguous":
26987
+ return `${message} Run 'ohmyhost init --root DIR --dry-run --json' from the Git repository root with the directory to deploy; ask the customer if it is unclear.`;
26988
+ case "application_root_not_found":
26989
+ case "application_root_unsupported":
26990
+ return `${message} init needs a package.json that depends on next (without vite or @tanstack/react-start), on vite, or on @tanstack/react-start, or a functions app with src/ohmyhost/worker.ts. Pass --root DIR with the app's directory, or fix its package.json, then run init again.`;
26991
+ case "application_root_invalid":
26992
+ case "application_root_missing":
26993
+ return `${message} Pass --root as a path relative to the repository root, for example apps/web.`;
26994
+ case "repository_configuration_invalid":
26995
+ return `${error.message.replace(/\.$/u, "")}. Fix that, then run 'ohmyhost init --dry-run --json' again; do not delete ohmyhost.yaml.`;
26996
+ case "repository_configuration_root_mismatch":
26997
+ return error.message;
26998
+ default:
26999
+ return `${message} Fix that and run 'ohmyhost init --json' again from the Git repository root.`;
27000
+ }
27001
+ };
26323
27002
  var FetchWorkosPublicTransport = class {
26324
27003
  parentSignal;
26325
27004
  timeoutMs;
@@ -26565,8 +27244,8 @@ var decodeUtf8 = (decoder, chunk, stream) => {
26565
27244
  throw new ResponseContractError();
26566
27245
  }
26567
27246
  };
26568
- var waitUntil = async (instant, signal) => {
26569
- const delay = Math.max(0, instant.getTime() - Date.now());
27247
+ var waitUntil = async (instant2, signal) => {
27248
+ const delay = Math.max(0, instant2.getTime() - Date.now());
26570
27249
  await new Promise((resolve3, reject) => {
26571
27250
  const cleanup = () => signal.removeEventListener("abort", cancel);
26572
27251
  const complete = () => {
@@ -26610,7 +27289,7 @@ var runOfflineLogout = async (registry, request, commandPrefix, io, diagnostics)
26610
27289
  subsystem: "credential_store",
26611
27290
  errorKind: "unavailable"
26612
27291
  });
26613
- result = errorDocument("logout", 3, "credential_store_unavailable", true, "Unlock or repair the selected credential store and retry.");
27292
+ result = errorDocument("logout", 3, "credential_store_unavailable", false, CREDENTIAL_STORE_UNAVAILABLE_ACTION);
26614
27293
  }
26615
27294
  io.writeStdout(result.stdout);
26616
27295
  return result.exitCode;
@@ -26645,7 +27324,14 @@ var readSecretValueFromStandardInput = async (input, signal) => {
26645
27324
  value.fill(0);
26646
27325
  }
26647
27326
  };
26648
- var invalidCommandFailure = (command) => errorDocument(command, 2, "invalid_command", false, "Check 'ohmyhost --help'.");
27327
+ var invalidCommandFailure = (argv, environment) => {
27328
+ let commandPrefix = "ohmyhost";
27329
+ try {
27330
+ commandPrefix = resolveProductCliEnvironment(environment).commandPrefix;
27331
+ } catch {
27332
+ }
27333
+ return errorDocument(recognizedCommandLabel(argv), 2, "invalid_command", false, invalidCommandAction(argv, commandPrefix));
27334
+ };
26649
27335
  var resultDocument = (command, exitCode, status, body = {}) => ({
26650
27336
  exitCode,
26651
27337
  stdout: `${JSON.stringify({ version: 1, command, status, ...body })}
@@ -26691,6 +27377,7 @@ export {
26691
27377
  InvalidCommandError,
26692
27378
  recognizedCommandLabel,
26693
27379
  parseProductCliCommand,
27380
+ parseProjectDataChangePlan2 as parseProjectDataChangePlan,
26694
27381
  parsePaidDomainPlan,
26695
27382
  parsePaidDomain,
26696
27383
  parseCloudflareDnsAuthorization,
@@ -26721,6 +27408,11 @@ export {
26721
27408
  PublicResponseTooLargeError,
26722
27409
  PsqlUnavailableError,
26723
27410
  cliMetadataResult,
27411
+ invalidCommandAction,
27412
+ INTERNAL_ERROR_ACTION,
27413
+ CREDENTIAL_STORE_UNAVAILABLE_ACTION,
27414
+ endedLoginAction,
27415
+ refreshFailedAction,
26724
27416
  ProductCli,
26725
27417
  ACCOUNT_SENSITIVE_PROBLEMS,
26726
27418
  classifyCliFailure,