For coding agents
Use the repository SKILL.md as the execution policy and these docs as the interface reference.
Connect from the homepage prompt
When a user starts with the prompt copied from opencloud.ai, begin with the product conversation. Ask what they want to build, who it is for, and which single workflow matters most before choosing infrastructure.
Inspect the execution surface first. If a terminal is available, use the CLI even when OpenCloud MCP tools are also present:
- Install the pinned release with the checksum-verifying script in the CLI reference.
- Confirm
opencloud --cli-versionis3.10.3. - When OpenCloud explicitly supplies an exact-app owner Agent session and assigned app ID, run
opencloud doctorandopencloud app get "$APP_ID". Use that session directly; do not run account login, create another app, or connect another workspace. - Otherwise run
opencloud auth statusandopencloud doctor. Reuse a healthy stored account login. If none exists, runopencloud login. The command prints and opens a short-lived HTTPS approval page. The user signs in with a one-time email link or configured password and explicitly approves the CLI; no localhost callback or pasted code is involved. - For that ordinary account flow, run
opencloud app listandopencloud app get "$APP_ID". Create a new private app withopencloud app createonly when the requested app does not already exist. From the app's source directory, runopencloud app connect "$APP_ID". This creates the non-secret.opencloud/app.jsonbinding and a protected, app-scoped credential. - Use the CLI for the complete draft, isolated-development, deployment, verification, and operation workflow.
The account login is reusable by later Codex or Claude Code sessions running as the same OS user. Its 15-minute access token and rotating 30-day refresh token are stored in the OS keyring under ai.opencloud.cli. A headless host without a usable keyring falls back to a mode-0600 per-user credential file under:
$XDG_CONFIG_HOME/opencloud/credentials(normally~/.config/opencloud/credentials) on Linux;~/Library/Application Support/OpenCloud/credentialson macOS; or%APPDATA%\OpenCloud\credentialson Windows.
The account token can list, read, and create apps and connect a workspace, but cannot deploy, configure, inspect secrets, or perform owner-only actions. app connect issues a separate 24-hour app credential and the CLI renews it when necessary. opencloud logout revokes the whole login family and every workspace credential issued from it. Never read or print either protected credential backend.
An exact-app owner Agent session is different from both credentials above. It is the AI SDK Codex harness v3 delegation for one assigned app and carries the same app authority as its human owner. OpenCloud exposes its protected path as OPENCLOUD_SESSION_FILE; never inspect or print that file. Through the public CLI, the Agent may perform every operation owned by that app—including deploy, production data and File mutations, rollback, backup restore, secret and access changes, archive, and deletion—without an additional confirmation solely because the operation is destructive or affects production. The Agent must still honor an explicit constraint such as “do not deploy,” use normal validation and idempotency, and verify the result.
The exact-app session cannot create or operate another app, change account settings, authorize or reveal account-owned provider credentials, administer the platform, or access the host or shared infrastructure. Account connection and consent remain human account actions; the Agent may bind or unbind an already available app-account integration only through the typed app-scoped contract. Do not infer exact-app owner authority from merely running as an Agent. AI SDK harness v1/v2 and ordinary human-directed CLI sessions retain their existing, narrower authority and approval behavior.
The CLI reference records the complete owner command tree in the pinned public CLI v3.10.3. Verify the installed version and command help before use. Do not invent a missing command or bypass the public CLI with a raw request; report an absent surface as a product limitation.
CLI success is a stable machine-readable JSON document. In the owner-parity release, failures are also structured, redacted JSON with a non-zero exit status. Parse fields instead of scraping prose. Give every mutation one stable idempotency key and reuse it for the same intended effect after an ambiguous response. An exact-app owner Agent must pass it through every mutating command's --idempotency-key option; a missing key is rejected before a journal entry or request is created. An artifact digest may identify input but does not identify the complete intent. Never change keys or delete journal state merely to bypass an ambiguous outcome or authority mismatch; inspect the exact operation and current app state first. Operation-starting commands follow with bounded waits by default; use --no-follow only when deliberately detaching and retain the returned operation ID. Reconcile without duplicating the mutation:
opencloud operation get "$OPERATION_ID" --follow
opencloud operation list "$APP_ID"A local timeout does not prove failure. Read the durable operation again. Terminal failure or cancellation exits non-zero with typed diagnostics; after repairing materially different input, use a new idempotency key. Successful app deletion returns terminal evidence before credential revocation and leaves only its exact bounded deletion receipt readable.
Use the hosted MCP workflow only when the surface has no usable terminal or direct API path:
- Call
get_opencloud_session. - If it reports disconnected, call
connect_opencloudfor a returning user or continue withstart_onboardingfor first registration. - For first registration, ask for the user's email only if it is not already available, then call
start_onboarding. Do not ask them to invent or check an app domain. - A new email receives
awaiting_email_verificationwith a retained request and no app or credential. The owner confirms their email within 24 hours, then can build personally. Creating a company is optional in Preferences. An account can belong to at most one company; an email domain alone never grants membership. - Give the user the returned
launchUrlas the primary owner link. It waits for both email confirmation and deployment, contains no confirmation secret, and then opens the project in the signed-in browser. - Existing emails also receive a project-aware confirmation request. After confirmation, call
complete_onboardingwith the same onboarding ID. Wait for the owner while confirmation is pending; do not create a replacement request. Existing valid provisional grants remain usable when confirmation delivery is delayed. - Resolve the assigned app from onboarding or
get_app, then callget_app_starterwith all required capability IDs. Treat its resolved checklist as required build input. - Use
create_draft, inspect files,apply_file_changes, andvalidate_draft; then run isolated dev, exact-revision verification, andpromote_dev_revision. If the owner asks to review without deploying, give them the dev session'sbrowserPreviewUrl. It requires an owner or builder browser login and opens a clearly marked Not live preview window with Full size, Tablet, Mobile, and Reload tools around isolated synthetic-user-A state; do not use the rawpreviewUrlas the human entry link. An explicit no-deploy request stops at this review point and does not authorizepromote_dev_revision.
No-terminal builders use https://mcp.<domain>/build; the host root https://mcp.<domain> remains the full lifecycle connector surface for ChatGPT, Claude, and other supported clients without a usable terminal or direct API path. /mcp and /mcp/build remain compatibility aliases for existing clients. MCP is stateful Streamable HTTP: reuse its opaque session ID, keep authorization server-side, and treat draft revisions, file hashes, receipts, and operation IDs as concurrency/evidence contracts. See ChatGPT, Claude, and MCP for the complete state and recovery model.
This MCP path needs no shell, local filesystem, Node.js installation, or CLI. A prompt cannot install a connector. If the tools are absent, state that the OpenCloud connector must be enabled on a supported client surface. Claude Code and Cowork can install the plugin for its workflow skill, but sessions with a usable terminal follow the CLI path above. Claude.ai, Claude Desktop, and Claude mobile can use the connector. Plugins are not currently available directly in ChatGPT mobile chats; a mobile user can steer a configured desktop task through ChatGPT Remote.
If neither the verified CLI nor hosted MCP is available, remain offline and do not claim registration, preview, deployment, or verification.
For MCP first registration, an unverified provisional grant pauses its apps after 24 hours without deleting data. The project-aware confirmation creates the browser session and returns to the non-secret owner launch page.
Never ask the user for an account password, session cookie, OAuth access token, email confirmation link, bootstrap token, service-role key, database credentials, secret values, or a copied connection pack. MCP retains provisional onboarding material server-side. The CLI retains credentials only in its protected per-user store.
Skill representations
Use https://opencloud.ai/skill when a client may require browser-readable HTML. It serves HTML by default and returns the exact Markdown source when the request explicitly accepts text/markdown. The stable https://opencloud.ai/SKILL.md endpoint and the MCP skill resource remain raw Markdown.
Reading order
- Read the hosted OpenCloud build skill completely.
- Read the quickstart.
- Load the control-plane OpenAPI for agent, CLI, deployment, and operations work. Load the separate app runtime OpenAPI for code running inside an app.
- Read only the capability guides required by the requested product.
- Read the relevant JavaScript namespace pages before writing frontend calls.
- Read the manifest, SQL, and verification references before deployment.
- For URL aliases, optional route parameters, or query-driven Functions, read App routes. For an owner-provided website hostname, read Custom domains.
- Keep the development loop, verification contract, and Functions/server library guide open while implementing.
First-turn checkpoint
Create a real opencloud.yaml and non-empty frontend in the first coherent file batch. Before selecting capabilities, make one monitoring decision: identify any important product failure that the platform's built-in app, deployment, operation, cron, background-job, HTTP-error-rate, and telemetry-freshness signals cannot express. Include telemetry only when a custom product signal is needed; this is an agent decision, not a user approval step. With MCP, call get_app_starter with every capability implied by the product (auth, data, files, functions, jobs, ai, email, notifications, realtime, telemetry, cron, and/or integrations). Implement every returned resolvedCapabilities and capabilityChecklist entry. Inspect the current draft, then use apply_file_changes and validate_draft; with the CLI, run the artifact checker and validate before polishing the UI. Once the first coherent artifact validates, start a development session. Sync each coherent change, inspect the stable preview, invoke Functions only explicitly, and obtain a receipt before promotion. Read Develop safely before production.
Contract rules
- Author new server-side/MCP app manifests with
schemaVersion: 3, no top-level release version, andruntime.sdk.version: 2.2.0. OpenCloud assigns the release name during promotion and stores it on the deployment row. The pinned CLI 3.10.3 defaults new versionless sources to schema 3. Use--versiononly for a deliberate legacy schema-2 source, and leave an existing schema-2 compatibility version stable across updates. Declare optionalfiles.access, Functionaccess: user | public | system, and asecretsmap whose values aregenerated,required, oroptional; declare app-owned email aliases only underemail.addresses. - For every brokered integration, creating or authorizing an account connection and completing provider consent remain human account actions. With
account: app, an exact-app owner Agent may list the app's integration declarations and current bindings plus bounded eligible-connection metadata and opaque IDs for the exact app's declared slots, then bind, configure, or unbind an already authorized connection through the owner-parityintegrationcommands. This app-context projection includes only the delegated owner's connections usable by declaredaccount: appslots. It cannot enumerate the general account catalog, manage connections, expose raw credentials, reveal undeclared providers, includecalling_userconnections, or cross into another owner's connections. Acalling_userbinding always remains that calling user's action and authority. - For Google Workspace, declare a named
google-calendar,google-drive,google-sheets,google-docs, orgoogle-slidesslot withaccount: app | calling_user,cardinality: one | many, and only the provider-scoped read/write capabilities needed. Functions use the typedintegrations.calendar/drive/sheets/docs/slides(slot)clients. Account connection and resource authorization are human actions; app-account binding follows the rule above. Never request OAuth tokens or raw Google API access. Discover exact bounded methods from the@opencloud/serverdeclarations or the brokered integrations guide, never by guessing a raw REST orbatchUpdateescape hatch. - For Google reporting, declare
google-analytics,google-search-console, orgoogle-adswithaccount: appand only its single read capability. The owner binds one exact property, site, or non-manager Ads customer. Functions use the provider-independentintegrations.use(slot).call(operation, input)surface; raw requests, GAQL, provider tokens, and unbound resource IDs are not public APIs. The broker owns operation validation so future providers do not require a core SDK release. - For Asana, declare
provider: asanawith only the requiredasana.*task, assignee, section, custom-field, attachment, story/comment, and event capabilities. Every slot binds one exact human-selected project. Functions useintegrations.asana(slot); never accept a raw Asana request, OAuth token, a connection ID outside the exact-app projection, or an out-of-project resource. Sections are list headings or board columns. Task mutations returning current task state also requireasana.tasks.read; assignee, section, and custom-field details stay gated by their own read capabilities. Incoming events requireaccount: app,asana.events.receive,asana.tasks.read, andevents.functiontargeting a declaredsystemFunction. HandleOpenCloudAsanaEvent.ididempotently because an app's own writes may be delivered back to it. Development uses deterministic synthetic data and contacts no Asana account. - For HubSpot CRM, declare
provider: hubspot-crm,account: app, and only the neededcrm.contacts.*,crm.companies.*,crm.deals.*,crm.owners.read,crm.pipelines.read,crm.notes.write, andcrm.associations.writecapabilities. Functions useintegrations.crm(slot)typed methods. A human authorizes the reusable portal; app binding follows the rule above. Never request a HubSpot token, issue raw provider requests, add delete operations, or invent CRM webhook events. Development uses deterministic synthetic CRM data and does not contact HubSpot. - For open banking, declare
gocardless-bank-account-datawith only the neededbank.accounts.read,bank.balances.read, and/orbank.transactions.readcapabilities. Functions useintegrations.bankAccountData(slot).bindings/account/balances/listTransactions. Bank consent and exact-account authorization are human actions; app binding follows the rule above. Never request bank login details, GoCardless credentials, requisition IDs, raw account IDs, or raw provider requests. Development uses synthetic data and contacts no bank. - For Wise transactions, declare the technical provider
wise-balance-webhookwithaccount: appand onlypayments.received.reconcile. Functions useintegrations.receivedPayments(slot).bindings/reconcile. A human creates exactly one Account deposit events webhook in Wise using the notification URL shown under Account Integrations and tests it; app binding follows the rule above. Do not ask them to create Transfer update or Transfer issue events. Never ask for a Wise token, certificate, login, full balance, raw transfer ID, or raw transaction feed; treatambiguousas manual review. - For Slack, declare
provider: slack,account: app, and onlyslack.messages.sendand/orslack.messages.receive. Receiving also declaresevents.message.functiontargeting asystemFunction. Functions useintegrations.slack(slot).bindings/sendMessage; inbound handlers receiveOpenCloudSlackMessageEvent. A human connects the reusable workspace and invites@OpenCloudto an exact channel. App binding selects that channel andmentionorall_messagesunder the rule above. Inbound delivery is at least once; use the stable eventidto make side effects idempotent. Never request a Slack token or a connection ID outside the exact-app projection, choose a channel in code, accept slash commands, or assume a Slack tag authorizes a sensitive app action. - For Telegram, declare
provider: telegram,account: app, and onlytelegram.messages.sendand/ortelegram.messages.receive. Receiving also declaresevents.message.functiontargeting asystemFunction. Functions useintegrations.telegram(slot).bindings/sendMessage; inbound handlers receiveOpenCloudTelegramMessageEvent. A human adds an exact private chat or group through the app's one-time link. App binding selects an owned paired chat anddirectedorall_messagesunder the rule above.cardinality: oneselects one chat at a time;manycan bind several. Private chats and directed groups do not require bot administrator access; all-message groups grant the bot minimal administrator access in that exact group while privacy mode remains enabled. Inbound delivery is at least once, so make side effects idempotent with the stable eventid. Never request a bot token or raw chat ID, handle raw Telegram updates, choose a chat in code, disable privacy mode, or treat a Telegram sender, mention, command, or reply as authorization for a sensitive action. - Import
opencloudfrom/_opencloud/sdk.js. Do not fetch runtime config, construct a client, import an immutable SDK URL, or handle credentials. - Use
data.table(...), managedfiles,functions.call/stream,notifications.status/subscribe/unsubscribe, andrealtime.subscribe/publish. Do not issue raw REST, Storage, Function, Web Push, or Realtime protocol requests. - Every app table declares
idas its primary key. Browser Files usesupload({ data, ... }),replace(file, { data, ... }), and flattenedattach({ data, table, ... }); retry keys and timeouts are private. - Prefer
files.attachwhen a file and its database metadata must be created together. It handles ambiguous writes and cleanup; store its opaque file ID, never a bucket or object path. - Implement Functions only as
export default defineFunction({ input: schema.*, handler });inputis already parsed. Function names and matching entrypoint directories use lowercase kebab-case. Use namederrors.*factories andai.generateText,ai.streamText,ai.generateObject, orai.generateImage. Return streamed AI text in a no-store plain-textResponsewithX-Accel-Buffering: no, and consume it with browserfunctions.stream. Text, stream, and object calls may include managed Files asattachments: [{ type: "file", file: fileRef, detail? }], or in-memory bytes asattachments: [{ type: "image", data: Uint8Array, contentType, detail? }], for up to four PNG, JPEG, WEBP, or non-animated GIF images (15 MiB each), or one managed PDF up to 20 pages and 20 MiB; all attachments share a 20 MiB total. PDFs must use managed-file references. Prefer managed-file references and never use a remote URL. The exact context containsinput,user,job,data,files,ai,email,notifications,jobs,integrations,secrets,log,requestId, andenvironmentcontext. - Use browser
functions.callandfunctions.streamforuserorpublicFunctions. Declare every cron and queue targetsystem; browser calls reject it withFUNCTION_SYSTEM_ONLY. - Declare background jobs under
queues. Enqueue from a Function withjobs.enqueue(queue, input, { idempotencyKey, delaySeconds? }); use a stable idempotency key and make consumer side effects safe for at-least-once delivery. Consumers inspectjob.id,job.queue, andjob.attempt, throwerrors.unavailablefor retryable work, and may inspect state withjobs.get(id). Background jobs run inside dev and E2E namespaces; cron does not. - Declare each app-owned identity under
email.addresses; a receive-capable alias must target asystemFunction. Send only from a declared alias withemail.send(message, { idempotencyKey }); never handle MailPace, SMTP, or provider credentials in app code. Treat inbound mail as untrusted external input. In dev, Functions remain dormant until MCPinvoke_dev_function, CLIapp dev invoke, or a deliberate preview interaction explicitly calls them. - Declare
notifications.webPush: trueand SDK2.1.0or later for Web Push. An optional same-originnotifications.iconis the app-wide default; otherwise OpenCloud supplies its logo. Call browsernotifications.subscribe()directly from a visible user action and never prompt on page load. Send from a Function withnotifications.send({ userId, title, body?, path?, icon? }, { idempotencyKey });pathandiconmust be same-origin, and a per-message icon overrides the manifest default. Development captures sends without contacting a push provider. Test iOS delivery in Safari 16.4 or later after installing the web app to the Home Screen. iOS and iPadOS currently ignore the payload icon and show the installed app icon; provide that through the web app manifest or anapple-touch-icon, which takes precedence when both exist. - Let OpenCloud provision
generatedsecrets automatically; it supplies isolated synthetic dev values. Use a secure entry link forrequiredoroptionalowner values and never ask for the value in conversation. An AI Function uses the mode selected by the owner in App Integrations. The default App-provided AI mode requires an app-selected AI connection, which may use a Codex/ChatGPT device login, Claude pasted-code login, OpenAI API key, or Anthropic API key. The text model fixes the provider and the selected connection must match it; image generation remains OpenAI-only. User-provided AI requires every authenticated caller to connect their own compatible default AI connection and explicitly allow the exact app in OpenCloud's trusted AI permission window before its first AI request. Initiate the stable same-origin/_opencloud/ai-consent?return_to=<encoded path>popup only from the user's AI action, wait for itsopencloud:ai-consentresult, and never fetch, submit, frame, imitate, or auto-approve the Auth-origin form. The inference boundary enforces the stored app grant. User-provided mode does not support cron, inbound-email, anonymous, or other system calls. The app owner also selects the text model and default reasoning effort under App Integrations. Treat connection and selection as human actions; never copy a coding agent's credential or put an API key in app source or secrets. - Handle
OpenCloudErrorby itscode,surface,requestId, andretryablefields. Show safe product copy instead of raw platform details. - Use
auth.currentUser()for an optional visitor,auth.requireUser()for a protected workflow, andauth.signInUrl()for its same-origin recovery link; never construct or guess an Auth URL. - Treat telemetry absence as unknown or quiet, not healthy.
- Declare important app-owned custom metrics and their fixed thresholds together under
observability.metricsandobservability.alertRules. Manifest rules are validated before promotion and activate atomically with the deployment; do not require a post-deployment rule-creation step for intended app behavior. Usealert-rule putonly for an immediate operational rule or override. If it becomes durable product intent, move it into the next manifest and remove the override. - Keep credentials and brokered tokens out of files, output, and reports.
- For an exact-app owner Agent, treat its assigned app—not operation type—as the authorization boundary. Do not add a per-operation approval for production or destructive work. For ordinary human-directed sessions and legacy or narrower Agent credentials, preserve the existing requirement for clear user authorization before destructive actions.
- Never invent or rotate a schema-3 release version in source. Read the publisher-assigned version from deployment results.
- Preserve exact operation, deployment, artifact, cron, and backup evidence.
- Replace the fail-closed starter
tests/opencloud.e2e.jswith REQ-tagged external tests for the requested primary outcomes. Drive visible controls, assert exact durable state with the distinct marker supplied to each test, useuniqueValue(prefix, maximumLength)for bounded fields, and clean fixtures through the UI. In dev, each test starts with a fresh schema, managed Files namespace, Function namespace, synthetic users, and browser contexts; five run in parallel by default. Scope duplicate labels through their dialog/card, useclickIfVisible(locator)for genuinely optional cleanup controls, close overlays infinally, and wait for cleanup state to converge. Textual locator queries may use bounded regular expressions, andselectOptionaccepts a raw value or bounded{ value },{ label }, and{ index }selectors. - Make at most ten
verify_dev_sessionattempts for one build. Diagnose with validation, preview reads, and Function evidence between attempts; stop with the exact structured failure if the tenth attempt still fails. - Use managed
opencloud.filesmethods and opaque file IDs; never construct raw Storage paths, buckets, object names, or authorization headers.
Common task recipes
import { opencloud } from "/_opencloud/sdk.js";
await opencloud.auth.requireUser();
const tasks = opencloud.data.table("tasks");
const rows = await tasks.list({
select: ["id", "title", "done"],
where: { done: false },
orderBy: { column: "created_at", direction: "desc" },
});
const created = await tasks.create({ title: "Ship the app" });
await tasks.updateById(created.id, { done: true });
const { file, record } = await opencloud.files.attach({
data: selectedFile,
name: selectedFile.name,
table: "task_files",
values: { task_id: created.id },
});
const summary = await opencloud.functions.call("summarize", {
taskId: created.id,
fileId: file.id,
});
const unsubscribe = await opencloud.realtime.subscribe(
"tasks",
({ event }) => event === "changed" && void refreshTasks(),
);
await opencloud.realtime.publish("tasks", "changed", { id: created.id });Keep the unsubscribe function for teardown. Use files.upload when only the managed file is needed; use files.attach when the app also needs an RLS row.
Definition of done
An online app is done only when:
- product checks and authoritative OpenCloud source validation pass;
- the durable deployment operation succeeds;
- the isolated dev preview passes and a receipt covers the exact promoted source and migrations;
- the authoritative MCP
verify_appor CLIapp verifyoperation passes; - the feature-aware verifier passes where configured;
- the monitoring decision was made and every required app-owned custom failure signal connects its metric, manifest rule, and real browser failure boundary;
- logs and telemetry were inspected honestly; and
- the canonical HTTPS app remains active, unless intentional app deletion completed with its exact terminal receipt; and
- MCP
promote_dev_revisionor CLIapp dev promotereported the live URL and the dev session stopped only after production verification.
If online credentials are absent, produce an offline-valid deterministic bundle and list the exact remaining live steps.
