@amerged/ohmyhost-mcp 0.1.53 → 0.1.54
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.
- package/README.md +1 -1
- package/dist/{chunk-4UGVN7B2.js → chunk-MMK46PWJ.js} +7 -7
- package/dist/index.js +1 -1
- package/dist/stdio-main.js +2 -2
- package/package.json +1 -1
- package/src/apps/mcp/package.json +1 -1
- package/src/apps/product-cli/package.json +1 -1
- package/src/packages/agent-skills/src/generated-skill-resources.ts +6 -6
- package/src/packages/contracts/package.json +0 -5
- package/src/packages/sdk-ts/package.json +1 -1
- package/src/packages/contracts/src/previews.ts +0 -179
|
@@ -62,7 +62,7 @@ export const GENERATED_SKILL_RESOURCES = Object.freeze([
|
|
|
62
62
|
title: "ohmyhost-build-portable-app: references/stack-contracts.md",
|
|
63
63
|
description: "Supporting resource for ohmyhost-build-portable-app.",
|
|
64
64
|
mimeType: "text/markdown",
|
|
65
|
-
text: "# Portable application contracts\n\nUse this reference only when implementing framework or capability code. Product decisions come from `ohmyhost init` and the public CLI, not from provider examples.\n\n## Source and compatibility\n\n- Pin exactly one of `npm`, `pnpm`, `yarn`, or `bun` in `packageManager` and commit exactly one matching frozen lockfile. The direct build commands are `npm run build`, `pnpm run build`, `yarn run build`, or `bun run build`. Bun is pinned to `1.2.22`. Installs are frozen and skip lifecycle scripts (`--ignore-scripts`, or Yarn `--mode=skip-build`); commit generated inputs instead of depending on postinstall. Yarn installs with the `node-modules` linker even when `.yarnrc.yml` selects Plug'n'Play.\n- Supported framework config extensions are `.js`, `.mjs`, and `.ts`.\n- Next.js, Vite and TanStack Start need at least their security floor, with no upper bound: Next `16.4.1` and later (every Next.js 15 release and Next.js 16.4.0 and older are refused since the security advisories of 22 October 2026), Vite `5.4.6`, TanStack Start `1.168.60`; prereleases and older majors are refused. The version that `package-lock.json` (v2/v3) or `pnpm-lock.yaml` (v6/v9) resolves decides, otherwise the exact version or `^`/`~` range in `package.json`; a range spanning the floor is `pending` until the build checks the installed version. Passing this check does not guarantee that a later release builds; OpenNext, for example, refuses a Next major it does not support yet. 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 deployment that is already live keeps running, but it stays exposed to the advisories of 22 October 2026 until you rebuild it on a fixed release.\n- `package.json` scripts.build is exactly `next build` or `next build --webpack` for Next.js. Vite and TanStack Start use `vite build`, optionally with one `tsc`, `tsc --noEmit`, `tsc -b` or `tsc --build` stage before or after it joined by `&&`. Arbitrary preparation, `cd`, traversal and lifecycle hooks are outside this build contract. Builds use Node.js 24 and stop after eight minutes; runtime secrets are absent from the build. Commit intentional public build values. Gzipped Worker limits are 10 MiB for Next.js and 5 MiB for other Workers. Ordinary public static assets are limited to 10 MiB per file and 25 MiB/1,000 files in total; a static runtime also needs `index.html`. The complete deployment archive, including public assets, private SSG cache, Worker modules, migrations and archive metadata, is at most 30 MiB compressed and 64 MiB expanded.\n- The platform overlay, not the customer repository, pins OpenNext `1.20.9` and Wrangler `4.125.0`. Do not commit those packages, generated Wrangler files, platform bindings, or `OHMYHOST_BASE_PATH` for hosting.\n\n## The customer chooses application authentication\n\nHosting does not automatically add end-user authentication. The customer or their agent integrates the chosen library/service into the application and owns its user flows, authorization and provider account/configuration. This is separate from WorkOS authenticating the customer to ohmyho.st, `ohmyhost login`, `OHMYHOST_TOKEN`, GitHub consent and protected Dev browser access. Never reuse ohmyho.st's WorkOS tenant, platform keys or agent token for an application's users.\n\nThe supported application-auth interfaces include [Better Auth](https://better-auth.com/docs/installation) and customer-owned [WorkOS AuthKit](https://workos.com/docs/authkit/). Better Auth has retained integration proof; hosted WorkOS Next.js has proof, while other framework/provider combinations need their own hosted evidence. Other OAuth/OIDC integrations must satisfy the actual browser, server, secrets and egress contracts and pass their application tests; a declared origin does not establish complete provider support. Public applications need no auth. Keep other existing customer choices; framework/runtime capability checks apply equally to all dependencies. Use the provider's current first-party SDK/guide for the actual browser or server runtime. Browser integrations use their documented public client identifiers and origin/callback settings; do not request or expose server API/client secrets in a Vite browser bundle. Server integrations use only customer-owned server runtime secrets.\n\nThe existing structured platform auth integration accepts `none` or pinned `better-auth`. `none` disables only that managed integration; it does not mean that the application has no login. Do not invent `auth.provider: workos` or another unsupported configuration field. Init reports `application-auth-review` for recognized, unselected auth SDK evidence without enabling managed database/auth/mail. Selecting the optional managed Better Auth integration requires explicit `auth.provider: better-auth` and `database.enabled: true`, with Better Auth pinned exactly to `1.7.7`. Managed mail is optional for verified-user/session/token flows. The private bridge uses ohmyho.st Mail for verification/reset sends; those calls need `mail.enabled: true`, Paid access and the customer's verified sender. For a custom Resend callback, preserve the application-owned Better Auth handler with `auth.provider: none`; that choice disables only the managed bridge, not application login. CLI 0.1.24 may incorrectly return `managed_auth_mail_required`; use the corrective 0.1.25 release or later and preserve the selected auth and sender. An ordinary Better Auth or mail SDK dependency selects no managed capability. A detected mail SDK is also evidence to review, not consent to enable Paid platform mail. Database migration files are separate evidence and do not identify an auth provider. Source admission does not reject SDKs by vendor name; the actual runtime, egress, migration and artifact contracts still apply. Older installed clients/platforms may report `better-auth-conversion` or `supabase_migration_required`: discover/update the installed release and report its limitation instead of treating it as consent to replace auth or delete users.\n\nThe released password-first `auth.provider: omh-native` path is restricted to projects approved by the existing launch allowlist and explicitly selects an Omh-operated, headless end-user directory. It requires Paid access, the customer's own verified sender authorized for the project, and a verified application identity on the same registrable main domain as that sender, plus a canonical origin; read readiness before claiming activation. Social sign-in is disabled server-side for this launch; do not offer Google or GitHub sign-in as available. It uses a separately metered project Auth database and separate Dev/Prod directories, even when business data is shared. Native alone does not require a business database or turn static Vite/Astro into a server; static files remain public. The app owns login screens and authorizes its business records after server-side session validation. Native reserves `/api/auth/v1`; detected literal route conflicts must move, and dynamic routes under that namespace are shadowed. Keep an existing customer provider unless the customer chooses otherwise, and never copy users or provider secrets between environments. Source deployment declares capability; an Owner pause survives redeployment. Configuration and user administration are Owner-only. Set the approved canonical origin and application-owned form paths through Owner configuration; `primary` selects the primary origin. The default allowed redirects are `/`, `/login`, `/reset-password` and `/accept-invitation`; reset and invitation paths must belong to the selected allowlist. Paths are local and contain no query, fragment, escapes or dot segments. Changing origin fences existing sessions and pending flows. An unavailable runtime is an error, not proof that a user is signed out. Native management commands and tools require CLI and MCP 0.1.50 or newer. Verify the approved project's readiness and real hosted flow; this release does not qualify every project, environment or lifecycle operation. General availability, performance, recovery and social qualification remain open.\n\nFor browser signup, verification and password-reset mail operations, create an operation key before the first call with `createNativeAuthIdempotencyKey()` and pass it as `headers: { 'Idempotency-Key': key }` in the Better Auth fetch options. Retain the same key for a retry after an uncertain result; do not automatically retry a mutation with a new key. The SDK may generate a fallback, but does not return that generated key after a lost response, so a caller cannot safely recover it later.\n\n`BETTER_AUTH_SECRET` and `BETTER_AUTH_URL` are reserved for the platform-managed integration and cannot be set through the customer secret command. An application that owns its Better Auth setup uses its own secret name, for example `APP_AUTH_SECRET`, and maps that value explicitly to Better Auth's `secret` option. Use an application-owned `APP_URL` for its `baseURL`; it must match the trusted Dev/Prod origin. Do not enable managed auth merely to acquire that name or weaken the reserved-name check. Keep platform-owned credentials and application-owned auth configuration separate.\n\nFor the agent's feedback, state:\n\n- The customer's chosen/existing auth system, whether it runs in the app or externally, and the evidence for runtime/SDK compatibility. Preserve the choice unless the customer authorizes a change.\n- Missing configuration: customer-owned provider tenant/project, exact Dev/Prod login/callback/logout origins, necessary egress destinations and required secret **names**. Use the normal secret CLI/stdin handoff for values. Public client IDs/publishable keys are different from private API keys; follow that provider's documentation.\n- Who stores users/sessions and who sends verification/reset mail. An external provider's mail does not automatically need ohmyho.st mail or Paid access. The managed Better Auth bridge requires its database and has a platform sender for email actions; verified-user/session/token flows need no sender. Keep a custom Resend callback in the application-owned Better Auth handler. Only calls through managed ohmyho.st Mail need its capability, Paid access and verified sender; missing sender for that capability is `mail_domain_required`. Report that dependency instead of removing email verification.\n- What was tested: sign-in, callback, authenticated and forbidden access, session handling and sign-out on the real application. For isolated environments, keep auth configuration/sessions/data isolated and register both callback origins; promotion must not copy Dev users or private credentials to Prod.\n- The specific blocker or next action. Keep “customer configuration missing”, “runtime incompatible” and “not yet verified” distinct in the explanation; these are explanatory categories, not new API error codes. Report a suspected platform limitation through feedback, without credentials or user records.\n\nExample feedback: “This app uses your WorkOS AuthKit account. The ohmyho.st CLI login is separate. Configure this app's Dev/Prod callback URLs and the listed server-secret names. End-user login is not verified until the deployed callback and protected-route tests pass.”\n\n## PostgreSQL and Auth\n\n- Access to ohmyho.st-managed PostgreSQL uses `OHMYHOST_DATABASE`. Database-provider management, direct migration credentials and managed connection URLs are platform-private. This does not forbid a customer's compatible auth SDK from calling their own external identity provider.\n- Call the database with `createPrivateDatabaseClient` from `@ohmyhost/customer-runtime`; see [database-runtime.md](database-runtime.md) for the working example, what is not available and what it costs. There is no connection string or customer socket; interactive transactions use the bounded `withConnection(callback)` scope. Keep canonical expand-only migrations under the path reported by init.\n- A held scope allows two concurrent transaction/auth connections per physical data area (shared Dev/Prod share them), 100 statements, 30 seconds total and five seconds idle. Close it before storage transfers, email, AI calls or other network waits. An adapter for an existing acquire/close port must release the underlying scope in `finally`; never keep a request-wide transaction open while processing a file.\n- An optional Kysely `0.29.5` dialect is available from `@ohmyhost/customer-runtime/database-kysely`: `createPrivateDatabaseDialect(env.OHMYHOST_DATABASE)` for `new Kysely({ dialect })`. Its `db.transaction().execute(async trx => ...)` uses the actual leased connection with BEGIN/COMMIT or ROLLBACK and close; the same bounds apply. For application-owned Better Auth `1.7.7`, use its own migrated schema, normally `public`: `database: { db: db.withSchema(\"public\"), type: \"postgres\", transaction: true }`. This is separate from the managed auth-lane bridge; `auth.provider: none` provisions no auth tables. The library transaction path is tested; hosted signup/mail still needs application verification. This does not claim Drizzle transaction support.\n- SQL `DATE` and `date[]` elements retain the `YYYY-MM-DD` wire string. They are calendar days, not timezone-bearing JavaScript dates; preserve existing timestamp decoding and normalize only the field that the application's contract requires.\n- When the customer selects the verified Better Auth integration, it owns schema `auth`, UUID IDs, `/api/auth`, secure host-only cookies, database sessions, verification/reset callbacks, and session revocation. Mail is selected separately; preserve the customer's configured sender. Authorization remains explicit in each use case.\n- Read hosted secrets/bindings from the framework context: for Next.js on this runtime, `getCloudflareContext({ async: true }).env`. Keep an explicit local test adapter where useful, but do not activate a localhost-only test factory in production or trust the request Host header as configuration. Check the session, tenant and application repositories through the same hosted database adapter as health.\n- A newly isolated database can have correct tables but no tenant, administrator or application configuration. Use the application's existing, owner-authorized bootstrap/seed path with its real password hashing and tenant/membership rules. Keep this as a controlled one-off script or existing private administrative workflow; do not add a public bootstrap route or a production test-mode switch. Verify login after bootstrap, without copying Dev users into Prod.\n\n- For interactive work a customer can issue a time-bound direct PostgreSQL login with `ohmyhost database access create` / MCP `database_access_create` (mode `read` or `write`, 5 minutes to 24 hours, at most three active per environment) and open it with `ohmyhost database psql`. The connection URI and `psql` command are returned exactly once: use them immediately, never store or commit a connection string or password, and revoke the credential when finished. Such a login can never change schema and row-level security still applies; application code keeps using `OHMYHOST_DATABASE`.\n\nApplication background: [Better Auth PostgreSQL](https://better-auth.com/docs/adapters/postgresql). Customer code uses the scoped database binding; provider connection examples do not replace it.\n\n## Files, mail, functions, and secrets\n\n- Import the storage client from `@ohmyhost/customer-runtime/storage`. The Storage Gateway owns raw R2 bindings, signed operations, quotas, receipts, and cleanup. `storage.jurisdiction` accepts `us` or `eu` and must match the project's recorded R2 bucket jurisdiction, including an existing bucket reservation. Existing buckets retain their location through a database or project-region migration; do not rewrite their configuration to the current project region. For a project without a recorded bucket or reservation, the chosen project region is used (`--region`, default `us`). A mismatch fails the plan with `storage_jurisdiction_conflict`. 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. `ohmyhost init` is offline: it writes `us` unless `--region eu` is supplied and preserves an existing configuration. Preserve established storage configuration when resuming a project; use its chosen region only when preparing new storage.\n- Upgrade the CLI/MCP to the current published version first. Then install the runtime under its import name as the exact npm alias that init reports under `runtime.packages.customerRuntime` for every framework; `companion.packages.customerRuntime` and `worker.packages.customerRuntime` are equivalent fields: `\"@ohmyhost/customer-runtime\": \"npm:@amerged/ohmyhost-runtime@<version>\"` in `package.json`. Refresh and commit the lockfile with the project's package manager. Every `@ohmyhost/customer-runtime/*` import stays unchanged. A bare `@ohmyhost/customer-runtime` version fails the platform build, and an `https://ohmyho.st/releases/…` runtime URL stops resolving once a later release replaces the website downloads, so replace such a URL with the alias. Use the version init reports; older runtime versions carry no compatibility promise.\n- A storage-enabled deployment needs four runtime values and **no** `FILES` bucket binding: the plain values `OHMYHOST_STORAGE_GATEWAY_URL`, `OHMYHOST_PROJECT_ID`, `OHMYHOST_ENVIRONMENT_ID`, and the secret `OHMYHOST_STORAGE_KEY`. Resolve them from the server framework context or Worker request and use `(request) => fetch(request)` for both `fetch` and `capabilityFetch`. A legacy customer gateway binding may remain attached until a later platform release removes it; that transport is unsupported. Invalid options answer non-retryable `gateway_configuration_invalid` naming the option; the clients run only on the server.\n- The gateway hop is global `fetch` to `OHMYHOST_STORAGE_GATEWAY_URL`: the egress policy handles the injected origin and forwards supported fixed POST routes to the private gateway. The origin is allowed automatically, does not count among the 13 customer origins, and never goes in `runtime.egress.allow`. Signed R2 URLs use global `fetch` too. Non-retryable 403 `gateway_not_enabled` means enable the capability and redeploy; 403 `gateway_route_denied` means use the current client's supported route, method and query. Neither is fixed by editing the allow-list. Mail and storage share a project-keyed Cloudflare limiter of 1,200 calls per minute per Cloudflare location; it is approximate rather than a globally strict quota. 429 `gateway_rate_limited` and transient 503 `gateway_unavailable` are retryable with a delay and the original idempotency keys. Malformed/non-JSON answers are `gateway_response_invalid` with the received status and no upstream text.\n- Files are create-once logical names within an immutable data area: shared Dev/Prod use one namespace, isolated environments use separate namespaces. Each name is at most 512 UTF-8 bytes; slash-separated segments start/end with letters or digits and contain only letters, digits, `.`, `_` and `-`, with no traversal. Idempotency keys are 1–128 letters/digits/`._:-`. Content type is a lowercase `type/subtype` without parameters. Uploads and the physical bucket quota are at most 1 GiB, including open reservations; signed capabilities last five minutes. A deletion never makes a used name reusable inside that identity. A reset creates a fresh namespace, so the same logical name can be used again. `storage_quota_exceeded` needs deleting unneeded objects or a smaller upload; `storage_conflict` needs resolving the original name/key receipt rather than retrying a conflicting payload. `storage_upload_expired` needs a new objectKey and idempotencyKey. `storage_delete_pending` means wait until retryAt, then repeat the original deletion; `data_change_in_progress` means observe that operation; `storage_binding_data_changed` means this environment's data area changed during the request: read its current state and issue a new request. After an isolate or reset, redeploy the affected environment before relying on its files: until then a previous deployment follows the environment's current file area while its database calls already fail. Data assignment changes retain exact existing locators, without moving blobs.\n- Store a file with `upload` (or `reserveUpload` → PUT to the signed URL → `completeUpload`) and serve it with `createSignedRead`; `deleteObject` releases quota. If `ohmyhost.yaml` declares storage while no source calls `createPrivateStorageClient`, init blocks `storage_client_missing` instead of reporting a clean deployment that fails at the first upload. After uncertain completion, repeat `upload()` with the same bytes, objectKey and idempotencyKey: a create-only PUT's 412 completes the existing reservation. `storage_upload_rejected` is retryable only for 5xx or 429. For recoverable multi-object work, persist reservation/transfer IDs and cleanup progress before transfers; call `completeUpload(transferId)` to reconcile.\n- `readMany({ objectKeys })` reads 1–4,000 distinct logical keys through one streaming gateway call. Iterate every result and consume or cancel each found body before the next item; `not_found` is explicit. Completion requires the final frame and exact EOF. Malformed, oversized or truncated framing fails as `gateway_response_invalid`; an aborted read is not a completed batch. `deleteMany({ objects: [{ objectKey, idempotencyKey }] })` accepts the same key limit and distinct per-object keys. Its ordered outcomes are completed, pending with retryAt, or failed with code/retryable. Control JSON/framing is at most 4 MiB, independent of streamed payload bytes. Keep an application manifest for selection/pagination; there is no bucket-list API. Persist outcomes and replay only pending/retryable items with their original keys.\n- A completed deletion requires verified payload erasure; an accepted request, elapsed upload link or old absence receipt alone does not release quota. Prior conditional uploads cannot restore a completed file. Large or interrupted batches may acknowledge only a subset: retain all keys and retry pending/retryable items. `deleteMany` applies one configured deadline across headers and complete JSON; timeout does not undo durable per-file progress. Binary `readMany` keeps its bounded per-frame timeout and requires complete iteration.\n- When the browser's CSP permits only same-origin connections, upload through an application route instead of exposing a provider URL or weakening CSP. Authorize an opaque, short-lived capability against the real upload reservation, tenant, MIME, byte length and expiry. Keep the platform storage key and signed R2 URLs server-side. Bound bytes while reading; an identical completed PUT retry may return success without writing again, while different bytes must conflict.\n- For inspected private files, use a distinct staging key per capability and an immutable final key. Read the staged ETag conditionally, inspect those bounded bytes, write the final object and verify its metadata before committing the application record. Keep staging through the commit; use a durable, bounded cleanup journal afterward, fenced against active upload/processing work. A comment or an unconsumed outbox event is not a running cleanup path. Original downloads remain authorized, conditional and bounded; a signed GET URL is not a signed HEAD capability.\n- Set upload limits to the smallest limit across ingress, storage, content inspection, downstream APIs and response handling. Reuse one application policy in browser hints, routes and provider adapters; a successful storage upload does not prove the parser or AI service can accept it. Preserve truthful decoder and validation failures.\n- Use authenticated same-origin framework routes for bounded request work. Vite may use the companion source contract returned by init; TanStack Start and Next.js retain their native server routes/functions. A project without a web framework sets `runtime.mode: functions` and writes `src/ohmyhost/worker.ts` as a module Worker: `export default { async fetch(request, env, ctx) {…}, async scheduled(controller, env, ctx) {…} }`. It keeps `build.install` only and receives the same database, files, mail, secret and egress bindings as an edge app.\n- Declare scheduled work only through `functions.crons` in `ohmyhost.yaml`: one to eight unique five-field UTC crons with a five-minute minimum. A field is `*` or one canonical integer; minute is an integer or `*/5` through `*/59`, never bare `*`. Sunday is `0`; ranges, lists, names and shortcuts are refused. Day-of-month and weekday must both match when both are fixed. The handler is the `scheduled(controller, env, ctx)` member of the **default export** of `src/ohmyhost/worker.ts` (functions runtime, Next.js, TanStack Start) or of the Vite companion `src/ohmyhost/companion.ts`. Named exports are never invoked; init blocks `worker_module_default_export_required` and `scheduled_handler_required` with the file path. The platform runs one attempt per cron and UTC minute with a 120-second deadline, retries a thrown error or platform failure up to three attempts, and honors `controller.noRetry()`. Retries retain the original `scheduledTime`, so make side effects idempotent. Dev and Prod schedules follow their own active deployments and can both write the same shared data; decide which owns a task. Finished runs are retained seven days. Each run is billed as one request plus its CPU credits. Runs are visible through `ohmyhost function runs` / MCP `function_runs_list` (per environment, newest first), not through deployment logs.\n- `src/ohmyhost/worker.ts` is bundled by the platform on its own, outside the framework build: tsconfig `paths` resolve, but Vite-only aliases, framework virtual modules and modules that declare TanStack Start server functions or Next.js route handlers do not. Import plain application modules (repositories, storage and database clients from `@ohmyhost/customer-runtime/database`, `/storage` or `/mail`) and keep framework entry code out of the Worker module.\n- HTTP and scheduled customer Workers have 50 ms CPU and ten subrequests per invocation. Network waiting does not consume CPU, but database, gateway and signed-object calls consume requests; batch and page work accordingly. A runtime limit answers HTTP 429 beginning `Runtime limit exceeded`. A functions module must always export a default `fetch`, including a schedules-only application. Init blocks `functions.crons` on a static runtime as `runtime_mode_unsupported`.\n- Select a small work batch and per-call timeouts that fit the 120-second scheduled deadline. Persist claims, retry state and cleanup progress before acknowledging work; close database scopes before provider calls. Import narrow runtime modules rather than barrel files that pull Next.js routes into the scheduled bundle. Verify that a due run actually completes through `function_runs_list`.\n- For LLMs, images, speech, classifiers and OCR through the ohmyho.st AI gateway, declare `ai: { enabled: true, jurisdiction: eu | any }` in `ohmyhost.yaml` (the jurisdiction has no default) and redeploy. The platform injects `OHMYHOST_AI_BASE_URL` (the gateway origin plus `/v1`) and the secret `OHMYHOST_AI_KEY` into the Worker; use an OpenAI-compatible SDK with those two values from server code only. The gateway origin is allowed automatically and never listed in `runtime.egress.allow`. The gateway authorizes the key against the active revision's plan, so a rollback or promotion without `ai` stops the calls. A static runtime refuses `ai` as `runtime_mode_unsupported`. Calls answer `402 paid_plan_required` on a Free workspace; see [the AI gateway reference](ai.md) for the error codes.\n- For ohmyho.st-managed transactional mail, use `createTransactionalMailClient` from `@ohmyhost/customer-runtime/mail` with `OHMYHOST_MAIL_GATEWAY_URL`, `OHMYHOST_MAIL_KEY`, `OHMYHOST_PROJECT_ID` and `fetch: (request) => fetch(request)` from server code. The egress policy delivers `POST /v1/send` to the private gateway; the injected origin is allowed automatically and never listed. A send can take several seconds: `timeoutMilliseconds` defaults to 10000, accepts 100–30000, and bounds response headers. Keep the same message and idempotency key after uncertainty. A missing URL or key is a configuration error. Enable the capability and redeploy for `gateway_not_enabled`; use supported routes for `gateway_route_denied`; report persistent gateway faults with `feedback_submit`. Legacy customer mail gateway bindings are unsupported. Do not call mail or storage from health routes: the deploy-time probe has no egress policy. Verification/reset mail owned by an external identity provider stays with that integration. The mail runtime values exist only when deployed source sets `mail.enabled: true`; a verified domain alone does not install them, and promotion/rollback of undeclared source removes them. Install application-owned private values through stdin-based CLI commands; source lists secret names, never values. See [mail.md](mail.md) for sending and receiving; runtime attachment downloads are currently unsupported.\n\nResolve `env` from the hosted server request. This complete file example uses the same global\ntransport for gateway calls and signed-object bytes; keep both the key and signed URLs server-side.\n\n```ts\nimport { createPrivateStorageClient } from \"@ohmyhost/customer-runtime/storage\";\n\nconst storage = createPrivateStorageClient({\n endpoint: env.OHMYHOST_STORAGE_GATEWAY_URL,\n key: env.OHMYHOST_STORAGE_KEY,\n projectId: env.OHMYHOST_PROJECT_ID,\n environmentId: env.OHMYHOST_ENVIRONMENT_ID,\n fetch: (request) => fetch(request),\n capabilityFetch: (request) => fetch(request),\n});\nawait storage.upload({\n idempotencyKey: \"example-upload-1\",\n objectKey: \"checks/example.txt\",\n contentType: \"text/plain\",\n bytes: new TextEncoder().encode(\"hello\"),\n});\nconst download = await storage.createSignedRead(\"checks/example.txt\");\nconst response = await fetch(\n new Request(download.url, {\n method: download.operation,\n headers: download.requiredHeaders,\n redirect: \"manual\",\n }),\n);\nif (!response.ok) throw new Error(\"File download failed\");\nconst contents = await response.text();\nawait storage.deleteObject({\n idempotencyKey: \"example-delete-1\",\n objectKey: \"checks/example.txt\",\n});\n```\n\nChoose a fresh logical key for each real file and durable idempotency keys for its upload and\ndeletion. For business data, persist the file/transfer identity before network work and close\ndatabase transactions before any transfer.\n\n## Source capability declarations\n\nPreserve an existing `ohmyhost.yaml`. Init detects SQL migrations under `postgres/migrations` or `supabase/migrations`, R2 declarations/bindings and `supabase/storage`; this is evidence to review, not permission to migrate an external service. A database declaration has `enabled: true` and its `migrations` path; files use `storage: { provider: r2, jurisdiction: us|eu, public: false }`. Static Vite/TanStack builds serve assets; Next.js, server TanStack and Vite companions use edge mode; plain Workers use functions mode. A runtime mismatch or `auto` fails planning as `framework_conversion_required`. Enable only actual application capabilities.\n\nServer egress uses `runtime.egress.allow`: at most 13 exact HTTPS customer origins, without trailing slash, paths, credentials, ports, IP literals or local names. The platform adds mail, storage-gateway and R2 origins itself; they are never listed and do not count against the 13. An omitted customer origin answers 403 `Egress denied`; a redirect answers 502 `Egress upstream unavailable` (304 is retained). Call the final destination and declare its origin. Customer Workers cannot open sockets. Browser origins below are a separate declaration and do not authorize server fetches.\n\nThe runtime does not inject a public app URL or an environment-name variable. Declare application-owned origin secrets separately for Dev and Prod. Only managed Better Auth receives its reserved platform base URL. Secret names must be uppercase letters/digits/underscore, begin with a letter and be at most 128 characters; `OHMYHOST_*` and the fixed platform names are reserved for set.\n\n`runtime.healthcheck` defaults to `/`, is a path without a query, and must answer a cookieless GET with 2xx directly within five seconds. An authenticated redirect fails. Health is a minimal public response without secrets; success does not prove other flows.\n\n## Browser media and microphone\n\nVideo/audio playback and microphone access are blocked by default. Server-rendered HTML can opt in with exactly these lines in `public/_headers` and a redeployment; static asset pages cannot opt in. Anything broader is ignored, and camera and geolocation stay blocked.\nMedia loads only from the application's origin or blob URLs; stream stored media through an\nauthorized application route instead of embedding a signed R2 URL.\n\n```text\n/*\n Content-Security-Policy: media-src 'self' blob:\n Permissions-Policy: microphone=(self)\n```\n\n## Browser security (CSP)\n\nNext.js and TanStack Start HTML run under nonce CSP: `script-src 'self' 'nonce-…'` and `style-src 'self' 'nonce-…'`, without unsafe-inline. Each request carries a fresh `x-nonce`; the server wrapper adds it to every rendered script/style tag, including hand-written blocks. Inline `style=\"\"` attributes (also inside SVG) and event handlers stay blocked. Move them to CSS/listeners. Tags created later in the browser need the current nonce (the wrapper supplies a `csp-nonce` meta tag); an unnonced tag is blocked. Vite/static pages get no automatic script/style nonces and permit no inline code. Check the deployed response's Content-Security-Policy and actual browser behavior.\n\nDeclare only the browser capabilities the application uses in the committed configuration:\n\n```yaml\nruntime:\n mode: edge\n browser:\n connect: [https://api.customer.example, wss://events.customer.example]\n scripts: [https://cdn.customer.example]\n styles: [https://cdn.customer.example]\n images: [https://images.customer.example]\n fonts: [https://fonts.customer.example]\n frames: [https://embed.customer.example]\n frameAncestors: [https://portal.customer.example]\n workers: { self: true, blob: false }\n cors:\n origins: [https://portal.customer.example]\n credentials: false\n```\n\nOmitted groups retain restrictive defaults. Each origin group permits at most sixteen distinct exact HTTPS origins; only `connect` also accepts WSS. No wildcards, paths or script/style inline/eval opt-out exist. Resource groups extend their matching CSP directive; worker flags allow only self/blob. `frameAncestors` controls who may embed the app; `frames` controls what it embeds. Application CORS must echo the actual declared origin and its own allowed methods/headers. Cross-origin requests with cookies also require explicit `credentials: true`; undeclared CORS headers are stripped. Same-origin non-GET requests still require matching Origin when cookies are present. Static pages have no default connect permission; use a declared browser destination or an edge companion for application API work. Verify required browser flows, including OAuth callback/pop-up behavior, on the deployed app: these tested declaration interfaces do not prove arbitrary providers or exported Lovable apps work.\n\nFrames, workers and external resources stay blocked when undeclared; service workers, eval/WASM execution and sockets have no general support promise. Do not change CSP to fake a decoder or authentication success.\nThe gateway replaces application CSP and drops Set-Cookie with a Domain attribute; keep session\ncookies host-only. OAuth form-post callbacks must satisfy the explicit CORS/origin policy;\nverify the provider's chosen callback mode instead of assuming a redirect proves a session.\n\n## Framework notes\n\n- Vite static applications need no server companion. With or without a companion, an unknown file path answers `index.html` with HTTP 200, so client-side routing reloads work; render not-found in the client router. Keep Vite `base` unset or `/`. Add the returned companion source only when the application uses database, Auth, mail, files, request functions, or schedules.\n- The functions runtime is a plain Worker module without a framework: `runtime.mode: functions`, `build.install` only, no `build.command` or `build.output`, HTTP through `fetch` and schedules through `scheduled`. Do not add customer Wrangler configuration.\n- TanStack Start uses its native server routes/functions. Import `tanstackStart` from `@tanstack/react-start/plugin/vite`. Static mode needs `tanstackStart({ prerender: { enabled: true } })` and no Cloudflare plugin; edge mode calls `cloudflare({ viteEnvironment: { name: \"ssr\" } })` before `tanstackStart()`. Unknown paths return 404. Keep an active TanStack Start Vite plugin; do not add a customer Wrangler file or platform base path.\n- Next.js Workers builds use the platform OpenNext 1.20.9 overlay and Webpack, including `proxy.ts` Node middleware. The service supplies `--webpack` for Next 16 and later; Next 15 already uses Webpack and does not accept that option; customers do not need to rename middleware or add platform tools/configuration. A committed `open-next.config.ts` is ignored because ohmyho.st writes its own OpenNext configuration, but two `open-next.config` variants (`.js`, `.mjs`, `.ts`) are still refused as `framework_config_ambiguous`; keep at most one. Custom loaders must support Webpack; a Turbopack-only configuration is not evidence of a compatible Workers build. Keep route handlers, RSC/SSR, assets and images framework-native. Use a default-exported next.config, without `output: standalone`/`export` or non-root `basePath`/`assetPrefix`, and pin exact React and react-dom versions. Cache Components (`cacheComponents: true`, also with `partialPrefetching: true` as `create-next-app` 16.4 writes them) build within the Cache Components rules below. CLI 0.1.32 and older still refuse them in `ohmyhost init` as `next_cache_components_unsupported`; upgrade the CLI and MCP instead of removing them. Unknown paths return 404.\n- The Webpack build ignores Turbopack rules. A next.config that names the loader `@tailwindcss/turbopack` is refused as `next_tailwind_turbopack_only` unless the application root has a PostCSS configuration that Webpack reads: a `postcss` object in package.json, `.postcssrc.json`, `postcss.config.json`, `.postcssrc.js` or `postcss.config.js`/`.mjs`/`.cjs` (not `postcss.config.ts`, and not a file in a parent directory). `create-next-app` 16.4 with Tailwind CSS, its `--yes` default, writes only that Turbopack rule, so the site would deploy without its Tailwind classes. Add `postcss.config.mjs` to the application root with `export default { plugins: { \"@tailwindcss/postcss\": {} } }` and remove the Turbopack rule. Use the package manager to add `@tailwindcss/postcss` to devDependencies at the same version as `tailwindcss` and to remove `@tailwindcss/turbopack`, so the lockfile records both; then commit, push and plan the new commit. An app created with `--no-tailwind` needs no change.\n- Next.js server files must be included in its output-file traces. The platform carries traced JSON, text and other non-executable inputs in the Worker filesystem at their application-relative paths, within the existing module and artifact budgets. For dynamically selected files that Next cannot infer, use the ordinary Next.js `outputFileTracingIncludes` configuration. These private runtime files do not become public HTTP assets; files under `public` keep their normal public behavior. Absolute paths, escaping symlinks, missing inputs and native addons are refused.\n- Workers cannot generate JavaScript from strings at request time (`eval` or `new Function`). In particular, rendering MDX through `next-mdx-remote/rsc` at request time fails even when a Node build succeeds. Compile trusted repository MDX to ordinary ES modules with `@mdx-js/mdx` and `outputFormat: \"program\"` during the Next build, then statically import the generated components. Preserve the canonical content, existing component mappings and remark plugins. A deterministic generator awaited by the default-exported Next config keeps `scripts.build` exactly `next build`; provide declarations for generated imports so a fresh checkout can typecheck. Verify actual article/detail routes and client navigation on the Worker, not just its landing page.\n\n- A dependency that loads WebAssembly through Node filesystem APIs needs a runtime-compatible entrypoint. Prefer its existing `workerd` conditional export, or a small package adapter that statically imports the same pinned `.wasm` modules for Workers and retains the Node entrypoint for local use. Preserve upstream licenses and validation. The shared pipeline carries validated Workers-compatible JavaScript, MJS and WASM modules for plain functions Workers, Vite edge companions, TanStack Start edge and Next.js. Auxiliary modules are at most 5 MiB each, and the Worker/artifact budgets still apply; do not turn them into arbitrary public assets or replace a failing decoder with an always-successful result.\n- Customer-owned custom domains use the normal Paid-domain flow. Native addons and non-functional Workers Node APIs remain typed blockers; the Node proxy filename alone is not a blocker.\n\n### Next.js private SSG cache\n\nPages rendered during the Next.js build use an immutable cache carried with that deployment.\nOrdinary request-time rendering and route handlers remain available. Cache payloads are private:\ndirect HTTP requests to the reserved cache paths must return 404. Verify both page rendering and\nthat private cache payloads cannot be downloaded from the deployed origin.\n\nThe private cache defaults to **32 MiB**. CLI/MCP **0.1.28 or later** accept optional\n`build.ssg_cache_max_mib` in `ohmyhost.yaml`: an integer from **1 to 64**, measured in MiB. Keep the\nexisting build settings and add this field only when choosing a different capacity, for example:\n\n```yaml\nbuild:\n install: pnpm install --frozen-lockfile --ignore-scripts\n command: pnpm run build\n output: .open-next/assets\n ssg_cache_max_mib: 48\n```\n\nUse the repository's own package manager and existing commands. A larger private cache does not\nincrease the separate **25 MiB** public-asset allowance. Public assets, private cache, Worker\nmodules, migrations and archive metadata together must still fit **30 MiB compressed and 64 MiB\nexpanded**. Leave room for the other archive contents when selecting the cache capacity: setting\n`ssg_cache_max_mib: 64` does not guarantee that 64 MiB of cache fits alongside the rest of the\ndeployment. Read the named limit in a failed build: increase this setting only for the private SSG-cache limit;\nreduce output when a different limit is exceeded. Commit and push the configuration change,\nthen plan and deploy that new commit.\n\nCloudflare Static Assets have no cache storage charge: provider storage cost **0 × 1.5 = 0 credits** on\nohmyho.st. Choosing a larger capacity does not add a storage fee. Build compute and Worker\nrequest/CPU charges remain unchanged; a cached page can still execute Worker code. See\n[Cloudflare Static Assets billing](https://developers.cloudflare.com/workers/static-assets/billing-and-limitations/).\n\nThis cache is refreshed by a new deployment. Time-based or on-demand revalidation is unsupported;\nCache Components follow the rules below. Preserve existing static pages; do not force every page to\nrender dynamically to work around a platform packaging error. If the build log begins a diagnostic\nwith `ohmyho.st platform:`, report its operation ID through feedback. An `ohmyho.st packaging:`\nline identifies an output limit or a Next.js cache rule the application can address. A prerendered\npage whose URL path is longer than about 95 bytes may not fit the deployment archive; that line then\nnames the page: shorten its URL or render it on request.\n\n### Next.js Cache Components\n\nNext.js 16 Cache Components build and run here, also with `partialPrefetching: true`. A page's\nprerendered static shell comes from the private SSG cache, and its Suspense holes render for each\nrequest. `\"use cache\"` directives are admitted, and so are these `next/cache` functions imported\nor re-exported by name: `cacheLife`, `cacheTag`, `unstable_cacheLife`, `unstable_cacheTag`, `io`,\n`navigation`, `prefetch`, `refresh` and `unstable_noStore`. These rules apply:\n\n- The cache is read-only: request-time `\"use cache\"` values are never stored, so a later request\n computes them again. Concurrent identical calls in one isolate may share one computation, so a\n cached function must depend on request data only through its arguments.\n- A `\"use cache\"` value in a static shell stays frozen until the next deployment. Every built-in\n `cacheLife` profile, including the default, revalidates, so the build refuses such a value unless\n it uses `cacheLife({ revalidate: Infinity, expire: Infinity })`. Otherwise call the cached\n function inside a Suspense boundary after reading request data (for example `await connection()`).\n- Every other `next/cache` use is refused, including `revalidateTag`, `revalidatePath`,\n `updateTag`, `unstable_expireTag`, `unstable_expirePath`, `unstable_cache`, default and\n namespace imports and `export *`. So are `res.revalidate`, fetch `next.revalidate` values other\n than `0` or `false`, and time-based revalidation of prerendered pages and route handlers.\n- Each dynamic page needs a static shell: read `params` inside a Suspense boundary, without\n `ensureStatic` and without a root layout inside a dynamic segment. With\n `partialPrefetching: true`, dynamic pages must not use `generateStaticParams`. Otherwise Next.js\n prerenders the page while a visitor waits, and Workers stop that request and later requests for\n the same page.\n- The runtime caps `experimental.maxPostponedStateSize` at 25 MB and keeps a smaller value.\n\nA refused build ends with one fixed `ohmyho.st packaging:` line that names the fix; apply it, then\ncommit, push and deploy the new commit. These lines begin:\n\n- `Next.js on-demand or time-based revalidation is not supported by this read-only hosting cache.`\n- `Next.js time-based revalidation of prerendered pages and route handlers is not supported by this read-only hosting cache.` (without Cache Components)\n- `Next.js time-based revalidation of prerendered pages and route handlers is not supported by this read-only hosting cache: a revalidate setting or a \"use cache\" value in a Cache Components static shell would stay frozen until the next deployment.`\n- `Next.js Cache Components pages that are prerendered while a visitor waits are not supported by this hosting:`\n\n## Completion\n\nVerify each requested capability through the customer's supported interfaces and the protected Dev application. Run promotion/rollback/deletion only within their authorized scope; repeated deletion and absence proofs are for explicitly disposable acceptance projects. A root HTTP `200` alone does not prove application authentication or other required flows.\n",
|
|
65
|
+
text: "# Portable application contracts\n\nUse this reference only when implementing framework or capability code. Product decisions come from `ohmyhost init` and the public CLI, not from provider examples.\n\n## Source and compatibility\n\n- Pin exactly one of `npm`, `pnpm`, `yarn`, or `bun` in `packageManager` and commit exactly one matching frozen lockfile. The direct build commands are `npm run build`, `pnpm run build`, `yarn run build`, or `bun run build`. Bun is pinned to `1.2.22`. Installs are frozen and skip lifecycle scripts (`--ignore-scripts`, or Yarn `--mode=skip-build`); commit generated inputs instead of depending on postinstall. Yarn installs with the `node-modules` linker even when `.yarnrc.yml` selects Plug'n'Play.\n- Supported framework config extensions are `.js`, `.mjs`, and `.ts`.\n- Next.js, Vite and TanStack Start need at least their security floor, with no upper bound: Next `16.4.1` and later (every Next.js 15 release and Next.js 16.4.0 and older are refused since the security advisories of 22 October 2026), Vite `5.4.6`, TanStack Start `1.168.60`; prereleases and older majors are refused. The version that `package-lock.json` (v2/v3) or `pnpm-lock.yaml` (v6/v9) resolves decides, otherwise the exact version or `^`/`~` range in `package.json`; a range spanning the floor is `pending` until the build checks the installed version. Passing this check does not guarantee that a later release builds; OpenNext, for example, refuses a Next major it does not support yet. 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 deployment that is already live keeps running, but it stays exposed to the advisories of 22 October 2026 until you rebuild it on a fixed release.\n- `package.json` scripts.build is exactly `next build` or `next build --webpack` for Next.js. Vite and TanStack Start use `vite build`, optionally with one `tsc`, `tsc --noEmit`, `tsc -b` or `tsc --build` stage before or after it joined by `&&`. Arbitrary preparation, `cd`, traversal and lifecycle hooks are outside this build contract. Builds use Node.js 24 and stop after eight minutes; runtime secrets are absent from the build. Commit intentional public build values. Gzipped Worker limits are 10 MiB for Next.js and 5 MiB for other Workers. Ordinary public static assets are limited to 10 MiB per file and 25 MiB/1,000 files in total; a static runtime also needs `index.html`. The complete deployment archive, including public assets, private SSG cache, Worker modules, migrations and archive metadata, is at most 30 MiB compressed and 64 MiB expanded.\n- The platform overlay, not the customer repository, pins OpenNext `1.20.9` and Wrangler `4.125.0`. Do not commit those packages, generated Wrangler files, platform bindings, or `OHMYHOST_BASE_PATH` for hosting.\n\n## The customer chooses application authentication\n\nHosting does not automatically add end-user authentication. The customer or their agent integrates the chosen library/service into the application and owns its user flows, authorization and provider account/configuration. This is separate from WorkOS authenticating the customer to ohmyho.st, `ohmyhost login`, `OHMYHOST_TOKEN`, GitHub consent and protected Dev browser access. Never reuse ohmyho.st's WorkOS tenant, platform keys or agent token for an application's users.\n\nThe supported application-auth interfaces include [Better Auth](https://better-auth.com/docs/installation) and customer-owned [WorkOS AuthKit](https://workos.com/docs/authkit/). Better Auth has retained integration proof; hosted WorkOS Next.js has proof, while other framework/provider combinations need their own hosted evidence. Other OAuth/OIDC integrations must satisfy the actual browser, server, secrets and egress contracts and pass their application tests; a declared origin does not establish complete provider support. Public applications need no auth. Keep other existing customer choices; framework/runtime capability checks apply equally to all dependencies. Use the provider's current first-party SDK/guide for the actual browser or server runtime. Browser integrations use their documented public client identifiers and origin/callback settings; do not request or expose server API/client secrets in a Vite browser bundle. Server integrations use only customer-owned server runtime secrets.\n\nThe existing structured platform auth integration accepts `none` or pinned `better-auth`. `none` disables only that managed integration; it does not mean that the application has no login. Do not invent `auth.provider: workos` or another unsupported configuration field. Init reports `application-auth-review` for recognized, unselected auth SDK evidence without enabling managed database/auth/mail. Selecting the optional managed Better Auth integration requires explicit `auth.provider: better-auth` and `database.enabled: true`, with Better Auth pinned exactly to `1.7.7`. Managed mail is optional for verified-user/session/token flows. The private bridge uses ohmyho.st Mail for verification/reset sends; those calls need `mail.enabled: true`, Paid access and the customer's verified sender. For a custom Resend callback, preserve the application-owned Better Auth handler with `auth.provider: none`; that choice disables only the managed bridge, not application login. CLI 0.1.24 may incorrectly return `managed_auth_mail_required`; use the corrective 0.1.25 release or later and preserve the selected auth and sender. An ordinary Better Auth or mail SDK dependency selects no managed capability. A detected mail SDK is also evidence to review, not consent to enable Paid platform mail. Database migration files are separate evidence and do not identify an auth provider. Source admission does not reject SDKs by vendor name; the actual runtime, egress, migration and artifact contracts still apply. Older installed clients/platforms may report `better-auth-conversion` or `supabase_migration_required`: discover/update the installed release and report its limitation instead of treating it as consent to replace auth or delete users.\n\nThe released password-first `auth.provider: omh-native` path is restricted to projects approved by the existing launch allowlist and explicitly selects an Omh-operated, headless end-user directory. It requires Paid access, the customer's own verified sender authorized for the project, and a verified application identity on the same registrable main domain as that sender, plus a canonical origin; read readiness before claiming activation. Social sign-in is disabled server-side for this launch; do not offer Google or GitHub sign-in as available. It uses a separately metered project Auth database and separate Dev/Prod directories, even when business data is shared. Native alone does not require a business database or turn static Vite/Astro into a server; static files remain public. The app owns login screens and authorizes its business records after server-side session validation. Native reserves `/api/auth/v1`; detected literal route conflicts must move, and dynamic routes under that namespace are shadowed. Keep an existing customer provider unless the customer chooses otherwise, and never copy users or provider secrets between environments. Source deployment declares capability; an Owner pause survives redeployment. Configuration and user administration are Owner-only. Set the approved canonical origin and application-owned form paths through Owner configuration; `primary` selects the primary origin. The default allowed redirects are `/`, `/login`, `/reset-password` and `/accept-invitation`; reset and invitation paths must belong to the selected allowlist. Paths are local and contain no query, fragment, escapes or dot segments. Changing origin fences existing sessions and pending flows. An unavailable runtime is an error, not proof that a user is signed out. Native management commands and tools require CLI and MCP 0.1.50 or newer. Verify the approved project's readiness and real hosted flow; this release does not qualify every project, environment or lifecycle operation. General availability, performance, recovery and social qualification remain open.\n\nFor browser signup, verification and password-reset mail operations, create an operation key before the first call with `createNativeAuthIdempotencyKey()` and pass it as `headers: { 'Idempotency-Key': key }` in the Better Auth fetch options. Retain the same key for a retry after an uncertain result; do not automatically retry a mutation with a new key. The SDK may generate a fallback, but does not return that generated key after a lost response, so a caller cannot safely recover it later.\n\n`BETTER_AUTH_SECRET` and `BETTER_AUTH_URL` are reserved for the platform-managed integration and cannot be set through the customer secret command. An application that owns its Better Auth setup uses its own secret name, for example `APP_AUTH_SECRET`, and maps that value explicitly to Better Auth's `secret` option. Use an application-owned `APP_URL` for its `baseURL`; it must match the trusted Dev/Prod origin. Do not enable managed auth merely to acquire that name or weaken the reserved-name check. Keep platform-owned credentials and application-owned auth configuration separate.\n\nFor the agent's feedback, state:\n\n- The customer's chosen/existing auth system, whether it runs in the app or externally, and the evidence for runtime/SDK compatibility. Preserve the choice unless the customer authorizes a change.\n- Missing configuration: customer-owned provider tenant/project, exact Dev/Prod login/callback/logout origins, necessary egress destinations and required secret **names**. Use the normal secret handoff for values: the CLI stdin command locally, or, from a native chat, a `secret_input_request` link for an application secret. Public client IDs/publishable keys are different from private API keys; follow that provider's documentation.\n- Who stores users/sessions and who sends verification/reset mail. An external provider's mail does not automatically need ohmyho.st mail or Paid access. The managed Better Auth bridge requires its database and has a platform sender for email actions; verified-user/session/token flows need no sender. Keep a custom Resend callback in the application-owned Better Auth handler. Only calls through managed ohmyho.st Mail need its capability, Paid access and verified sender; missing sender for that capability is `mail_domain_required`. Report that dependency instead of removing email verification.\n- What was tested: sign-in, callback, authenticated and forbidden access, session handling and sign-out on the real application. For isolated environments, keep auth configuration/sessions/data isolated and register both callback origins; promotion must not copy Dev users or private credentials to Prod.\n- The specific blocker or next action. Keep “customer configuration missing”, “runtime incompatible” and “not yet verified” distinct in the explanation; these are explanatory categories, not new API error codes. Report a suspected platform limitation through feedback, without credentials or user records.\n\nExample feedback: “This app uses your WorkOS AuthKit account. The ohmyho.st CLI login is separate. Configure this app's Dev/Prod callback URLs and the listed server-secret names. End-user login is not verified until the deployed callback and protected-route tests pass.”\n\n## PostgreSQL and Auth\n\n- Access to ohmyho.st-managed PostgreSQL uses `OHMYHOST_DATABASE`. Database-provider management, direct migration credentials and managed connection URLs are platform-private. This does not forbid a customer's compatible auth SDK from calling their own external identity provider.\n- Call the database with `createPrivateDatabaseClient` from `@ohmyhost/customer-runtime`; see [database-runtime.md](database-runtime.md) for the working example, what is not available and what it costs. There is no connection string or customer socket; interactive transactions use the bounded `withConnection(callback)` scope. Keep canonical expand-only migrations under the path reported by init.\n- A held scope allows two concurrent transaction/auth connections per physical data area (shared Dev/Prod share them), 100 statements, 30 seconds total and five seconds idle. Close it before storage transfers, email, AI calls or other network waits. An adapter for an existing acquire/close port must release the underlying scope in `finally`; never keep a request-wide transaction open while processing a file.\n- An optional Kysely `0.29.5` dialect is available from `@ohmyhost/customer-runtime/database-kysely`: `createPrivateDatabaseDialect(env.OHMYHOST_DATABASE)` for `new Kysely({ dialect })`. Its `db.transaction().execute(async trx => ...)` uses the actual leased connection with BEGIN/COMMIT or ROLLBACK and close; the same bounds apply. For application-owned Better Auth `1.7.7`, use its own migrated schema, normally `public`: `database: { db: db.withSchema(\"public\"), type: \"postgres\", transaction: true }`. This is separate from the managed auth-lane bridge; `auth.provider: none` provisions no auth tables. The library transaction path is tested; hosted signup/mail still needs application verification. This does not claim Drizzle transaction support.\n- SQL `DATE` and `date[]` elements retain the `YYYY-MM-DD` wire string. They are calendar days, not timezone-bearing JavaScript dates; preserve existing timestamp decoding and normalize only the field that the application's contract requires.\n- When the customer selects the verified Better Auth integration, it owns schema `auth`, UUID IDs, `/api/auth`, secure host-only cookies, database sessions, verification/reset callbacks, and session revocation. Mail is selected separately; preserve the customer's configured sender. Authorization remains explicit in each use case.\n- Read hosted secrets/bindings from the framework context: for Next.js on this runtime, `getCloudflareContext({ async: true }).env`. Keep an explicit local test adapter where useful, but do not activate a localhost-only test factory in production or trust the request Host header as configuration. Check the session, tenant and application repositories through the same hosted database adapter as health.\n- A newly isolated database can have correct tables but no tenant, administrator or application configuration. Use the application's existing, owner-authorized bootstrap/seed path with its real password hashing and tenant/membership rules. Keep this as a controlled one-off script or existing private administrative workflow; do not add a public bootstrap route or a production test-mode switch. Verify login after bootstrap, without copying Dev users into Prod.\n\n- For interactive work a customer can issue a time-bound direct PostgreSQL login with `ohmyhost database access create` / MCP `database_access_create` (mode `read` or `write`, 5 minutes to 24 hours, at most three active per environment) and open it with `ohmyhost database psql`. The connection URI and `psql` command are returned exactly once: use them immediately, never store or commit a connection string or password, and revoke the credential when finished. Such a login can never change schema and row-level security still applies; application code keeps using `OHMYHOST_DATABASE`.\n\nApplication background: [Better Auth PostgreSQL](https://better-auth.com/docs/adapters/postgresql). Customer code uses the scoped database binding; provider connection examples do not replace it.\n\n## Files, mail, functions, and secrets\n\n- Import the storage client from `@ohmyhost/customer-runtime/storage`. The Storage Gateway owns raw R2 bindings, signed operations, quotas, receipts, and cleanup. `storage.jurisdiction` accepts `us` or `eu` and must match the project's recorded R2 bucket jurisdiction, including an existing bucket reservation. Existing buckets retain their location through a database or project-region migration; do not rewrite their configuration to the current project region. For a project without a recorded bucket or reservation, the chosen project region is used (`--region`, default `us`). A mismatch fails the plan with `storage_jurisdiction_conflict`. 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. `ohmyhost init` is offline: it writes `us` unless `--region eu` is supplied and preserves an existing configuration. Preserve established storage configuration when resuming a project; use its chosen region only when preparing new storage.\n- Upgrade the CLI/MCP to the current published version first. Then install the runtime under its import name as the exact npm alias that init reports under `runtime.packages.customerRuntime` for every framework; `companion.packages.customerRuntime` and `worker.packages.customerRuntime` are equivalent fields: `\"@ohmyhost/customer-runtime\": \"npm:@amerged/ohmyhost-runtime@<version>\"` in `package.json`. Refresh and commit the lockfile with the project's package manager. Every `@ohmyhost/customer-runtime/*` import stays unchanged. A bare `@ohmyhost/customer-runtime` version fails the platform build, and an `https://ohmyho.st/releases/…` runtime URL stops resolving once a later release replaces the website downloads, so replace such a URL with the alias. Use the version init reports; older runtime versions carry no compatibility promise.\n- A storage-enabled deployment needs four runtime values and **no** `FILES` bucket binding: the plain values `OHMYHOST_STORAGE_GATEWAY_URL`, `OHMYHOST_PROJECT_ID`, `OHMYHOST_ENVIRONMENT_ID`, and the secret `OHMYHOST_STORAGE_KEY`. Resolve them from the server framework context or Worker request and use `(request) => fetch(request)` for both `fetch` and `capabilityFetch`. A legacy customer gateway binding may remain attached until a later platform release removes it; that transport is unsupported. Invalid options answer non-retryable `gateway_configuration_invalid` naming the option; the clients run only on the server.\n- The gateway hop is global `fetch` to `OHMYHOST_STORAGE_GATEWAY_URL`: the egress policy handles the injected origin and forwards supported fixed POST routes to the private gateway. The origin is allowed automatically, does not count among the 13 customer origins, and never goes in `runtime.egress.allow`. Signed R2 URLs use global `fetch` too. Non-retryable 403 `gateway_not_enabled` means enable the capability and redeploy; 403 `gateway_route_denied` means use the current client's supported route, method and query. Neither is fixed by editing the allow-list. Mail and storage share a project-keyed Cloudflare limiter of 1,200 calls per minute per Cloudflare location; it is approximate rather than a globally strict quota. 429 `gateway_rate_limited` and transient 503 `gateway_unavailable` are retryable with a delay and the original idempotency keys. Malformed/non-JSON answers are `gateway_response_invalid` with the received status and no upstream text.\n- Files are create-once logical names within an immutable data area: shared Dev/Prod use one namespace, isolated environments use separate namespaces. Each name is at most 512 UTF-8 bytes; slash-separated segments start/end with letters or digits and contain only letters, digits, `.`, `_` and `-`, with no traversal. Idempotency keys are 1–128 letters/digits/`._:-`. Content type is a lowercase `type/subtype` without parameters. Uploads and the physical bucket quota are at most 1 GiB, including open reservations; signed capabilities last five minutes. A deletion never makes a used name reusable inside that identity. A reset creates a fresh namespace, so the same logical name can be used again. `storage_quota_exceeded` needs deleting unneeded objects or a smaller upload; `storage_conflict` needs resolving the original name/key receipt rather than retrying a conflicting payload. `storage_upload_expired` needs a new objectKey and idempotencyKey. `storage_delete_pending` means wait until retryAt, then repeat the original deletion; `data_change_in_progress` means observe that operation; `storage_binding_data_changed` means this environment's data area changed during the request: read its current state and issue a new request. After an isolate or reset, redeploy the affected environment before relying on its files: until then a previous deployment follows the environment's current file area while its database calls already fail. Data assignment changes retain exact existing locators, without moving blobs.\n- Store a file with `upload` (or `reserveUpload` → PUT to the signed URL → `completeUpload`) and serve it with `createSignedRead`; `deleteObject` releases quota. If `ohmyhost.yaml` declares storage while no source calls `createPrivateStorageClient`, init blocks `storage_client_missing` instead of reporting a clean deployment that fails at the first upload. After uncertain completion, repeat `upload()` with the same bytes, objectKey and idempotencyKey: a create-only PUT's 412 completes the existing reservation. `storage_upload_rejected` is retryable only for 5xx or 429. For recoverable multi-object work, persist reservation/transfer IDs and cleanup progress before transfers; call `completeUpload(transferId)` to reconcile.\n- `readMany({ objectKeys })` reads 1–4,000 distinct logical keys through one streaming gateway call. Iterate every result and consume or cancel each found body before the next item; `not_found` is explicit. Completion requires the final frame and exact EOF. Malformed, oversized or truncated framing fails as `gateway_response_invalid`; an aborted read is not a completed batch. `deleteMany({ objects: [{ objectKey, idempotencyKey }] })` accepts the same key limit and distinct per-object keys. Its ordered outcomes are completed, pending with retryAt, or failed with code/retryable. Control JSON/framing is at most 4 MiB, independent of streamed payload bytes. Keep an application manifest for selection/pagination; there is no bucket-list API. Persist outcomes and replay only pending/retryable items with their original keys.\n- A completed deletion requires verified payload erasure; an accepted request, elapsed upload link or old absence receipt alone does not release quota. Prior conditional uploads cannot restore a completed file. Large or interrupted batches may acknowledge only a subset: retain all keys and retry pending/retryable items. `deleteMany` applies one configured deadline across headers and complete JSON; timeout does not undo durable per-file progress. Binary `readMany` keeps its bounded per-frame timeout and requires complete iteration.\n- When the browser's CSP permits only same-origin connections, upload through an application route instead of exposing a provider URL or weakening CSP. Authorize an opaque, short-lived capability against the real upload reservation, tenant, MIME, byte length and expiry. Keep the platform storage key and signed R2 URLs server-side. Bound bytes while reading; an identical completed PUT retry may return success without writing again, while different bytes must conflict.\n- For inspected private files, use a distinct staging key per capability and an immutable final key. Read the staged ETag conditionally, inspect those bounded bytes, write the final object and verify its metadata before committing the application record. Keep staging through the commit; use a durable, bounded cleanup journal afterward, fenced against active upload/processing work. A comment or an unconsumed outbox event is not a running cleanup path. Original downloads remain authorized, conditional and bounded; a signed GET URL is not a signed HEAD capability.\n- Set upload limits to the smallest limit across ingress, storage, content inspection, downstream APIs and response handling. Reuse one application policy in browser hints, routes and provider adapters; a successful storage upload does not prove the parser or AI service can accept it. Preserve truthful decoder and validation failures.\n- Use authenticated same-origin framework routes for bounded request work. Vite may use the companion source contract returned by init; TanStack Start and Next.js retain their native server routes/functions. A project without a web framework sets `runtime.mode: functions` and writes `src/ohmyhost/worker.ts` as a module Worker: `export default { async fetch(request, env, ctx) {…}, async scheduled(controller, env, ctx) {…} }`. It keeps `build.install` only and receives the same database, files, mail, secret and egress bindings as an edge app.\n- Declare scheduled work only through `functions.crons` in `ohmyhost.yaml`: one to eight unique five-field UTC crons with a five-minute minimum. A field is `*` or one canonical integer; minute is an integer or `*/5` through `*/59`, never bare `*`. Sunday is `0`; ranges, lists, names and shortcuts are refused. Day-of-month and weekday must both match when both are fixed. The handler is the `scheduled(controller, env, ctx)` member of the **default export** of `src/ohmyhost/worker.ts` (functions runtime, Next.js, TanStack Start) or of the Vite companion `src/ohmyhost/companion.ts`. Named exports are never invoked; init blocks `worker_module_default_export_required` and `scheduled_handler_required` with the file path. The platform runs one attempt per cron and UTC minute with a 120-second deadline, retries a thrown error or platform failure up to three attempts, and honors `controller.noRetry()`. Retries retain the original `scheduledTime`, so make side effects idempotent. Dev and Prod schedules follow their own active deployments and can both write the same shared data; decide which owns a task. Finished runs are retained seven days. Each run is billed as one request plus its CPU credits. Runs are visible through `ohmyhost function runs` / MCP `function_runs_list` (per environment, newest first), not through deployment logs.\n- `src/ohmyhost/worker.ts` is bundled by the platform on its own, outside the framework build: tsconfig `paths` resolve, but Vite-only aliases, framework virtual modules and modules that declare TanStack Start server functions or Next.js route handlers do not. Import plain application modules (repositories, storage and database clients from `@ohmyhost/customer-runtime/database`, `/storage` or `/mail`) and keep framework entry code out of the Worker module.\n- HTTP and scheduled customer Workers have 50 ms CPU and ten subrequests per invocation. Network waiting does not consume CPU, but database, gateway and signed-object calls consume requests; batch and page work accordingly. A runtime limit answers HTTP 429 beginning `Runtime limit exceeded`. A functions module must always export a default `fetch`, including a schedules-only application. Init blocks `functions.crons` on a static runtime as `runtime_mode_unsupported`.\n- Select a small work batch and per-call timeouts that fit the 120-second scheduled deadline. Persist claims, retry state and cleanup progress before acknowledging work; close database scopes before provider calls. Import narrow runtime modules rather than barrel files that pull Next.js routes into the scheduled bundle. Verify that a due run actually completes through `function_runs_list`.\n- For LLMs, images, speech, classifiers and OCR through the ohmyho.st AI gateway, declare `ai: { enabled: true, jurisdiction: eu | any }` in `ohmyhost.yaml` (the jurisdiction has no default) and redeploy. The platform injects `OHMYHOST_AI_BASE_URL` (the gateway origin plus `/v1`) and the secret `OHMYHOST_AI_KEY` into the Worker; use an OpenAI-compatible SDK with those two values from server code only. The gateway origin is allowed automatically and never listed in `runtime.egress.allow`. The gateway authorizes the key against the active revision's plan, so a rollback or promotion without `ai` stops the calls. A static runtime refuses `ai` as `runtime_mode_unsupported`. Calls answer `402 paid_plan_required` on a Free workspace; see [the AI gateway reference](ai.md) for the error codes.\n- For ohmyho.st-managed transactional mail, use `createTransactionalMailClient` from `@ohmyhost/customer-runtime/mail` with `OHMYHOST_MAIL_GATEWAY_URL`, `OHMYHOST_MAIL_KEY`, `OHMYHOST_PROJECT_ID` and `fetch: (request) => fetch(request)` from server code. The egress policy delivers `POST /v1/send` to the private gateway; the injected origin is allowed automatically and never listed. A send can take several seconds: `timeoutMilliseconds` defaults to 10000, accepts 100–30000, and bounds response headers. Keep the same message and idempotency key after uncertainty. A missing URL or key is a configuration error. Enable the capability and redeploy for `gateway_not_enabled`; use supported routes for `gateway_route_denied`; report persistent gateway faults with `feedback_submit`. Legacy customer mail gateway bindings are unsupported. Do not call mail or storage from health routes: the deploy-time probe has no egress policy. Verification/reset mail owned by an external identity provider stays with that integration. The mail runtime values exist only when deployed source sets `mail.enabled: true`; a verified domain alone does not install them, and promotion/rollback of undeclared source removes them. Install application-owned private values through stdin-based CLI commands; source lists secret names, never values. See [mail.md](mail.md) for sending and receiving; runtime attachment downloads are currently unsupported.\n\nResolve `env` from the hosted server request. This complete file example uses the same global\ntransport for gateway calls and signed-object bytes; keep both the key and signed URLs server-side.\n\n```ts\nimport { createPrivateStorageClient } from \"@ohmyhost/customer-runtime/storage\";\n\nconst storage = createPrivateStorageClient({\n endpoint: env.OHMYHOST_STORAGE_GATEWAY_URL,\n key: env.OHMYHOST_STORAGE_KEY,\n projectId: env.OHMYHOST_PROJECT_ID,\n environmentId: env.OHMYHOST_ENVIRONMENT_ID,\n fetch: (request) => fetch(request),\n capabilityFetch: (request) => fetch(request),\n});\nawait storage.upload({\n idempotencyKey: \"example-upload-1\",\n objectKey: \"checks/example.txt\",\n contentType: \"text/plain\",\n bytes: new TextEncoder().encode(\"hello\"),\n});\nconst download = await storage.createSignedRead(\"checks/example.txt\");\nconst response = await fetch(\n new Request(download.url, {\n method: download.operation,\n headers: download.requiredHeaders,\n redirect: \"manual\",\n }),\n);\nif (!response.ok) throw new Error(\"File download failed\");\nconst contents = await response.text();\nawait storage.deleteObject({\n idempotencyKey: \"example-delete-1\",\n objectKey: \"checks/example.txt\",\n});\n```\n\nChoose a fresh logical key for each real file and durable idempotency keys for its upload and\ndeletion. For business data, persist the file/transfer identity before network work and close\ndatabase transactions before any transfer.\n\n## Source capability declarations\n\nPreserve an existing `ohmyhost.yaml`. Init detects SQL migrations under `postgres/migrations` or `supabase/migrations`, R2 declarations/bindings and `supabase/storage`; this is evidence to review, not permission to migrate an external service. A database declaration has `enabled: true` and its `migrations` path; files use `storage: { provider: r2, jurisdiction: us|eu, public: false }`. Static Vite/TanStack builds serve assets; Next.js, server TanStack and Vite companions use edge mode; plain Workers use functions mode. A runtime mismatch or `auto` fails planning as `framework_conversion_required`. Enable only actual application capabilities.\n\nServer egress uses `runtime.egress.allow`: at most 13 exact HTTPS customer origins, without trailing slash, paths, credentials, ports, IP literals or local names. The platform adds mail, storage-gateway and R2 origins itself; they are never listed and do not count against the 13. An omitted customer origin answers 403 `Egress denied`; a redirect answers 502 `Egress upstream unavailable` (304 is retained). Call the final destination and declare its origin. Customer Workers cannot open sockets. Browser origins below are a separate declaration and do not authorize server fetches.\n\nThe runtime does not inject a public app URL or an environment-name variable. Declare application-owned origin secrets separately for Dev and Prod. Only managed Better Auth receives its reserved platform base URL. Secret names must be uppercase letters/digits/underscore, begin with a letter and be at most 128 characters; `OHMYHOST_*` and the fixed platform names are reserved for set.\n\n`runtime.healthcheck` defaults to `/`, is a path without a query, and must answer a cookieless GET with 2xx directly within five seconds. An authenticated redirect fails. Health is a minimal public response without secrets; success does not prove other flows.\n\n## Browser media and microphone\n\nVideo/audio playback and microphone access are blocked by default. Server-rendered HTML can opt in with exactly these lines in `public/_headers` and a redeployment; static asset pages cannot opt in. Anything broader is ignored, and camera and geolocation stay blocked.\nMedia loads only from the application's origin or blob URLs; stream stored media through an\nauthorized application route instead of embedding a signed R2 URL.\n\n```text\n/*\n Content-Security-Policy: media-src 'self' blob:\n Permissions-Policy: microphone=(self)\n```\n\n## Browser security (CSP)\n\nNext.js and TanStack Start HTML run under nonce CSP: `script-src 'self' 'nonce-…'` and `style-src 'self' 'nonce-…'`, without unsafe-inline. Each request carries a fresh `x-nonce`; the server wrapper adds it to every rendered script/style tag, including hand-written blocks. Inline `style=\"\"` attributes (also inside SVG) and event handlers stay blocked. Move them to CSS/listeners. Tags created later in the browser need the current nonce (the wrapper supplies a `csp-nonce` meta tag); an unnonced tag is blocked. Vite/static pages get no automatic script/style nonces and permit no inline code. Check the deployed response's Content-Security-Policy and actual browser behavior.\n\nDeclare only the browser capabilities the application uses in the committed configuration:\n\n```yaml\nruntime:\n mode: edge\n browser:\n connect: [https://api.customer.example, wss://events.customer.example]\n scripts: [https://cdn.customer.example]\n styles: [https://cdn.customer.example]\n images: [https://images.customer.example]\n fonts: [https://fonts.customer.example]\n frames: [https://embed.customer.example]\n frameAncestors: [https://portal.customer.example]\n workers: { self: true, blob: false }\n cors:\n origins: [https://portal.customer.example]\n credentials: false\n```\n\nOmitted groups retain restrictive defaults. Each origin group permits at most sixteen distinct exact HTTPS origins; only `connect` also accepts WSS. No wildcards, paths or script/style inline/eval opt-out exist. Resource groups extend their matching CSP directive; worker flags allow only self/blob. `frameAncestors` controls who may embed the app; `frames` controls what it embeds. Application CORS must echo the actual declared origin and its own allowed methods/headers. Cross-origin requests with cookies also require explicit `credentials: true`; undeclared CORS headers are stripped. Same-origin non-GET requests still require matching Origin when cookies are present. Static pages have no default connect permission; use a declared browser destination or an edge companion for application API work. Verify required browser flows, including OAuth callback/pop-up behavior, on the deployed app: these tested declaration interfaces do not prove arbitrary providers or exported Lovable apps work.\n\nFrames, workers and external resources stay blocked when undeclared; service workers, eval/WASM execution and sockets have no general support promise. Do not change CSP to fake a decoder or authentication success.\nThe gateway replaces application CSP and drops Set-Cookie with a Domain attribute; keep session\ncookies host-only. OAuth form-post callbacks must satisfy the explicit CORS/origin policy;\nverify the provider's chosen callback mode instead of assuming a redirect proves a session.\n\n## Framework notes\n\n- Vite static applications need no server companion. With or without a companion, an unknown file path answers `index.html` with HTTP 200, so client-side routing reloads work; render not-found in the client router. Keep Vite `base` unset or `/`. Add the returned companion source only when the application uses database, Auth, mail, files, request functions, or schedules.\n- The functions runtime is a plain Worker module without a framework: `runtime.mode: functions`, `build.install` only, no `build.command` or `build.output`, HTTP through `fetch` and schedules through `scheduled`. Do not add customer Wrangler configuration.\n- TanStack Start uses its native server routes/functions. Import `tanstackStart` from `@tanstack/react-start/plugin/vite`. Static mode needs `tanstackStart({ prerender: { enabled: true } })` and no Cloudflare plugin; edge mode calls `cloudflare({ viteEnvironment: { name: \"ssr\" } })` before `tanstackStart()`. Unknown paths return 404. Keep an active TanStack Start Vite plugin; do not add a customer Wrangler file or platform base path.\n- Next.js Workers builds use the platform OpenNext 1.20.9 overlay and Webpack, including `proxy.ts` Node middleware. The service supplies `--webpack` for Next 16 and later; Next 15 already uses Webpack and does not accept that option; customers do not need to rename middleware or add platform tools/configuration. A committed `open-next.config.ts` is ignored because ohmyho.st writes its own OpenNext configuration, but two `open-next.config` variants (`.js`, `.mjs`, `.ts`) are still refused as `framework_config_ambiguous`; keep at most one. Custom loaders must support Webpack; a Turbopack-only configuration is not evidence of a compatible Workers build. Keep route handlers, RSC/SSR, assets and images framework-native. Use a default-exported next.config, without `output: standalone`/`export` or non-root `basePath`/`assetPrefix`, and pin exact React and react-dom versions. Cache Components (`cacheComponents: true`, also with `partialPrefetching: true` as `create-next-app` 16.4 writes them) build within the Cache Components rules below. CLI 0.1.32 and older still refuse them in `ohmyhost init` as `next_cache_components_unsupported`; upgrade the CLI and MCP instead of removing them. Unknown paths return 404.\n- The Webpack build ignores Turbopack rules. A next.config that names the loader `@tailwindcss/turbopack` is refused as `next_tailwind_turbopack_only` unless the application root has a PostCSS configuration that Webpack reads: a `postcss` object in package.json, `.postcssrc.json`, `postcss.config.json`, `.postcssrc.js` or `postcss.config.js`/`.mjs`/`.cjs` (not `postcss.config.ts`, and not a file in a parent directory). `create-next-app` 16.4 with Tailwind CSS, its `--yes` default, writes only that Turbopack rule, so the site would deploy without its Tailwind classes. Add `postcss.config.mjs` to the application root with `export default { plugins: { \"@tailwindcss/postcss\": {} } }` and remove the Turbopack rule. Use the package manager to add `@tailwindcss/postcss` to devDependencies at the same version as `tailwindcss` and to remove `@tailwindcss/turbopack`, so the lockfile records both; then commit, push and plan the new commit. An app created with `--no-tailwind` needs no change.\n- Next.js server files must be included in its output-file traces. The platform carries traced JSON, text and other non-executable inputs in the Worker filesystem at their application-relative paths, within the existing module and artifact budgets. For dynamically selected files that Next cannot infer, use the ordinary Next.js `outputFileTracingIncludes` configuration. These private runtime files do not become public HTTP assets; files under `public` keep their normal public behavior. Absolute paths, escaping symlinks, missing inputs and native addons are refused.\n- Workers cannot generate JavaScript from strings at request time (`eval` or `new Function`). In particular, rendering MDX through `next-mdx-remote/rsc` at request time fails even when a Node build succeeds. Compile trusted repository MDX to ordinary ES modules with `@mdx-js/mdx` and `outputFormat: \"program\"` during the Next build, then statically import the generated components. Preserve the canonical content, existing component mappings and remark plugins. A deterministic generator awaited by the default-exported Next config keeps `scripts.build` exactly `next build`; provide declarations for generated imports so a fresh checkout can typecheck. Verify actual article/detail routes and client navigation on the Worker, not just its landing page.\n\n- A dependency that loads WebAssembly through Node filesystem APIs needs a runtime-compatible entrypoint. Prefer its existing `workerd` conditional export, or a small package adapter that statically imports the same pinned `.wasm` modules for Workers and retains the Node entrypoint for local use. Preserve upstream licenses and validation. The shared pipeline carries validated Workers-compatible JavaScript, MJS and WASM modules for plain functions Workers, Vite edge companions, TanStack Start edge and Next.js. Auxiliary modules are at most 5 MiB each, and the Worker/artifact budgets still apply; do not turn them into arbitrary public assets or replace a failing decoder with an always-successful result.\n- Customer-owned custom domains use the normal Paid-domain flow. Native addons and non-functional Workers Node APIs remain typed blockers; the Node proxy filename alone is not a blocker.\n\n### Next.js private SSG cache\n\nPages rendered during the Next.js build use an immutable cache carried with that deployment.\nOrdinary request-time rendering and route handlers remain available. Cache payloads are private:\ndirect HTTP requests to the reserved cache paths must return 404. Verify both page rendering and\nthat private cache payloads cannot be downloaded from the deployed origin.\n\nThe private cache defaults to **32 MiB**. CLI/MCP **0.1.28 or later** accept optional\n`build.ssg_cache_max_mib` in `ohmyhost.yaml`: an integer from **1 to 64**, measured in MiB. Keep the\nexisting build settings and add this field only when choosing a different capacity, for example:\n\n```yaml\nbuild:\n install: pnpm install --frozen-lockfile --ignore-scripts\n command: pnpm run build\n output: .open-next/assets\n ssg_cache_max_mib: 48\n```\n\nUse the repository's own package manager and existing commands. A larger private cache does not\nincrease the separate **25 MiB** public-asset allowance. Public assets, private cache, Worker\nmodules, migrations and archive metadata together must still fit **30 MiB compressed and 64 MiB\nexpanded**. Leave room for the other archive contents when selecting the cache capacity: setting\n`ssg_cache_max_mib: 64` does not guarantee that 64 MiB of cache fits alongside the rest of the\ndeployment. Read the named limit in a failed build: increase this setting only for the private SSG-cache limit;\nreduce output when a different limit is exceeded. Commit and push the configuration change,\nthen plan and deploy that new commit.\n\nCloudflare Static Assets have no cache storage charge: provider storage cost **0 × 1.5 = 0 credits** on\nohmyho.st. Choosing a larger capacity does not add a storage fee. Build compute and Worker\nrequest/CPU charges remain unchanged; a cached page can still execute Worker code. See\n[Cloudflare Static Assets billing](https://developers.cloudflare.com/workers/static-assets/billing-and-limitations/).\n\nThis cache is refreshed by a new deployment. Time-based or on-demand revalidation is unsupported;\nCache Components follow the rules below. Preserve existing static pages; do not force every page to\nrender dynamically to work around a platform packaging error. If the build log begins a diagnostic\nwith `ohmyho.st platform:`, report its operation ID through feedback. An `ohmyho.st packaging:`\nline identifies an output limit or a Next.js cache rule the application can address. A prerendered\npage whose URL path is longer than about 95 bytes may not fit the deployment archive; that line then\nnames the page: shorten its URL or render it on request.\n\n### Next.js Cache Components\n\nNext.js 16 Cache Components build and run here, also with `partialPrefetching: true`. A page's\nprerendered static shell comes from the private SSG cache, and its Suspense holes render for each\nrequest. `\"use cache\"` directives are admitted, and so are these `next/cache` functions imported\nor re-exported by name: `cacheLife`, `cacheTag`, `unstable_cacheLife`, `unstable_cacheTag`, `io`,\n`navigation`, `prefetch`, `refresh` and `unstable_noStore`. These rules apply:\n\n- The cache is read-only: request-time `\"use cache\"` values are never stored, so a later request\n computes them again. Concurrent identical calls in one isolate may share one computation, so a\n cached function must depend on request data only through its arguments.\n- A `\"use cache\"` value in a static shell stays frozen until the next deployment. Every built-in\n `cacheLife` profile, including the default, revalidates, so the build refuses such a value unless\n it uses `cacheLife({ revalidate: Infinity, expire: Infinity })`. Otherwise call the cached\n function inside a Suspense boundary after reading request data (for example `await connection()`).\n- Every other `next/cache` use is refused, including `revalidateTag`, `revalidatePath`,\n `updateTag`, `unstable_expireTag`, `unstable_expirePath`, `unstable_cache`, default and\n namespace imports and `export *`. So are `res.revalidate`, fetch `next.revalidate` values other\n than `0` or `false`, and time-based revalidation of prerendered pages and route handlers.\n- Each dynamic page needs a static shell: read `params` inside a Suspense boundary, without\n `ensureStatic` and without a root layout inside a dynamic segment. With\n `partialPrefetching: true`, dynamic pages must not use `generateStaticParams`. Otherwise Next.js\n prerenders the page while a visitor waits, and Workers stop that request and later requests for\n the same page.\n- The runtime caps `experimental.maxPostponedStateSize` at 25 MB and keeps a smaller value.\n\nA refused build ends with one fixed `ohmyho.st packaging:` line that names the fix; apply it, then\ncommit, push and deploy the new commit. These lines begin:\n\n- `Next.js on-demand or time-based revalidation is not supported by this read-only hosting cache.`\n- `Next.js time-based revalidation of prerendered pages and route handlers is not supported by this read-only hosting cache.` (without Cache Components)\n- `Next.js time-based revalidation of prerendered pages and route handlers is not supported by this read-only hosting cache: a revalidate setting or a \"use cache\" value in a Cache Components static shell would stay frozen until the next deployment.`\n- `Next.js Cache Components pages that are prerendered while a visitor waits are not supported by this hosting:`\n\n## Completion\n\nVerify each requested capability through the customer's supported interfaces and the protected Dev application. Run promotion/rollback/deletion only within their authorized scope; repeated deletion and absence proofs are for explicitly disposable acceptance projects. A root HTTP `200` alone does not prove application authentication or other required flows.\n",
|
|
66
66
|
},
|
|
67
67
|
{
|
|
68
68
|
skillName: "ohmyhost-deploy-github",
|
|
@@ -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 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",
|
|
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, one call per secret. Give\n each returned `url` only to the person who sets that value (no sign-in, used up by its first save\n even if the write fails, expires after one hour), and observe the private input through\n `connection_status`. Before relying on `secret_submitted`, confirm the name with `secrets_list`\n (names only). 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",
|
|
@@ -80,9 +80,9 @@ export const GENERATED_SKILL_RESOURCES = Object.freeze([
|
|
|
80
80
|
uri: "skill://ohmyhost/ohmyhost-deploy/SKILL.md",
|
|
81
81
|
title: "ohmyhost-deploy",
|
|
82
82
|
description:
|
|
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.",
|
|
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. From a native chat, also set private application secrets with a one-hour link that needs no sign-in. 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. 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",
|
|
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. From a native chat, also set private application secrets with a one-hour link that needs no sign-in. 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 one call per secret. Give its `url` only to the person who sets that value. It needs no sign-in,\n names the secret, can generate a random value (dice button) and shows or hides the value (eye\n button). A valid save uses up the link, whether or not the save succeeds, and the link expires\n after one hour; call again for a fresh link if it expired or `connection_status` reports\n `secret_failed`. `connection_status` reports `pending` until the link is used, `secret_submitted`\n once it was used for a save (recorded before the write, so confirm the name with `secrets_list`\n before relying on it), `secret_failed` when that save did not complete, and `expired` for an\n unused link past its hour. `secrets_list` shows metadata only. Keep the link out of notes,\n source and logs. 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 chooses application authentication.\nPassword-first Native Auth is released only for projects approved by the existing launch allowlist. Setup and administration require current Owner authority; the project needs Paid access, its own authorized verified sender and the approved application identity and origin. Check that project\'s readiness and actual hosted flow before claiming it is usable. Google and GitHub sign-in remain disabled, and general availability, performance and recovery qualification remain open. Choosing Native does not authorize a provider migration or copying users. See the [runtime contracts](../ohmyhost-build-portable-app/references/stack-contracts.md) for the requirements. 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. Native Auth management commands and tools require version 0.1.50 or newer. 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 @amerged/ohmyhost-mcp`). 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\nNative Auth management requires the separate `auth:read` or `auth:manage` permission in addition to current Owner authority. Existing tokens retain their recorded permissions; a token limited to environment secrets does not gain Native Auth access. For a project approved by the existing Native launch allowlist, a current Owner may explicitly authorize creation of a new Native-capable token with the needed permissions. Never revoke or replace an existing token without the customer\'s scoped authorization.\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 chooses application authentication.\nPassword-first Native Auth is released only for projects approved by the existing launch allowlist. Setup and administration require current Owner authority; the project needs Paid access, its own authorized verified sender and the approved application identity and origin. Check that project\'s readiness and actual hosted flow before claiming it is usable. Google and GitHub sign-in remain disabled, and general availability, performance and recovery qualification remain open. Choosing Native does not authorize a provider migration or copying users. See the [runtime contracts](../ohmyhost-build-portable-app/references/stack-contracts.md) for the requirements. 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`, one call per secret. Give its `url`\nonly to the person who sets the value: it needs no sign-in, names the secret, can generate a random\nvalue, is used up by its first save even if the write fails, and expires after one hour (`expires_at`).\nCall again for a fresh link if needed, then observe `connection_status`. `secret_submitted` is recorded\nbefore the write, so confirm the name with `secrets_list` (names only) before relying on it. The form\ntakes one line and browsers strip line breaks, so a multi-line value such as a PEM key goes through the\nlocal stdin path (`secret_set_command` or the CLI stdin) instead. Remote-only tools like this one are\nnot in the surfaces list, so use the connector\'s `tools/list`. Values stay out of chat, source, tools\nand 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. Native Auth management commands and tools require version 0.1.50 or newer. 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 @amerged/ohmyhost-mcp`). 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\nNative Auth management requires the separate `auth:read` or `auth:manage` permission in addition to current Owner authority. Existing tokens retain their recorded permissions; a token limited to environment secrets does not gain Native Auth access. For a project approved by the existing Native launch allowlist, a current Owner may explicitly authorize creation of a new Native-capable token with the needed permissions. Never revoke or replace an existing token without the customer\'s scoped authorization.\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",
|
|
@@ -150,7 +150,7 @@ export const GENERATED_SKILL_RESOURCES = Object.freeze([
|
|
|
150
150
|
description:
|
|
151
151
|
"Migrate customer-selected Supabase capabilities in a TypeScript application to portable PostgreSQL and the ohmyho.st runtime. Use when real Supabase database, Auth, Storage, Functions or Realtime usage needs conversion; not merely because an SDK is installed.",
|
|
152
152
|
mimeType: "text/markdown",
|
|
153
|
-
text: "---\nname: ohmyhost-migrate-supabase-postgres\ndescription: Migrate customer-selected Supabase capabilities in a TypeScript application to portable PostgreSQL and the ohmyho.st runtime. Use when real Supabase database, Auth, Storage, Functions or Realtime usage needs conversion; not merely because an SDK is installed.\n---\n\n# Migrate a Supabase application\n\nKeep the application working while converting only the capabilities the customer selected.\n\n## Inspect and choose\n\nRun `ohmyhost init --dry-run --json` in the chosen repository. Inventory actual database queries, RPCs, migrations, RLS assumptions, Auth sessions, Storage calls, Edge Functions and Realtime subscriptions. Distinguish used services from unused dependencies and preserve a valid hosting configuration.\n\nExplain what can remain external, what needs application changes, and any unsupported runtime capability. Do not replace the customer's auth provider or discard data without their decision. A database dump does not migrate every Supabase service.\n\nRead [provider contracts](references/provider-contracts.md) only for the affected capabilities. The [portable baseline helper](scripts/create-portable-baseline.mjs) can prepare a reviewed migration baseline; inspect its documented input/output and the selected migration files before running it. It does not grant database authority or prove that application data has moved.\n\n## Convert the selected capabilities\n\n- Replace selected Supabase-specific database calls with typed SQL or narrow repositories through `createPrivateDatabaseClient` and `OHMYHOST_DATABASE`. Use `transaction()` for predefined statements and bounded `withConnection()` for read-decide-write; see [database runtime](../ohmyhost-build-portable-app/references/database-runtime.md). Preserve transaction boundaries, constraints and tenant filtering.\n- Review SQL functions and RLS-dependent assumptions explicitly. Keep immutable migrations in `YYYYMMDDHHMMSS_name.sql` format and follow [expand-only admission](../ohmyhost-manage-database/SKILL.md). Ordinary row changes, destructive schema changes, CREATE OR REPLACE, roles/grants and changes to existing constraints are refused. Reviewed policy migrations and ENABLE/FORCE on explicitly selected ordinary `public`/`private` tables use the backend RLS contract.\n- To retain Supabase policies, explicitly select the baseline helper's `preserve-rls` mode, review every original RLS table and supply the identity/claim mapping described in [provider contracts](references/provider-contracts.md#preserving-backend-rls). Keep every policy or stop on a concrete unsupported construct; never fall back to policy removal. Verify the application session on the server and pass only selected, server-trusted claims through `withRls`; the backend derives `sub` and the `anon`/`authenticated` role. Browser IDs and editable user metadata are never authority. This choice does not change the customer's auth provider, and a UUID cast requires confirmed identity compatibility.\n- Preserve or integrate the customer-chosen application auth. Configure its callback URLs and private server secrets. Test login, protected access, session refresh/reload and logout; hosting login is separate.\n- Convert selected Storage, Functions and Realtime behavior only when the current runtime contract supports the equivalent application behavior. If it does not, state the gap and agree on the feature decision instead of inserting a fake success or silent fallback.\n- Selected Edge Functions become authenticated framework-native server routes or plain Worker handlers. Declare schedules through `functions.crons`; the platform owns cron delivery/retries, rather than customer Queue/Workflow bindings. Existing external functions may stay when the customer chooses them and their declared interfaces are verified.\n- Optional managed Better Auth uses explicit `auth.provider: better-auth` and its database. Managed mail is optional for verified-user/session/token flows; the bridge's verification/reset sends use its platform sender and need `mail.enabled: true`, Paid access and a verified sender. Keep a custom Resend callback in an application-owned Better Auth handler with `auth.provider: none`; this disables only the managed bridge, not Better Auth or login. SDK dependency detection selects no managed capability. CLI 0.1.24 may incorrectly return `managed_auth_mail_required`; use the corrective 0.1.25 release or later and preserve the selected auth and sender.\n- Keep application runtime secrets separate from repository/build inputs.
|
|
153
|
+
text: "---\nname: ohmyhost-migrate-supabase-postgres\ndescription: Migrate customer-selected Supabase capabilities in a TypeScript application to portable PostgreSQL and the ohmyho.st runtime. Use when real Supabase database, Auth, Storage, Functions or Realtime usage needs conversion; not merely because an SDK is installed.\n---\n\n# Migrate a Supabase application\n\nKeep the application working while converting only the capabilities the customer selected.\n\n## Inspect and choose\n\nRun `ohmyhost init --dry-run --json` in the chosen repository. Inventory actual database queries, RPCs, migrations, RLS assumptions, Auth sessions, Storage calls, Edge Functions and Realtime subscriptions. Distinguish used services from unused dependencies and preserve a valid hosting configuration.\n\nExplain what can remain external, what needs application changes, and any unsupported runtime capability. Do not replace the customer's auth provider or discard data without their decision. A database dump does not migrate every Supabase service.\n\nRead [provider contracts](references/provider-contracts.md) only for the affected capabilities. The [portable baseline helper](scripts/create-portable-baseline.mjs) can prepare a reviewed migration baseline; inspect its documented input/output and the selected migration files before running it. It does not grant database authority or prove that application data has moved.\n\n## Convert the selected capabilities\n\n- Replace selected Supabase-specific database calls with typed SQL or narrow repositories through `createPrivateDatabaseClient` and `OHMYHOST_DATABASE`. Use `transaction()` for predefined statements and bounded `withConnection()` for read-decide-write; see [database runtime](../ohmyhost-build-portable-app/references/database-runtime.md). Preserve transaction boundaries, constraints and tenant filtering.\n- Review SQL functions and RLS-dependent assumptions explicitly. Keep immutable migrations in `YYYYMMDDHHMMSS_name.sql` format and follow [expand-only admission](../ohmyhost-manage-database/SKILL.md). Ordinary row changes, destructive schema changes, CREATE OR REPLACE, roles/grants and changes to existing constraints are refused. Reviewed policy migrations and ENABLE/FORCE on explicitly selected ordinary `public`/`private` tables use the backend RLS contract.\n- To retain Supabase policies, explicitly select the baseline helper's `preserve-rls` mode, review every original RLS table and supply the identity/claim mapping described in [provider contracts](references/provider-contracts.md#preserving-backend-rls). Keep every policy or stop on a concrete unsupported construct; never fall back to policy removal. Verify the application session on the server and pass only selected, server-trusted claims through `withRls`; the backend derives `sub` and the `anon`/`authenticated` role. Browser IDs and editable user metadata are never authority. This choice does not change the customer's auth provider, and a UUID cast requires confirmed identity compatibility.\n- Preserve or integrate the customer-chosen application auth. Configure its callback URLs and private server secrets. Test login, protected access, session refresh/reload and logout; hosting login is separate.\n- Convert selected Storage, Functions and Realtime behavior only when the current runtime contract supports the equivalent application behavior. If it does not, state the gap and agree on the feature decision instead of inserting a fake success or silent fallback.\n- Selected Edge Functions become authenticated framework-native server routes or plain Worker handlers. Declare schedules through `functions.crons`; the platform owns cron delivery/retries, rather than customer Queue/Workflow bindings. Existing external functions may stay when the customer chooses them and their declared interfaces are verified.\n- Optional managed Better Auth uses explicit `auth.provider: better-auth` and its database. Managed mail is optional for verified-user/session/token flows; the bridge's verification/reset sends use its platform sender and need `mail.enabled: true`, Paid access and a verified sender. Keep a custom Resend callback in an application-owned Better Auth handler with `auth.provider: none`; this disables only the managed bridge, not Better Auth or login. SDK dependency detection selects no managed capability. CLI 0.1.24 may incorrectly return `managed_auth_mail_required`; use the corrective 0.1.25 release or later and preserve the selected auth and sender.\n- Keep application runtime secrets separate from repository/build inputs. Locally, use `secret_set_command` with the intended environment ID for private values. From a native chat, use `secret_input_request` with the environment name (`dev` or `prod`) and give its link only to the person who sets the value.\n\n## Verify and deploy\n\nRun relevant application tests and the real framework build, then rerun `init` until the selected conversion requirements are resolved. Use **ohmyhost-build-portable-app** for the resulting runtime and **ohmyhost-deploy-github** for publication.\n\nCheck real login, data reads/writes, constraints and any required files, email or functions on the hosted Dev app. Promote only within the customer's request, preserving Prod records. Rollback or deletion tests belong only to separately requested or explicitly disposable test resources.\n\nFor an unsupported operation or suspected platform bug, use `feedback_submit` with a minimal redacted reproduction and the safe operation/error ID. Keep the unresolved capability visible; do not claim the migration complete until the requested application behavior works.\n",
|
|
154
154
|
},
|
|
155
155
|
{
|
|
156
156
|
skillName: "ohmyhost-migrate-supabase-postgres",
|