@amerged/ohmyhost-mcp 0.1.20 → 0.1.21

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.
@@ -26,7 +26,7 @@ import {
26
26
  serverIdentityOf,
27
27
  setNegotiatedProtocolVersion,
28
28
  validateEnvelopeMeta
29
- } from "./chunk-NESQZ3BS.js";
29
+ } from "./chunk-4WY7AAPR.js";
30
30
 
31
31
  // node_modules/.pnpm/@modelcontextprotocol+server@2.0.0/node_modules/@modelcontextprotocol/server/dist/stdio.mjs
32
32
  var StdioServerTransport = class {
@@ -516,7 +516,7 @@ function toError(value) {
516
516
  var failures = {
517
517
  operation_abandoned: [
518
518
  "The operation stopped renewing its execution lease and was ended.",
519
- "Inspect project status and deployment diagnostics before creating a fresh plan. Keep this operation ID; the expired execution cannot resume."
519
+ "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."
520
520
  ],
521
521
  database_write_rejected: [
522
522
  "The database rejected this write; it was not committed.",
@@ -580,7 +580,7 @@ var failures = {
580
580
  ],
581
581
  build_failed: [
582
582
  "The build did not produce a verified deployable artifact.",
583
- "Inspect the deployment build diagnostics and verify the install and build commands for the admitted commit. Correct the reported failure before planning a new deployment."
583
+ "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."
584
584
  ],
585
585
  insufficient_organization_credits: [
586
586
  "The organization does not have sufficient available credits for this operation.",
@@ -596,7 +596,7 @@ var failures = {
596
596
  ],
597
597
  runtime_candidate_failed: [
598
598
  "The deployed candidate failed its runtime checks.",
599
- "Inspect deployment runtime diagnostics, required secrets and the health-check route before creating a new deployment plan."
599
+ "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."
600
600
  ],
601
601
  runtime_candidate_rejected: [
602
602
  "The runtime rejected the built Worker script.",
@@ -799,7 +799,7 @@ function exact2(v, keys) {
799
799
  // apps/product-cli/package.json
800
800
  var package_default = {
801
801
  name: "@ohmyhost/product-cli",
802
- version: "0.1.20",
802
+ version: "0.1.21",
803
803
  private: true,
804
804
  ohmyhost: {
805
805
  deployment: "production",
@@ -2168,6 +2168,11 @@ var PROBLEM_POLICY = {
2168
2168
  retryable: false,
2169
2169
  action: "Finish database provisioning, then request the export again. No backup was accepted."
2170
2170
  },
2171
+ powered_by_flag_required: {
2172
+ status: 409,
2173
+ retryable: false,
2174
+ action: "Keep the flag on, remove the custom domain with 'ohmyhost domain paid delete', or buy Paid, then switch the flag off."
2175
+ },
2171
2176
  rate_limited: { status: 429, retryable: true, action: "Retry after the server delay." },
2172
2177
  insufficient_organization_credits: {
2173
2178
  status: 402,
@@ -2182,7 +2187,7 @@ var PROBLEM_POLICY = {
2182
2187
  paid_plan_required: {
2183
2188
  status: 402,
2184
2189
  retryable: false,
2185
- action: "Ask the organization Owner to activate Paid through billing checkout, then retry. Top-ups and signup credits do not activate Paid."
2190
+ 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."
2186
2191
  },
2187
2192
  interactive_login_required: {
2188
2193
  status: 403,
@@ -2290,6 +2295,18 @@ var parseDevAccessState = (value) => {
2290
2295
  throw new ResponseContractError();
2291
2296
  return input;
2292
2297
  };
2298
+ var parsePoweredByFlag = (value) => {
2299
+ const input = exactRecord2(value, ["project_id", "enabled", "enabled_at"]);
2300
+ if (!isUlid(input["project_id"]) || typeof input["enabled"] !== "boolean" || (input["enabled"] ? !isIsoInstant(input["enabled_at"]) : input["enabled_at"] !== null))
2301
+ throw new ResponseContractError();
2302
+ return input;
2303
+ };
2304
+ var parseOrganizationReferral = (value) => {
2305
+ const input = exactRecord2(value, ["organization_id", "referral_url"]);
2306
+ if (!isUlid(input["organization_id"]) || input["referral_url"] !== `https://ohmyho.st/?r=ref-${String(input["organization_id"]).toLowerCase()}`)
2307
+ throw new ResponseContractError();
2308
+ return input;
2309
+ };
2293
2310
  var parseDevAccessTicket = (value) => {
2294
2311
  const input = exactRecord2(value, [
2295
2312
  "ticket_id",
@@ -2381,6 +2398,7 @@ var parseSafeProblem = (value) => {
2381
2398
  var parseOperation = (value) => {
2382
2399
  const input = exactRecord2(value, [
2383
2400
  "blocking_operation_id",
2401
+ "deployment_id",
2384
2402
  "id",
2385
2403
  "state",
2386
2404
  "created_at",
@@ -2396,7 +2414,8 @@ var parseOperation = (value) => {
2396
2414
  const updatedAt = input["updated_at"];
2397
2415
  const result = input["result"];
2398
2416
  const blockingOperationId = input["blocking_operation_id"];
2399
- if (blockingOperationId !== void 0 && (state !== "queued" || !isUlid(blockingOperationId) || blockingOperationId === id3))
2417
+ const deploymentId = input["deployment_id"];
2418
+ if (blockingOperationId !== void 0 && (state !== "queued" || !isUlid(blockingOperationId) || blockingOperationId === id3) || deploymentId !== void 0 && !isUlid(deploymentId))
2400
2419
  throw new ResponseContractError();
2401
2420
  if (!isUlid(id3) || !isOperationState(state) || !isIsoInstant(createdAt) || !isIsoInstant(updatedAt) || result !== void 0 && !isRecord2(result)) {
2402
2421
  throw new ResponseContractError();
@@ -2443,6 +2462,7 @@ var parseOperation = (value) => {
2443
2462
  created_at: createdAt,
2444
2463
  updated_at: updatedAt,
2445
2464
  ...blockingOperationId === void 0 ? {} : { blocking_operation_id: blockingOperationId },
2465
+ ...deploymentId === void 0 ? {} : { deployment_id: deploymentId },
2446
2466
  ...progress === void 0 ? {} : { progress },
2447
2467
  ...result === void 0 ? {} : { result },
2448
2468
  ...error === void 0 ? {} : { error },
@@ -2597,6 +2617,7 @@ var HELP_USAGE = Object.freeze({
2597
2617
  "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)",
2598
2618
  "export get": "ohmyhost export get EXPORT_ULID --project ULID --json (poll the original job; signed ZIP download lasts 24 hours)",
2599
2619
  "credits account": "ohmyhost credits account --organization ULID --json",
2620
+ "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)",
2600
2621
  "credits balance": "ohmyhost credits balance --organization ULID --json",
2601
2622
  "billing recharge get": "ohmyhost billing recharge get --organization ULID --json",
2602
2623
  "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)",
@@ -2626,6 +2647,8 @@ var HELP_USAGE = Object.freeze({
2626
2647
  "project dev-share rotate": "ohmyhost project dev-share rotate --project ULID --idempotency-key KEY --yes --json",
2627
2648
  "project dev-share revoke": "ohmyhost project dev-share revoke --project ULID --idempotency-key KEY --yes --json",
2628
2649
  "project dev-access mode": "ohmyhost project dev-access mode --project ULID --mode protected|public --idempotency-key KEY --yes --json",
2650
+ "project flag status": "ohmyhost project flag status --project ULID --json",
2651
+ "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)",
2629
2652
  "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)",
2630
2653
  "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)",
2631
2654
  "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.",
@@ -3567,6 +3590,23 @@ var getGithubOrganizationConnection = (parameters, options) => {
3567
3590
  ...params
3568
3591
  });
3569
3592
  };
3593
+ var getOrganizationReferral = (parameters, options) => {
3594
+ const params = buildClientParams([parameters], [
3595
+ {
3596
+ args: [
3597
+ { in: "headers", key: "X-Request-Id" },
3598
+ { in: "path", key: "organization_id" }
3599
+ ]
3600
+ }
3601
+ ]);
3602
+ return (options?.client ?? client).get({
3603
+ responseStyle: "data",
3604
+ security: [{ scheme: "bearer", type: "http" }],
3605
+ url: "/v1/organizations/{organization_id}/referral",
3606
+ ...options,
3607
+ ...params
3608
+ });
3609
+ };
3570
3610
  var getOrganizationAccount = (parameters, options) => {
3571
3611
  const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "organization_id" }] }]);
3572
3612
  return (options?.client ?? client).get({
@@ -4171,6 +4211,47 @@ var setProjectDevAccessMode = (parameters, options) => {
4171
4211
  }
4172
4212
  });
4173
4213
  };
4214
+ var getProjectPoweredByFlag = (parameters, options) => {
4215
+ const params = buildClientParams([parameters], [
4216
+ {
4217
+ args: [
4218
+ { in: "headers", key: "X-Request-Id" },
4219
+ { in: "path", key: "project_id" }
4220
+ ]
4221
+ }
4222
+ ]);
4223
+ return (options?.client ?? client).get({
4224
+ responseStyle: "data",
4225
+ security: [{ scheme: "bearer", type: "http" }],
4226
+ url: "/v1/projects/{project_id}/powered-by-flag",
4227
+ ...options,
4228
+ ...params
4229
+ });
4230
+ };
4231
+ var setProjectPoweredByFlag = (parameters, options) => {
4232
+ const params = buildClientParams([parameters], [
4233
+ {
4234
+ args: [
4235
+ { in: "headers", key: "X-Request-Id" },
4236
+ { in: "headers", key: "Idempotency-Key" },
4237
+ { in: "path", key: "project_id" },
4238
+ { key: "setPoweredByFlagRequest", map: "body" }
4239
+ ]
4240
+ }
4241
+ ]);
4242
+ return (options?.client ?? client).put({
4243
+ responseStyle: "data",
4244
+ security: [{ scheme: "bearer", type: "http" }],
4245
+ url: "/v1/projects/{project_id}/powered-by-flag",
4246
+ ...options,
4247
+ ...params,
4248
+ headers: {
4249
+ "Content-Type": "application/json",
4250
+ ...options?.headers,
4251
+ ...params.headers
4252
+ }
4253
+ });
4254
+ };
4174
4255
  var createProjectDevAccessTicket = (parameters, options) => {
4175
4256
  const params = buildClientParams([parameters], [
4176
4257
  {
@@ -5429,6 +5510,9 @@ var GeneratedSdkProductApi = class {
5429
5510
  async getAccountProfile() {
5430
5511
  return parseAccountProfile(await getAccountProfile({ client: this.#client }));
5431
5512
  }
5513
+ getOrganizationReferral(organizationId) {
5514
+ return getOrganizationReferral({ organization_id: organizationId }, { client: this.#client });
5515
+ }
5432
5516
  getOrganizationAccount(organizationId) {
5433
5517
  return getOrganizationAccount({ organization_id: organizationId }, { client: this.#client });
5434
5518
  }
@@ -5602,6 +5686,16 @@ var GeneratedSdkProductApi = class {
5602
5686
  setDevAccessModeRequest: { mode }
5603
5687
  }, { client: this.#client });
5604
5688
  }
5689
+ async getPoweredByFlag(projectId) {
5690
+ return getProjectPoweredByFlag({ project_id: projectId }, { client: this.#client });
5691
+ }
5692
+ async setPoweredByFlag(projectId, enabled, idempotencyKey2) {
5693
+ return setProjectPoweredByFlag({
5694
+ project_id: projectId,
5695
+ "Idempotency-Key": idempotencyKey2,
5696
+ setPoweredByFlagRequest: { enabled }
5697
+ }, { client: this.#client });
5698
+ }
5605
5699
  async createDevAccessTicket(projectId) {
5606
5700
  return createProjectDevAccessTicket({ project_id: projectId }, { client: this.#client });
5607
5701
  }
@@ -6361,6 +6455,9 @@ var GeneratedSdkLocalMcpClient = class {
6361
6455
  assertOrganizationAccount(account);
6362
6456
  return account;
6363
6457
  }
6458
+ async getOrganizationReferral(organizationId) {
6459
+ return parseOrganizationReferral(await getOrganizationReferral({ organization_id: organizationId }, { client: this.#client }));
6460
+ }
6364
6461
  getOrganizationCredits(organizationId) {
6365
6462
  return getOrganizationCredits({ organization_id: organizationId }, { client: this.#client });
6366
6463
  }
@@ -6489,6 +6586,16 @@ var GeneratedSdkLocalMcpClient = class {
6489
6586
  setDevAccessModeRequest: { mode: input.mode }
6490
6587
  }, { client: this.#client }));
6491
6588
  }
6589
+ async getPoweredByFlag(projectId) {
6590
+ return parsePoweredByFlag(await getProjectPoweredByFlag({ project_id: projectId }, { client: this.#client }));
6591
+ }
6592
+ async setPoweredByFlag(input) {
6593
+ return parsePoweredByFlag(await setProjectPoweredByFlag({
6594
+ project_id: input.projectId,
6595
+ "Idempotency-Key": input.idempotencyKey,
6596
+ setPoweredByFlagRequest: { enabled: input.enabled }
6597
+ }, { client: this.#client }));
6598
+ }
6492
6599
  async rotateDevShareLink(input) {
6493
6600
  return parseDevAccessState(await rotateProjectDevShareLink({ project_id: input.projectId, "Idempotency-Key": input.idempotencyKey }, { client: this.#client }));
6494
6601
  }
@@ -6894,6 +7001,7 @@ function createLocalOhmyhostMcpServer(dependencies) {
6894
7001
  ...input.cursor ? { cursor: input.cursor } : {}
6895
7002
  }));
6896
7003
  registerProductTool(server, dependencies, "organization_account_get", "Owner-only: read the effective Free/Paid plan, its Stripe or granted source, available expiring Free credits and purchased credits that never expire, reservations and next expiry. Granted Paid features do not imply a Stripe subscription or extra monthly Paid allowance. This read grants no access or credits.", external_exports.object({ organization_id: identifier2 }), (client2, input) => client2.getOrganizationAccount(input.organization_id));
7004
+ registerProductTool(server, dependencies, "referral_link_get", "Read the workspace's referral link to share. A new user whose first workspace comes from it starts with a free Paid month and 1,000 credits; that workspace's first payment gives this workspace the same. Any member may read it.", external_exports.object({ organization_id: identifier2 }), (client2, input) => client2.getOrganizationReferral(input.organization_id));
6897
7005
  registerProductTool(server, dependencies, "organization_credits_get", "Read the owner's shared organization credit pool, seven-day grace_started_at/grace_expires_at and published rate_cards. Existing services/domains are not immediately shut down at zero credit; an explicit project stop budget is separate. One credit is 1000000 microcredits. Each rate states credit_micros for units_per_charge; publication alone does not enable billing. active_meters identifies billed sources; provider outages and platform overrun are not customer debt. Works at zero credit.", external_exports.object({ organization_id: identifier2 }), (client2, input) => client2.getOrganizationCredits(input.organization_id));
6898
7006
  registerProductTool(server, dependencies, "project_budget_get", "Read the owner's project UTC-month budget, measured usage and open reservations. A budget never allocates a second wallet.", external_exports.object({ project_id: identifier2 }), (client2, input) => client2.getProjectCreditBudget(input.project_id));
6899
7007
  registerProductTool(server, dependencies, "project_budget_set", "Set an owner's optional monthly project budget in microcredits (1000000 = one credit). continue is the default and uses the org pool after the threshold; stop blocks new billable work. null clears the budget and requires continue. Changing it never resets usage or adds credits. Replay the same key after uncertainty; read the budget again for current usage.", external_exports.object({
@@ -7095,6 +7203,12 @@ function createLocalOhmyhostMcpServer(dependencies) {
7095
7203
  mode: input.mode,
7096
7204
  idempotencyKey: input.idempotency_key
7097
7205
  }), destructiveAnnotations());
7206
+ registerProductTool(server, dependencies, "powered_by_flag_get", 'Read whether the production site shows the opt-in "Powered by ohmyho.st" flag.', external_exports.object({ project_id: identifier2 }), (client2, input) => client2.getPoweredByFlag(input.project_id));
7207
+ registerProductTool(server, dependencies, "powered_by_flag_set", `Owner only, ask the human first: show or hide a small "Powered by ohmyho.st" flag on the right edge of the production site. While it is on, a Free workspace may connect its own domain to this project free of the domain fee, and each Paid period adds 250 credits. Hiding it is refused while a Free workspace's domain depends on it. Replay the same key and value after uncertainty to get the first answer without switching again; every new switch needs a new key.`, external_exports.object({ project_id: identifier2, enabled: external_exports.boolean(), idempotency_key: idempotencyKey }), (client2, input) => client2.setPoweredByFlag({
7208
+ projectId: input.project_id,
7209
+ enabled: input.enabled,
7210
+ idempotencyKey: input.idempotency_key
7211
+ }), mutationAnnotations());
7098
7212
  registerProductTool(server, dependencies, "project_dev_share_link_rotate", "Owner only: replace the persistent Dev link and immediately revoke old links and sessions. Repeat the same key after uncertainty.", external_exports.object({ project_id: identifier2, idempotency_key: idempotencyKey }), (client2, input) => client2.rotateDevShareLink({
7099
7213
  projectId: input.project_id,
7100
7214
  idempotencyKey: input.idempotency_key
@@ -7145,7 +7259,7 @@ function createLocalOhmyhostMcpServer(dependencies) {
7145
7259
  ...input.limit === void 0 ? {} : { limit: input.limit }
7146
7260
  }));
7147
7261
  registerProductTool(server, dependencies, "deployment_get", "Get one deployment", external_exports.object({ project_id: identifier2, deployment_id: identifier2 }), (client2, input) => client2.getDeployment({ projectId: input.project_id, deploymentId: input.deployment_id }));
7148
- registerProductTool(server, dependencies, "deployment_logs", "List the newest normalized diagnostics of one deployment (build, control, runtime and function failures with catalog codes). A BUILD_FAILED item carries excerpt: the sanitized tail of the customer's own install/build output, newest lines last, with excerpt_truncated when earlier output was omitted. Read it after build_failed before changing source; scheduled runs are in function_runs_list, not here.", external_exports.object({
7262
+ registerProductTool(server, dependencies, "deployment_logs", "List the newest normalized diagnostics of one deployment (build, control, runtime and function failures with catalog codes). A failed operation_get names the deployment as deployment_id. A BUILD_FAILED item carries excerpt: the sanitized tail of the customer's own install/build output, newest lines last, with excerpt_truncated when earlier output was omitted. A HEALTH_CHECK_FAILED item carries the probed route and status_code and, only for a project owner, excerpt: the head (at most 4096 characters, control characters removed, never a header) of what the app answered. The probe is a cookieless GET that follows no redirect, 5 s per try and up to four tries, so a 3xx or an empty 503 is unhealthy too. Read the item before changing source; scheduled runs are in function_runs_list, not here.", external_exports.object({
7149
7263
  project_id: identifier2,
7150
7264
  deployment_id: identifier2,
7151
7265
  limit: external_exports.number().int().min(1).max(100).default(100)
@@ -7154,7 +7268,7 @@ function createLocalOhmyhostMcpServer(dependencies) {
7154
7268
  deploymentId: input.deployment_id,
7155
7269
  limit: input.limit
7156
7270
  }));
7157
- registerProductTool(server, dependencies, "operation_get", "Get durable operation status and current deployment progress/reconciliation guidance. Follow progress.phase and progress.suggested_action. waiting_for_mail requires mail_domain_status now for the current verification issue and DNS records; publishing means the build completed but runtime preparation/publication is unfinished. Poll this operation after progress.next_poll_after_seconds. A queued/running operation with reconciliation.state=required needs one confirmed operation_reconcile using the same operation; pending means poll this operation after 60 seconds. Never infer deployment success from a completed reconciliation attempt or start another build to recover it.", external_exports.object({ operation_id: identifier2 }), (client2, input) => client2.getOperation(input.operation_id));
7271
+ registerProductTool(server, dependencies, "operation_get", "Get durable operation status and current deployment progress/reconciliation guidance. Follow progress.phase and progress.suggested_action. waiting_for_mail requires mail_domain_status now for the current verification issue and DNS records; publishing means the build completed but runtime preparation/publication is unfinished. Poll this operation after progress.next_poll_after_seconds. A queued/running operation with reconciliation.state=required needs one confirmed operation_reconcile using the same operation; pending means poll this operation after 60 seconds. Never infer deployment success from a completed reconciliation attempt or start another build to recover it. A deploy, promotion or rollback names its deployment_id in every state; when state is failed, read that deployment with deployment_logs and follow error.suggested_action before changing source.", external_exports.object({ operation_id: identifier2 }), (client2, input) => client2.getOperation(input.operation_id));
7158
7272
  registerProductTool(server, dependencies, "operation_logs", "Read available operation events for at most ten seconds, stopping earlier at max_events or a terminal event. This is a bounded snapshot, not a wait for deployment completion. Use operation_get for current progress and keep the same operation. Stream failures are errors, not empty logs.", external_exports.object({
7159
7273
  operation_id: identifier2,
7160
7274
  max_events: external_exports.number().int().min(1).max(100).default(50)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amerged/ohmyhost-mcp",
3
- "version": "0.1.20",
3
+ "version": "0.1.21",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22"
@@ -26,6 +26,8 @@ import {
26
26
  createProjectDevAccessTicket,
27
27
  ensureProjectDevShareLink,
28
28
  setProjectDevAccessMode,
29
+ getProjectPoweredByFlag,
30
+ setProjectPoweredByFlag,
29
31
  rotateProjectDevShareLink,
30
32
  revokeProjectDevShareLink,
31
33
  createCloudflareDnsAuthorization,
@@ -52,6 +54,7 @@ import {
52
54
  deletePaidProjectDomain,
53
55
  getOrganizationCredits,
54
56
  getOrganizationAccount,
57
+ getOrganizationReferral,
55
58
  getOrganizationCreditUsage,
56
59
  getProjectCreditBudget,
57
60
  setProjectCreditBudget,
@@ -93,6 +96,8 @@ import {
93
96
  streamOperationEvents,
94
97
  type DevAccessTicket,
95
98
  type DevAccessState,
99
+ type PoweredByFlag,
100
+ type OrganizationReferral,
96
101
  type CloudflareDnsAuthorization,
97
102
  type CloudflareDnsAuthorizationStatus,
98
103
  type OhMyHostClient,
@@ -113,6 +118,8 @@ import {
113
118
  ProductApiAuthenticationError,
114
119
  parseDevAccessTicket,
115
120
  parseDevAccessState,
121
+ parsePoweredByFlag,
122
+ parseOrganizationReferral,
116
123
  parseCloudflareDnsAuthorization,
117
124
  parseCloudflareDnsAuthorizationStatus,
118
125
  parsePaidDomain,
@@ -419,6 +426,11 @@ export class GeneratedSdkLocalMcpClient implements LocalMcpProductClient {
419
426
  assertOrganizationAccount(account);
420
427
  return account;
421
428
  }
429
+ public async getOrganizationReferral(organizationId: string): Promise<OrganizationReferral> {
430
+ return parseOrganizationReferral(
431
+ await getOrganizationReferral({ organization_id: organizationId }, { client: this.#client }),
432
+ );
433
+ }
422
434
  public getOrganizationCredits(organizationId: string) {
423
435
  return getOrganizationCredits({ organization_id: organizationId }, { client: this.#client });
424
436
  }
@@ -606,6 +618,27 @@ export class GeneratedSdkLocalMcpClient implements LocalMcpProductClient {
606
618
  ),
607
619
  );
608
620
  }
621
+ public async getPoweredByFlag(projectId: string): Promise<PoweredByFlag> {
622
+ return parsePoweredByFlag(
623
+ await getProjectPoweredByFlag({ project_id: projectId }, { client: this.#client }),
624
+ );
625
+ }
626
+ public async setPoweredByFlag(input: {
627
+ projectId: string;
628
+ enabled: boolean;
629
+ idempotencyKey: string;
630
+ }): Promise<PoweredByFlag> {
631
+ return parsePoweredByFlag(
632
+ await setProjectPoweredByFlag(
633
+ {
634
+ project_id: input.projectId,
635
+ "Idempotency-Key": input.idempotencyKey,
636
+ setPoweredByFlagRequest: { enabled: input.enabled },
637
+ },
638
+ { client: this.#client },
639
+ ),
640
+ );
641
+ }
609
642
  public async rotateDevShareLink(input: {
610
643
  projectId: string;
611
644
  idempotencyKey: string;
@@ -70,6 +70,12 @@ export interface LocalMcpProductClient {
70
70
  projectId: string;
71
71
  idempotencyKey: string;
72
72
  }): Promise<import("@ohmyhost/sdk-ts").DevAccessState>;
73
+ getPoweredByFlag(projectId: string): Promise<import("@ohmyhost/sdk-ts").PoweredByFlag>;
74
+ setPoweredByFlag(input: {
75
+ projectId: string;
76
+ enabled: boolean;
77
+ idempotencyKey: string;
78
+ }): Promise<import("@ohmyhost/sdk-ts").PoweredByFlag>;
73
79
  revokeDevShareLink(input: {
74
80
  projectId: string;
75
81
  idempotencyKey: string;
@@ -156,6 +162,9 @@ export interface LocalMcpProductClient {
156
162
  getOrganizationAccount(
157
163
  organizationId: string,
158
164
  ): Promise<import("@ohmyhost/sdk-ts").OrganizationAccount>;
165
+ getOrganizationReferral(
166
+ organizationId: string,
167
+ ): Promise<import("@ohmyhost/sdk-ts").OrganizationReferral>;
159
168
  getOrganizationCredits(
160
169
  organizationId: string,
161
170
  ): Promise<import("@ohmyhost/sdk-ts").OrganizationCredits>;
@@ -706,6 +715,14 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
706
715
  z.object({ organization_id: identifier }),
707
716
  (client, input) => client.getOrganizationAccount(input.organization_id),
708
717
  );
718
+ registerProductTool(
719
+ server,
720
+ dependencies,
721
+ "referral_link_get",
722
+ "Read the workspace's referral link to share. A new user whose first workspace comes from it starts with a free Paid month and 1,000 credits; that workspace's first payment gives this workspace the same. Any member may read it.",
723
+ z.object({ organization_id: identifier }),
724
+ (client, input) => client.getOrganizationReferral(input.organization_id),
725
+ );
709
726
  registerProductTool(
710
727
  server,
711
728
  dependencies,
@@ -1163,6 +1180,28 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
1163
1180
  }),
1164
1181
  destructiveAnnotations(),
1165
1182
  );
1183
+ registerProductTool(
1184
+ server,
1185
+ dependencies,
1186
+ "powered_by_flag_get",
1187
+ 'Read whether the production site shows the opt-in "Powered by ohmyho.st" flag.',
1188
+ z.object({ project_id: identifier }),
1189
+ (client, input) => client.getPoweredByFlag(input.project_id),
1190
+ );
1191
+ registerProductTool(
1192
+ server,
1193
+ dependencies,
1194
+ "powered_by_flag_set",
1195
+ 'Owner only, ask the human first: show or hide a small "Powered by ohmyho.st" flag on the right edge of the production site. While it is on, a Free workspace may connect its own domain to this project free of the domain fee, and each Paid period adds 250 credits. Hiding it is refused while a Free workspace\'s domain depends on it. Replay the same key and value after uncertainty to get the first answer without switching again; every new switch needs a new key.',
1196
+ z.object({ project_id: identifier, enabled: z.boolean(), idempotency_key: idempotencyKey }),
1197
+ (client, input) =>
1198
+ client.setPoweredByFlag({
1199
+ projectId: input.project_id,
1200
+ enabled: input.enabled,
1201
+ idempotencyKey: input.idempotency_key,
1202
+ }),
1203
+ mutationAnnotations(),
1204
+ );
1166
1205
  registerProductTool(
1167
1206
  server,
1168
1207
  dependencies,
@@ -1308,7 +1347,7 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
1308
1347
  server,
1309
1348
  dependencies,
1310
1349
  "deployment_logs",
1311
- "List the newest normalized diagnostics of one deployment (build, control, runtime and function failures with catalog codes). A BUILD_FAILED item carries excerpt: the sanitized tail of the customer's own install/build output, newest lines last, with excerpt_truncated when earlier output was omitted. Read it after build_failed before changing source; scheduled runs are in function_runs_list, not here.",
1350
+ "List the newest normalized diagnostics of one deployment (build, control, runtime and function failures with catalog codes). A failed operation_get names the deployment as deployment_id. A BUILD_FAILED item carries excerpt: the sanitized tail of the customer's own install/build output, newest lines last, with excerpt_truncated when earlier output was omitted. A HEALTH_CHECK_FAILED item carries the probed route and status_code and, only for a project owner, excerpt: the head (at most 4096 characters, control characters removed, never a header) of what the app answered. The probe is a cookieless GET that follows no redirect, 5 s per try and up to four tries, so a 3xx or an empty 503 is unhealthy too. Read the item before changing source; scheduled runs are in function_runs_list, not here.",
1312
1351
  z.object({
1313
1352
  project_id: identifier,
1314
1353
  deployment_id: identifier,
@@ -1325,7 +1364,7 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
1325
1364
  server,
1326
1365
  dependencies,
1327
1366
  "operation_get",
1328
- "Get durable operation status and current deployment progress/reconciliation guidance. Follow progress.phase and progress.suggested_action. waiting_for_mail requires mail_domain_status now for the current verification issue and DNS records; publishing means the build completed but runtime preparation/publication is unfinished. Poll this operation after progress.next_poll_after_seconds. A queued/running operation with reconciliation.state=required needs one confirmed operation_reconcile using the same operation; pending means poll this operation after 60 seconds. Never infer deployment success from a completed reconciliation attempt or start another build to recover it.",
1367
+ "Get durable operation status and current deployment progress/reconciliation guidance. Follow progress.phase and progress.suggested_action. waiting_for_mail requires mail_domain_status now for the current verification issue and DNS records; publishing means the build completed but runtime preparation/publication is unfinished. Poll this operation after progress.next_poll_after_seconds. A queued/running operation with reconciliation.state=required needs one confirmed operation_reconcile using the same operation; pending means poll this operation after 60 seconds. Never infer deployment success from a completed reconciliation attempt or start another build to recover it. A deploy, promotion or rollback names its deployment_id in every state; when state is failed, read that deployment with deployment_logs and follow error.suggested_action before changing source.",
1329
1368
  z.object({ operation_id: identifier }),
1330
1369
  (client, input) => client.getOperation(input.operation_id),
1331
1370
  );
@@ -80,6 +80,8 @@ import {
80
80
  parseProjectCreditBudget,
81
81
  parseDevAccessTicket,
82
82
  parseDevAccessState,
83
+ parsePoweredByFlag,
84
+ parseOrganizationReferral,
83
85
  parseGuardedActionPlan,
84
86
  parseEnvironmentSecret,
85
87
  parseEnvironmentSecretDeletion,
@@ -304,6 +306,8 @@ const HELP_USAGE = Object.freeze({
304
306
  "export get":
305
307
  "ohmyhost export get EXPORT_ULID --project ULID --json (poll the original job; signed ZIP download lasts 24 hours)",
306
308
  "credits account": "ohmyhost credits account --organization ULID --json",
309
+ "referral link":
310
+ "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)",
307
311
  "credits balance": "ohmyhost credits balance --organization ULID --json",
308
312
  "billing recharge get": "ohmyhost billing recharge get --organization ULID --json",
309
313
  "billing recharge set":
@@ -350,6 +354,9 @@ const HELP_USAGE = Object.freeze({
350
354
  "ohmyhost project dev-share revoke --project ULID --idempotency-key KEY --yes --json",
351
355
  "project dev-access mode":
352
356
  "ohmyhost project dev-access mode --project ULID --mode protected|public --idempotency-key KEY --yes --json",
357
+ "project flag status": "ohmyhost project flag status --project ULID --json",
358
+ "project flag set":
359
+ "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)",
353
360
  "project handle check":
354
361
  "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)",
355
362
  "project handle set":
@@ -619,6 +626,13 @@ export class ProductCli {
619
626
  }
620
627
  return success(name, { status: "succeeded", billing: result });
621
628
  }
629
+ if (command.kind === "referral-link") {
630
+ const api = await this.authenticatedApi(command.credentialStore, signal);
631
+ const referral = parseOrganizationReferral(
632
+ await api.getOrganizationReferral(command.organizationId),
633
+ );
634
+ return success("referral link", { status: "succeeded", referral });
635
+ }
622
636
  if (command.kind === "credits-account") {
623
637
  const api = await this.authenticatedApi(command.credentialStore, signal);
624
638
  const account = await api.getOrganizationAccount(command.organizationId);
@@ -809,6 +823,22 @@ export class ProductCli {
809
823
  if (command.kind === "project-status") return await this.getProjectStatus(command, signal);
810
824
  if (command.kind === "project-dev-access-create")
811
825
  return await this.createDevAccessTicket(command, signal);
826
+ if (command.kind === "project-flag-status" || command.kind === "project-flag-set") {
827
+ const api = await this.authenticatedApi(command.credentialStore, signal);
828
+ const flag = parsePoweredByFlag(
829
+ command.kind === "project-flag-status"
830
+ ? await api.getPoweredByFlag(command.projectId)
831
+ : await api.setPoweredByFlag(
832
+ command.projectId,
833
+ command.enabled,
834
+ command.idempotencyKey,
835
+ ),
836
+ );
837
+ return success(
838
+ command.kind === "project-flag-status" ? "project flag status" : "project flag set",
839
+ { status: "succeeded", flag },
840
+ );
841
+ }
812
842
  if (
813
843
  command.kind === "project-dev-share-link" ||
814
844
  command.kind === "project-dev-share-rotate" ||
@@ -2450,6 +2480,7 @@ const parsedCommandName = (command: ProductCliCommand): CommandLabel => {
2450
2480
  if (command.kind === "billing-status") return "billing status";
2451
2481
  if (command.kind === "billing-portal") return "billing portal";
2452
2482
  if (command.kind === "credits-account") return "credits account";
2483
+ if (command.kind === "referral-link") return "referral link";
2453
2484
  if (command.kind === "credits-balance") return "credits balance";
2454
2485
  if (command.kind === "credits-usage") return "credits usage";
2455
2486
  if (command.kind === "budget-get") return "budget get";
@@ -2475,6 +2506,8 @@ const parsedCommandName = (command: ProductCliCommand): CommandLabel => {
2475
2506
  if (command.kind === "project-dev-share-rotate") return "project dev-share rotate";
2476
2507
  if (command.kind === "project-dev-share-revoke") return "project dev-share revoke";
2477
2508
  if (command.kind === "project-dev-access-mode-set") return "project dev-access mode";
2509
+ if (command.kind === "project-flag-status") return "project flag status";
2510
+ if (command.kind === "project-flag-set") return "project flag set";
2478
2511
  if (command.kind === "project-handle-check") return "project handle check";
2479
2512
  if (command.kind === "project-handle-set") return "project handle set";
2480
2513
  if (command.kind === "database-write") return "database write";
@@ -127,6 +127,11 @@ export type ProductCliCommand =
127
127
  readonly organizationId: string;
128
128
  readonly credentialStore: "native";
129
129
  }
130
+ | {
131
+ readonly kind: "referral-link";
132
+ readonly organizationId: string;
133
+ readonly credentialStore: "native";
134
+ }
130
135
  | {
131
136
  readonly kind: "credits-balance";
132
137
  readonly organizationId: string;
@@ -256,6 +261,18 @@ export type ProductCliCommand =
256
261
  readonly projectId: string;
257
262
  readonly credentialStore: "native";
258
263
  }
264
+ | {
265
+ readonly kind: "project-flag-status";
266
+ readonly projectId: string;
267
+ readonly credentialStore: "native";
268
+ }
269
+ | {
270
+ readonly kind: "project-flag-set";
271
+ readonly projectId: string;
272
+ readonly enabled: boolean;
273
+ readonly idempotencyKey: string;
274
+ readonly credentialStore: "native";
275
+ }
259
276
  | {
260
277
  readonly kind: "database-write";
261
278
  readonly projectId: string;
@@ -549,6 +566,7 @@ export type RecognizedCommandLabel =
549
566
  | "version"
550
567
  | "whoami"
551
568
  | "credits account"
569
+ | "referral link"
552
570
  | "credits balance"
553
571
  | "credits usage"
554
572
  | "budget get"
@@ -577,6 +595,8 @@ export type RecognizedCommandLabel =
577
595
  | "project dev-share rotate"
578
596
  | "project dev-share revoke"
579
597
  | "project dev-access mode"
598
+ | "project flag status"
599
+ | "project flag set"
580
600
  | "project handle check"
581
601
  | "project handle set"
582
602
  | "database write"
@@ -656,6 +676,7 @@ export const recognizedCommandLabel = (argv: readonly string[]): RecognizedComma
656
676
  if (value === "github" && values[0] === "status") return "github status";
657
677
  if (value === "operation" && values[0] === "get") return "operation get";
658
678
  if (value === "credits" && values[0] === "account") return "credits account";
679
+ if (value === "referral" && values[0] === "link") return "referral link";
659
680
  if (value === "credits" && values[0] === "balance") return "credits balance";
660
681
  if (value === "billing" && values[0] === "recharge" && values[1] === "get")
661
682
  return "billing recharge get";
@@ -701,6 +722,10 @@ export const recognizedCommandLabel = (argv: readonly string[]): RecognizedComma
701
722
  return "project dev-access mode";
702
723
  if (value === "project" && values[0] === "dev-access" && values[1] === "create")
703
724
  return "project dev-access create";
725
+ if (value === "project" && values[0] === "flag" && values[1] === "status")
726
+ return "project flag status";
727
+ if (value === "project" && values[0] === "flag" && values[1] === "set")
728
+ return "project flag set";
704
729
  if (value === "database" && values[0] === "compute" && values[1] === "set")
705
730
  return "database compute set";
706
731
  if (value === "database" && values[0] === "compute" && values[1] === "get")
@@ -1080,6 +1105,12 @@ export const parseProductCliCommand = (argv: readonly string[]): ProductCliComma
1080
1105
  const options = parseOptions(values, ["--organization"]);
1081
1106
  return { kind: "credits-account", organizationId: options["--organization"], credentialStore };
1082
1107
  }
1108
+ if (first === "referral" && values[0] === "link") {
1109
+ values.shift();
1110
+ if (yes || wait || follow || stdin) throw new InvalidCommandError();
1111
+ const options = parseOptions(values, ["--organization"]);
1112
+ return { kind: "referral-link", organizationId: options["--organization"], credentialStore };
1113
+ }
1083
1114
  if (first === "credits" && values.shift() === "balance") {
1084
1115
  if (yes || wait || follow || stdin) throw new InvalidCommandError();
1085
1116
  const options = parseOptions(values, ["--organization"]);
@@ -1266,6 +1297,27 @@ export const parseProductCliCommand = (argv: readonly string[]): ProductCliComma
1266
1297
  credentialStore,
1267
1298
  };
1268
1299
  }
1300
+ if (subcommand === "flag") {
1301
+ const action = values.shift();
1302
+ if (yes || wait || follow || stdin) throw new InvalidCommandError();
1303
+ if (action === "status") {
1304
+ const options = parseOptions(values, ["--project"]);
1305
+ return { kind: "project-flag-status", projectId: options["--project"], credentialStore };
1306
+ }
1307
+ if (action === "set") {
1308
+ const options = parseOptions(values, ["--project", "--enabled", "--idempotency-key"]);
1309
+ if (options["--enabled"] !== "true" && options["--enabled"] !== "false")
1310
+ throw new InvalidCommandError();
1311
+ return {
1312
+ kind: "project-flag-set",
1313
+ projectId: options["--project"],
1314
+ enabled: options["--enabled"] === "true",
1315
+ idempotencyKey: options["--idempotency-key"],
1316
+ credentialStore,
1317
+ };
1318
+ }
1319
+ throw new InvalidCommandError();
1320
+ }
1269
1321
  if (subcommand === "dev-access" && values.shift() === "create") {
1270
1322
  if (yes || wait || follow || stdin) throw new InvalidCommandError();
1271
1323
  const options = parseOptions(values, ["--project"]);