@amerged/ohmyhost-mcp 0.1.47 → 0.1.49

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.
@@ -17,7 +17,7 @@ export const GENERATED_SKILL_RESOURCES = Object.freeze([
17
17
  title: "ohmyhost-build-portable-app: references/ai.md",
18
18
  description: "Supporting resource for ohmyhost-build-portable-app.",
19
19
  mimeType: "text/markdown",
20
- text: "# Build AI features\n\nUse the ohmyho.st AI gateway for LLM chat and Responses, images, speech-to-text,\ntext-to-speech, classifiers and OCR. It serves the models orq.ai offers on the routes\nbelow, with no separate platform model catalog. The customer needs a Paid workspace,\navailable prepaid credit and an explicit jurisdiction choice.\n\n## Setup\n\n1. Ask the customer which jurisdiction their data may use. `any` permits every model\n served by orq.ai. `eu` requires the exact model's current orq metadata to list\n exclusively EU locations. Mixed, global, non-EU and empty locations are refused;\n names and provider prefixes never prove location. If metadata cannot be verified,\n the call fails with `503 ai_unavailable` before a credit hold. The EU model list\n contains only enabled models that meet this rule.\n2. Preserve the application's existing configuration and add:\n\n ```yaml\n ai:\n enabled: true\n jurisdiction: eu\n ```\n\n The jurisdiction has no default. Use `any` only when the customer chose it.\n\n3. Deploy the configuration. The platform injects the secret `OHMYHOST_AI_KEY` and\n `OHMYHOST_AI_BASE_URL` (the gateway origin plus `/v1`). Never set these yourself.\n The gateway origin is allowed automatically; omit it from `runtime.egress.allow`.\n4. Make a real call through a server route on the deployed app and inspect its spend\n with **ohmyhost-usage-and-budgets**. A health check must not make a paid AI call.\n\nRead the injected values from the framework's actual request context. Keep the key\nin server code: never send it to a browser, prompt or log. A static site cannot hold\nit; use an edge runtime, a Vite companion or a plain functions Worker. Static `ai`\ndeclarations are refused as `runtime_mode_unsupported`.\n\nThe active environment revision decides whether its key may be used and which\njurisdiction applies. Promoting or rolling back to source without `ai` stops calls.\n\n## Choose a model and call it\n\nUse `GET {baseURL}/models` for the current model ids allowed by the project's\njurisdiction. Choose a model that supports the desired route with the customer; the\nlist is not a promise that every model implements every modality. Read orq.ai's\n[supported models](https://docs.orq.ai/ai-gateway/supported-models) and\n[OpenAI-compatible API](https://docs.orq.ai/ai-gateway/features/openai-compatible-api) for the\nmodel's request fields. Do not replace the customer's model with a fixed platform\nlist or ask them for an orq API key.\n\nInstall the official `openai` package using the application's package manager. This\nserver example takes the model id selected for chat from that list. Resolve `env`\nfrom the hosted request; do not read it from a browser bundle.\n\n```ts\nimport OpenAI from \"openai\";\n\nexport function createChat(\n env: { OHMYHOST_AI_BASE_URL: string; OHMYHOST_AI_KEY: string },\n chosenModel: string,\n) {\n const ai = new OpenAI({\n baseURL: env.OHMYHOST_AI_BASE_URL,\n apiKey: env.OHMYHOST_AI_KEY,\n maxRetries: 0,\n });\n\n return {\n async answer(question: string): Promise<string> {\n const reply = await ai.chat.completions.create({\n model: chosenModel,\n messages: [{ role: \"user\", content: question }],\n max_tokens: 512,\n });\n return reply.choices[0]?.message.content ?? \"\";\n },\n async stream(question: string, onText: (text: string) => void): Promise<void> {\n const chunks = await ai.chat.completions.create({\n model: chosenModel,\n messages: [{ role: \"user\", content: question }],\n max_tokens: 512,\n stream: true,\n });\n for await (const chunk of chunks) onText(chunk.choices[0]?.delta.content ?? \"\");\n },\n };\n}\n```\n\nSet output limits appropriate to the selected model. The example disables automatic\nSDK retries so a failed response does not silently start another paid generation.\nIf the application retries, bound it and honor `Retry-After`. Handle\n`OpenAI.APIError.code` in the server route and return a useful application error\nwithout exposing the gateway key or private upstream details.\n\n## Routes and limits\n\nAll paths below are relative to `OHMYHOST_AI_BASE_URL`. Use the OpenAI SDK where it\nimplements the route, or ordinary server-side `fetch` with `Authorization: Bearer\n<injected key>` for orq's classifiers and OCR.\n\n| Method | Path | Body |\n| ------ | ----------------------- | ----------------------------------------------- |\n| POST | `/chat/completions` | JSON; `stream: true` returns Server-Sent Events |\n| POST | `/responses` | JSON |\n| POST | `/images/generations` | JSON |\n| POST | `/images/edits` | JSON or multipart form data |\n| POST | `/audio/transcriptions` | JSON or multipart form data |\n| POST | `/audio/speech` | JSON; the response can be audio bytes |\n| POST | `/decisions` | JSON |\n| POST | `/ocr` | JSON |\n| GET | `/models` | No body |\n| GET | `/realtime?model=<id>` | Server-side WebSocket upgrade; see below |\n\nEvery POST body requires a non-empty `model` id. JSON bodies are at most **16 MiB**;\nmultipart bodies are at most **26 MiB**, including form fields and boundaries. An\nupload needs exactly one `model` field. Let the SDK or `FormData` set its multipart\nboundary. Send `application/json` for JSON. Requests outside these routes, or query\nparameters on POST routes and `/models`, answer `404 not_found`. Embeddings,\nrerank, moderation, video generation and audio translation routes are not offered.\n\nThe gateway checks model identity and reserves routing control. These exact\ntop-level JSON keys or multipart field names are refused as `invalid_request` in\nboth jurisdictions: `orq`, `load_balancer`, `fallbacks`, `fallback_on_codes`, `retry`,\n`cache`, `timeout`, `plugins`, `models`, `route` and `routing`. Other model-specific\nfields follow orq's API. JSON request bytes are forwarded unchanged after checking;\nduplicate top-level JSON keys are refused as `invalid_request`. Multipart forms are\nrebuilt with a fresh boundary.\n\nA request body must arrive within 30 seconds. The upstream response headers have a\n120-second deadline; response streams end after 60 seconds without a chunk or ten\nminutes overall. Address and project rate limits also apply. A workspace may have\nat most eight unresolved AI calls; a ninth answers `429 ai_rate_limited` with\n`Retry-After: 5`. Other rate limits carry their own `Retry-After`.\n\n## Realtime voice\n\nThe `/realtime` WebSocket route is implemented and tested locally. Live acceptance\nis still pending: the orq workspace's xAI provider was inactive at the last check.\nDo not claim it works in the customer's deployed app until a real session passes.\n\nConnect from server code with the injected Bearer key. A browser WebSocket cannot\nset that header; keep the upstream socket in an authenticated application server\nroute. Use exactly one `model` query parameter and `Upgrade: websocket`; extra or\nrepeated parameters answer `400 invalid_request`. A session ends after five\nminutes, after one minute without messages in either direction, or when either\npeer closes or fails. Verify actual voice input, audio output and closure through\nthe hosted application.\n\n## Credit and failures\n\nCalls need sufficient available credit for a temporary hold before forwarding.\nThey settle against the cost orq records using the active `ai.cost` rate card and\ncount toward the project's Stop budget. Recorded zero is charged zero. A call whose\ncost cannot be read settles at zero and is logged for operator reconciliation;\nthe platform absorbs that unknown cost, with no automatic later debit. No guessed\nminimum, character or duration charge applies. Read the current factor on\n[the pricing page](https://ohmyho.st/pricing). Usage and credit balances come from\n**ohmyhost-usage-and-budgets**. No active AI rate card means calls are unavailable.\n\nGateway failures use `{ \"error\": { \"message\", \"type\", \"code\" } }`. Branch on\n`code`, not on the message; an invalid request can retain orq's rejection status.\n\n| Code | HTTP | Action |\n| ----------------------------------- | -------------------------------- | ------------------------------------------------------------------------------------ |\n| `invalid_api_key` | 401 | Redeploy the enabled capability; report a persistent failure with `feedback_submit`. |\n| `paid_plan_required` | 402 | Start a Paid workspace. |\n| `insufficient_organization_credits` | 402 | Add enough credit for the call's hold. |\n| `model_outside_jurisdiction` | 403 | Choose an allowed model from `/models`. |\n| `model_not_found` | 404 | Check the model id and its route. |\n| `not_found` | 404 | Use a route from the table. |\n| `invalid_request` | 400 or upstream rejection status | Fix the body, fields or content type. |\n| `request_too_large` | 413 | Reduce the body below the route's limit. |\n| `project_budget_exceeded` | 429 | Review the project's Stop budget. |\n| `ai_rate_limited` | 429 | Honor `Retry-After`. |\n| `ai_upstream_unavailable` | 502 | The provider failed; retry only within the application's policy. |\n| `ai_unavailable` | 503 | Retry later; report a persistent failure. |\n",
20
+ text: '# Build AI features\n\nUse the ohmyho.st AI gateway for LLM chat and Responses, images, speech-to-text,\ntext-to-speech, classifiers and OCR. It serves the models orq.ai offers on the routes\nbelow, with no separate platform model catalog. The customer needs a Paid workspace,\navailable prepaid credit and an explicit jurisdiction choice.\n\n## Setup\n\n1. Ask the customer which jurisdiction their data may use. `any` permits every model\n served by orq.ai. `eu` requires the exact model\'s current orq metadata to list\n exclusively EU locations. Mixed, global, non-EU and empty locations are refused;\n names and provider prefixes never prove location. If metadata cannot be verified,\n the call fails with `503 ai_unavailable` before a credit hold. The EU model list\n contains only enabled models that meet this rule.\n2. Preserve the application\'s existing configuration and add:\n\n ```yaml\n ai:\n enabled: true\n jurisdiction: eu\n ```\n\n The jurisdiction has no default. Use `any` only when the customer chose it.\n\n3. Deploy the configuration. The platform injects the secret `OHMYHOST_AI_KEY` and\n `OHMYHOST_AI_BASE_URL` (the gateway origin plus `/v1`). Never set these yourself.\n The gateway origin is allowed automatically; omit it from `runtime.egress.allow`.\n4. Make a real call through a server route on the deployed app and inspect its spend\n with **ohmyhost-usage-and-budgets**. A health check must not make a paid AI call.\n\nRead the injected values from the framework\'s actual request context. Keep the key\nin server code: never send it to a browser, prompt or log. A static site cannot hold\nit; use an edge runtime, a Vite companion or a plain functions Worker. Static `ai`\ndeclarations are refused as `runtime_mode_unsupported`.\n\nThe active environment revision decides whether its key may be used and which\njurisdiction applies. Promoting or rolling back to source without `ai` stops calls.\n\n## Choose a model and call it\n\nUse `GET {baseURL}/models` for the current model ids allowed by the project\'s\njurisdiction. Choose a model that supports the desired route with the customer; the\nlist is not a promise that every model implements every modality. Read orq.ai\'s\n[supported models](https://docs.orq.ai/ai-gateway/supported-models) and\n[OpenAI-compatible API](https://docs.orq.ai/ai-gateway/features/openai-compatible-api) for the\nmodel\'s request fields. Do not replace the customer\'s model with a fixed platform\nlist or ask them for an orq API key.\n\nInstall the official `openai` package using the application\'s package manager. This\nserver example takes the model id selected for chat from that list. Resolve `env`\nfrom the hosted request; do not read it from a browser bundle.\n\n```ts\nimport OpenAI from "openai";\n\nexport function createChat(\n env: { OHMYHOST_AI_BASE_URL: string; OHMYHOST_AI_KEY: string },\n chosenModel: string,\n) {\n const ai = new OpenAI({\n baseURL: env.OHMYHOST_AI_BASE_URL,\n apiKey: env.OHMYHOST_AI_KEY,\n maxRetries: 0,\n });\n\n return {\n async answer(question: string): Promise<string> {\n const reply = await ai.chat.completions.create({\n model: chosenModel,\n messages: [{ role: "user", content: question }],\n max_tokens: 512,\n });\n return reply.choices[0]?.message.content ?? "";\n },\n async stream(question: string, onText: (text: string) => void): Promise<void> {\n const chunks = await ai.chat.completions.create({\n model: chosenModel,\n messages: [{ role: "user", content: question }],\n max_tokens: 512,\n stream: true,\n });\n for await (const chunk of chunks) onText(chunk.choices[0]?.delta.content ?? "");\n },\n };\n}\n```\n\nSet output limits appropriate to the selected model. The example disables automatic\nSDK retries so a failed response does not silently start another paid generation.\nIf the application retries, bound it and honor `Retry-After`. Handle\n`OpenAI.APIError.code` in the server route and return a useful application error\nwithout exposing the gateway key or private upstream details.\n\n## Routes and limits\n\nAll paths below are relative to `OHMYHOST_AI_BASE_URL`. Use the OpenAI SDK where it\nimplements the route, or ordinary server-side `fetch` with `Authorization: Bearer\n<injected key>` for orq\'s classifiers and OCR.\n\n| Method | Path | Body |\n| ------ | ----------------------- | ----------------------------------------------- |\n| POST | `/chat/completions` | JSON; `stream: true` returns Server-Sent Events |\n| POST | `/responses` | JSON |\n| POST | `/images/generations` | JSON |\n| POST | `/images/edits` | JSON or multipart form data |\n| POST | `/audio/transcriptions` | JSON or multipart form data |\n| POST | `/audio/speech` | JSON; the response can be audio bytes |\n| POST | `/decisions` | JSON |\n| POST | `/ocr` | JSON |\n| GET | `/models` | No body |\n| GET | `/realtime?model=<id>` | Server-side WebSocket upgrade; see below |\n\nEvery POST body requires a non-empty `model` id. JSON bodies are at most **16 MiB**;\nmultipart bodies are at most **26 MiB**, including form fields and boundaries. An\nupload needs exactly one `model` field. Let the SDK or `FormData` set its multipart\nboundary. Send `application/json` for JSON. Requests outside these routes, or query\nparameters on POST routes and `/models`, answer `404 not_found`. Embeddings,\nrerank, moderation, video generation and audio translation routes are not offered.\n\nThe gateway checks model identity and reserves routing control. These exact\ntop-level JSON keys or multipart field names are refused as `invalid_request` in\nboth jurisdictions: `orq`, `load_balancer`, `fallbacks`, `fallback_on_codes`, `retry`,\n`cache`, `timeout`, `plugins`, `models`, `route` and `routing`. Other model-specific\nfields follow orq\'s API. JSON request bytes are forwarded unchanged after checking;\nduplicate top-level JSON keys are refused as `invalid_request`. Multipart forms are\nrebuilt with a fresh boundary.\n\nA request body must arrive within 30 seconds. The upstream response headers have a\n120-second deadline; response streams end after 60 seconds without a chunk or ten\nminutes overall. Address and project rate limits also apply. A workspace may have\nat most eight unresolved AI calls; a ninth answers `429 ai_rate_limited` with\n`Retry-After: 5`. Other rate limits carry their own `Retry-After`.\n\n## Realtime voice\n\nThe `/realtime` WebSocket route is implemented and tested locally. Live acceptance\nis still pending: authenticated upstream sessions closed with code `1011` before\nmessages or audio. Do not claim voice works until a real hosted session passes.\n\nConnect from server code with the injected Bearer key. A browser WebSocket cannot\nset that header; keep the upstream socket in an authenticated application server\nroute. Use exactly one `model` query parameter and `Upgrade: websocket`; extra or\nrepeated parameters answer `400 invalid_request`. A session ends after five\nminutes, after one minute without messages in either direction, or when either\npeer closes or fails. Verify actual voice input, audio output and closure through\nthe hosted application.\n\n## Credit and failures\n\nCalls need sufficient available credit for a temporary hold before forwarding.\nThey settle against the cost orq records using the active `ai.cost` rate card and\ncount toward the project\'s Stop budget. Recorded zero is charged zero. A call whose\ncost cannot be read after bounded retries uses a **USD 0.01 cost fallback only for\n`/chat/completions` and `/responses`**, then the active rate-card factor. This is\nnot a minimum: any reported cost, including zero or a smaller value, takes priority.\nOther routes with unavailable cost settle at zero and are logged; no character or\nduration tariff applies. No later automatic correction is posted. Read the current factor on\n[the pricing page](https://ohmyho.st/pricing). Usage and credit balances come from\n**ohmyhost-usage-and-budgets**. No active AI rate card means calls are unavailable.\n\nGateway failures use `{ "error": { "message", "type", "code" } }`. Branch on\n`code`, not on the message; an invalid request can retain orq\'s rejection status.\n\n| Code | HTTP | Action |\n| ----------------------------------- | -------------------------------- | ------------------------------------------------------------------------------------ |\n| `invalid_api_key` | 401 | Redeploy the enabled capability; report a persistent failure with `feedback_submit`. |\n| `paid_plan_required` | 402 | Start a Paid workspace. |\n| `insufficient_organization_credits` | 402 | Add enough credit for the call\'s hold. |\n| `model_outside_jurisdiction` | 403 | Choose an allowed model from `/models`. |\n| `model_not_found` | 404 | Check the model id and its route. |\n| `not_found` | 404 | Use a route from the table. |\n| `invalid_request` | 400 or upstream rejection status | Fix the body, fields or content type. |\n| `request_too_large` | 413 | Reduce the body below the route\'s limit. |\n| `project_budget_exceeded` | 429 | Review the project\'s Stop budget. |\n| `ai_rate_limited` | 429 | Honor `Retry-After`. |\n| `ai_upstream_unavailable` | 502 | The provider failed; retry only within the application\'s policy. |\n| `ai_unavailable` | 503 | Retry later; report a persistent failure. |\n',
21
21
  },
22
22
  {
23
23
  skillName: "ohmyhost-build-portable-app",
@@ -53,7 +53,7 @@ export const GENERATED_SKILL_RESOURCES = Object.freeze([
53
53
  title: "ohmyhost-build-portable-app: references/row-level-security.md",
54
54
  description: "Supporting resource for ohmyhost-build-portable-app.",
55
55
  mimeType: "text/markdown",
56
- text: '# Backend row level security\n\nUse PostgreSQL policies when the customer wants database-enforced row permissions. Backend RLS is part of the coordinated 0.1.30 cohort and requires both the matching customer-runtime package with `withRls` and the deployed database service exposing `openRls`, including trusted SQL-helper preparation. Source code or a package import alone does not prove the deployed capability. Report a missing capability; never replace a refused RLS call with a context-free query. The earlier 0.1.29 package does not provide `withRls`.\n\nThe application authenticates its own users before accessing protected data. An ohmyho.st login, API key, MCP project grant or protected Dev ticket is not the application\'s end-user identity. Authentication tables and existing unprotected application tables are not automatically changed.\n\n## Define a policy in a new migration\n\nFor a new application table, add an immutable `YYYYMMDDHHMMSS_name.sql` migration:\n\n```sql\nCREATE TABLE public.notes (\n id text PRIMARY KEY,\n owner_id text NOT NULL,\n body text NOT NULL\n);\nCREATE POLICY own_notes ON public.notes\n FOR ALL TO PUBLIC\n USING (owner_id = ohmyhost.user_id())\n WITH CHECK (owner_id = ohmyhost.user_id());\nALTER TABLE public.notes ENABLE ROW LEVEL SECURITY;\nALTER TABLE public.notes FORCE ROW LEVEL SECURITY;\n```\n\nNative v1 policy changes cover CREATE POLICY, ALTER POLICY conditions, DROP POLICY with RESTRICT/default and single ENABLE/FORCE statements. Targets are explicitly qualified, migrator-owned ordinary tables in `public` or `private`, with `TO PUBLIC` or omitted role. The complete migration run must leave every touched table both enabled and forced. No policy means default-deny. Append a new migration for policy changes; do not edit an applied file. Ordinary destructive schema/data operations, role/grant changes, DISABLE/NO FORCE, CASCADE and policy renames remain unsupported.\n\nExisting SQL table grants still determine which commands a login may execute. Policies further restrict the rows; they do not create privileges. A missing grant or WITH CHECK violation produces SQLSTATE `42501`; a USING denial can simply return zero rows. Verify that a denied write left its intended target unchanged. Multiple permissive policies combine with OR; restrictive policies add AND requirements and need an applicable permissive policy.\n\n## Bind a verified application user\n\n`subject` is opaque text, supporting UUIDs and provider-specific IDs. Use the ID from a verified server session; only pass selected server-controlled authorization claims. Reject missing, tampered, expired or revoked sessions before opening a protected data transaction. Better Auth\'s server session API or the customer\'s WorkOS server SDK performs authentication; decoding a cookie/JWT or accepting a body `user_id` does not.\n\nAfter the route has obtained a verified session and its actual Worker binding:\n\n```ts\nimport { createPrivateDatabaseClient } from "@ohmyhost/customer-runtime/database";\n\nconst database = createPrivateDatabaseClient(env.OHMYHOST_DATABASE);\nconst rows = await database.withRls({ subject: session.user.id }, async (tx) => {\n const result = await tx.query({\n text: "SELECT id, body FROM public.notes ORDER BY id",\n values: [],\n });\n return result.rows;\n});\n```\n\nThe callback contains no explicit BEGIN/COMMIT/ROLLBACK, savepoints or session-control SQL. It receives only query, on one service-owned transaction. The service sets complete transaction-local claims; any query or result-validation error prevents commit even when the callback catches it. Unawaited dispatched work is settled before completion, and captured handles cannot be used afterwards. An uncertain COMMIT raises nonretryable `database_transaction_outcome_unknown`: inspect the business outcome before an explicitly bounded retry; do not assume it rolled back.\n\n`database.withRls(null, callback)` explicitly selects anonymous access. Policies can use `ohmyhost.claims()->>\'role\'`, whose value is `authenticated` or `anon`, and `ohmyhost.user_id()`, which is NULL for anonymous/context-free access. Additional claims are passed through `claims`; callers cannot override reserved `sub` or `role`. Identity is limited to 512 UTF-8 bytes; the complete plain-JSON claims document is at most 16 KiB, depth eight, 1,000 nodes and 128-byte keys. Current query/result limits, 100 statements, thirty-second lifetime and five-second idle timeout apply. Do not wait for mail, uploads or external providers inside the transaction.\n\nFor organization or administrator policies, derive claims from checked membership or server authority. Freely editable user profile metadata is not authorization. Background jobs receive no automatic service role; define their allowed rows through an explicit trusted identity and policy. These PostgreSQL settings are assertions by trusted backend code, not protection from a compromised backend or arbitrary SQL injection.\n\n## Use Kysely within the same scope\n\n```ts\nimport { Kysely } from "kysely";\nimport { createRlsTransactionDialect } from "@ohmyhost/customer-runtime/database-kysely";\n\nconst rows = await database.withRls({ subject: session.user.id }, async (tx) => {\n const db = new Kysely<{ notes: { id: string; owner_id: string; body: string } }>({\n dialect: createRlsTransactionDialect(tx),\n });\n try {\n return await db.selectFrom("notes").select(["id", "body"]).execute();\n } finally {\n await db.destroy();\n }\n});\n```\n\nDo not open a nested Kysely transaction or use its streaming API in this scope. Destroying this dialect releases its wrappers, not the owner transaction. The existing private dialect remains available for non-RLS work and authentication/session lookup.\n\n## Adopt on an existing application\n\nFirst deploy identity-aware application code while tables remain unenforced. Then add policy/enforcement migrations only after every live consumer of the physical database is verified context-ready. Shared Dev/Prod data requires both active applications ready immediately before enforcement, because migrations precede the new Worker cutover. After activation, only a verified context-ready artifact is a supported rollback target; never disable RLS automatically to rescue old code.\n\nOrdinary CLI/MCP reads/writes and temporary SQL logins do not manufacture an application actor or bypass row policies. An empty result is not proof that the database has no rows. Authorized complete export uses the existing private privileged recovery path; verify that the archive contains hidden rows, the SQL helpers and policies and can be restored with different environment role names. Do not use a restricted query result as a full backup.\n\nFor Supabase schema baselines, use the [explicit reviewed RLS conversion mode](../../ohmyhost-migrate-supabase-postgres/references/provider-contracts.md#preserving-backend-rls). Unsupported policies, roles or claim mappings stop conversion. Source migration, authentication choice, data movement and provider rollout remain separate authorized actions.\n',
56
+ text: '# Backend row level security\n\nUse PostgreSQL policies when the customer wants database-enforced row permissions. Backend RLS is part of the coordinated 0.1.30 cohort and requires both the matching customer-runtime package with `withRls` and the deployed database service exposing `openRls`, including trusted SQL-helper preparation. Source code or a package import alone does not prove the deployed capability. Report a missing capability; never replace a refused RLS call with a context-free query. The earlier 0.1.29 package does not provide `withRls`.\n\nThe application authenticates its own users before accessing protected data. An ohmyho.st login, API key, MCP connection or protected Dev ticket is not the application\'s end-user identity. Authentication tables and existing unprotected application tables are not automatically changed.\n\n## Define a policy in a new migration\n\nFor a new application table, add an immutable `YYYYMMDDHHMMSS_name.sql` migration:\n\n```sql\nCREATE TABLE public.notes (\n id text PRIMARY KEY,\n owner_id text NOT NULL,\n body text NOT NULL\n);\nCREATE POLICY own_notes ON public.notes\n FOR ALL TO PUBLIC\n USING (owner_id = ohmyhost.user_id())\n WITH CHECK (owner_id = ohmyhost.user_id());\nALTER TABLE public.notes ENABLE ROW LEVEL SECURITY;\nALTER TABLE public.notes FORCE ROW LEVEL SECURITY;\n```\n\nNative v1 policy changes cover CREATE POLICY, ALTER POLICY conditions, DROP POLICY with RESTRICT/default and single ENABLE/FORCE statements. Targets are explicitly qualified, migrator-owned ordinary tables in `public` or `private`, with `TO PUBLIC` or omitted role. The complete migration run must leave every touched table both enabled and forced. No policy means default-deny. Append a new migration for policy changes; do not edit an applied file. Ordinary destructive schema/data operations, role/grant changes, DISABLE/NO FORCE, CASCADE and policy renames remain unsupported.\n\nExisting SQL table grants still determine which commands a login may execute. Policies further restrict the rows; they do not create privileges. A missing grant or WITH CHECK violation produces SQLSTATE `42501`; a USING denial can simply return zero rows. Verify that a denied write left its intended target unchanged. Multiple permissive policies combine with OR; restrictive policies add AND requirements and need an applicable permissive policy.\n\n## Bind a verified application user\n\n`subject` is opaque text, supporting UUIDs and provider-specific IDs. Use the ID from a verified server session; only pass selected server-controlled authorization claims. Reject missing, tampered, expired or revoked sessions before opening a protected data transaction. Better Auth\'s server session API or the customer\'s WorkOS server SDK performs authentication; decoding a cookie/JWT or accepting a body `user_id` does not.\n\nAfter the route has obtained a verified session and its actual Worker binding:\n\n```ts\nimport { createPrivateDatabaseClient } from "@ohmyhost/customer-runtime/database";\n\nconst database = createPrivateDatabaseClient(env.OHMYHOST_DATABASE);\nconst rows = await database.withRls({ subject: session.user.id }, async (tx) => {\n const result = await tx.query({\n text: "SELECT id, body FROM public.notes ORDER BY id",\n values: [],\n });\n return result.rows;\n});\n```\n\nThe callback contains no explicit BEGIN/COMMIT/ROLLBACK, savepoints or session-control SQL. It receives only query, on one service-owned transaction. The service sets complete transaction-local claims; any query or result-validation error prevents commit even when the callback catches it. Unawaited dispatched work is settled before completion, and captured handles cannot be used afterwards. An uncertain COMMIT raises nonretryable `database_transaction_outcome_unknown`: inspect the business outcome before an explicitly bounded retry; do not assume it rolled back.\n\n`database.withRls(null, callback)` explicitly selects anonymous access. Policies can use `ohmyhost.claims()->>\'role\'`, whose value is `authenticated` or `anon`, and `ohmyhost.user_id()`, which is NULL for anonymous/context-free access. Additional claims are passed through `claims`; callers cannot override reserved `sub` or `role`. Identity is limited to 512 UTF-8 bytes; the complete plain-JSON claims document is at most 16 KiB, depth eight, 1,000 nodes and 128-byte keys. Current query/result limits, 100 statements, thirty-second lifetime and five-second idle timeout apply. Do not wait for mail, uploads or external providers inside the transaction.\n\nFor organization or administrator policies, derive claims from checked membership or server authority. Freely editable user profile metadata is not authorization. Background jobs receive no automatic service role; define their allowed rows through an explicit trusted identity and policy. These PostgreSQL settings are assertions by trusted backend code, not protection from a compromised backend or arbitrary SQL injection.\n\n## Use Kysely within the same scope\n\n```ts\nimport { Kysely } from "kysely";\nimport { createRlsTransactionDialect } from "@ohmyhost/customer-runtime/database-kysely";\n\nconst rows = await database.withRls({ subject: session.user.id }, async (tx) => {\n const db = new Kysely<{ notes: { id: string; owner_id: string; body: string } }>({\n dialect: createRlsTransactionDialect(tx),\n });\n try {\n return await db.selectFrom("notes").select(["id", "body"]).execute();\n } finally {\n await db.destroy();\n }\n});\n```\n\nDo not open a nested Kysely transaction or use its streaming API in this scope. Destroying this dialect releases its wrappers, not the owner transaction. The existing private dialect remains available for non-RLS work and authentication/session lookup.\n\n## Adopt on an existing application\n\nFirst deploy identity-aware application code while tables remain unenforced. Then add policy/enforcement migrations only after every live consumer of the physical database is verified context-ready. Shared Dev/Prod data requires both active applications ready immediately before enforcement, because migrations precede the new Worker cutover. After activation, only a verified context-ready artifact is a supported rollback target; never disable RLS automatically to rescue old code.\n\nOrdinary CLI/MCP reads/writes and temporary SQL logins do not manufacture an application actor or bypass row policies. An empty result is not proof that the database has no rows. Authorized complete export uses the existing private privileged recovery path; verify that the archive contains hidden rows, the SQL helpers and policies and can be restored with different environment role names. Do not use a restricted query result as a full backup.\n\nFor Supabase schema baselines, use the [explicit reviewed RLS conversion mode](../../ohmyhost-migrate-supabase-postgres/references/provider-contracts.md#preserving-backend-rls). Unsupported policies, roles or claim mappings stop conversion. Source migration, authentication choice, data movement and provider rollout remain separate authorized actions.\n',
57
57
  },
58
58
  {
59
59
  skillName: "ohmyhost-build-portable-app",
@@ -72,7 +72,7 @@ export const GENERATED_SKILL_RESOURCES = Object.freeze([
72
72
  description:
73
73
  "Deploy an explicitly selected or already bound GitHub application to ohmyho.st, verify Dev and publish the exact version to Prod. Also use for its requested rollback, deletion or Dev/Prod data changes. Use ohmyhost-deploy to choose a source for an unbound app; use troubleshooting for a stuck operation.",
74
74
  mimeType: "text/markdown",
75
- text: "---\nname: ohmyhost-deploy-github\ndescription: Deploy an explicitly selected or already bound GitHub application to ohmyho.st, verify Dev and publish the exact version to Prod. Also use for its requested rollback, deletion or Dev/Prod data changes. Use ohmyhost-deploy to choose a source for an unbound app; use troubleshooting for a stuck operation.\n---\n\n# Deploy a GitHub app\n\nRead the installed CLI help or MCP tool schemas before supplying arguments. Use the customer's selected repository and branch.\nWrite concise customer-facing guidance in the customer's language and preserve their chosen scope.\n\nRead the saved checkout binding and `source_get` before this workflow. A managed binding is\nauthoritative; use **ohmyhost-deploy** rather than adding a GitHub remote or replacing its source.\nAn existing Git repository is not proof that it is hosted on GitHub: inspect its remotes and\nthe selected project. If an unbound local app has no GitHub source and the customer has not\nchosen one, ask once whether to keep its versions with ohmyho.st or set up GitHub. An explicit\nchoice already authorizes that route and needs no repeated routing question.\n\nConverting a GitHub-backed project to managed source uses the same project, URLs and data.\nFollow the explicit switch path in **ohmyhost-deploy**; do not create a replacement project or\ninvent a source generation. Before a generation-aware change, call `source_get` with\n`include_binding: true`; the default keeps the released GitHub source view. Do not\nunlink the existing source before its replacement is complete.\nFor a native switch, stage the intended complete tree with `source_upload_prepare`\n(`mode: \"switch\"`) and `source_blob_put`, then finalize `source_switch` from the receipts\nusing the original GitHub connection/generation. The existing binding stays until completion;\nproject URLs and data remain, GitHub auto-deploy is disabled, and deployment is separate.\nThe remote hosting connection cannot read GitHub files by itself: use the client's actual\nrepository/file context or state the missing capability; never invent a replacement tree.\n\n## Connected chat with a GitHub source\n\nThe source choice follows the selected repository and user preference, including when the agent\nruns in a cloud environment. Use this flow when the authenticated remote tools are available;\nthe local CLI flow below remains available for a local checkout.\n\n1. Complete the remote OAuth and project-grant flow in **ohmyhost-get-started**. Reuse the saved\n project through `project_list`, `project_get` and `project_context_get`. For a new GitHub app,\n send `source_provider: \"github\"` explicitly to `project_create`, together with the user's\n organization, name, region, data mode and Dev access choice. This avoids queuing a managed\n template over the selected GitHub source.\n2. Develop and push the actual repository through the client's available repository tools.\n Inspect the repository-root `ohmyhost.yaml`, lockfile and selected application root through\n those tools. Use **ohmyhost-build-portable-app** to resolve framework/configuration blockers.\n If this client cannot read or write the selected repository, state the concrete missing\n capability; a connected hosting tool does not itself provide GitHub editing.\n3. Read `github_status` for the approved organization. When a connection is needed, call\n `github_connect` with one saved key and present its private `url`. The existing signed-in\n portal leads the user through **Continue to GitHub** and the official GitHub authorization.\n Return to `github_status` to confirm the result. Do not request a provider token or construct\n a GitHub callback. Then call `source_link` with the same project and selected repository\n owner/name, observe its accepted operation, and confirm the binding with `source_get`.\n4. Supply required credentials with `secret_input_request` for Dev or Prod and observe the\n private input through `connection_status`; never put secret values into chat or tool calls.\n Plan the exact pushed commit with `deployment_plan`, create the reviewed plan with\n `deployment_create`, and observe its original `operation_get`. Verify Dev with the returned\n URL and, when protected, `project_dev_share_link_get` for the user or a one-use\n `project_dev_access_create` ticket for the agent. Keep entry links out of source and logs.\n5. When publication is requested and its separate project grant permits it, use `promotion_plan`\n and `deployment_promote` with the actual ETag, confirmation and idempotency guards. Shared\n data requires Dev followed by promotion; isolated data also permits an exact commit planned\n into Prod. Supply Prod's own credentials, observe completion and verify the public result.\n\n## Local checkout with GitHub\n\n1. Run `ohmyhost init --dry-run --json` in that repository. A blocked repository reports `status: \"blocked\"` and exits non-zero while still returning the full analysis; read `blockers` rather than the exit code alone. Resolve returned blockers and requirements; preserve existing auth, migrations and configuration. Without an `ohmyhost.yaml`, run `ohmyhost init --json` once no blockers remain: it writes the file at the repository root (`--root DIR` picks one of several apps, `--region eu` for an EU project). Commit and push it before planning. Init never overwrites an existing file: it answers `repository_configuration_exists`, while `--dry-run` still analyzes the repository. Use the portable-app Skill for source changes, or the Supabase Skill only for a requested migration.\n2. Complete installation, login and organization selection with the ohmyhost-get-started Skill; use `identity_get` to confirm the selected organization and `projects_list` to reuse an existing project. When several ohmyho.st logins are saved, or the prompt names a user and organization, run every authenticated call as that one login (`--profile-name` / `profile_name`) and confirm it with `identity_get` first; `init` is offline and takes no login flag. For a new project, ask: \"Should your Dev page be public or protected by a shareable link?\" Protected is the default; public lets anyone with the Dev URL open it, including when Dev and Prod share data. Explain isolated Dev/Prod data versus shared data and the hosting region, then use `project_create` with the chosen `dev_access_mode`, data mode and `region`. Recommend isolated data; two databases consume credits separately. `data_mode` is optional and defaults to `shared`; send the customer's choice explicitly. A later change uses the confirmed data-change flow below and never copies data. An explicit region wins over a supplied browser-location hint; without either, ask once. Preserve an existing project's region and never use the agent IP. The API defaults to `us`; new projects provision their PGCF database, new files, build sandbox and build objects in the selected US or EU region. Existing projects moved to US with their owner's authorization use US databases. Read `database_compute_get.region` for the observed database location. A database migration preserves existing R2 object locations. The customer API cannot change the region after creation and prices are identical in both regions. `storage.jurisdiction` in `ohmyhost.yaml` must match an existing R2 bucket or reservation's recorded jurisdiction, which survives an operator database or project-region migration. Only new storage uses the current project region. Read the `Storage jurisdiction` line in `ohmyhost project context --project ULID --json` (MCP `project_context_get`) before changing storage configuration. If it reports `unavailable`, report the request through feedback; do not substitute the current project region. Preserve established storage configuration; offline `init` writes `us` unless it gets `--region eu`. Transactional mail is sent from the platform's mail region and is not a per-project choice. Hosting needs no mail domain: ask whether the app should send or receive email, and configure one only when the customer wants mail or the app declares `mail.enabled`; a Dev URL, a new project or a Better Auth package alone never calls for one. Mail requires Paid access: without it, `mail_setup` answers `paid_plan_required`. An app declaring `mail.enabled` also needs its configured mail domain before deployment (`mail_domain_required`); activate Paid and configure mail, or set `mail.enabled: false` when the app sends no mail.\n3. Read `github_status` for the workspace. When needed, use `github_connect` once, open its single `authorization_url` and repeat the same key until connected; on `failed` or `expired`, resolve `last_failure` as the get-started Skill describes and connect with a new key. Then use `source_link` for the selected repository and observe its returned operation; it needs no second browser consent for a covered repository. Missing repository access is repaired through status's `connection.settings_url`, followed by the same link/key. Errors name the account the call ran as (`acting_as`): `resource_not_found` means that login cannot see the project, `github_connection_required` means this workspace has no GitHub connection yet, and `repository_not_installed` means the App installation does not cover the repository. Each workspace connects GitHub on its own, so the same repository can be linked in the workspaces of separate accounts. Read `source_get`, then `deployment_plan` for the exact pushed commit. Review the plan's costs, requirements and effects with the customer's existing authorization; quote `estimated_cost.credits` as the credits held for this build only. Measured build seconds are charged and the rest is released.\n4. Read `project_status` for environment IDs. Supply required application secrets through the stdin command returned by `secret_set_command`. Use the chosen environment; Dev is not Prod.\n5. Execute `deployment_create` with the returned plan and one saved idempotency key. Reuse that key if the response is uncertain. Poll `operation_get` for the accepted operation at its suggested interval; another build is not a status check. After success, some requests can still reach the previous version for up to 30 seconds; recheck before calling a change missing.\n6. Read `dev_access_mode` and the resulting URL from project status. Public Dev opens at the clean URL. For protected Dev, call `project_dev_share_link_get`: its owner-only `share_url` can be reused by multiple visitors without automatic expiry. Open it to receive a browser session, then verify the clean URL, application login if present, and a real read/write flow. For declared `functions.crons`, confirm in `function_runs_list` that a due run succeeded; deployment logs do not show runs. Keep the link private outside intended recipients; `project_dev_share_link_rotate` or `project_dev_share_link_revoke` blocks old links and sessions on their next request. Opening the link signs that browser in for 12 hours and lands on the Dev root `/`; open it again after that. After rotation or revocation, that browser's page loads redirect to <https://ohmyho.st/>. `project_dev_access_mode_set` switches an existing project between public and protected Dev; switching back to protected creates a new link. Never put the link in notes, source or logs.\n7. If production publication is requested, either promote the verified Dev deployment with `promotion_plan` and `promotion_execute`, or plan the commit straight into Prod with `deployment_plan` and `environment: \"prod\"`. Before either, set Prod's own secrets with `secret_set_command` and the Prod environment ID from `project_status`: secrets are never copied from Dev, and a missing one often fails Prod's health check (`runtime_candidate_failed`). A project whose Dev and Prod share one database must deploy to Dev and promote; a direct Prod plan returns `shared_data_requires_promotion`. Isolated promotion applies schema migrations without copying Dev records; test that existing Prod records survive. \"Dev only\" means leaving Prod undeployed; no separate project mode is needed.\n8. After the first working Prod deployment, ask the customer once whether their site should show the small \"Powered by ohmyho.st\" flag on its right edge. While it shows, the project's custom domain uses no domain credits on any plan and also works on Free. Each Stripe Paid period adds 250 credits to the workspace, however many projects show the flag: 1,250 instead of 1,000 monthly credits, 25% more. Switch it only on their answer with `powered_by_flag_set` (`ohmyhost project flag set`); never enable it by default. It shows on Prod addresses within about 30 seconds and only in HTML pages a browser opens; check it in a browser, or send `Sec-Fetch-Dest: document` for an automated check.\n9. To undo a release when the customer asks, read `deployments_list`: the live deployment has `status: active`, and an earlier one that was live has `status: rolled_back`. Use `rollback_plan` for that deployment, then `rollback_execute` with the plan's `resource_etag` as `if_match`, its `confirmation_token` and a saved idempotency key; poll the returned operation (CLI `ohmyhost rollback plan` and `ohmyhost rollback`). Rollback reactivates the artifact in its own environment without a rebuild and switches its crons, but never reverts migrations: the older code must work with the current schema. If its health check fails, `runtime_candidate_failed` and the `HEALTH_CHECK_FAILED` deployment diagnostic explain the failure, and the current version stays live. Promotion, rollback and deletion plans expire after ten minutes; on `confirmation_expired` or `etag_mismatch`, plan again.\n\nReturn the working URL, deployed commit and any remaining action. If an operation stalls, use the troubleshooting Skill. Read `project_context_get` when resuming; use `project_notes_set` with its current notes version to retain a short decision or unfinished task, never credentials or signed links.\n\n## Change Dev/Prod data or reset Dev\n\nOnly a workspace Owner/Admin can change data assignments. Read `project_context_get` and\n`project_status`, then use `project_data_plan` (CLI `ohmyhost project data plan --project ULID\n--change CHANGE --json`) for the customer's chosen action:\n\n| Change | Result |\n| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `isolate_prod_keeps_data` | Shared to isolated: Prod keeps the existing database and its files; Dev gets a fresh empty data area. Redeploy Dev. |\n| `isolate_dev_keeps_data` | Shared to isolated: Dev keeps the existing database and its files; Prod gets a fresh empty data area and is unavailable until redeployed. |\n| `share_prod_keeps_data` | Isolated to shared: requires an existing Prod database, keeps Prod's data and files, and deletes Dev's database, files and deployment. Redeploy Dev to use Prod's data. |\n| `reset_dev` | Isolated only: deletes Dev's database, files and deployment and gives Dev a fresh empty data area. Redeploy Dev; Prod stays. |\n\nReview the plan's effects and destructive effects with the customer before execution. After their\nexplicit confirmation, pass the exact `change`, `resource_etag` as `if_match`,\n`confirmation_token`, one saved `idempotency_key` and `confirmed: true` to `project_data_change`.\nThe CLI equivalent is `ohmyhost project data change --project ULID --change CHANGE --if-match ETAG\n--confirmation-token TOKEN --idempotency-key KEY --yes --wait --json`. The plan lives ten minutes;\nan expired confirmation or changed ETag requires a fresh plan and review. Follow `operation_get`;\nafter an uncertain response reuse the same key, never start another reset to retry. Finish or\nreconcile an operation already changing that project before requesting another change.\n\nNo action copies data, and ordinary usage prices apply. A new database is provisioned lazily\nwhen a database-enabled app next deploys. Cloudflare R2 files follow the same data identity; shared\nDev/Prod see the same file keys, while a fresh isolated or reset data area can reuse keys from\nthe old area. A key remains create-once within one data area, including after deletion.\nCustomer secrets, access mode and region stay; sharing and reset renew a protected Dev link,\nso retrieve its new `share_url` after the operation. Deleted Dev data and files cannot be restored\nby switching back; a SQL export contains database records only, not files.\n\n## Delete a project\n\nDelete only on the customer's explicit request. `delete_plan` (CLI `ohmyhost delete plan`)\nreturns the effects, `resource_etag` and a `confirmation_token` valid for ten minutes. Review\nthose effects and obtain confirmation before `delete_execute` (CLI `ohmyhost delete --project\nULID --if-match ETAG --confirmation-token TOKEN --idempotency-key KEY --yes --json`); follow\nthe returned operation. Deletion removes Dev and Prod deployments, databases, files, function\nschedules, stored SQL exports, and the domain, mail and DNS records ohmyho.st created. It cannot\nbe undone; when database records matter, create and download a SQL export first. Files need\ntheir own application download before deletion. If the operation ends `failed`, follow its\nsuggested action and plan deletion again once no other project operation runs.\n",
75
+ text: "---\nname: ohmyhost-deploy-github\ndescription: Deploy an explicitly selected or already bound GitHub application to ohmyho.st, verify Dev and publish the exact version to Prod. Also use for its requested rollback, deletion or Dev/Prod data changes. Use ohmyhost-deploy to choose a source for an unbound app; use troubleshooting for a stuck operation.\n---\n\n# Deploy a GitHub app\n\nRead the installed CLI help or MCP tool schemas before supplying arguments. Use the customer's selected repository and branch.\nWrite concise customer-facing guidance in the customer's language and preserve their chosen scope.\n\nRead the saved checkout binding and `source_get` before this workflow. A managed binding is\nauthoritative; use **ohmyhost-deploy** rather than adding a GitHub remote or replacing its source.\nAn existing Git repository is not proof that it is hosted on GitHub: inspect its remotes and\nthe selected project. If an unbound local app has no GitHub source and the customer has not\nchosen one, ask once whether to keep its versions with ohmyho.st or set up GitHub. An explicit\nchoice already authorizes that route and needs no repeated routing question.\n\nConverting a GitHub-backed project to managed source uses the same project, URLs and data.\nFollow the explicit switch path in **ohmyhost-deploy**; do not create a replacement project or\ninvent a source generation. Before a generation-aware change, call `source_get` with\n`include_binding: true`; the default keeps the released GitHub source view. Do not\nunlink the existing source before its replacement is complete.\nFor a native switch, stage the intended complete tree with `source_upload_prepare`\n(`mode: \"switch\"`) and `source_blob_put`, then finalize `source_switch` from the receipts\nusing the original GitHub connection/generation. The existing binding stays until completion;\nproject URLs and data remain, GitHub auto-deploy is disabled, and deployment is separate.\nThe remote hosting connection cannot read GitHub files by itself: use the client's actual\nrepository/file context or state the missing capability; never invent a replacement tree.\n\n## Connected chat with a GitHub source\n\nThe source choice follows the selected repository and user preference, including when the agent\nruns in a cloud environment. Use this flow when the authenticated remote tools are available;\nthe local CLI flow below remains available for a local checkout.\n\n1. Complete the remote OAuth flow in **ohmyhost-get-started**. Reuse the saved\n project through `project_list`, `project_get` and `project_context_get`. For a new GitHub app,\n send `source_provider: \"github\"` explicitly to `project_create`, together with the user's\n organization, name, region, data mode and Dev access choice. This avoids queuing a managed\n template over the selected GitHub source.\n2. Develop and push the actual repository through the client's available repository tools.\n Inspect the repository-root `ohmyhost.yaml`, lockfile and selected application root through\n those tools. Use **ohmyhost-build-portable-app** to resolve framework/configuration blockers.\n If this client cannot read or write the selected repository, state the concrete missing\n capability; a connected hosting tool does not itself provide GitHub editing.\n3. Read `github_status` for the approved organization. When a connection is needed, call\n `github_connect` with one saved key and present its private `url`. The existing signed-in\n portal leads the user through **Continue to GitHub** and the official GitHub authorization.\n Return to `github_status` to confirm the result. Do not request a provider token or construct\n a GitHub callback. Then call `source_link` with the same project and selected repository\n owner/name, observe its accepted operation, and confirm the binding with `source_get`.\n4. Supply required credentials with `secret_input_request` for Dev or Prod and observe the\n private input through `connection_status`; never put secret values into chat or tool calls.\n Plan the exact pushed commit with `deployment_plan`, create the reviewed plan with\n `deployment_create`, and observe its original `operation_get`. Verify Dev with the returned\n URL and, when protected, `project_dev_share_link_get` for the user or a one-use\n `project_dev_access_create` ticket for the agent. Keep entry links out of source and logs.\n5. When publication is requested and the current role and token permit it, use `promotion_plan`\n and `deployment_promote` with the actual ETag, confirmation and idempotency guards. Shared\n data requires Dev followed by promotion; isolated data also permits an exact commit planned\n into Prod. Supply Prod's own credentials, observe completion and verify the public result.\n\n## Local checkout with GitHub\n\n1. Run `ohmyhost init --dry-run --json` in that repository. A blocked repository reports `status: \"blocked\"` and exits non-zero while still returning the full analysis; read `blockers` rather than the exit code alone. Resolve returned blockers and requirements; preserve existing auth, migrations and configuration. Without an `ohmyhost.yaml`, run `ohmyhost init --json` once no blockers remain: it writes the file at the repository root (`--root DIR` picks one of several apps, `--region eu` for an EU project). Commit and push it before planning. Init never overwrites an existing file: it answers `repository_configuration_exists`, while `--dry-run` still analyzes the repository. Use the portable-app Skill for source changes, or the Supabase Skill only for a requested migration.\n2. Complete installation, login and organization selection with the ohmyhost-get-started Skill; use `identity_get` to confirm the selected organization and `projects_list` to reuse an existing project. When several ohmyho.st logins are saved, or the prompt names a user and organization, run every authenticated call as that one login (`--profile-name` / `profile_name`) and confirm it with `identity_get` first; `init` is offline and takes no login flag. For a new project, ask: \"Should your Dev page be public or protected by a shareable link?\" Protected is the default; public lets anyone with the Dev URL open it, including when Dev and Prod share data. Explain isolated Dev/Prod data versus shared data and the hosting region, then use `project_create` with the chosen `dev_access_mode`, data mode and `region`. Recommend isolated data; two databases consume credits separately. `data_mode` is optional and defaults to `shared`; send the customer's choice explicitly. A later change uses the confirmed data-change flow below and never copies data. An explicit region wins over a supplied browser-location hint; without either, ask once. Preserve an existing project's region and never use the agent IP. The API defaults to `us`; new projects provision their PGCF database, new files, build sandbox and build objects in the selected US or EU region. Existing projects moved to US with their owner's authorization use US databases. Read `database_compute_get.region` for the observed database location. A database migration preserves existing R2 object locations. The customer API cannot change the region after creation and prices are identical in both regions. `storage.jurisdiction` in `ohmyhost.yaml` must match an existing R2 bucket or reservation's recorded jurisdiction, which survives an operator database or project-region migration. Only new storage uses the current project region. Read the `Storage jurisdiction` line in `ohmyhost project context --project ULID --json` (MCP `project_context_get`) before changing storage configuration. If it reports `unavailable`, report the request through feedback; do not substitute the current project region. Preserve established storage configuration; offline `init` writes `us` unless it gets `--region eu`. Transactional mail is sent from the platform's mail region and is not a per-project choice. Hosting needs no mail domain: ask whether the app should send or receive email, and configure one only when the customer wants mail or the app declares `mail.enabled`; a Dev URL, a new project or a Better Auth package alone never calls for one. Mail requires Paid access: without it, `mail_setup` answers `paid_plan_required`. An app declaring `mail.enabled` also needs its configured mail domain before deployment (`mail_domain_required`); activate Paid and configure mail, or set `mail.enabled: false` when the app sends no mail.\n3. Read `github_status` for the workspace. When needed, use `github_connect` once, open its single `authorization_url` and repeat the same key until connected; on `failed` or `expired`, resolve `last_failure` as the get-started Skill describes and connect with a new key. Then use `source_link` for the selected repository and observe its returned operation; it needs no second browser consent for a covered repository. Missing repository access is repaired through status's `connection.settings_url`, followed by the same link/key. Errors name the account the call ran as (`acting_as`): `resource_not_found` means that login cannot see the project, `github_connection_required` means this workspace has no GitHub connection yet, and `repository_not_installed` means the App installation does not cover the repository. Each workspace connects GitHub on its own, so the same repository can be linked in the workspaces of separate accounts. Read `source_get`, then `deployment_plan` for the exact pushed commit. Review the plan's costs, requirements and effects with the customer's existing authorization; quote `estimated_cost.credits` as the credits held for this build only. Measured build seconds are charged and the rest is released.\n4. Read `project_status` for environment IDs. Supply required application secrets through the stdin command returned by `secret_set_command`. Use the chosen environment; Dev is not Prod.\n5. Execute `deployment_create` with the returned plan and one saved idempotency key. Reuse that key if the response is uncertain. Poll `operation_get` for the accepted operation at its suggested interval; another build is not a status check. After success, some requests can still reach the previous version for up to 30 seconds; recheck before calling a change missing.\n6. Read `dev_access_mode` and the resulting URL from project status. Public Dev opens at the clean URL. For protected Dev, call `project_dev_share_link_get`: its owner-only `share_url` can be reused by multiple visitors without automatic expiry. Open it to receive a browser session, then verify the clean URL, application login if present, and a real read/write flow. For declared `functions.crons`, confirm in `function_runs_list` that a due run succeeded; deployment logs do not show runs. Keep the link private outside intended recipients; `project_dev_share_link_rotate` or `project_dev_share_link_revoke` blocks old links and sessions on their next request. Opening the link signs that browser in for 12 hours and lands on the Dev root `/`; open it again after that. After rotation or revocation, that browser's page loads redirect to <https://ohmyho.st/>. `project_dev_access_mode_set` switches an existing project between public and protected Dev; switching back to protected creates a new link. Never put the link in notes, source or logs.\n7. If production publication is requested, either promote the verified Dev deployment with `promotion_plan` and `promotion_execute`, or plan the commit straight into Prod with `deployment_plan` and `environment: \"prod\"`. Before either, set Prod's own secrets with `secret_set_command` and the Prod environment ID from `project_status`: secrets are never copied from Dev, and a missing one often fails Prod's health check (`runtime_candidate_failed`). A project whose Dev and Prod share one database must deploy to Dev and promote; a direct Prod plan returns `shared_data_requires_promotion`. Isolated promotion applies schema migrations without copying Dev records; test that existing Prod records survive. \"Dev only\" means leaving Prod undeployed; no separate project mode is needed.\n8. After the first working Prod deployment, ask the customer once whether their site should show the small \"Powered by ohmyho.st\" flag on its right edge. While it shows, the project's custom domain uses no domain credits on any plan and also works on Free. Each Stripe Paid period adds 250 credits to the workspace, however many projects show the flag: 1,250 instead of 1,000 monthly credits, 25% more. Switch it only on their answer with `powered_by_flag_set` (`ohmyhost project flag set`); never enable it by default. It shows on Prod addresses within about 30 seconds and only in HTML pages a browser opens; check it in a browser, or send `Sec-Fetch-Dest: document` for an automated check.\n9. To undo a release when the customer asks, read `deployments_list`: the live deployment has `status: active`, and an earlier one that was live has `status: rolled_back`. Use `rollback_plan` for that deployment, then `rollback_execute` with the plan's `resource_etag` as `if_match`, its `confirmation_token` and a saved idempotency key; poll the returned operation (CLI `ohmyhost rollback plan` and `ohmyhost rollback`). Rollback reactivates the artifact in its own environment without a rebuild and switches its crons, but never reverts migrations: the older code must work with the current schema. If its health check fails, `runtime_candidate_failed` and the `HEALTH_CHECK_FAILED` deployment diagnostic explain the failure, and the current version stays live. Promotion, rollback and deletion plans expire after ten minutes; on `confirmation_expired` or `etag_mismatch`, plan again.\n\nReturn the working URL, deployed commit and any remaining action. If an operation stalls, use the troubleshooting Skill. Read `project_context_get` when resuming; use `project_notes_set` with its current notes version to retain a short decision or unfinished task, never credentials or signed links.\n\n## Change Dev/Prod data or reset Dev\n\nOnly a workspace Owner/Admin can change data assignments. Read `project_context_get` and\n`project_status`, then use `project_data_plan` (CLI `ohmyhost project data plan --project ULID\n--change CHANGE --json`) for the customer's chosen action:\n\n| Change | Result |\n| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `isolate_prod_keeps_data` | Shared to isolated: Prod keeps the existing database and its files; Dev gets a fresh empty data area. Redeploy Dev. |\n| `isolate_dev_keeps_data` | Shared to isolated: Dev keeps the existing database and its files; Prod gets a fresh empty data area and is unavailable until redeployed. |\n| `share_prod_keeps_data` | Isolated to shared: requires an existing Prod database, keeps Prod's data and files, and deletes Dev's database, files and deployment. Redeploy Dev to use Prod's data. |\n| `reset_dev` | Isolated only: deletes Dev's database, files and deployment and gives Dev a fresh empty data area. Redeploy Dev; Prod stays. |\n\nReview the plan's effects and destructive effects with the customer before execution. After their\nexplicit confirmation, pass the exact `change`, `resource_etag` as `if_match`,\n`confirmation_token`, one saved `idempotency_key` and `confirmed: true` to `project_data_change`.\nThe CLI equivalent is `ohmyhost project data change --project ULID --change CHANGE --if-match ETAG\n--confirmation-token TOKEN --idempotency-key KEY --yes --wait --json`. The plan lives ten minutes;\nan expired confirmation or changed ETag requires a fresh plan and review. Follow `operation_get`;\nafter an uncertain response reuse the same key, never start another reset to retry. Finish or\nreconcile an operation already changing that project before requesting another change.\n\nNo action copies data, and ordinary usage prices apply. A new database is provisioned lazily\nwhen a database-enabled app next deploys. Cloudflare R2 files follow the same data identity; shared\nDev/Prod see the same file keys, while a fresh isolated or reset data area can reuse keys from\nthe old area. A key remains create-once within one data area, including after deletion.\nCustomer secrets, access mode and region stay; sharing and reset renew a protected Dev link,\nso retrieve its new `share_url` after the operation. Deleted Dev data and files cannot be restored\nby switching back; a SQL export contains database records only, not files.\n\n## Delete a project\n\nDelete only on the customer's explicit request. `delete_plan` (CLI `ohmyhost delete plan`)\nreturns the effects, `resource_etag` and a `confirmation_token` valid for ten minutes. Review\nthose effects and obtain confirmation before `delete_execute` (CLI `ohmyhost delete --project\nULID --if-match ETAG --confirmation-token TOKEN --idempotency-key KEY --yes --json`); follow\nthe returned operation. Deletion removes Dev and Prod deployments, databases, files, function\nschedules, stored SQL exports, and the domain, mail and DNS records ohmyho.st created. It cannot\nbe undone; when database records matter, create and download a SQL export first. Files need\ntheir own application download before deletion. If the operation ends `failed`, follow its\nsuggested action and plan deletion again once no other project operation runs.\n",
76
76
  },
77
77
  {
78
78
  skillName: "ohmyhost-deploy",
@@ -82,7 +82,7 @@ export const GENERATED_SKILL_RESOURCES = Object.freeze([
82
82
  description:
83
83
  "Develop, version, preview and publish an application with ohmyho.st from a local checkout or a connected native chat. Reuse the saved project and source, select managed source or GitHub only when needed, and switch an existing project only on request. Use troubleshooting for a stuck deployment.",
84
84
  mimeType: "text/markdown",
85
- text: "---\nname: ohmyhost-deploy\ndescription: Develop, version, preview and publish an application with ohmyho.st from a local checkout or a connected native chat. Reuse the saved project and source, select managed source or GitHub only when needed, and switch an existing project only on request. Use troubleshooting for a stuck deployment.\n---\n\n# Develop and deploy an app\n\nRead available MCP tool schemas or installed CLI help. Complete access with\n**ohmyhost-get-started**, preserving the user's selected account, organization and project.\nKeep explanations in the user's language; users need app versions, a preview and a working\naddress, not the storage provider's implementation details.\n\n## Select the source once\n\nThe saved project binding and `source_get` determine how to continue an existing application.\nReuse them before interpreting local Git remotes. Never silently replace a source because the\nagent changed, a checkout was cloned, or a GitHub remote is missing on this computer.\nFor generation-aware inspection, initialization, commits or a source switch, request\n`source_get` with `include_binding: true`. Its default preserves the existing GitHub view;\nthe opt-in returns the exact source connection and generation. Managed source already includes\nthose guards in either view. `ohmyhost source status` opts in for the local source workflow.\n\n| Situation | Continue with |\n| ------------------------------------------------------------------- | ------------------------------------------------------------ |\n| Existing managed binding | Managed source on that project |\n| Existing GitHub binding | **ohmyhost-deploy-github** |\n| Explicit request to use GitHub | **ohmyhost-deploy-github**, including GitHub setup if needed |\n| Explicit request to store/deploy directly with ohmyho.st | Managed source |\n| New application developed entirely in native chat | Managed source |\n| Unbound local checkout with a selected GitHub remote | **ohmyhost-deploy-github** |\n| Unbound local checkout without GitHub and no explicit source choice | Ask once: \"Keep versions with ohmyho.st, or set up GitHub?\" |\n\nA local `.git` directory may belong to a non-GitHub host or have no remote. A copied GitHub\nremote alone does not override the saved project binding. If the existing source conflicts\nwith an explicit new choice, use the source-switch flow after its concrete effects are reviewed.\nThe direct managed route requires neither GitHub signup nor a copied platform/device token.\n\n## Local managed checkout\n\nRun the installed source/deploy help and inspect the repository with `ohmyhost init --dry-run\n--json`. Resolve real framework/configuration blockers through **ohmyhost-build-portable-app**;\npreserve the app's authentication, migrations, selected application root and region. A supported\napp needs its actual `ohmyhost.yaml` and lockfile, not a generated pretend-success result.\n\nUse `ohmyhost source inspect --directory PATH --json` for read-only evidence, then\n`ohmyhost source publish` to bind or continue the selected project. Inspect its installed help\nbefore choosing `--mode initialize`, `commit` or the explicitly requested `switch`, and supply\nthe saved `--expected-source-generation` and source connection when that mode requires them,\nwith a concise `--message` and one saved `--idempotency-key`. For an existing managed checkout,\nomit `--expected-commit` to use its own `last_confirmed_commit_sha` as the optimistic base.\nSupply an explicit newer SHA only after reading and merging the other chat's changes into these\nlocal files; never attach a newly fetched remote head to an unchanged stale checkout. It stages the filtered\napplication manifest, uploads the changed blobs and commits\nthe completed tree. Preserve executable bits. Exclude credentials, `.env` files, local caches,\ndependency directories and build outputs. Do not require `git push` to GitHub for this route.\nSave the returned project/source binding; the accepted server commit identifies the version\nto plan and deploy. An upload is not itself a successful build or publication.\n\nIf publication is pending, poll its saved `operation_get` and then run\n`ohmyhost source publish complete --project ULID --directory PATH --operation ULID --json`\nunder the same login. This confirms the persisted version and finishes the local binding.\nResume a pending upload from `source inspect`'s `pending_publications`; preserve its operation\nand key. Completing an already authorized publication needs no second routing question.\n\n| Local task | Installed command |\n| ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |\n| Read the persisted source | `ohmyhost source status --project ULID --json` |\n| List or read a version's files | `ohmyhost source files` / `ohmyhost source file --path PATH`, with `--project ULID` and optional `--commit SHA` |\n| Inspect history or a diff | `ohmyhost source versions` / `ohmyhost source diff --from SHA --to SHA`, scoped to the same project |\n| Observe a saved upload | `ohmyhost source upload --project ULID --operation ULID --json` |\n| Finish an accepted local publication | `ohmyhost source publish complete --project ULID --directory PATH --operation ULID --json` |\n| Initialize an explicitly chosen template | `ohmyhost source initialize`, with the project, template, current source generation and saved key |\n| Restore files as a new version | `ohmyhost source restore`, with the current source generation/head, `--restore-commit SHA`, message and saved key |\n\nUse the returned exact schemas and [the generated surface reference](../ohmyhost-get-started/references/surfaces.md)\nfor flags. Restoring files is separate from publication and does not import local Git history.\nLocal MCP exposes the corresponding `source_inspect`, `source_publish`, `source_publish_complete`, `source_upload`,\n`source_initialize`, `source_files`, `source_file`, `source_versions`, `source_diff`,\n`source_changes` and `source_restore` tools. `source_inspect` and `source_publish` operate on\nthis computer's checkout; never use their local paths as native-chat source files. Local\n`source_publish` can use the cached optimistic base when `expected_commit_sha` is omitted;\n`source_publish_complete` finishes pending work using the same project, directory and profile.\nLocal MCP `source_file` reads default to `format: \"text\"`: valid UTF-8 returns `content_text`\nwith `encoding: \"utf8\"`; a file that cannot be decoded returns base64. Choose `format: \"base64\"`\nfor exact binary contents. The REST/SDK wire format remains base64.\n\n## Managed application in native chat\n\nThis workflow needs the authenticated remote tools; it has no local filesystem or shell\ndependency. Use `connection_grants` and the approved `connection_request`/`connection_status`\nflow from the get-started Skill before project actions.\n\n1. Reuse the chosen project via `project_list`/`project_get` and read `project_context_get`. For a new\n app, use `project_create` with its approved organization, name, region, data mode and Dev\n access mode, `source_provider: \"managed\"` and the intended `template`. Choose `vite-react`\n for a basic web app, or `empty` for a specifically chosen supported stack. Preserve an\n existing project's region and data; do not infer region from an\n agent's IP. Shared Dev/Prod data requires Dev followed by promotion; isolated data permits\n independent environments and incurs separate database usage.\n2. Follow the accepted project operation, then read `source_get` with `include_binding: true` until its managed source is\n `ready` and has a persisted commit. Creation queues source initialization after the project\n is provisioned; a completed project operation can still have `source.status: \"pending\"`.\n Keep polling that source rather than starting a duplicate initialization. A `failed` source\n needs its recorded failure diagnosed. When `initialization_operation_id` is returned, follow\n that actual child operation with `operation_get`; otherwise use the returned\n `initialization_error_code`. An `authorization_changed` failure needs the approved connection\n and project grant restored through **ohmyhost-get-started** before retrying the source action.\n For an existing project with no source and no pending\n initialization, `source_initialize` can create the explicitly chosen template under the\n current generation and a saved key. Observe that accepted operation and reread `source_get`\n before writing files. A GitHub source is not permission to initialize over it.\n3. Read the manifest with `source_files`, then pass `path` for each relevant file at the current\n commit. `format: \"text\"` is the default; it returns `content_text` with `encoding: \"utf8\"`\n when valid UTF-8, otherwise `content_base64` with `encoding: \"base64\"`. Choose the explicit\n base64 format for binary contents. Apply purposeful\n batches with `source_changes`: exact `expected_source_generation`, `expected_commit_sha`,\n a concise commit `message`, and each changed path's plain UTF-8 `content_text` and executable\n flag. Supply exactly one content field per change: use `content_base64` for binary contents\n or `content_base64: null` to delete the file. Do not supply both text and base64. Preserve\n the returned executable flags when editing existing files. Reuse the idempotency key after an uncertain\n response. If another chat advanced the source, read the current version and reconcile the\n changes; never force-overwrite the other chat's work. Follow each accepted source operation\n to success, then read `source_get` for its persisted commit before planning a build.\n Source generation changes when a binding is initialized or switched, not on every commit;\n concurrent edits are guarded by the expected commit. Copy both guards from current state.\n A change batch accepts at most 2 MiB of decoded content. For a complete atomic tree or\n related edits larger than that batch, use the remote upload flow below rather than\n splitting one intended version into several unrelated commits.\n4. Use `source_versions` and `source_diff` to inspect saved work. `source_restore` creates a\n new version from the selected earlier commit under the same revision guards; it does not\n delete history or roll back database migrations. Observe its accepted operation before\n reading the resulting current commit.\n5. Set required application credentials using `secret_input_request` for the chosen environment.\n Give the private portal URL to the user and observe `connection_status`. `secrets_list` shows\n metadata only. Never put a password, model API key or runtime secret into a tool argument,\n source file, commit, project note or chat message.\n6. Call `deployment_plan` for the exact accepted commit and `environment: \"dev\"`; review its\n real costs/effects within the user's existing authorization, then `deployment_create` with\n the plan and one saved key. Follow the same `operation_get` until its terminal state; use\n `deployment_get` and `deployment_logs` for diagnosis. A running operation is not a reason\n to start another build.\n7. Read `project_status` for the Dev URL and access mode. Public Dev opens directly. For\n protected Dev, give the user the private reusable `share_url` from\n `project_dev_share_link_get`. To inspect it as the agent, use `project_dev_access_create`\n and open its one-time `redeem_url`; do not issue another ticket while the first is unused.\n Then verify the clean preview URL. Keep these entry links out of notes, source and logs.\n Verify the app's meaningful\n interaction, expected content and any requested data flow before calling it ready. If the\n chat cannot inspect the preview, say exactly what remains unverified and keep the same project.\n8. Publish only when requested and allowed by the project's explicit publishing grant. Use\n `promotion_plan` then `deployment_promote` for the verified Dev deployment, including the\n exact ETag, confirmation and idempotency guards. Alternatively plan an exact commit into\n isolated Prod when that is the requested supported route. Prod needs its own secret inputs;\n they are not copied from Dev. Poll the accepted operation and verify the public result.\n\nReturn the project's working address, exact commit and verification result, plus any required\nuser action. Do not claim a plugin installation, finished OAuth page, saved commit or accepted\ndeployment proves that the app is live.\n\n## Save a complete tree in native chat\n\nRemote uploads use application file bytes, never a directory on this computer. Read the\ncomplete intended files from the client's actual available file or repository context.\nIf that context or payload transfer is unavailable, state the concrete capability gap;\ndo not invent missing files or hashes. For a new app that starts from an uploaded tree,\nchoose an explicitly intended `empty` managed template, observe its initialization, then\ncommit the replacement tree. Use `initialize` only for a genuinely unbound project with\nno pending initialization.\n\n1. Read `source_get` with `include_binding: true`. Call `source_upload_prepare` once with\n `project_id`, `mode`, the original `expected_source_generation`,\n `expected_source_connection_id`, `expected_commit_sha`, a concise `message` and one\n saved `idempotency_key`. Initialize uses null connection/head and generation 0;\n commit uses the current managed connection/generation and reconciled parent SHA;\n switch uses the original GitHub connection/generation and a null parent. Retain the\n accepted `operation.id` as the upload's operation ID.\n2. Call `source_blob_put` for each complete file with the same `project_id` and\n `operation_id`, its repository-relative `path`, `executable` flag, a saved per-file\n key and exactly one of `content_text` or `content_base64`. The server validates the\n actual bytes and computes SHA-256 and byte length. Keep its confirmed\n `{path, sha256, byte_length, executable}` receipt unchanged. Never supply or invent a\n hash or length. This is not a chunk-upload protocol for oversized individual files.\n3. Finalize the complete intended `files` manifest, including unchanged files that should\n remain, from receipts of **this upload operation**. Every staged file must occur exactly;\n do not omit an already staged extra or replace a staged path's bytes/mode. An omitted\n previously saved path is absent from the replacement tree. Use `source_upload_commit`\n for initialize/commit with the upload's `operation_id`, original generation and one\n finalize key. Use `source_switch` only for switch mode, as described below.\n4. Observe the same `operation_get` and `source_upload`. Require operation `succeeded`\n and receipt `state: \"committed\"` before treating the receipt's own `commit_sha` as saved.\n A sealed receipt can already name an unpushed candidate; it is not deployable proof.\n Current main can later advance, so keep this operation's own committed SHA for its build.\n\n`source_upload` reports state, original generation/parent, expiry and commit; it is not a\nfile inventory. Recover a lost blob receipt by repeating identical `source_blob_put`\narguments and key. After an uncertain prepare/finalize, retain the original operation,\nmanifest and keys and observe it before another attempt. Staged paths are immutable.\nAn expired timestamp alone does not prove a completed upload failed. Diagnose a reported\nfailure or conflict, read and merge current state, then deliberately start new work.\n\nThe complete tree is at most 8 MiB decoded and 2,000 files; each complete file is at most\n2 MiB. Use safe archive-compatible ASCII paths. Credentials, private `.env` contents,\ndependency directories, caches and build output are not application source. These server\nbounds do not establish the chosen native client's transfer capacity; report a real client\nlimit rather than claiming an unverified large upload. Saving the version starts no build\nor publication; use the separate preview/deployment workflow for the confirmed exact SHA.\n\n## Switch source on the existing project\n\nOnly switch on the user's explicit request. Read the current project and `source_get` with\n`include_binding: true` for the source connection and generation first; explain which\nsource will be replaced. Upload the complete intended\nreplacement tree using the supported switch operation with those original guards. Finalize it\nbefore updating the checkout binding. A conflict requires a fresh source read and review.\n\nIn native chat, use `source_upload_prepare` with `mode: \"switch\"`, the existing GitHub\n`expected_source_connection_id` and generation, and `expected_commit_sha: null`. Stage all\nintended files through `source_blob_put`, retain their receipts, then call `source_switch`\nwith `project_id`, the original `upload_operation_id`, original source connection/generation,\nthe complete `files` manifest and a saved key. Do not use `source_upload_commit` for switch.\nThe old binding stays active until successful completion. Observe that same operation and\nupload receipt, then reread the full source binding; its generation increases once. The\nproject's GitHub auto-deploy is disabled by switching; the GitHub repository is not deleted.\nGitHub history is not imported. The local equivalent remains `source publish --mode switch`.\n\nKeep the same project ID, environment URLs, domain, runtime secrets and database/data-mode\nidentity. Do not create another project, delete the GitHub repository, alter its remotes or\ncopy customer data as a shortcut. A failed or uncertain switch is reconciled under its original\noperation/idempotency key. Read `source_get` after completion before planning the new commit.\n\nRollback, deletion, data-mode changes and domain/mail changes retain their own reviewed plans\nand scopes. Use the existing deployment, database and domains Skills as appropriate; switching\nsource alone authorizes none of them.\n",
85
+ text: "---\nname: ohmyhost-deploy\ndescription: Develop, version, preview and publish an application with ohmyho.st from a local checkout or a connected native chat. Reuse the saved project and source, select managed source or GitHub only when needed, and switch an existing project only on request. Use troubleshooting for a stuck deployment.\n---\n\n# Develop and deploy an app\n\nRead available MCP tool schemas or installed CLI help. Complete access with\n**ohmyhost-get-started**, preserving the user's selected account, organization and project.\nKeep explanations in the user's language; users need app versions, a preview and a working\naddress, not the storage provider's implementation details.\n\n## Select the source once\n\nThe saved project binding and `source_get` determine how to continue an existing application.\nReuse them before interpreting local Git remotes. Never silently replace a source because the\nagent changed, a checkout was cloned, or a GitHub remote is missing on this computer.\nFor generation-aware inspection, initialization, commits or a source switch, request\n`source_get` with `include_binding: true`. Its default preserves the existing GitHub view;\nthe opt-in returns the exact source connection and generation. Managed source already includes\nthose guards in either view. `ohmyhost source status` opts in for the local source workflow.\n\n| Situation | Continue with |\n| ------------------------------------------------------------------- | ------------------------------------------------------------ |\n| Existing managed binding | Managed source on that project |\n| Existing GitHub binding | **ohmyhost-deploy-github** |\n| Explicit request to use GitHub | **ohmyhost-deploy-github**, including GitHub setup if needed |\n| Explicit request to store/deploy directly with ohmyho.st | Managed source |\n| New application developed entirely in native chat | Managed source |\n| Unbound local checkout with a selected GitHub remote | **ohmyhost-deploy-github** |\n| Unbound local checkout without GitHub and no explicit source choice | Ask once: \"Keep versions with ohmyho.st, or set up GitHub?\" |\n\nA local `.git` directory may belong to a non-GitHub host or have no remote. A copied GitHub\nremote alone does not override the saved project binding. If the existing source conflicts\nwith an explicit new choice, use the source-switch flow after its concrete effects are reviewed.\nThe direct managed route requires neither GitHub signup nor a copied platform/device token.\n\n## Local managed checkout\n\nRun the installed source/deploy help and inspect the repository with `ohmyhost init --dry-run\n--json`. Resolve real framework/configuration blockers through **ohmyhost-build-portable-app**;\npreserve the app's authentication, migrations, selected application root and region. A supported\napp needs its actual `ohmyhost.yaml` and lockfile, not a generated pretend-success result.\n\nUse `ohmyhost source inspect --directory PATH --json` for read-only evidence, then\n`ohmyhost source publish` to bind or continue the selected project. Inspect its installed help\nbefore choosing `--mode initialize`, `commit` or the explicitly requested `switch`, and supply\nthe saved `--expected-source-generation` and source connection when that mode requires them,\nwith a concise `--message` and one saved `--idempotency-key`. For an existing managed checkout,\nomit `--expected-commit` to use its own `last_confirmed_commit_sha` as the optimistic base.\nSupply an explicit newer SHA only after reading and merging the other chat's changes into these\nlocal files; never attach a newly fetched remote head to an unchanged stale checkout. It stages the filtered\napplication manifest, uploads the changed blobs and commits\nthe completed tree. Preserve executable bits. Exclude credentials, `.env` files, local caches,\ndependency directories and build outputs. Do not require `git push` to GitHub for this route.\nSave the returned project/source binding; the accepted server commit identifies the version\nto plan and deploy. An upload is not itself a successful build or publication.\n\nIf publication is pending, poll its saved `operation_get` and then run\n`ohmyhost source publish complete --project ULID --directory PATH --operation ULID --json`\nunder the same login. This confirms the persisted version and finishes the local binding.\nResume a pending upload from `source inspect`'s `pending_publications`; preserve its operation\nand key. Completing an already authorized publication needs no second routing question.\n\n| Local task | Installed command |\n| ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |\n| Read the persisted source | `ohmyhost source status --project ULID --json` |\n| List or read a version's files | `ohmyhost source files` / `ohmyhost source file --path PATH`, with `--project ULID` and optional `--commit SHA` |\n| Inspect history or a diff | `ohmyhost source versions` / `ohmyhost source diff --from SHA --to SHA`, scoped to the same project |\n| Observe a saved upload | `ohmyhost source upload --project ULID --operation ULID --json` |\n| Finish an accepted local publication | `ohmyhost source publish complete --project ULID --directory PATH --operation ULID --json` |\n| Initialize an explicitly chosen template | `ohmyhost source initialize`, with the project, template, current source generation and saved key |\n| Restore files as a new version | `ohmyhost source restore`, with the current source generation/head, `--restore-commit SHA`, message and saved key |\n\nUse the returned exact schemas and [the generated surface reference](../ohmyhost-get-started/references/surfaces.md)\nfor flags. Restoring files is separate from publication and does not import local Git history.\nLocal MCP exposes the corresponding `source_inspect`, `source_publish`, `source_publish_complete`, `source_upload`,\n`source_initialize`, `source_files`, `source_file`, `source_versions`, `source_diff`,\n`source_changes` and `source_restore` tools. `source_inspect` and `source_publish` operate on\nthis computer's checkout; never use their local paths as native-chat source files. Local\n`source_publish` can use the cached optimistic base when `expected_commit_sha` is omitted;\n`source_publish_complete` finishes pending work using the same project, directory and profile.\nLocal MCP `source_file` reads default to `format: \"text\"`: valid UTF-8 returns `content_text`\nwith `encoding: \"utf8\"`; a file that cannot be decoded returns base64. Choose `format: \"base64\"`\nfor exact binary contents. The REST/SDK wire format remains base64.\n\n## Managed application in native chat\n\nThis workflow needs the authenticated remote tools; it has no local filesystem or shell\ndependency. Verify the connected account and workspace using the get-started Skill before project actions.\n\n1. Reuse the chosen project via `project_list`/`project_get` and read `project_context_get`. For a new\n app, use `project_create` with its approved organization, name, region, data mode and Dev\n access mode, `source_provider: \"managed\"` and the intended `template`. Choose `vite-react`\n for a basic web app, or `empty` for a specifically chosen supported stack. Preserve an\n existing project's region and data; do not infer region from an\n agent's IP. Shared Dev/Prod data requires Dev followed by promotion; isolated data permits\n independent environments and incurs separate database usage.\n2. Follow the accepted project operation, then read `source_get` with `include_binding: true` until its managed source is\n `ready` and has a persisted commit. Creation queues source initialization after the project\n is provisioned; a completed project operation can still have `source.status: \"pending\"`.\n Keep polling that source rather than starting a duplicate initialization. A `failed` source\n needs its recorded failure diagnosed. When `initialization_operation_id` is returned, follow\n that actual child operation with `operation_get`; otherwise use the returned\n `initialization_error_code`. An `authorization_changed` failure needs the current account, role and token access checked through **ohmyhost-get-started** before retrying the source action.\n For an existing project with no source and no pending\n initialization, `source_initialize` can create the explicitly chosen template under the\n current generation and a saved key. Observe that accepted operation and reread `source_get`\n before writing files. A GitHub source is not permission to initialize over it.\n3. Read the manifest with `source_files`, then pass `path` for each relevant file at the current\n commit. `format: \"text\"` is the default; it returns `content_text` with `encoding: \"utf8\"`\n when valid UTF-8, otherwise `content_base64` with `encoding: \"base64\"`. Choose the explicit\n base64 format for binary contents. Apply purposeful\n batches with `source_changes`: exact `expected_source_generation`, `expected_commit_sha`,\n a concise commit `message`, and each changed path's plain UTF-8 `content_text` and executable\n flag. Supply exactly one content field per change: use `content_base64` for binary contents\n or `content_base64: null` to delete the file. Do not supply both text and base64. Preserve\n the returned executable flags when editing existing files. Reuse the idempotency key after an uncertain\n response. If another chat advanced the source, read the current version and reconcile the\n changes; never force-overwrite the other chat's work. Follow each accepted source operation\n to success, then read `source_get` for its persisted commit before planning a build.\n Source generation changes when a binding is initialized or switched, not on every commit;\n concurrent edits are guarded by the expected commit. Copy both guards from current state.\n A change batch accepts at most 2 MiB of decoded content. For a complete atomic tree or\n related edits larger than that batch, use the remote upload flow below rather than\n splitting one intended version into several unrelated commits.\n4. Use `source_versions` and `source_diff` to inspect saved work. `source_restore` creates a\n new version from the selected earlier commit under the same revision guards; it does not\n delete history or roll back database migrations. Observe its accepted operation before\n reading the resulting current commit.\n5. Set required application credentials using `secret_input_request` for the chosen environment.\n Give the private portal URL to the user and observe `connection_status`. `secrets_list` shows\n metadata only. Never put a password, model API key or runtime secret into a tool argument,\n source file, commit, project note or chat message.\n6. Call `deployment_plan` for the exact accepted commit and `environment: \"dev\"`; review its\n real costs/effects within the user's existing authorization, then `deployment_create` with\n the plan and one saved key. Follow the same `operation_get` until its terminal state; use\n `deployment_get` and `deployment_logs` for diagnosis. A running operation is not a reason\n to start another build.\n7. Read `project_status` for the Dev URL and access mode. Public Dev opens directly. For\n protected Dev, give the user the private reusable `share_url` from\n `project_dev_share_link_get`. To inspect it as the agent, use `project_dev_access_create`\n and open its one-time `redeem_url`; do not issue another ticket while the first is unused.\n Then verify the clean preview URL. Keep these entry links out of notes, source and logs.\n Verify the app's meaningful\n interaction, expected content and any requested data flow before calling it ready. If the\n chat cannot inspect the preview, say exactly what remains unverified and keep the same project.\n8. Publish only when requested and authorized by the current role and token. Use\n `promotion_plan` then `deployment_promote` for the verified Dev deployment, including the\n exact ETag, confirmation and idempotency guards. Alternatively plan an exact commit into\n isolated Prod when that is the requested supported route. Prod needs its own secret inputs;\n they are not copied from Dev. Poll the accepted operation and verify the public result.\n\nReturn the project's working address, exact commit and verification result, plus any required\nuser action. Do not claim a plugin installation, finished OAuth page, saved commit or accepted\ndeployment proves that the app is live.\n\n## Save a complete tree in native chat\n\nRemote uploads use application file bytes, never a directory on this computer. Read the\ncomplete intended files from the client's actual available file or repository context.\nIf that context or payload transfer is unavailable, state the concrete capability gap;\ndo not invent missing files or hashes. For a new app that starts from an uploaded tree,\nchoose an explicitly intended `empty` managed template, observe its initialization, then\ncommit the replacement tree. Use `initialize` only for a genuinely unbound project with\nno pending initialization.\n\n1. Read `source_get` with `include_binding: true`. Call `source_upload_prepare` once with\n `project_id`, `mode`, the original `expected_source_generation`,\n `expected_source_connection_id`, `expected_commit_sha`, a concise `message` and one\n saved `idempotency_key`. Initialize uses null connection/head and generation 0;\n commit uses the current managed connection/generation and reconciled parent SHA;\n switch uses the original GitHub connection/generation and a null parent. Retain the\n accepted `operation.id` as the upload's operation ID.\n2. Call `source_blob_put` for each complete file with the same `project_id` and\n `operation_id`, its repository-relative `path`, `executable` flag, a saved per-file\n key and exactly one of `content_text` or `content_base64`. The server validates the\n actual bytes and computes SHA-256 and byte length. Keep its confirmed\n `{path, sha256, byte_length, executable}` receipt unchanged. Never supply or invent a\n hash or length. This is not a chunk-upload protocol for oversized individual files.\n3. Finalize the complete intended `files` manifest, including unchanged files that should\n remain, from receipts of **this upload operation**. Every staged file must occur exactly;\n do not omit an already staged extra or replace a staged path's bytes/mode. An omitted\n previously saved path is absent from the replacement tree. Use `source_upload_commit`\n for initialize/commit with the upload's `operation_id`, original generation and one\n finalize key. Use `source_switch` only for switch mode, as described below.\n4. Observe the same `operation_get` and `source_upload`. Require operation `succeeded`\n and receipt `state: \"committed\"` before treating the receipt's own `commit_sha` as saved.\n A sealed receipt can already name an unpushed candidate; it is not deployable proof.\n Current main can later advance, so keep this operation's own committed SHA for its build.\n\n`source_upload` reports state, original generation/parent, expiry and commit; it is not a\nfile inventory. Recover a lost blob receipt by repeating identical `source_blob_put`\narguments and key. After an uncertain prepare/finalize, retain the original operation,\nmanifest and keys and observe it before another attempt. Staged paths are immutable.\nAn expired timestamp alone does not prove a completed upload failed. Diagnose a reported\nfailure or conflict, read and merge current state, then deliberately start new work.\n\nThe complete tree is at most 8 MiB decoded and 2,000 files; each complete file is at most\n2 MiB. Use safe archive-compatible ASCII paths. Credentials, private `.env` contents,\ndependency directories, caches and build output are not application source. These server\nbounds do not establish the chosen native client's transfer capacity; report a real client\nlimit rather than claiming an unverified large upload. Saving the version starts no build\nor publication; use the separate preview/deployment workflow for the confirmed exact SHA.\n\n## Switch source on the existing project\n\nOnly switch on the user's explicit request. Read the current project and `source_get` with\n`include_binding: true` for the source connection and generation first; explain which\nsource will be replaced. Upload the complete intended\nreplacement tree using the supported switch operation with those original guards. Finalize it\nbefore updating the checkout binding. A conflict requires a fresh source read and review.\n\nIn native chat, use `source_upload_prepare` with `mode: \"switch\"`, the existing GitHub\n`expected_source_connection_id` and generation, and `expected_commit_sha: null`. Stage all\nintended files through `source_blob_put`, retain their receipts, then call `source_switch`\nwith `project_id`, the original `upload_operation_id`, original source connection/generation,\nthe complete `files` manifest and a saved key. Do not use `source_upload_commit` for switch.\nThe old binding stays active until successful completion. Observe that same operation and\nupload receipt, then reread the full source binding; its generation increases once. The\nproject's GitHub auto-deploy is disabled by switching; the GitHub repository is not deleted.\nGitHub history is not imported. The local equivalent remains `source publish --mode switch`.\n\nKeep the same project ID, environment URLs, domain, runtime secrets and database/data-mode\nidentity. Do not create another project, delete the GitHub repository, alter its remotes or\ncopy customer data as a shortcut. A failed or uncertain switch is reconciled under its original\noperation/idempotency key. Read `source_get` after completion before planning the new commit.\n\nRollback, deletion, data-mode changes and domain/mail changes retain their own reviewed plans\nand scopes. Use the existing deployment, database and domains Skills as appropriate; switching\nsource alone authorizes none of them.\n",
86
86
  },
87
87
  {
88
88
  skillName: "ohmyhost-domains-and-mail",
@@ -112,7 +112,7 @@ export const GENERATED_SKILL_RESOURCES = Object.freeze([
112
112
  description:
113
113
  "Connect a customer agent or native chat to ohmyho.st, verify the intended account and workspace, and continue an application from its saved source. Use managed OAuth for a remote chat and the existing local login for a terminal agent. Also use for account selection, customer support, or private automation credentials, and to route LLM, image, speech, classifier or OCR requests to the app Skill; use ohmyhost-deploy once access is ready.",
114
114
  mimeType: "text/markdown",
115
- text: '---\nname: ohmyhost-get-started\ndescription: Connect a customer agent or native chat to ohmyho.st, verify the intended account and workspace, and continue an application from its saved source. Use managed OAuth for a remote chat and the existing local login for a terminal agent. Also use for account selection, customer support, or private automation credentials, and to route LLM, image, speech, classifier or OCR requests to the app Skill; use ohmyhost-deploy once access is ready.\n---\n\n# Start with ohmyho.st\n\nConnect this agent to the customer\'s account, then continue with the selected app and its saved source.\n\n## What ohmyho.st is and what you can do with it\n\nohmyho.st hosts a customer\'s app: Vite/React, TanStack Start, Next.js or a plain Worker\nmodule with HTTP handlers and optional cron schedules. A coding agent uses the local MCP server or\nCLI; a connected native chat uses the remote MCP server. Both use the same product REST contract.\nSource can be a connected GitHub repository or an ohmyho.st-managed repository. Deploy an exact commit to Dev or Prod,\nverify the app, and promote a tested Dev artifact when appropriate. Dev is protected by default;\nthe deployment Skill handles public Dev, data choices and the shared-database promotion rule.\nYou can also give the app Postgres, private runtime secrets, Cloudflare R2 files, optional\ntransactional mail, LLMs, images, speech, classifiers and OCR through an OpenAI-compatible gateway, and a custom domain, export its database as an encrypted SQL ZIP, and read\nusage, credits and project budgets. The customer keeps their own application authentication;\nnothing here replaces it. Sign-up is open and free to start.\n\nThe initial connection asks the customer to sign in and approve the access they want to give this\nagent. GitHub authorization is needed only for the GitHub source route. A working saved local login\nor API token can avoid another local sign-in; a remote chat uses its managed OAuth connection.\nOptional payments, DNS/account consent or harness\nreloads can require further customer action; use the relevant Skill for the feature they chose.\n\nThe local-agent path, and the proof that each step is done:\n\n| Step | Result | Proof | Section |\n| ---- | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ------------ |\n| 1 | You know what is installed and who is signed in | `ohmyhost --version`, `ohmyhost whoami --json` or MCP `identity_get`, and the MCP tool list | Step 1 |\n| 2 | CLI and MCP are current and registered | Installed versions match the published release; the harness\'s MCP `tools/list` exposes the tools after any required reload | Step 2 |\n| 3 | The intended account is authenticated | `ohmyhost whoami --json` or MCP `identity_get` returns the intended user | Step 3 |\n| 4 | A workspace is selected | The identity returns the intended organization | Step 4 |\n| 5 | Access survives this session | The same identity check succeeds from a fresh process with its chosen login or private token source | Step 5 |\n| 6 | GitHub is connected and existing projects are known | MCP `github_status` returns `connected`; `projects_list` identifies a project to reuse | Step 6 |\n| 7 | The repository is linked and the app is deployed and verified | MCP `source_get` confirms the linked repository; the deployment operation succeeds and the selected app URL passes its checks | Deploy Skill |\n\nEach step is a separate result. Confirm it using the Proof column; a completed browser page,\na saved MCP configuration or a started process does not substitute for its returned state.\n\nWhich Skill to read next:\n\n| The customer wants to … | Skill |\n| ---------------------------------------------------------------- | ---------------------------------- |\n| prepare or adapt an app so it runs here | ohmyhost-build-portable-app |\n| convert a real Supabase database, Auth, Storage or Functions use | ohmyhost-migrate-supabase-postgres |\n| deploy a commit, verify it, promote Dev to Prod | ohmyhost-deploy |\n| deploy an explicitly selected GitHub repository | ohmyhost-deploy-github |\n| understand a queued, stuck or failed deployment, or report a bug | ohmyhost-troubleshoot-deployment |\n| read or change data, database size or Dev-to-Prod migrations | ohmyhost-manage-database |\n| connect a custom domain or mail sender | ohmyhost-domains-and-mail |\n| add LLMs, images, speech, classifiers or OCR to the app | ohmyhost-build-portable-app |\n| export the database as an encrypted backup | ohmyhost-export-database |\n| see usage, credits or set a spending limit | ohmyhost-usage-and-budgets |\n\nUsers normally reach this Skill from a prompt such as "connect this agent to ohmyho.st and deploy\nthis project". In that case finish the relevant access steps with as few messages as possible, then continue\nwith the deployment Skill without asking again for anything already decided.\n\n## Choose the existing project context first\n\nBefore continuing an existing app, inspect the execution environment, any reachable checkout and\nits saved project binding, then verify the intended account and workspace with the matching\ncredential context.\nReuse a working local CLI/MCP login or private token source that matches that project unless the\nuser explicitly chooses a remote connection; the availability of `connection_request` alone\ndoes not select the remote path.\nIf a remote connection cannot see the intended project, verify that connection\'s account and\nworkspace before requesting additional access, and stop the access-link loop when they do not match.\nIf the previous local workspace or credential store is unreachable in this session, explain that\nconcrete environment gap without claiming credentials were deleted, changing the saved source\nor using an unrelated Review/demo account as a substitute.\n\n## Native chat with a remote connection\n\nUse this path when the task\'s established access context calls for the authenticated remote\nserver and it exposes `connection_request`.\nDiscover its tools and call `identity_get`; do not install a CLI,\nstart a device-code login, or ask for an API key in this chat. The client completes the managed\nOAuth sign-in itself. A configured plugin or completed browser page is not proof of authorization.\n\nVerify the account and read `connection_grants` for the returned organization silently.\nA returned `allow_create: true` records consent to create a new application, even when `projects`\nis empty; do not request another project-selection approval just because that list is empty.\nEvery operation still checks current human and project authorization.\n`allow_publish_created_projects` records consent inherited only by newly created apps; an existing\napp needs its own entry in `projects`.\nReuse the customer\'s selected project when its grant allows the requested action. Otherwise\nuse `connection_request` for that exact\norganization and the intended `project_ids`; request `allow_create: true` only when the user\nwants a new application, and `allow_publish: true` only when they asked to publish. Prefer the\n**Review connection** card when the client displays it; otherwise present the actual returned\nprivate approval URL. Let the host preserve its actual return-to-chat context; pass `return_url`\nonly when the actual conversation URL is available, never derive it from an anonymized session\nID. After the user completes approval, verify `connection_status` with its `request_id` and\nreread the grants silently. The browser must be signed in as the same person as the chat connection.\nContinue with app creation, editing and Dev previews when authorized. Explain missing publication\npermission only when a requested publication needs its own approval handoff; never promise\npublication before that access is confirmed.\n\nIf the intended existing project is not yet visible to this client, request the workspace\'s\nportal picker with `project_ids: []` and `allow_create: false`. The signed-in user selects their\nexisting project there. After approval, reread `connection_grants` and `project_list`; do not\ninvent a project ID or create a replacement application to gain visibility.\n\nAn identity with no organization needs `workspace_setup` before project work. Present its\nreturned portal URL and ask the customer to sign in there with the same connected account.\nAfter setup, call `identity_get` again, then obtain the selected workspace\'s project grant.\nReconnect OAuth if its actual authorization response requires it. Never\nguess an organization from a project name or treat identity-only access as a project grant.\n\nContinue with **ohmyhost-deploy**. A new application developed entirely in this chat uses managed\nsource. Read the project\'s source and context before continuing a saved app; changing chats does\nnot create another project. Before using source connection/generation guards, request\n`source_get` with `include_binding: true`; ordinary source selection may use its default\nreleased GitHub view, while managed source includes those guards in either view.\nFor a complete native file tree or an explicitly requested GitHub-to-managed switch,\n**ohmyhost-deploy** uses `source_upload_prepare`, `source_blob_put`, `source_upload`,\n`source_upload_commit` or `source_switch`; retain the actual upload operation and receipts.\nThese remote tools accept bounded file contents, not local directory paths or credentials.\nFor private application credentials, use `secret_input_request` and its portal handoff,\nthen `connection_status`. Values stay out of chat, source, tools and logs.\nIf the required remote tools are unavailable, state that concrete capability gap; local device\ntokens are not a substitute for a missing remote connection.\n\n## How to talk to the customer here\n\n- One action per message, in short plain sentences. Give the link, then what they will see.\n- For native `connection_request` handoffs, use only one short localized sentence plus the actual\n **Review connection** card or returned approval link, such as "Please confirm the connection."\n Let the portal show the requested rights. Omit rights lists, routine status summaries such as\n "not confirmed" or "no app created", and timestamps or validity details unless the customer asks\n or an expired link needs action. Verify the actual grant internally after approval.\n- Write in the language the customer writes in. Translate the message templates below; copy no\n other sentence from this file into the chat.\n- Keep customer-facing messages focused on the action and why it is needed. Avoid narrating\n routine internal steps; explain an actual limitation when it prevents the requested work.\n- After the verified connection, if the customer has not said what they want to build or host,\n ask only "What would you like to host?" in their language (for example, "Was möchtest du hosten?").\n If the app or task is already known, continue directly without this question.\n- Keep raw account/grant IDs, permission tables, `projectId: null` and routine access diagnostics\n out of normal chat replies. Use recognizable account or application names when a choice is needed.\n- Wait for required browser input before taking actions that depend on it. Independent repository\n inspection can continue while the customer signs in; do not start another sign-in for the same\n account or repeat the instruction without new information. Respect the customer\'s existing authorization and scope.\n- Never ask for a password, an email code or a token value. Never paste a credential into chat,\n source or a command argument.\n- After they report back, verify with a command instead of trusting the report.\n\n## Prepare a domain before the first deployment\n\nA customer can authorize Cloudflare before the website is live. Follow the domains Skill: plan the requested hostname, apply it once and retain its idempotency key. `awaiting_deployment` means the name is declared, with no DNS changes, certificate or domain charges yet. Return the Cloudflare authorization URL, verify consent, then deploy and verify Prod on the platform address. Repeat the same domain apply to activate the hostname. The callback and a successful deployment never switch customer DNS by themselves. A current CLI/MCP is required to read the new state.\n\n## Prepare a Next.js application\n\nFor Next.js, run the portable-app inspection before deployment. Supported versions start at the security floor 16.4.1, with no upper bound. Since the Next.js security advisories of 22 October 2026, planning and the build refuse Next.js 16.4.0 and older 16.x releases and every Next.js 15 release. A deployment that is already live keeps running, but it stays exposed to those advisories until you rebuild it on a fixed release. When inspection reports an unsupported version, update package.json and its lockfile to the reported `minimum` or a later stable release, then commit and plan the new commit. Next.js 15 builds stopped on 21 October 2026, when the pinned OpenNext started refusing Next.js 15 as an unsupported major, so a Next.js 15 app moves to Next.js 16.4.1 or later; use Next.js 16 for new work. A `create-next-app` 16.4 app with Tailwind CSS, its `--yes` default, loads Tailwind only through a Turbopack rule that the Webpack build here ignores; planning refuses it as `next_tailwind_turbopack_only` until the application root has a PostCSS configuration, as that refusal describes. Runtime data files need Next\'s normal file tracing. MDX that compiles JavaScript at request time must instead compile to static ES modules during the build; the portable-app Skill describes that conversion. Verify every content route the app uses before calling a deployment complete.\n\nNext.js pages rendered during the build use a private, immutable cache. Its capacity defaults to\n32 MiB; CLI/MCP 0.1.28 or later accept optional `build.ssg_cache_max_mib` in `ohmyhost.yaml`, an\ninteger from 1 to 64. Public static assets retain their separate 25 MiB allowance. The selected\ncache, public assets, Worker modules, migrations and archive metadata together must fit the\ndeployment artifact ceilings of 30 MiB compressed and 64 MiB expanded. Choose a cache capacity\nthat leaves room for the other archive contents. See the\n[portable-app runtime contracts](https://ohmyho.st/skills/ohmyhost-build-portable-app/references/stack-contracts.md)\nbefore changing a limit. Cache storage has provider cost 0, so cost × 1.5 is still 0 credits; build and\nruntime charges continue unchanged. New deployments replace this build-time cache; time-based\nand on-demand revalidation are not supported. Cache Components and `"use cache"` are supported:\nrequest-time `"use cache"` values are never stored, build-time values stay until the next\ndeployment, and each dynamic page needs a static shell. A CLI older than 0.1.33 still refuses Cache\nComponents in `ohmyhost init`; upgrade it instead of removing them. The runtime contracts list the\nexact rules.\n\n## Step 1 — determine the state before doing anything\n\nUse CLI and local MCP version 0.1.47 or newer together. Older clients reject the credits and usage\nresponses that 0.1.41 and 0.1.43 changed (credits is now only the balance) with\n`response_contract_invalid`, the native `psql` access transport needs 0.1.39 or newer, the\ndatabase webhook, schedule and executions commands and tools need 0.1.42 or newer, the\n`database watch` commands and tools need 0.1.45 or newer, and the `database table` commands and tools need 0.1.46 or newer. The `ai` block in `ohmyhost.yaml` needs 0.1.47 or newer: `ai: {enabled: true, jurisdiction: eu}` (or `any`) declares LLM, image, speech, classifier and OCR use, and `jurisdiction` is required whenever `enabled` is true. A Vite app with `ai` uses `runtime.mode: edge` and a companion Worker, as the init refusal names. Models are served through [orq.ai](https://orq.ai). `ohmyhost whoami` and MCP `identity_get` return the current account\'s email and the\ncredential\'s organizations with IDs and current names. Compare that email and workspace with the account the customer intended before\nchanging resources. Service credentials have `email: null`.\n\nIf the customer\'s prompt names an account ("Use my ohmyho.st account user … in organization …"),\nread "Several accounts on one computer" first. Use that login\'s `--profile-name` on authenticated\nchecks when needed. Then run these checks:\n\n```sh\nohmyhost --version\nohmyhost whoami --json\nohmyhost profile list --json\n```\n\nAlso list the MCP tools of the `ohmyho` server. A saved configuration alone is not a working connection.\n\nRead the result:\n\n| Observation | State | Continue with |\n| --------------------------------------------------------- | -------------------------- | ---------------- |\n| `ohmyhost` missing, or the MCP server exposes no tools | not installed | Step 2 |\n| `--version` is older than the published release | outdated | Step 2 |\n| CLI runs, `whoami` fails with `authentication_required` | installed, signed out | Step 3 |\n| `whoami` fails with `credential_store_unavailable` | no usable credential store | Step 5 |\n| `whoami` returns an identity with an organization | ready | Step 5 |\n| `whoami` returns `next_action` instead of an organization | signed in, no workspace | Step 4 |\n| `whoami` fails with `profile_selection_required` | several saved logins | Several accounts |\n\n`whoami` selects the workspace itself when the customer has exactly one, so an identity that\narrives with an organization needs no workspace step. Without an organization, `next_action`\nmeans the choice would be a guess or no workspace exists yet. With an organization,\n`next_action` can name `github connect`: that workspace has no GitHub connection yet, and Step 6\nhandles it.\n\nIf `OHMYHOST_TOKEN` is set in this process, that token is the credential: the CLI and MCP ignore any\nsaved login. Verify its returned identity, organization and selected platform against the task,\neven if a browser is already signed in. If `whoami` succeeds for that account, go to Step 5 without\nstarting another login. If it fails, ask the customer to update the private credential source,\nnot to paste a replacement value into chat.\nDo not send them to a sign-in link, because `ohmyhost login` refuses to run while the variable is set.\nWith a working token, `profile list` can still answer `credential_store_unavailable` on a host\nwithout a credential store; no repair is needed unless the task names a saved login.\n\nSay nothing about a state that needs nothing from the customer. A ready agent deploys without a\nsingle question. Report a state only in the message that also asks them to act, so they never\nreceive one message about the problem and a second one about the link.\n\n## Several accounts on one computer\n\nEach `ohmyhost login` saves one login: one user in one organization, kept in the operating\nsystem\'s credential store. `ohmyhost profile list --json` (MCP `profile_list`) shows each login\'s\nname, user and organization, never a token. There is no active login for the whole computer: with\none saved login every command uses it; with several, every command names one with\n`--profile-name NAME`, MCP tools take `profile_name`, and `OHMYHOST_PROFILE=NAME` binds a whole\nprocess or MCP server. A command with `--organization` (MCP `organization_id`) uses that\norganization\'s login by itself. A project command can also select it from a checkout link,\nincluding a link for its explicit `--project`; `whoami` and `project list` do not select a login\nfrom checkout links, so name one when several are saved. `ohmyhost init` uses no login and\nrefuses `--profile-name`. Another agent\'s choice never changes which account your command runs as.\n\n- When the prompt names a user and an organization, act only as the saved login with exactly that\n user and organization, and confirm it with `whoami --profile-name NAME` before any change. These\n IDs are context, not credentials. Never guess an account and never use another login instead.\n- If no saved login matches, add it and send its link and code as in Step 3:\n `ohmyhost login --organization ORGANIZATION_ID --user USER_ID --json`. If the browser is\n signed in as another account, the login saves nothing and answers `login_account_mismatch`:\n ask the customer to switch the browser to the named account (or use a private window), then\n repeat the login.\n- When the prompt names only a user ("it has no organization yet"), use the saved login with that\n user and no organization, confirmed with `whoami --profile-name NAME`. If none exists, add it\n with `ohmyhost login --user USER_ID --json`, then continue with Step 4.\n- `profile_selection_required` means several logins could run the command: ask the customer which\n account to use. `profile_not_found` names the login to add, `profile_context_mismatch` means the\n request contradicts its binding, organization or user, and `environment_token_context_mismatch`\n means `OHMYHOST_TOKEN` belongs to another account.\n- A saved login never switches organizations. For another workspace, add its own login with\n `ohmyhost login --organization ORGANIZATION_ID --user USER_ID --json`.\n `ohmyhost logout --profile-name NAME` removes only that login.\n- `secret_set_command` takes `profile_name` like every tool and returns a command that names the\n same login with `--profile-name` plus its user and organization (`--profile-user`,\n `--profile-organization`); an MCP server with `OHMYHOST_TOKEN` names its key\'s user and\n organization with `--token-user` and `--token-organization` instead. Keep those flags when you\n run the command, so the secret is written as exactly this account. A login of that name that\n belongs to another user or organization, for example on another computer, answers\n `profile_context_mismatch`. The key form runs only where `OHMYHOST_TOKEN` holds a key of that\n user and organization, never with a saved login: it answers `environment_token_required` without\n a key and `environment_token_context_mismatch` with another account\'s key. None of these\n refusals reads the value or sends anything.\n- Tokens stay in the operating system\'s credential store; the list of saved logins (names, users\n and organizations, never a token) is kept in `~/.ohmyhost/profiles/`, so agents that sign in at\n the same moment never lose each other\'s login. One environment keeps up to 64 saved logins;\n beyond that `login` answers `profile_limit_reached` and saves nothing: ask the customer which\n saved login to remove with `ohmyhost logout --profile-name NAME`.\n- One checkout can be linked for several organizations, one link each. With several links, name\n the login with `--profile-name`; an older link without organization is used only after the API\n confirms that the chosen login can see its project (`linked_project_organization_mismatch`\n otherwise).\n- Errors that depend on the account name it in `acting_as`: `resource_not_found` with another\n account\'s login means the wrong login, `github_connection_required` means that workspace has no\n GitHub connection yet, and `repository_not_installed` means its GitHub App installation does not\n cover the repository.\n\n## Step 2 — install what is missing\n\nRead <https://ohmyho.st/llms.txt> and the [CLI/MCP installation guide](https://docs.ohmyho.st/agents/mcp).\nCompare the installed versions with <https://ohmyho.st/client-release.json>: `ohmyhost --version`\nnames the CLI, and `npm ls --global --depth=0` lists both packages (`ohmyhost-mcp` has no\n`--version` flag; it starts the server). When either is missing or older, install both from the\nsource already in use: the current archive URLs from that guide, or npm\n(`npm install --global @amerged/ohmyhost-cli@0.1.47 @amerged/ohmyhost-mcp@0.1.47`). Both provide the same\n`ohmyhost` and `ohmyhost-mcp` commands, so uninstall one pair before switching to the other.\nAn older client lacks commands and rejects newer server responses with `response_contract_invalid`;\nan older MCP server reports the same case as `client_request_failed` although connection and login\nwork. Upgrade both clients and reload MCP before treating either as a platform fault; if both are\ncurrent, report it with `feedback_submit`.\n\nRead [harness setup](references/harness-setup.md) and register the local `ohmyhost-mcp` command with this harness\'s documented settings. Preserve other MCP servers, model choices and permission settings. Use `OHMYHOST_ENVIRONMENT=production` for CLI and MCP unless the customer explicitly selected the development platform.\n\nEvery CLI command and MCP tool is listed in [surfaces](references/surfaces.md); use it to find the exact name of a capability a customer asks for instead of guessing or assuming it is missing.\n\nReload the MCP connection after every install or upgrade, then verify `tools/list` and `resources/list`. A running server keeps the tool list it started with, so a freshly installed version is invisible until it restarts. Repeat Step 1 afterwards.\n\n## Step 3 — the customer signs in once\n\n```sh\nohmyhost login --json\n```\n\nWhen the prompt named a user and organization, add\n`--organization ORGANIZATION_ID --user USER_ID`, so nothing is saved unless the browser signs in\nas exactly that account. Each login is saved under a name derived from its organization;\n`--profile-name NAME` chooses another (lowercase letters, digits, `-` and `_`, starting with a\nletter or digit, at most 63 characters; another name answers `invalid_command`). If that name\nalready belongs to another saved login (`profile_name_conflict`), choose a different name;\nremove the other login only when the customer asks.\n\nStart it as a background process and read its output while it runs: a sign-in link, a confirmation\ncode such as `ABCD-EFGH` and how many minutes both stay valid appear on stderr at once. The\ncommand waits up to 30 minutes for the customer and saves the login only when it ends by itself;\na harness timeout that stops it earlier saves nothing. The sign-in page shows the same code and\nasks the customer to confirm it. Send one message with the full link, code and validity, then\nwait for the customer while leaving the command running.\n\n> I found no valid ohmyho.st login for this account on this computer. Open this link to connect it:\n>\n> [full link exactly as printed]\n>\n> The page shows the code **[code]**. Continue only if it shows exactly this code.\n> Sign in there, or choose **Sign up** on that same page if you do not have an account yet.\n> Link and code are valid for [minutes] minutes; if the page rejects the code, say so and I will send a new one.\n> Tell me when you are done.\n\nRules for this step:\n\n- Always show the code. Every message that carries a sign-in link also carries its code, the first\n time and after every repeated login. The customer checks it against the page; a code that\n appears on the page but never in the chat gives them nothing to check.\n- Write the full link on its own line, exactly as the CLI printed it, so the customer sees the\n address before opening it. Never hide it behind words like "this link" or "sign-in link" and\n never shorten it; a bare address the chat makes clickable is fine. The customer types nothing.\n- State how long link and code are valid, taking the number from the CLI\'s own message rather than\n inventing one.\n- Do not ask whether they have an account. The same page serves both, so naming both costs one\n sentence and saves a round trip.\n- Sign-up is open. There is no invitation, no waitlist and no access code. Never send the customer\n somewhere else to request access.\n- Wait for the customer. The command completes on its own once they finish; do not start a second\n login while the first is still open.\n- A confirmation code lives only a few minutes. While `ohmyhost login --json` still runs, it\n replaces a lapsed code by itself for up to 30 minutes and prints the new link and code: send\n them the same way. Run the original login command again, keeping its account flags, only\n after it ended with `device_authorization_expired`. This is expected, not a failure: do not\n report an error or suggest the customer did something wrong.\n\nWhen the command returns, verify and continue:\n\n```sh\nohmyhost whoami --json\n```\n\n## Step 4 — make sure a workspace is selected\n\n`login` and `whoami` select the workspace themselves when the customer has exactly one, and their\nresponse names the selected organization. They report `next_action` with several choices, and then\nthe customer decides; with no workspace at all, create the first one. `organization use` binds a\nlogin that has no organization yet; a login that already has one keeps it (use a separate login\nfor another workspace, see "Several accounts on one computer").\n\nAlways look before creating. A verified first portal signup creates or reuses **My workspace**,\nincluding a direct signup or one with an invalid referral. The customer may also have a workspace\nfrom an earlier session:\n\n```sh\nohmyhost organization list --json\nohmyhost organization use --organization "$ORGANIZATION_ID" --json\n```\n\nCreate a workspace only when that list is empty, with a name the customer gave you:\n\n```sh\nohmyhost organization create --name "$ORGANIZATION_NAME" --source "$SIGNUP_SOURCE" --idempotency-key "$ORGANIZATION_REQUEST_KEY" --json\nohmyhost whoami --json\n```\n\n- `--source` (MCP `signup_source`) is optional attribution. If the task mentioned a campaign,\n referral (`https://ohmyho.st/?r=ref-…`) or flag (`https://ohmyho.st/?r=flag-…`) link, pass its\n single accepted `r` value unchanged (lowercase letters, digits, `-` and `_`, starting with a\n letter or digit, at most 64 characters); otherwise omit it. The portal treats a malformed\n value as Direct, so missing or invalid attribution never blocks signup and earns no referral\n bonus. The server decides any once-per-user benefit: an eligible\n referral or active flag on the first workspace gives 1,000 credits and 30 days of Paid, and a\n registered campaign can add credits; promise nothing else. Attribution is never an access\n code or secret and is fixed per user: later workspaces pass the same Signup source shown on\n the portal\'s Profile page (omit the flag for Direct); another value answers `forbidden`.\n- Reuse the same name, source and idempotency key after an interrupted response instead of creating a second organization.\n- Creating a workspace selects it immediately for a login that had none; `whoami` or `identity_get` confirms the selection before you create a project. A login already in another workspace keeps it, and the response names the `login` that adds one for the new workspace.\n- Over MCP, `organization_create`, `organization_list` and `organization_use` do the same and report the same `selected` workspace.\n- Creating, listing and selecting a workspace need the interactive login. An API token can do none of them, and says so.\n- Never create another workspace on your own when the customer already has one.\n- A session that selected none lists no projects: `projects_list` and `ohmyhost project list` answer `organization_required` instead of an empty page. Select a workspace, then read the list again.\n\n## Step 5 — keep access for later\n\nThe current CLI login is enough to continue; MCP uses it. Ordinary account connection,\ndevelopment and deployment do not authorize creating a persistent API token.\n\n`ohmyhost login` saves each login in the operating system\'s credential store, and MCP reads it\nthere. `credential_store_unavailable` means the CLI cannot use that store. On a desktop, ask the\ncustomer to unlock it, then retry. In a container, CI runner or headless Linux, do not start a\nsign-in that cannot be saved: ask the customer to create a user token at\n<https://app.ohmyho.st/tokens> and load it as `OHMYHOST_TOKEN` from a private env file.\n\nOnly when the customer explicitly requests an automation credential and authorizes its\naccount, workspace and private output file, use `token_create` with `out_file`, or\n`ohmyhost token create --organization ULID --name NAME --idempotency-key KEY --out .env.local --json`.\nThe client appends `OHMYHOST_TOKEN` once to that private env file, sets mode `600` and returns\nonly metadata; the value is never shown in the client result and cannot be read again. The file\nname must start or end with `.env`, be ignored by Git or lie outside the repository\n(`token_file_not_ignored`), and not already hold `OHMYHOST_TOKEN` (`token_file_has_token`).\nConfigure the process to load it. Preserve existing credentials and never put the value in chat,\nsource or a command argument. New user tokens remain valid until revoked; a general hosting\nrequest is not consent for that lasting access. On replay, reuse the original private file.\nIf `token_file_changed` names an issued key that could not be saved, or\n`token_value_unavailable` means the original file is unavailable, do not revoke or replace an\nexisting key automatically. Revoke or replace only the exact key covered by scoped customer\nauthorization; existing explicit cleanup authorization can cover that action without another\nquestion. Otherwise explain the failed save and obtain that authorization first. Never change\nan already working credential merely to make a replay return a new value.\n\n`OHMYHOST_TOKEN` overrides the saved logins in any process where it is set. A token alone runs every\ncommand in these Skills except these, which need the interactive login: `login`, `logout` (including\n`logout --revoke`), `organization create|list|use`, and `token create|list|revoke`. Run those in a\nprocess without the variable. Never delete a saved token file. A token belongs to one account and\norganization: a command that names another (`--profile-name`, `--organization`, or a checkout\nlinked for another organization) is refused with `environment_token_context_mismatch` before\nanything is sent.\n\n## Step 6 — continue with the app\n\nFirst use **ohmyhost-deploy** to resolve the saved source or the customer\'s explicit source\nchoice. The GitHub connection instructions below apply only to the GitHub route. A managed\nsource does not require a GitHub account, App installation, repository or provider token.\n\nConfirm the selected directory and GitHub repository. Read `github_status` for the selected workspace. If it is not connected, an Owner or Admin uses `github_connect` (CLI below), opens its single `authorization_url`, then repeats the same request/key after the browser completes until the returned status is `connected`.\n\n```sh\nohmyhost github status --organization "$ORGANIZATION_ID" --json\nohmyhost github connect --organization "$ORGANIZATION_ID" --idempotency-key "$GITHUB_CONNECT_KEY" --json\n```\n\nThe one link handles the required installation/user authorization. Do not construct a second installation link, replay OAuth callbacks, or ask for an installation ID or provider token. Use the intended GitHub browser profile. A connected installation covers only its selected repositories; if one is missing, open `connection.settings_url` from status, add the repository and repeat its original source-link request/key.\n\nMCP/REST returns these objects directly. CLI JSON wraps the handoff in `authorization` and status\nin `github`: read `authorization.authorization_url` and `github.connection.settings_url`.\nThe handoff lives 30 minutes (`expires_at`). While `pending`, a `last_failure` of\n`provider_unavailable` or `authorization_code_rejected` means reopen the returned\n`authorization_url` with the same key. `failed` or `expired` is final: `account_admin_required`\nneeds the GitHub account owner or organization admin, `installation_access_required` means the\nApp was not installed for that account, `session_inactive` needs a fresh login by a workspace\nOwner or Admin, and `denied` means the customer declined. Resolve the cause, then connect with a\nnew key for the same workspace; never poll a terminal failure.\n\nUse `projects_list` to reuse a project and `project_context_get` when resuming one. Preserve an existing project\'s region. For a new project, an explicit customer region wins; otherwise use a browser-location hint supplied in the customer\'s onboarding prompt and send that region explicitly. Without either, ask once for US or EU. Never infer customer location from the agent/server IP. The API default remains US. New projects provision their PGCF database, new files and builds in the selected region; the customer API cannot change it later. `database_compute_get` reports actual database location. Existing projects migrated to US with their owner\'s authorization use US databases, while their existing R2 object locations remain recorded. Read `Storage jurisdiction` in `project_context_get` before changing established storage configuration.\n\nContinue with **ohmyhost-deploy-github** when GitHub is the selected source, or return to\n**ohmyhost-deploy** for managed source. Login, workspace creation, source connection and project\nlinking are distinct results; check each returned state rather than treating a completed browser\npage as deployment success.\n\nFor a repository containing several apps, run local `init` from its Git root and check that the\nrepository-root `ohmyhost.yaml` selects the intended `applicationRoot` before planning the pushed\ncommit. For example, `applicationRoot: apps/web` selects that app\'s sources and migrations;\nan `ohmyhost.yaml` inside `apps/web` does not override the repository\'s selection. Compare the\nlocal result with the plan\'s `application_root` and keep any admission diagnostic visible.\n\nSupport runs through this agent. When the customer needs help, reports a bug or asks for a feature, submit a redacted report with `feedback_submit` (CLI `ohmyhost feedback submit`), give the customer the receipt ID and read replies later with `feedback_status`; the **ohmyhost-troubleshoot-deployment** Skill describes a good report. Point the customer to https://ohmyho.st/contact only when they cannot sign in, a billing issue names `contact_support`, or they ask about privacy or the DPA. The customer page is https://docs.ohmyho.st/support.\n',
115
+ text: '---\nname: ohmyhost-get-started\ndescription: Connect a customer agent or native chat to ohmyho.st, verify the intended account and workspace, and continue an application from its saved source. Use managed OAuth for a remote chat and the existing local login for a terminal agent. Also use for account selection, customer support, or private automation credentials, and to route LLM, image, speech, classifier or OCR requests to the app Skill; use ohmyhost-deploy once access is ready.\n---\n\n# Start with ohmyho.st\n\nConnect this agent to the customer\'s account, then continue with the selected app and its saved source.\n\n## What ohmyho.st is and what you can do with it\n\nohmyho.st hosts a customer\'s app: Vite/React, TanStack Start, Next.js or a plain Worker\nmodule with HTTP handlers and optional cron schedules. A coding agent uses the local MCP server or\nCLI; a connected native chat uses the remote MCP server. Both use the same product REST contract.\nSource can be a connected GitHub repository or an ohmyho.st-managed repository. Deploy an exact commit to Dev or Prod,\nverify the app, and promote a tested Dev artifact when appropriate. Dev is protected by default;\nthe deployment Skill handles public Dev, data choices and the shared-database promotion rule.\nYou can also give the app Postgres, private runtime secrets, Cloudflare R2 files, optional\ntransactional mail, LLMs, images, speech, classifiers and OCR through an OpenAI-compatible gateway, and a custom domain, export its database as an encrypted SQL ZIP, and read\nusage, credits and project budgets. The customer keeps their own application authentication;\nnothing here replaces it. Sign-up is open and free to start.\n\nThe initial connection asks the customer to sign in and approve the access they want to give this\nagent. GitHub authorization is needed only for the GitHub source route. A working saved local login\nor API token can avoid another local sign-in; a remote chat uses its managed OAuth connection.\nOptional payments, DNS/account consent or harness\nreloads can require further customer action; use the relevant Skill for the feature they chose.\n\nThe local-agent path, and the proof that each step is done:\n\n| Step | Result | Proof | Section |\n| ---- | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ------------ |\n| 1 | You know what is installed and who is signed in | `ohmyhost --version`, `ohmyhost whoami --json` or MCP `identity_get`, and the MCP tool list | Step 1 |\n| 2 | CLI and MCP are current and registered | Installed versions match the published release; the harness\'s MCP `tools/list` exposes the tools after any required reload | Step 2 |\n| 3 | The intended account is authenticated | `ohmyhost whoami --json` or MCP `identity_get` returns the intended user | Step 3 |\n| 4 | A workspace is selected | The identity returns the intended organization | Step 4 |\n| 5 | Access survives this session | The same identity check succeeds from a fresh process with its chosen login or private token source | Step 5 |\n| 6 | GitHub is connected and existing projects are known | MCP `github_status` returns `connected`; `projects_list` identifies a project to reuse | Step 6 |\n| 7 | The repository is linked and the app is deployed and verified | MCP `source_get` confirms the linked repository; the deployment operation succeeds and the selected app URL passes its checks | Deploy Skill |\n\nEach step is a separate result. Confirm it using the Proof column; a completed browser page,\na saved MCP configuration or a started process does not substitute for its returned state.\n\nWhich Skill to read next:\n\n| The customer wants to … | Skill |\n| ---------------------------------------------------------------- | ---------------------------------- |\n| prepare or adapt an app so it runs here | ohmyhost-build-portable-app |\n| convert a real Supabase database, Auth, Storage or Functions use | ohmyhost-migrate-supabase-postgres |\n| deploy a commit, verify it, promote Dev to Prod | ohmyhost-deploy |\n| deploy an explicitly selected GitHub repository | ohmyhost-deploy-github |\n| understand a queued, stuck or failed deployment, or report a bug | ohmyhost-troubleshoot-deployment |\n| read or change data, database size or Dev-to-Prod migrations | ohmyhost-manage-database |\n| connect a custom domain or mail sender | ohmyhost-domains-and-mail |\n| add LLMs, images, speech, classifiers or OCR to the app | ohmyhost-build-portable-app |\n| export the database as an encrypted backup | ohmyhost-export-database |\n| see usage, credits or set a spending limit | ohmyhost-usage-and-budgets |\n\nUsers normally reach this Skill from a prompt such as "connect this agent to ohmyho.st and deploy\nthis project". In that case finish the relevant access steps with as few messages as possible, then continue\nwith the deployment Skill without asking again for anything already decided.\n\n## Choose the existing project context first\n\nBefore continuing an existing app, inspect the execution environment, any reachable checkout and\nits saved project binding, then verify the intended account and workspace with the matching\ncredential context.\nReuse a working local CLI/MCP login or private token source that matches that project unless the\nuser explicitly chooses a remote connection; the availability of remote tools alone\ndoes not select the remote path.\nIf a remote connection cannot see the intended project, verify that connection\'s account and\nworkspace and current role, and stop when they do not match.\nIf the previous local workspace or credential store is unreachable in this session, explain that\nconcrete environment gap without claiming credentials were deleted, changing the saved source\nor using an unrelated Review/demo account as a substitute.\n\n## Native chat with a remote connection\n\nUse this path when the task\'s established access context calls for the authenticated remote\nserver.\nDiscover its tools and call `identity_get`; do not install a CLI,\nstart a device-code login, or ask for an API key in this chat. The client completes the managed\nOAuth sign-in itself. A configured plugin or completed browser page is not proof of authorization.\n\nVerify the account with `identity_get` and use `project_list` to find the intended project.\nCurrent workspace membership, role and any explicit token restrictions determine access. Continue\nwith the requested work when authorized; a role denial needs the appropriate workspace role,\nnot a separate project approval. Do not create a replacement project to gain visibility.\n\nAn identity with no organization needs `workspace_setup` before project work. Present its\nreturned portal URL and ask the customer to sign in there with the same connected account.\nAfter setup, call `identity_get` again. Never guess an organization from a project name.\n\nContinue with **ohmyhost-deploy**. A new application developed entirely in this chat uses managed\nsource. Read the project\'s source and context before continuing a saved app; changing chats does\nnot create another project. Before using source connection/generation guards, request\n`source_get` with `include_binding: true`; ordinary source selection may use its default\nreleased GitHub view, while managed source includes those guards in either view.\nFor a complete native file tree or an explicitly requested GitHub-to-managed switch,\n**ohmyhost-deploy** uses `source_upload_prepare`, `source_blob_put`, `source_upload`,\n`source_upload_commit` or `source_switch`; retain the actual upload operation and receipts.\nThese remote tools accept bounded file contents, not local directory paths or credentials.\nFor private application credentials, use `secret_input_request` and its portal handoff,\nthen `connection_status`. Values stay out of chat, source, tools and logs.\nIf the required remote tools are unavailable, state that concrete capability gap; local device\ntokens are not a substitute for a missing remote connection.\n\n## How to talk to the customer here\n\n- One action per message, in short plain sentences. Give the link, then what they will see.\n- Write in the language the customer writes in. Translate the message templates below; copy no\n other sentence from this file into the chat.\n- Keep customer-facing messages focused on the action and why it is needed. Avoid narrating\n routine internal steps; explain an actual limitation when it prevents the requested work.\n- After the verified connection, if the customer has not said what they want to build or host,\n ask only "What would you like to host?" in their language (for example, "Was möchtest du hosten?").\n If the app or task is already known, continue directly without this question.\n- Keep raw account IDs, permission tables, `projectId: null` and routine access diagnostics\n out of normal chat replies. Use recognizable account or application names when a choice is needed.\n- Wait for required browser input before taking actions that depend on it. Independent repository\n inspection can continue while the customer signs in; do not start another sign-in for the same\n account or repeat the instruction without new information. Respect the customer\'s existing authorization and scope.\n- Never ask for a password, an email code or a token value. Never paste a credential into chat,\n source or a command argument.\n- After they report back, verify with a command instead of trusting the report.\n\n## Prepare a domain before the first deployment\n\nA customer can authorize Cloudflare before the website is live. Follow the domains Skill: plan the requested hostname, apply it once and retain its idempotency key. `awaiting_deployment` means the name is declared, with no DNS changes, certificate or domain charges yet. Return the Cloudflare authorization URL, verify consent, then deploy and verify Prod on the platform address. Repeat the same domain apply to activate the hostname. The callback and a successful deployment never switch customer DNS by themselves. A current CLI/MCP is required to read the new state.\n\n## Prepare a Next.js application\n\nFor Next.js, run the portable-app inspection before deployment. Supported versions start at the security floor 16.4.1, with no upper bound. Since the Next.js security advisories of 22 October 2026, planning and the build refuse Next.js 16.4.0 and older 16.x releases and every Next.js 15 release. A deployment that is already live keeps running, but it stays exposed to those advisories until you rebuild it on a fixed release. When inspection reports an unsupported version, update package.json and its lockfile to the reported `minimum` or a later stable release, then commit and plan the new commit. Next.js 15 builds stopped on 21 October 2026, when the pinned OpenNext started refusing Next.js 15 as an unsupported major, so a Next.js 15 app moves to Next.js 16.4.1 or later; use Next.js 16 for new work. A `create-next-app` 16.4 app with Tailwind CSS, its `--yes` default, loads Tailwind only through a Turbopack rule that the Webpack build here ignores; planning refuses it as `next_tailwind_turbopack_only` until the application root has a PostCSS configuration, as that refusal describes. Runtime data files need Next\'s normal file tracing. MDX that compiles JavaScript at request time must instead compile to static ES modules during the build; the portable-app Skill describes that conversion. Verify every content route the app uses before calling a deployment complete.\n\nNext.js pages rendered during the build use a private, immutable cache. Its capacity defaults to\n32 MiB; CLI/MCP 0.1.28 or later accept optional `build.ssg_cache_max_mib` in `ohmyhost.yaml`, an\ninteger from 1 to 64. Public static assets retain their separate 25 MiB allowance. The selected\ncache, public assets, Worker modules, migrations and archive metadata together must fit the\ndeployment artifact ceilings of 30 MiB compressed and 64 MiB expanded. Choose a cache capacity\nthat leaves room for the other archive contents. See the\n[portable-app runtime contracts](https://ohmyho.st/skills/ohmyhost-build-portable-app/references/stack-contracts.md)\nbefore changing a limit. Cache storage has provider cost 0, so cost × 1.5 is still 0 credits; build and\nruntime charges continue unchanged. New deployments replace this build-time cache; time-based\nand on-demand revalidation are not supported. Cache Components and `"use cache"` are supported:\nrequest-time `"use cache"` values are never stored, build-time values stay until the next\ndeployment, and each dynamic page needs a static shell. A CLI older than 0.1.33 still refuses Cache\nComponents in `ohmyhost init`; upgrade it instead of removing them. The runtime contracts list the\nexact rules.\n\n## Step 1 — determine the state before doing anything\n\nUse CLI and local MCP version 0.1.48 or newer together. Older clients reject the credits and usage\nresponses that 0.1.41 and 0.1.43 changed (credits is now only the balance) with\n`response_contract_invalid`, the native `psql` access transport needs 0.1.39 or newer, the\ndatabase webhook, schedule and executions commands and tools need 0.1.42 or newer, the\n`database watch` commands and tools need 0.1.45 or newer, and the `database table` commands and tools need 0.1.46 or newer. The `ai` block in `ohmyhost.yaml` needs 0.1.47 or newer: `ai: {enabled: true, jurisdiction: eu}` (or `any`) declares LLM, image, speech, classifier and OCR use, and `jurisdiction` is required whenever `enabled` is true. A Vite app with `ai` uses `runtime.mode: edge` and a companion Worker, as the init refusal names. Models are served through [orq.ai](https://orq.ai). `ohmyhost whoami` and MCP `identity_get` return the current account\'s email and the\ncredential\'s organizations with IDs and current names. Compare that email and workspace with the account the customer intended before\nchanging resources. Service credentials have `email: null`.\n\nIf the customer\'s prompt names an account ("Use my ohmyho.st account user … in organization …"),\nread "Several accounts on one computer" first. Use that login\'s `--profile-name` on authenticated\nchecks when needed. Then run these checks:\n\n```sh\nohmyhost --version\nohmyhost whoami --json\nohmyhost profile list --json\n```\n\nAlso list the MCP tools of the `ohmyho` server. A saved configuration alone is not a working connection.\n\nRead the result:\n\n| Observation | State | Continue with |\n| --------------------------------------------------------- | -------------------------- | ---------------- |\n| `ohmyhost` missing, or the MCP server exposes no tools | not installed | Step 2 |\n| `--version` is older than the published release | outdated | Step 2 |\n| CLI runs, `whoami` fails with `authentication_required` | installed, signed out | Step 3 |\n| `whoami` fails with `credential_store_unavailable` | no usable credential store | Step 5 |\n| `whoami` returns an identity with an organization | ready | Step 5 |\n| `whoami` returns `next_action` instead of an organization | signed in, no workspace | Step 4 |\n| `whoami` fails with `profile_selection_required` | several saved logins | Several accounts |\n\n`whoami` selects the workspace itself when the customer has exactly one, so an identity that\narrives with an organization needs no workspace step. Without an organization, `next_action`\nmeans the choice would be a guess or no workspace exists yet. With an organization,\n`next_action` can name `github connect`: that workspace has no GitHub connection yet, and Step 6\nhandles it.\n\nIf `OHMYHOST_TOKEN` is set in this process, that token is the credential: the CLI and MCP ignore any\nsaved login. Verify its returned identity, organization and selected platform against the task,\neven if a browser is already signed in. If `whoami` succeeds for that account, go to Step 5 without\nstarting another login. If it fails, ask the customer to update the private credential source,\nnot to paste a replacement value into chat.\nDo not send them to a sign-in link, because `ohmyhost login` refuses to run while the variable is set.\nWith a working token, `profile list` can still answer `credential_store_unavailable` on a host\nwithout a credential store; no repair is needed unless the task names a saved login.\n\nSay nothing about a state that needs nothing from the customer. A ready agent deploys without a\nsingle question. Report a state only in the message that also asks them to act, so they never\nreceive one message about the problem and a second one about the link.\n\n## Several accounts on one computer\n\nEach `ohmyhost login` saves one login: one user in one organization, kept in the operating\nsystem\'s credential store. `ohmyhost profile list --json` (MCP `profile_list`) shows each login\'s\nname, user and organization, never a token. There is no active login for the whole computer: with\none saved login every command uses it; with several, every command names one with\n`--profile-name NAME`, MCP tools take `profile_name`, and `OHMYHOST_PROFILE=NAME` binds a whole\nprocess or MCP server. A command with `--organization` (MCP `organization_id`) uses that\norganization\'s login by itself. A project command can also select it from a checkout link,\nincluding a link for its explicit `--project`; `whoami` and `project list` do not select a login\nfrom checkout links, so name one when several are saved. `ohmyhost init` uses no login and\nrefuses `--profile-name`. Another agent\'s choice never changes which account your command runs as.\n\n- When the prompt names a user and an organization, act only as the saved login with exactly that\n user and organization, and confirm it with `whoami --profile-name NAME` before any change. These\n IDs are context, not credentials. Never guess an account and never use another login instead.\n- If no saved login matches, add it and send its link and code as in Step 3:\n `ohmyhost login --organization ORGANIZATION_ID --user USER_ID --json`. If the browser is\n signed in as another account, the login saves nothing and answers `login_account_mismatch`:\n ask the customer to switch the browser to the named account (or use a private window), then\n repeat the login.\n- When the prompt names only a user ("it has no organization yet"), use the saved login with that\n user and no organization, confirmed with `whoami --profile-name NAME`. If none exists, add it\n with `ohmyhost login --user USER_ID --json`, then continue with Step 4.\n- `profile_selection_required` means several logins could run the command: ask the customer which\n account to use. `profile_not_found` names the login to add, `profile_context_mismatch` means the\n request contradicts its binding, organization or user, and `environment_token_context_mismatch`\n means `OHMYHOST_TOKEN` belongs to another account.\n- A saved login never switches organizations. For another workspace, add its own login with\n `ohmyhost login --organization ORGANIZATION_ID --user USER_ID --json`.\n `ohmyhost logout --profile-name NAME` removes only that login.\n- `secret_set_command` takes `profile_name` like every tool and returns a command that names the\n same login with `--profile-name` plus its user and organization (`--profile-user`,\n `--profile-organization`); an MCP server with `OHMYHOST_TOKEN` names its key\'s user and\n organization with `--token-user` and `--token-organization` instead. Keep those flags when you\n run the command, so the secret is written as exactly this account. A login of that name that\n belongs to another user or organization, for example on another computer, answers\n `profile_context_mismatch`. The key form runs only where `OHMYHOST_TOKEN` holds a key of that\n user and organization, never with a saved login: it answers `environment_token_required` without\n a key and `environment_token_context_mismatch` with another account\'s key. None of these\n refusals reads the value or sends anything.\n- Tokens stay in the operating system\'s credential store; the list of saved logins (names, users\n and organizations, never a token) is kept in `~/.ohmyhost/profiles/`, so agents that sign in at\n the same moment never lose each other\'s login. One environment keeps up to 64 saved logins;\n beyond that `login` answers `profile_limit_reached` and saves nothing: ask the customer which\n saved login to remove with `ohmyhost logout --profile-name NAME`.\n- One checkout can be linked for several organizations, one link each. With several links, name\n the login with `--profile-name`; an older link without organization is used only after the API\n confirms that the chosen login can see its project (`linked_project_organization_mismatch`\n otherwise).\n- Errors that depend on the account name it in `acting_as`: `resource_not_found` with another\n account\'s login means the wrong login, `github_connection_required` means that workspace has no\n GitHub connection yet, and `repository_not_installed` means its GitHub App installation does not\n cover the repository.\n\n## Step 2 — install what is missing\n\nRead <https://ohmyho.st/llms.txt> and the [CLI/MCP installation guide](https://docs.ohmyho.st/agents/mcp).\nCompare the installed versions with <https://ohmyho.st/client-release.json>: `ohmyhost --version`\nnames the CLI, and `npm ls --global --depth=0` lists both packages (`ohmyhost-mcp` has no\n`--version` flag; it starts the server). When either is missing or older, install both from the\nsource already in use: the current archive URLs from that guide, or npm\n(`npm install --global @amerged/ohmyhost-cli@0.1.47 @amerged/ohmyhost-mcp@0.1.47`). Both provide the same\n`ohmyhost` and `ohmyhost-mcp` commands, so uninstall one pair before switching to the other.\nAn older client lacks commands and rejects newer server responses with `response_contract_invalid`;\nan older MCP server reports the same case as `client_request_failed` although connection and login\nwork. Upgrade both clients and reload MCP before treating either as a platform fault; if both are\ncurrent, report it with `feedback_submit`.\n\nRead [harness setup](references/harness-setup.md) and register the local `ohmyhost-mcp` command with this harness\'s documented settings. Preserve other MCP servers, model choices and permission settings. Use `OHMYHOST_ENVIRONMENT=production` for CLI and MCP unless the customer explicitly selected the development platform.\n\nEvery CLI command and MCP tool is listed in [surfaces](references/surfaces.md); use it to find the exact name of a capability a customer asks for instead of guessing or assuming it is missing.\n\nReload the MCP connection after every install or upgrade, then verify `tools/list` and `resources/list`. A running server keeps the tool list it started with, so a freshly installed version is invisible until it restarts. Repeat Step 1 afterwards.\n\n## Step 3 — the customer signs in once\n\n```sh\nohmyhost login --json\n```\n\nWhen the prompt named a user and organization, add\n`--organization ORGANIZATION_ID --user USER_ID`, so nothing is saved unless the browser signs in\nas exactly that account. Each login is saved under a name derived from its organization;\n`--profile-name NAME` chooses another (lowercase letters, digits, `-` and `_`, starting with a\nletter or digit, at most 63 characters; another name answers `invalid_command`). If that name\nalready belongs to another saved login (`profile_name_conflict`), choose a different name;\nremove the other login only when the customer asks.\n\nStart it as a background process and read its output while it runs: a sign-in link, a confirmation\ncode such as `ABCD-EFGH` and how many minutes both stay valid appear on stderr at once. The\ncommand waits up to 30 minutes for the customer and saves the login only when it ends by itself;\na harness timeout that stops it earlier saves nothing. The sign-in page shows the same code and\nasks the customer to confirm it. Send one message with the full link, code and validity, then\nwait for the customer while leaving the command running.\n\n> I found no valid ohmyho.st login for this account on this computer. Open this link to connect it:\n>\n> [full link exactly as printed]\n>\n> The page shows the code **[code]**. Continue only if it shows exactly this code.\n> Sign in there, or choose **Sign up** on that same page if you do not have an account yet.\n> Link and code are valid for [minutes] minutes; if the page rejects the code, say so and I will send a new one.\n> Tell me when you are done.\n\nRules for this step:\n\n- Always show the code. Every message that carries a sign-in link also carries its code, the first\n time and after every repeated login. The customer checks it against the page; a code that\n appears on the page but never in the chat gives them nothing to check.\n- Write the full link on its own line, exactly as the CLI printed it, so the customer sees the\n address before opening it. Never hide it behind words like "this link" or "sign-in link" and\n never shorten it; a bare address the chat makes clickable is fine. The customer types nothing.\n- State how long link and code are valid, taking the number from the CLI\'s own message rather than\n inventing one.\n- Do not ask whether they have an account. The same page serves both, so naming both costs one\n sentence and saves a round trip.\n- Sign-up is open. There is no invitation, no waitlist and no access code. Never send the customer\n somewhere else to request access.\n- Wait for the customer. The command completes on its own once they finish; do not start a second\n login while the first is still open.\n- A confirmation code lives only a few minutes. While `ohmyhost login --json` still runs, it\n replaces a lapsed code by itself for up to 30 minutes and prints the new link and code: send\n them the same way. Run the original login command again, keeping its account flags, only\n after it ended with `device_authorization_expired`. This is expected, not a failure: do not\n report an error or suggest the customer did something wrong.\n\nWhen the command returns, verify and continue:\n\n```sh\nohmyhost whoami --json\n```\n\n## Step 4 — make sure a workspace is selected\n\n`login` and `whoami` select the workspace themselves when the customer has exactly one, and their\nresponse names the selected organization. They report `next_action` with several choices, and then\nthe customer decides; with no workspace at all, create the first one. `organization use` binds a\nlogin that has no organization yet; a login that already has one keeps it (use a separate login\nfor another workspace, see "Several accounts on one computer").\n\nAlways look before creating. A verified first portal signup creates or reuses **My workspace**,\nincluding a direct signup or one with an invalid referral. The customer may also have a workspace\nfrom an earlier session:\n\n```sh\nohmyhost organization list --json\nohmyhost organization use --organization "$ORGANIZATION_ID" --json\n```\n\nCreate a workspace only when that list is empty, with a name the customer gave you:\n\n```sh\nohmyhost organization create --name "$ORGANIZATION_NAME" --source "$SIGNUP_SOURCE" --idempotency-key "$ORGANIZATION_REQUEST_KEY" --json\nohmyhost whoami --json\n```\n\n- `--source` (MCP `signup_source`) is optional attribution. If the task mentioned a campaign,\n referral (`https://ohmyho.st/?r=ref-…`) or flag (`https://ohmyho.st/?r=flag-…`) link, pass its\n single accepted `r` value unchanged (lowercase letters, digits, `-` and `_`, starting with a\n letter or digit, at most 64 characters); otherwise omit it. The portal treats a malformed\n value as Direct, so missing or invalid attribution never blocks signup and earns no referral\n bonus. The server decides any once-per-user benefit: an eligible\n referral or active flag on the first workspace gives 1,000 credits and 30 days of Paid, and a\n registered campaign can add credits; promise nothing else. Attribution is never an access\n code or secret and is fixed per user: later workspaces pass the same Signup source shown on\n the portal\'s Profile page (omit the flag for Direct); another value answers `forbidden`.\n- Reuse the same name, source and idempotency key after an interrupted response instead of creating a second organization.\n- Creating a workspace selects it immediately for a login that had none; `whoami` or `identity_get` confirms the selection before you create a project. A login already in another workspace keeps it, and the response names the `login` that adds one for the new workspace.\n- Over MCP, `organization_create`, `organization_list` and `organization_use` do the same and report the same `selected` workspace.\n- Creating, listing and selecting a workspace need the interactive login. An API token can do none of them, and says so.\n- Never create another workspace on your own when the customer already has one.\n- A session that selected none lists no projects: `projects_list` and `ohmyhost project list` answer `organization_required` instead of an empty page. Select a workspace, then read the list again.\n\n## Step 5 — keep access for later\n\nThe current CLI login is enough to continue; MCP uses it. Ordinary account connection,\ndevelopment and deployment do not authorize creating a persistent API token.\n\n`ohmyhost login` saves each login in the operating system\'s credential store, and MCP reads it\nthere. `credential_store_unavailable` means the CLI cannot use that store. On a desktop, ask the\ncustomer to unlock it, then retry. In a container, CI runner or headless Linux, do not start a\nsign-in that cannot be saved: ask the customer to create a user token at\n<https://app.ohmyho.st/tokens> and load it as `OHMYHOST_TOKEN` from a private env file.\n\nOnly when the customer explicitly requests an automation credential and authorizes its\naccount, workspace and private output file, use `token_create` with `out_file`, or\n`ohmyhost token create --organization ULID --name NAME --idempotency-key KEY --out .env.local --json`.\nThe client appends `OHMYHOST_TOKEN` once to that private env file, sets mode `600` and returns\nonly metadata; the value is never shown in the client result and cannot be read again. The file\nname must start or end with `.env`, be ignored by Git or lie outside the repository\n(`token_file_not_ignored`), and not already hold `OHMYHOST_TOKEN` (`token_file_has_token`).\nConfigure the process to load it. Preserve existing credentials and never put the value in chat,\nsource or a command argument. New user tokens remain valid until revoked; a general hosting\nrequest is not consent for that lasting access. On replay, reuse the original private file.\nIf `token_file_changed` names an issued key that could not be saved, or\n`token_value_unavailable` means the original file is unavailable, do not revoke or replace an\nexisting key automatically. Revoke or replace only the exact key covered by scoped customer\nauthorization; existing explicit cleanup authorization can cover that action without another\nquestion. Otherwise explain the failed save and obtain that authorization first. Never change\nan already working credential merely to make a replay return a new value.\n\n`OHMYHOST_TOKEN` overrides the saved logins in any process where it is set. A token alone runs every\ncommand in these Skills except these, which need the interactive login: `login`, `logout` (including\n`logout --revoke`), `organization create|list|use`, and `token create|list|revoke`. Run those in a\nprocess without the variable. Never delete a saved token file. A token belongs to one account and\norganization: a command that names another (`--profile-name`, `--organization`, or a checkout\nlinked for another organization) is refused with `environment_token_context_mismatch` before\nanything is sent.\n\n## Step 6 — continue with the app\n\nFirst use **ohmyhost-deploy** to resolve the saved source or the customer\'s explicit source\nchoice. The GitHub connection instructions below apply only to the GitHub route. A managed\nsource does not require a GitHub account, App installation, repository or provider token.\n\nConfirm the selected directory and GitHub repository. Read `github_status` for the selected workspace. If it is not connected, an Owner or Admin uses `github_connect` (CLI below), opens its single `authorization_url`, then repeats the same request/key after the browser completes until the returned status is `connected`.\n\n```sh\nohmyhost github status --organization "$ORGANIZATION_ID" --json\nohmyhost github connect --organization "$ORGANIZATION_ID" --idempotency-key "$GITHUB_CONNECT_KEY" --json\n```\n\nThe one link handles the required installation/user authorization. Do not construct a second installation link, replay OAuth callbacks, or ask for an installation ID or provider token. Use the intended GitHub browser profile. A connected installation covers only its selected repositories; if one is missing, open `connection.settings_url` from status, add the repository and repeat its original source-link request/key.\n\nMCP/REST returns these objects directly. CLI JSON wraps the handoff in `authorization` and status\nin `github`: read `authorization.authorization_url` and `github.connection.settings_url`.\nThe handoff lives 30 minutes (`expires_at`). While `pending`, a `last_failure` of\n`provider_unavailable` or `authorization_code_rejected` means reopen the returned\n`authorization_url` with the same key. `failed` or `expired` is final: `account_admin_required`\nneeds the GitHub account owner or organization admin, `installation_access_required` means the\nApp was not installed for that account, `session_inactive` needs a fresh login by a workspace\nOwner or Admin, and `denied` means the customer declined. Resolve the cause, then connect with a\nnew key for the same workspace; never poll a terminal failure.\n\nUse `projects_list` to reuse a project and `project_context_get` when resuming one. Preserve an existing project\'s region. For a new project, an explicit customer region wins; otherwise use a browser-location hint supplied in the customer\'s onboarding prompt and send that region explicitly. Without either, ask once for US or EU. Never infer customer location from the agent/server IP. The API default remains US. New projects provision their PGCF database, new files and builds in the selected region; the customer API cannot change it later. `database_compute_get` reports actual database location. Existing projects migrated to US with their owner\'s authorization use US databases, while their existing R2 object locations remain recorded. Read `Storage jurisdiction` in `project_context_get` before changing established storage configuration.\n\nContinue with **ohmyhost-deploy-github** when GitHub is the selected source, or return to\n**ohmyhost-deploy** for managed source. Login, workspace creation, source connection and project\nlinking are distinct results; check each returned state rather than treating a completed browser\npage as deployment success.\n\nFor a repository containing several apps, run local `init` from its Git root and check that the\nrepository-root `ohmyhost.yaml` selects the intended `applicationRoot` before planning the pushed\ncommit. For example, `applicationRoot: apps/web` selects that app\'s sources and migrations;\nan `ohmyhost.yaml` inside `apps/web` does not override the repository\'s selection. Compare the\nlocal result with the plan\'s `application_root` and keep any admission diagnostic visible.\n\nSupport runs through this agent. When the customer needs help, reports a bug or asks for a feature, submit a redacted report with `feedback_submit` (CLI `ohmyhost feedback submit`), give the customer the receipt ID and read replies later with `feedback_status`; the **ohmyhost-troubleshoot-deployment** Skill describes a good report. Point the customer to https://ohmyho.st/contact only when they cannot sign in, a billing issue names `contact_support`, or they ask about privacy or the DPA. The customer page is https://docs.ohmyho.st/support.\n',
116
116
  },
117
117
  {
118
118
  skillName: "ohmyhost-get-started",
@@ -121,7 +121,7 @@ export const GENERATED_SKILL_RESOURCES = Object.freeze([
121
121
  title: "ohmyhost-get-started: references/harness-setup.md",
122
122
  description: "Supporting resource for ohmyhost-get-started.",
123
123
  mimeType: "text/markdown",
124
- text: "# Connect the product MCP\n\n## Native chat\n\nThe remote product endpoint is `https://app.ohmyho.st/mcp`. A native ChatGPT or Claude\nconnection uses managed OAuth, not `OHMYHOST_TOKEN` or a local device-code profile. Installing\na plugin adds its Skills/configuration; verify that its remote connector is enabled and signed\nin, then discover tools and call `identity_get`. Use `connection_request` and `connection_status`\nfor the user's explicit project/create/publish grants. Never paste credentials into the\nconnection configuration or chat. The native flow is in **ohmyhost-get-started** and\n**ohmyhost-deploy**.\n\nOnly claim the connection works after this client's actual authenticated tool call succeeds.\nThe local package or an entered URL does not prove remote OAuth, publication, or directory\napproval. Muse/Grok and other clients need their own supported authenticated remote-connector\npath; do not imply that an API-side MCP option installs a consumer-chat integration.\n\n## Local terminal agent\n\nRead https://docs.ohmyho.st/agents/mcp for current installation and full customer instructions. Install the current CLI and MCP only when needed. Authenticate one of two ways: load `OHMYHOST_TOKEN` from a private credential source into the server environment, or sign in with `ohmyhost login --json` and let MCP use the saved logins. With several saved logins, pass `profile_name` on each call (`profile_list` shows the names), or bind the server to one login with `OHMYHOST_PROFILE=NAME` next to `OHMYHOST_ENVIRONMENT`; a bound server refuses calls for another login (`profile_context_mismatch`). A server registered for the user serves every session on this computer, so bind it only when the customer asks, and reload it afterwards. The token wins wherever it is set and needs no browser. New user API tokens are optional and remain valid until revoked; the portal shows a new value once, while CLI/MCP token creation saves it directly to the chosen private env file. Login and token lifetimes are separate.\n\nInspect existing configuration before adding the one server. An existing `ohmyho` entry that runs `npx` with a versioned `https://ohmyho.st/releases/<version>/` archive stays on that version and fails once the archive is superseded (older archive URLs answer 404): install the current CLI and MCP and point the entry at `ohmyhost-mcp`. Preserve unrelated servers, models, environment values and approval settings. Check installed help when an executable or flag differs.\n\n| Harness | Register the local server | Confirm in the running harness |\n| ----------- | --------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |\n| Codex | `codex mcp add ohmyho --env OHMYHOST_ENVIRONMENT=production -- ohmyhost-mcp` | Reload if requested; call `identity_get` and read the Skill resources. |\n| Claude Code | `claude mcp add --transport stdio --scope user --env OHMYHOST_ENVIRONMENT=production ohmyho -- ohmyhost-mcp` | Open `/mcp`, confirm connection and call `identity_get`. |\n| Cursor | Merge the token-free https://ohmyho.st/mcp.json server into the existing global or project MCP config. | Confirm in settings; current CLI supports `agent mcp list-tools ohmyho`. Resolve installed executable/help. |\n| Hermes | `hermes mcp add ohmyho --command ohmyhost-mcp --env OHMYHOST_ENVIRONMENT=production` | `hermes mcp test ohmyho`, then reload the agent session. |\n| OpenClaw | `openclaw mcp add ohmyho --command ohmyhost-mcp --env OHMYHOST_ENVIRONMENT=production` where the installed native registry supports it. | `openclaw mcp probe ohmyho --json`, then verify runtime-visible tools. |\n\nCursor uses `.cursor/mcp.json` per project or `~/.cursor/mcp.json` globally, with `mcpServers.ohmyho.command = \"ohmyhost-mcp\"` and only `OHMYHOST_ENVIRONMENT=production` in the public configuration. A host on another computer needs its own installation and authorized local login.\n\nUse the documented remote endpoint only for a supported remote connection. Do not substitute an\ninvented URL for local stdio registration or run OpenClaw's `mcp serve` as a client setup.\nMintlify search reads documentation; it does not authorize product resources. Do not alter an\napproval policy merely to connect a server.\n\nAfter setup, discover `tools/list` and `resources/list`, call `identity_get`, select the authorized organization, and read `project_context_get` before resuming a project. A saved configuration or successful process start alone is not a successful authenticated connection. If the host requires a reload, return that concrete step and resume after it; never pretend tools are loaded.\n\nOfficial references: https://cursor.com/docs/cli/mcp, https://hermes-agent.nousresearch.com/docs/user-guide/features/mcp, https://docs.openclaw.ai/cli/mcp/registry. Codex and Claude command syntax was also checked against installed help; always retain version-appropriate behavior.\n",
124
+ text: "# Connect the product MCP\n\n## Native chat\n\nThe remote product endpoint is `https://app.ohmyho.st/mcp`. A native ChatGPT or Claude\nconnection uses managed OAuth, not `OHMYHOST_TOKEN` or a local device-code profile. Installing\na plugin adds its Skills/configuration; verify that its remote connector is enabled and signed\nin, then discover tools and call `identity_get`. Current account membership and roles determine access; use `connection_status` for protected secret and GitHub handoffs. Never paste credentials into the\nconnection configuration or chat. The native flow is in **ohmyhost-get-started** and\n**ohmyhost-deploy**.\n\nOnly claim the connection works after this client's actual authenticated tool call succeeds.\nThe local package or an entered URL does not prove remote OAuth, publication, or directory\napproval. Muse/Grok and other clients need their own supported authenticated remote-connector\npath; do not imply that an API-side MCP option installs a consumer-chat integration.\n\n## Local terminal agent\n\nRead https://docs.ohmyho.st/agents/mcp for current installation and full customer instructions. Install the current CLI and MCP only when needed. Authenticate one of two ways: load `OHMYHOST_TOKEN` from a private credential source into the server environment, or sign in with `ohmyhost login --json` and let MCP use the saved logins. With several saved logins, pass `profile_name` on each call (`profile_list` shows the names), or bind the server to one login with `OHMYHOST_PROFILE=NAME` next to `OHMYHOST_ENVIRONMENT`; a bound server refuses calls for another login (`profile_context_mismatch`). A server registered for the user serves every session on this computer, so bind it only when the customer asks, and reload it afterwards. The token wins wherever it is set and needs no browser. New user API tokens are optional and remain valid until revoked; the portal shows a new value once, while CLI/MCP token creation saves it directly to the chosen private env file. Login and token lifetimes are separate.\n\nInspect existing configuration before adding the one server. An existing `ohmyho` entry that runs `npx` with a versioned `https://ohmyho.st/releases/<version>/` archive stays on that version and fails once the archive is superseded (older archive URLs answer 404): install the current CLI and MCP and point the entry at `ohmyhost-mcp`. Preserve unrelated servers, models, environment values and approval settings. Check installed help when an executable or flag differs.\n\n| Harness | Register the local server | Confirm in the running harness |\n| ----------- | --------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |\n| Codex | `codex mcp add ohmyho --env OHMYHOST_ENVIRONMENT=production -- ohmyhost-mcp` | Reload if requested; call `identity_get` and read the Skill resources. |\n| Claude Code | `claude mcp add --transport stdio --scope user --env OHMYHOST_ENVIRONMENT=production ohmyho -- ohmyhost-mcp` | Open `/mcp`, confirm connection and call `identity_get`. |\n| Cursor | Merge the token-free https://ohmyho.st/mcp.json server into the existing global or project MCP config. | Confirm in settings; current CLI supports `agent mcp list-tools ohmyho`. Resolve installed executable/help. |\n| Hermes | `hermes mcp add ohmyho --command ohmyhost-mcp --env OHMYHOST_ENVIRONMENT=production` | `hermes mcp test ohmyho`, then reload the agent session. |\n| OpenClaw | `openclaw mcp add ohmyho --command ohmyhost-mcp --env OHMYHOST_ENVIRONMENT=production` where the installed native registry supports it. | `openclaw mcp probe ohmyho --json`, then verify runtime-visible tools. |\n\nCursor uses `.cursor/mcp.json` per project or `~/.cursor/mcp.json` globally, with `mcpServers.ohmyho.command = \"ohmyhost-mcp\"` and only `OHMYHOST_ENVIRONMENT=production` in the public configuration. A host on another computer needs its own installation and authorized local login.\n\nUse the documented remote endpoint only for a supported remote connection. Do not substitute an\ninvented URL for local stdio registration or run OpenClaw's `mcp serve` as a client setup.\nMintlify search reads documentation; it does not authorize product resources. Do not alter an\napproval policy merely to connect a server.\n\nAfter setup, discover `tools/list` and `resources/list`, call `identity_get`, select the authorized organization, and read `project_context_get` before resuming a project. A saved configuration or successful process start alone is not a successful authenticated connection. If the host requires a reload, return that concrete step and resume after it; never pretend tools are loaded.\n\nOfficial references: https://cursor.com/docs/cli/mcp, https://hermes-agent.nousresearch.com/docs/user-guide/features/mcp, https://docs.openclaw.ai/cli/mcp/registry. Codex and Claude command syntax was also checked against installed help; always retain version-appropriate behavior.\n",
125
125
  },
126
126
  {
127
127
  skillName: "ohmyhost-get-started",
@@ -197,6 +197,6 @@ export const GENERATED_SKILL_RESOURCES = Object.freeze([
197
197
  description:
198
198
  "Explain ohmyho.st measured usage, remaining credits and project spending limits, and buy credits or Paid when the owner asks. Use for cost reports, low-credit questions, an app paused with HTTP 402, budget changes, top-ups, Paid, auto-recharge, invoices, referral links or powered-by flag credits.",
199
199
  mimeType: "text/markdown",
200
- text: '---\nname: ohmyhost-usage-and-budgets\ndescription: Explain ohmyho.st measured usage, remaining credits and project spending limits, and buy credits or Paid when the owner asks. Use for cost reports, low-credit questions, an app paused with HTTP 402, budget changes, top-ups, Paid, auto-recharge, invoices, referral links or powered-by flag credits.\n---\n\n# Explain usage and spending\n\nUse `identity_get` to select the organization, then `organization_credits_get` and `organization_usage_get`, which lists the billed charges of the last up to 14 UTC days by day, project and meter and each project\'s use so far this month. Older days are not kept. Read `project_budget_get` for a project-specific limit and `project_context_get` for current project actions.\n\nReport the available balance, reserved credits, the days covered and the largest project/environment/meter costs. Distinguish billed consumption from reservations; charges arrive up to an hour after the usage, so recent measurements are not zero usage. Explain quantities and credits together, for example database RAM and active time versus separately metered customer files. Reporting remains available at zero credits.\n\nA wallet is shared across the organization. A new workspace starts Free. Paid feature access may come from a Stripe subscription or a granted entitlement; a grant does not create a subscription or another monthly Paid allowance. A referral grant adds 1,000 credits once and thirty days of Paid; read its `paid_until`. When a finite grant ends, the workspace returns to Free, managed mail and AI calls stop, and its custom domain needs the powered-by flag to keep serving.\n\nMonthly credits expire when their period ends and renew with the next period: 200 credits on Free, 1,000 credits on Paid. Monthly credits are spent before top-ups. Top-up, bonus and referral credits never expire. `available_micros` is the monthly plus the one-time credit minus the credit that open builds and AI calls have reserved. When the credit is used up, services keep running for seven days (`grace_expires_at`); the next monthly renewal ends the exhaustion. Only effective Paid workspaces buy top-ups or enable recharge. An unpaid Stripe renewal keeps Paid features for up to fourteen days after the period end (`paid_until` includes them); otherwise the workspace returns to Free.\n\nUse `organization_account_get` or `ohmyhost credits account --organization "$ORGANIZATION_ID" --json` for the effective plan/source and the monthly, one-time and reserved credit. A project that shows the opt-in "Powered by ohmyho.st" flag (`powered_by_flag_get`) pays no custom-hostname credits and may use its own domain on Free. Each Paid period actually bought through Stripe adds 250 credits once per workspace, however many projects show the flag; granted/referral days add none. Switch with `powered_by_flag_set` only within the customer\'s explicit choice; hiding it is refused while a Free domain depends on it.\n\nFor the referral link use `referral_link_get` / `ohmyhost referral link --organization "$ORGANIZATION_ID" --json`, also the portal\'s "Refer and earn" chip. This is a recommendation link, not an invitation to join the existing workspace. A new user\'s first workspace receives 1,000 non-expiring credits and thirty Paid days; its first payment gives the referrer the same credit reward plus thirty granted days unless a Stripe subscription or an unbounded grant already covers it. Do not infer extra rewards from retries or additional workspaces.\n\nA project budget is a limit, not another balance. Mode `stop` pauses both Dev and Prod when measured usage plus reservations reach it: HTTP 402 begins `Project execution paused`. Mode `continue` does not pause. The same serving pause applies after the organization\'s seven-day zero-credit grace; a custom domain also pauses without Paid/flag eligibility. Serving resumes within about five minutes after resolving the relevant cause: raise/remove the limit, choose continue, reach a new UTC month, add credits or restore domain eligibility. Explain Stop\'s effect before an authorized `project_budget_set`, then read the result back. Do not change a budget merely to explain a report.\n\nFor database savings, use [the database Skill](../ohmyhost-manage-database/SKILL.md): the five RAM sizes cost 10, 20, 40, 80 or 160 credits per active hour, prorated. The 256 MiB size includes 500 MiB total disk and is available on Free; larger sizes require Paid. Disk and seven-day backups are included, with no separate database CPU, network or connection charges. Databases sleep after 300 idle seconds and then accrue no active-RAM charge. Customer files and application Workers remain separately metered. AI gateway calls reserve credit before forwarding, then consume credit from the cost orq.ai records under the active AI rate card and the published minimum or fallback rules. They count toward the project\'s Stop budget. Read current terms on https://ohmyho.st/pricing and [the AI reference](../ohmyhost-build-portable-app/references/ai.md); missing cost telemetry never means a free call. Retained published scripts accrue `wfp.script` charges for their lifetime even without traffic; each deployment/promotion can add one rollback script. Failed private candidates are drained. Scripts remain until their cleanup or project deletion; reset/share also remove the Dev deployment and retained rollback scripts, while append-only deployment metadata remains listable. A custom hostname accrues `domain.custom_hostname` charges except while its project shows the flag. Read the wallet\'s grace expiry when credits are exhausted; do not promise that unfunded services run indefinitely. New work still needs the credits quoted by its plan.\n\nDatabase schedules and webhook deliveries have no meter and no price of their own. Their counts are readable per hour or day (`database_execution_usage_get`), but what they cost is the awake time of the database, which the database RAM charge already includes. A report must not show an execution charge.\n\n## Purchases and invoices\n\nOnly start `billing_checkout_create` when the owner requested or approved that purchase. `paid` starts a USD 10/month subscription granting 1,000 credits per paid period. `topup` (Paid only; otherwise `paid_plan_required`) buys 1–100 USD 10 packs and does not extend Paid. In one checkout, dollars up to USD 100 buy 100 credits each, and dollars above buy 125 each: 25% more credits for that portion. Ten packs give 10,000 credits and twenty give 22,500. Prices exclude tax. Complete the returned checkout, then verify `billing_checkout_get` and the wallet. A browser return is not payment confirmation.\n\nUse `billing_portal_create` for invoice history, payment methods and subscription management. Every successful purchase, including a one-time top-up, has an invoice. If checkout is unavailable for the selected platform, report that response; never call a test payment a real purchase.\n\nReturn a concise cost explanation and the requested next action. For a suspected incorrect charge, use `feedback_submit` with the period and safe receipt/error identifiers, without payment details or raw records.\n\nThe portal project card\'s **$** button opens **Usage & limits**, using the same `project_budget_set` contract: no limit, or credits per UTC calendar month with continue/stop. Preserve the selected mode and read back changes. Existing work and delayed measurements may settle after reaching a limit. IDs in a copied project prompt identify context only; authenticate and check current scope before retrieving details.\n\n## Auto-recharge\n\nRead `billing_recharge_get` before changing auto-recharge. It is off by default and can be enabled only with active Paid access: each refill adds 1,000 top-up credits for USD 9 plus tax when available credits fall below 100. Without Paid access no refill starts; the saved consent stays until the Owner turns it off, and refills resume with Paid. The monthly limit is USD 10–1,000 including tax and uses UTC calendar months; it does not override project stop budgets or enable Paid features.\n\nOnly enable after the Owner explicitly approves these recurring off-session charges and a gross monthly limit. Call `billing_recharge_configure` with the current `revision`, the approved `monthly_limit_minor` in USD cents, `enabled: true`, `consent: "off_session_v1"` and a saved `idempotency_key`. Return `setup_url` to the human to save a card at Stripe, then read again. Never reuse approval for a one-off purchase as recurring-payment consent.\n\nTo turn it off, use the current revision, `enabled: false` and `consent: null`; already initiated payments may complete. Replay the same key and payload after uncertainty. `payment_required` pauses further attempts: return the private `invoice_url` when present, or ask the human to review Billing. Do not repeatedly re-enable or create another purchase to bypass a decline. `monthly_limit` resumes next UTC month; `needs_reconciliation` requires checking the original attempt rather than a new charge. Every paid refill has an invoice. Refunds/chargebacks adjust only their original credit lot and pause further automatic refills.\n\nCLI read: `ohmyhost billing recharge get --organization "$ORGANIZATION_ID" --json`. Authorized change: `ohmyhost billing recharge set --organization "$ORGANIZATION_ID" --enabled true --monthly-limit-minor 10000 --revision 0 --consent off_session_v1 --idempotency-key "$REQUEST_KEY" --json`; replace the example revision and USD 100 cap with the current read and approved amount. When disabling, use the latest revision and current saved limit, omit `--consent` and use `--enabled false`. Out-of-range limits or consent with disabled state answer `invalid_command`. If billing is unavailable for the chosen platform, report that result; do not switch the customer\'s environment.\n\n## Resolve an existing billing issue\n\nRead `billing_issue` from `billing_checkout_get` or `billing_recharge_get`; retain its `invoice_id`, `code`, observation time and `required_action`. An authorized project context may also surface that next action. `billing_tax_location_required` / `open_billing_portal` means call `billing_portal_create` and give the human a fresh private URL to correct billing details. `billing_tax_calculation_failed` or `billing_tax_configuration_required` / `contact_support` means use https://ohmyho.st/contact about the original invoice.\n\nAfter correction, inspect that same checkout/recharge policy and the actual credit account again. Reading does not authorize or initiate another charge. Keep the original invoice: never disable tax, create a second subscription/top-up, discard the invoice or repeatedly re-enable automatic refills to repair the issue. `tax_required` is a paused attempt, not a successful payment. Historical payment confirmation does not establish current Paid coverage or available credits. Preserve the approved gross monthly cap and recurring-payment consent.\n',
200
+ text: '---\nname: ohmyhost-usage-and-budgets\ndescription: Explain ohmyho.st measured usage, remaining credits and project spending limits, and buy credits or Paid when the owner asks. Use for cost reports, low-credit questions, an app paused with HTTP 402, budget changes, top-ups, Paid, auto-recharge, invoices, referral links or powered-by flag credits.\n---\n\n# Explain usage and spending\n\nUse `identity_get` to select the organization, then `organization_credits_get` and `organization_usage_get`, which lists the billed charges of the last up to 14 UTC days by day, project and meter and each project\'s use so far this month. Older days are not kept. Read `project_budget_get` for a project-specific limit and `project_context_get` for current project actions.\n\nReport the available balance, reserved credits, the days covered and the largest project/environment/meter costs. Distinguish billed consumption from reservations; charges arrive up to an hour after the usage, so recent measurements are not zero usage. Explain quantities and credits together, for example database RAM and active time versus separately metered customer files. Reporting remains available at zero credits.\n\nA wallet is shared across the organization. A new workspace starts Free. Paid feature access may come from a Stripe subscription or a granted entitlement; a grant does not create a subscription or another monthly Paid allowance. A referral grant adds 1,000 credits once and thirty days of Paid; read its `paid_until`. When a finite grant ends, the workspace returns to Free, managed mail and AI calls stop, and its custom domain needs the powered-by flag to keep serving.\n\nMonthly credits expire when their period ends and renew with the next period: 200 credits on Free, 1,000 credits on Paid. Monthly credits are spent before top-ups. Top-up, bonus and referral credits never expire. `available_micros` is the monthly plus the one-time credit minus the credit that open builds and AI calls have reserved. When the credit is used up, services keep running for seven days (`grace_expires_at`); the next monthly renewal ends the exhaustion. Only effective Paid workspaces buy top-ups or enable recharge. An unpaid Stripe renewal keeps Paid features for up to fourteen days after the period end (`paid_until` includes them); otherwise the workspace returns to Free.\n\nUse `organization_account_get` or `ohmyhost credits account --organization "$ORGANIZATION_ID" --json` for the effective plan/source and the monthly, one-time and reserved credit. A project that shows the opt-in "Powered by ohmyho.st" flag (`powered_by_flag_get`) pays no custom-hostname credits and may use its own domain on Free. Each Paid period actually bought through Stripe adds 250 credits once per workspace, however many projects show the flag; granted/referral days add none. Switch with `powered_by_flag_set` only within the customer\'s explicit choice; hiding it is refused while a Free domain depends on it.\n\nFor the referral link use `referral_link_get` / `ohmyhost referral link --organization "$ORGANIZATION_ID" --json`, also the portal\'s "Refer and earn" chip. This is a recommendation link, not an invitation to join the existing workspace. A new user\'s first workspace receives 1,000 non-expiring credits and thirty Paid days; its first payment gives the referrer the same credit reward plus thirty granted days unless a Stripe subscription or an unbounded grant already covers it. Do not infer extra rewards from retries or additional workspaces.\n\nA project budget is a limit, not another balance. Mode `stop` pauses both Dev and Prod when measured usage plus reservations reach it: HTTP 402 begins `Project execution paused`. Mode `continue` does not pause. The same serving pause applies after the organization\'s seven-day zero-credit grace; a custom domain also pauses without Paid/flag eligibility. Serving resumes within about five minutes after resolving the relevant cause: raise/remove the limit, choose continue, reach a new UTC month, add credits or restore domain eligibility. Explain Stop\'s effect before an authorized `project_budget_set`, then read the result back. Do not change a budget merely to explain a report.\n\nFor database savings, use [the database Skill](../ohmyhost-manage-database/SKILL.md): the five RAM sizes cost 10, 20, 40, 80 or 160 credits per active hour, prorated. The 256 MiB size includes 500 MiB total disk and is available on Free; larger sizes require Paid. Disk and seven-day backups are included, with no separate database CPU, network or connection charges. Databases sleep after 300 idle seconds and then accrue no active-RAM charge. Customer files and application Workers remain separately metered. AI gateway calls reserve credit before forwarding and use the active AI rate card. Reported orq cost, including zero, always takes priority. Only chat completions and Responses with no usable cost after bounded retries use a USD 0.01 cost fallback before the card factor; smaller measured costs are never raised to that amount. Other routes with unavailable cost settle at zero. Fallback settlements carry the numeric `fallback_nano_usd` counter in operator credit-log detail. There is no automatic later correction. AI counts toward the project\'s Stop budget. Read current terms on https://ohmyho.st/pricing and [the AI reference](../ohmyhost-build-portable-app/references/ai.md). Retained published scripts accrue `wfp.script` charges for their lifetime even without traffic; each deployment/promotion can add one rollback script. Failed private candidates are drained. Scripts remain until their cleanup or project deletion; reset/share also remove the Dev deployment and retained rollback scripts, while append-only deployment metadata remains listable. A custom hostname accrues `domain.custom_hostname` charges except while its project shows the flag. Read the wallet\'s grace expiry when credits are exhausted; do not promise that unfunded services run indefinitely. New work still needs the credits quoted by its plan.\n\nDatabase schedules and webhook deliveries have no meter and no price of their own. Their counts are readable per hour or day (`database_execution_usage_get`), but what they cost is the awake time of the database, which the database RAM charge already includes. A report must not show an execution charge.\n\n## Purchases and invoices\n\nOnly start `billing_checkout_create` when the owner requested or approved that purchase. `paid` starts a USD 10/month subscription granting 1,000 credits per paid period. `topup` (Paid only; otherwise `paid_plan_required`) buys 1–100 USD 10 packs and does not extend Paid. In one checkout, dollars up to USD 100 buy 100 credits each, and dollars above buy 125 each: 25% more credits for that portion. Ten packs give 10,000 credits and twenty give 22,500. Prices exclude tax. Complete the returned checkout, then verify `billing_checkout_get` and the wallet. A browser return is not payment confirmation.\n\nUse `billing_portal_create` for invoice history, payment methods and subscription management. Every successful purchase, including a one-time top-up, has an invoice. If checkout is unavailable for the selected platform, report that response; never call a test payment a real purchase.\n\nReturn a concise cost explanation and the requested next action. For a suspected incorrect charge, use `feedback_submit` with the period and safe receipt/error identifiers, without payment details or raw records.\n\nThe portal project card\'s **$** button opens **Usage & limits**, using the same `project_budget_set` contract: no limit, or credits per UTC calendar month with continue/stop. Preserve the selected mode and read back changes. Existing work and delayed measurements may settle after reaching a limit. IDs in a copied project prompt identify context only; authenticate and check current scope before retrieving details.\n\n## Auto-recharge\n\nRead `billing_recharge_get` before changing auto-recharge. It is off by default and can be enabled only with active Paid access: each refill adds 1,000 top-up credits for USD 9 plus tax when available credits fall below 100. Without Paid access no refill starts; the saved consent stays until the Owner turns it off, and refills resume with Paid. The monthly limit is USD 10–1,000 including tax and uses UTC calendar months; it does not override project stop budgets or enable Paid features.\n\nOnly enable after the Owner explicitly approves these recurring off-session charges and a gross monthly limit. Call `billing_recharge_configure` with the current `revision`, the approved `monthly_limit_minor` in USD cents, `enabled: true`, `consent: "off_session_v1"` and a saved `idempotency_key`. Return `setup_url` to the human to save a card at Stripe, then read again. Never reuse approval for a one-off purchase as recurring-payment consent.\n\nTo turn it off, use the current revision, `enabled: false` and `consent: null`; already initiated payments may complete. Replay the same key and payload after uncertainty. `payment_required` pauses further attempts: return the private `invoice_url` when present, or ask the human to review Billing. Do not repeatedly re-enable or create another purchase to bypass a decline. `monthly_limit` resumes next UTC month; `needs_reconciliation` requires checking the original attempt rather than a new charge. Every paid refill has an invoice. Refunds/chargebacks adjust only their original credit lot and pause further automatic refills.\n\nCLI read: `ohmyhost billing recharge get --organization "$ORGANIZATION_ID" --json`. Authorized change: `ohmyhost billing recharge set --organization "$ORGANIZATION_ID" --enabled true --monthly-limit-minor 10000 --revision 0 --consent off_session_v1 --idempotency-key "$REQUEST_KEY" --json`; replace the example revision and USD 100 cap with the current read and approved amount. When disabling, use the latest revision and current saved limit, omit `--consent` and use `--enabled false`. Out-of-range limits or consent with disabled state answer `invalid_command`. If billing is unavailable for the chosen platform, report that result; do not switch the customer\'s environment.\n\n## Resolve an existing billing issue\n\nRead `billing_issue` from `billing_checkout_get` or `billing_recharge_get`; retain its `invoice_id`, `code`, observation time and `required_action`. An authorized project context may also surface that next action. `billing_tax_location_required` / `open_billing_portal` means call `billing_portal_create` and give the human a fresh private URL to correct billing details. `billing_tax_calculation_failed` or `billing_tax_configuration_required` / `contact_support` means use https://ohmyho.st/contact about the original invoice.\n\nAfter correction, inspect that same checkout/recharge policy and the actual credit account again. Reading does not authorize or initiate another charge. Keep the original invoice: never disable tax, create a second subscription/top-up, discard the invoice or repeatedly re-enable automatic refills to repair the issue. `tax_required` is a paused attempt, not a successful payment. Historical payment confirmation does not establish current Paid coverage or available credits. Preserve the approved gross monthly cap and recurring-payment consent.\n',
201
201
  },
202
202
  ] as const);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ohmyhost/sdk-ts",
3
- "version": "0.1.47",
3
+ "version": "0.1.49",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {