@amerged/ohmyhost-mcp 0.1.14 → 0.1.17

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.
@@ -21,12 +21,13 @@ import {
21
21
  } from "@ohmyhost/contracts/operation-failure";
22
22
  export type { PublicOperationFailure } from "@ohmyhost/contracts/operation-failure";
23
23
 
24
- const PROBLEM_POLICY = {
24
+ /** Exported for the documentation build, which publishes one page per code. */
25
+ export const PROBLEM_POLICY = {
25
26
  cloudflare_zone_not_bound: {
26
27
  status: 409,
27
28
  retryable: false,
28
29
  action:
29
- "Configure the project domain first with 'ohmyhost domain paid plan' then 'ohmyhost domain paid apply', or configure its sender domain with 'ohmyhost mail domain set'. Then request Cloudflare authorization for that domain's zone.",
30
+ "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.",
30
31
  },
31
32
  github_connection_required: {
32
33
  status: 409,
@@ -197,6 +198,12 @@ const PROBLEM_POLICY = {
197
198
  retryable: false,
198
199
  action: "Select the current succeeded dev deployment and target prod.",
199
200
  },
201
+ mail_capacity_unavailable: {
202
+ status: 503,
203
+ retryable: true,
204
+ action:
205
+ "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.",
206
+ },
200
207
  mail_domain_conflict: {
201
208
  status: 409,
202
209
  retryable: false,
@@ -205,7 +212,8 @@ const PROBLEM_POLICY = {
205
212
  mail_domain_required: {
206
213
  status: 409,
207
214
  retryable: false,
208
- action: "Run 'ohmyhost mail domain set' for this project, then plan again.",
215
+ action:
216
+ "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.",
209
217
  },
210
218
  storage_jurisdiction_conflict: {
211
219
  status: 409,
@@ -213,6 +221,18 @@ const PROBLEM_POLICY = {
213
221
  action:
214
222
  "Set storage.jurisdiction in ohmyhost.yaml to the project's hosting region shown by project status, then plan again.",
215
223
  },
224
+ shared_data_requires_promotion: {
225
+ status: 409,
226
+ retryable: false,
227
+ action:
228
+ "Plan and deploy this commit to Dev, then run 'ohmyhost deployment promote plan' to reach Prod.",
229
+ },
230
+ production_deployment_required: {
231
+ status: 409,
232
+ retryable: false,
233
+ action:
234
+ "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.",
235
+ },
216
236
  framework_conversion_required: {
217
237
  status: 409,
218
238
  retryable: false,
@@ -308,6 +328,8 @@ export class ResponseContractError extends Error {
308
328
 
309
329
  export interface SafeProblem {
310
330
  readonly requestId: string;
331
+ /** The error's documentation page, when the server names one on docs.ohmyho.st. */
332
+ readonly docsUrl?: string;
311
333
  readonly retryAfterSeconds?: number;
312
334
  readonly code: ProblemCode;
313
335
  readonly retryable: boolean;
@@ -690,6 +712,28 @@ export const parseCloudflareDnsAuthorizationStatus = (
690
712
  return input as unknown as PublicCloudflareDnsAuthorizationStatus;
691
713
  };
692
714
 
715
+ export interface PublicDevAccessState {
716
+ readonly project_id: string;
717
+ readonly mode: "protected" | "public";
718
+ readonly share_url: string | null;
719
+ }
720
+
721
+ export const parseDevAccessState = (value: unknown): PublicDevAccessState => {
722
+ const input = exactRecord(value, ["project_id", "mode", "share_url"]);
723
+ if (
724
+ !isUlid(input["project_id"]) ||
725
+ (input["mode"] !== "protected" && input["mode"] !== "public") ||
726
+ (input["mode"] === "public" && input["share_url"] !== null) ||
727
+ (input["share_url"] !== null &&
728
+ (typeof input["share_url"] !== "string" ||
729
+ !/^https:\/\/dev-[a-z0-9]+(?:-[a-z0-9]+){0,4}\.(?:dev\.)?check\.omh\.st\/\.ohmyhost\/dev-access\/redeem\?stage=dev&ticket=[A-Za-z0-9_-]{43}$/u.test(
730
+ input["share_url"],
731
+ )))
732
+ )
733
+ throw new ResponseContractError();
734
+ return input as unknown as PublicDevAccessState;
735
+ };
736
+
693
737
  export const parseDevAccessTicket = (value: unknown): PublicDevAccessTicket => {
694
738
  const input = exactRecord(value, [
695
739
  "ticket_id",
@@ -943,6 +987,7 @@ export const parseProjectStatus = (value: unknown): Readonly<Record<string, unkn
943
987
  "etag",
944
988
  "handle",
945
989
  "region",
990
+ "dev_access_mode",
946
991
  "lifecycle",
947
992
  "source",
948
993
  "default_environment",
@@ -961,6 +1006,7 @@ export const parseProjectStatus = (value: unknown): Readonly<Record<string, unkn
961
1006
  !boundedString(handle, 3, 59) ||
962
1007
  !/^[a-z0-9]+(?:-[a-z0-9]+){0,4}$/u.test(handle) ||
963
1008
  (input["region"] !== "us" && input["region"] !== "eu") ||
1009
+ (input["dev_access_mode"] !== "protected" && input["dev_access_mode"] !== "public") ||
964
1010
  (input["lifecycle"] !== "active" &&
965
1011
  input["lifecycle"] !== "deleting" &&
966
1012
  input["lifecycle"] !== "deleted") ||
@@ -1308,6 +1354,7 @@ export const parseDeploymentPlan = (value: unknown): Readonly<Record<string, unk
1308
1354
  const input = exactRecord(value, [
1309
1355
  "id",
1310
1356
  "project_id",
1357
+ "environment",
1311
1358
  "commit_sha",
1312
1359
  "source_digest",
1313
1360
  "build_plan_digest",
@@ -1329,6 +1376,7 @@ export const parseDeploymentPlan = (value: unknown): Readonly<Record<string, unk
1329
1376
  "currency",
1330
1377
  "rate_card_version",
1331
1378
  "credit_micros",
1379
+ "credits",
1332
1380
  "provider_cost_micros",
1333
1381
  "scope",
1334
1382
  "reserved_seconds",
@@ -1336,6 +1384,7 @@ export const parseDeploymentPlan = (value: unknown): Readonly<Record<string, unk
1336
1384
  if (
1337
1385
  !isUlid(input["id"]) ||
1338
1386
  !isUlid(input["project_id"]) ||
1387
+ (input["environment"] !== "dev" && input["environment"] !== "prod") ||
1339
1388
  !isCommitSha(input["commit_sha"]) ||
1340
1389
  !isDigest(input["source_digest"]) ||
1341
1390
  !isDigest(input["build_plan_digest"]) ||
@@ -1355,6 +1404,8 @@ export const parseDeploymentPlan = (value: unknown): Readonly<Record<string, unk
1355
1404
  !boundedString(cost["currency"], 1, 16) ||
1356
1405
  !boundedString(cost["rate_card_version"], 1, 128) ||
1357
1406
  !decimal(cost["credit_micros"]) ||
1407
+ typeof cost["credits"] !== "string" ||
1408
+ !/^[0-9]+(?:\.[0-9]{1,6})?$/u.test(cost["credits"]) ||
1358
1409
  !decimal(cost["provider_cost_micros"]) ||
1359
1410
  cost["scope"] !== "build_compute" ||
1360
1411
  !decimal(cost["reserved_seconds"]) ||
@@ -1497,8 +1548,10 @@ export const parseSafeProblem = (value: unknown): SafeProblem => {
1497
1548
  retryAfter > 86400)
1498
1549
  )
1499
1550
  throw new ResponseContractError();
1551
+ const docsUrl = String(input["type"]);
1500
1552
  return {
1501
1553
  requestId: input["request_id"],
1554
+ ...(docsUrl.startsWith("https://docs.ohmyho.st/errors/") ? { docsUrl } : {}),
1502
1555
  code: code as ProblemCode,
1503
1556
  retryable: policy.retryable,
1504
1557
  suggestedAction: