omg/docs

Deploy from a local machine

Run an omg app locally and understand the source-snapshot deploy path the forthcoming omg CLI will use.

CLI status: the platform-side deploy contract is implemented in control-plane/lib/cli-deploy.ts and control-plane/lib/cli.ts on feat/cli-deploy-from-snapshot (PR #944). The omg CLI binary is still being built and is not available to install yet. This page documents the platform path and local runtime today; it does not invent a command surface for an unreleased binary.

Run the app locally

The templates/react-ts app is a normal public-npm project. From a copy of the template:

bun install
bun run dev

No omg token or VM is required for package installation or the local dev server. The Vite plugin starts the Bun API server in the same process, runs schema codegen and safe migrations, writes the local SQLite database at .vibes/data.db, streams realtime updates, and schedules cron() handlers in-process.

For a production-shaped local build:

bun run build
bun x vibes-build

vibes-build is the binary exposed by @omg-dev/vite-plugin. It emits the client in dist/ and the server bundle, seeded database, and manifest under .vibes/, including .vibes/server.mjs, .vibes/data.db, and .vibes/artifacts.json.

Local runtime boundaries

CapabilityOn a plain local machine
Vite UI, functions/, schema codegen, migrations, SQLite, realtimeWorks
cron()Works; dev mode uses the in-process scheduler
@omg-dev/aiBring your own ANTHROPIC_API_KEY (or the matching OpenAI configuration) because the sandbox proxy is absent
@omg-dev/mediaVM-only; calls expect the sandbox-injected OMG_MEDIA_URL proxy
App billingVM-only; the runtime client calls the in-VM agent's /_billing/* routes and has no local dev transport

These boundaries come from the package transports themselves: packages/ai/src/index.ts, packages/media/src/index.ts, packages/server/src/billing.ts, and packages/server/src/triggers.ts.

Auth on localhost

deriveAppId() only recognizes deployed hosts such as <slug>.apps.omg.dev; on localhost it returns null. The auth provider then falls back to "local", and POST https://auth.omg.dev/token returns 404 because lazy app registration is only allowed from omg.dev or an omg app origin.

Set the app id explicitly to a slug you own:

VIBES_APP_ID=my-existing-slug bun run dev

The Vite plugin injects that value into the page, so the SDK requests a token for the real app rather than "local". The relevant code is packages/sdk/src/auth/client.ts (deriveAppId) and packages/vite-plugin/src/index.ts (readPreviewAppId).

The deploy path

The forthcoming CLI uses the existing deploy pipeline; local source does not introduce a second build or artifact type:

  1. Create an owner-scoped sandbox through POST /v1/sandboxes.
  2. Upload the local working tree into /home/user/project.
  3. Call POST /v1/sandboxes/{id}/tarball. The sandbox stays running while infra captures the project tree and returns a durable snapshot with kind: "files_only".
  4. Send its id as snapshotId to POST https://omg.dev/api/cli/apps/deploy.
  5. Control-plane registers a project and run, then calls the same projectDeploys.publishForUser path used by dashboard publishing.
  6. Infra forks a short-lived builder from the source snapshot, runs the normal production build asynchronously, and publishes either static assets or a serverful build artifact. The client follows the build through GET /api/cli/apps/status?slug=....

The snapshot from step 3 is a source carrier. It is not the production artifact. For serverful apps, deploys.artifactId identifies the build output created in step 6 and retained for runtime creation and cold-start restore.

See Deploy flow for the builder, static/serverful split, atomic flip, and rollback details.

Endpoint contracts

POST /v1/sandboxes/{id}/tarball returns the standard snapshot shape. The fields relevant to deploy are:

{
  "id": "snapshot-id",
  "kind": "files_only",
  "sizeBytes": 0,
  "tarballSizeBytes": 123456,
  "sourceSandboxId": "sandbox-id"
}

The control-plane request body is:

{
  "name": "My App",
  "snapshotId": "snapshot-id",
  "projectId": "optional-existing-project-id"
}

name is optional and defaults to "My App". snapshotId is required. projectId is optional on the first deploy. Pass the returned projectId on later deploys to publish a new run under the same project and slug.

The response is:

{
  "slug": "my-app",
  "url": "https://my-app.apps.omg.dev",
  "status": "building",
  "projectId": "project-id",
  "runId": "run-id"
}

apps/deploy returns after infra accepts the build; it does not hold the HTTP request open for a terminal state. Poll:

GET https://omg.dev/api/cli/apps/status?slug=my-app
Authorization: Bearer <user-jwt>

The status response includes status, buildError, phase, buildLog, static, version, runId, and updatedAt. A client should only report a successful deploy after status becomes "ready" and should surface buildError when it becomes "failed".

The future CLI is expected to persist the returned project binding in .omg/project.json; that behavior is documented in control-plane/lib/cli-deploy.ts, but no public CLI flags or commands are promised yet.

Why deploy goes through control-plane

The two hosts intentionally accept different credentials:

SurfaceUser JWT (aud=vibes)omg_sk_ keyService JWT (aud=vibes-infra)
/v1/sandboxes* on infraYesYesYes, with X-On-Behalf-Of for owner-scoped calls
/v1/deploys, /v1/models, /v1/messages on infraNoNoYes
/api/cli/apps/deploy on control-planeYesNoInternal service credential not required

A signed-in client can mint the user JWT with POST https://auth.omg.dev/token and { "appId": "vibes" }; the session cookie identifies the user. That JWT can own the temporary sandbox and call the control-plane CLI surface, but it cannot call infra's service-only deploy route directly. Control-plane is the single owner of the privileged publish hop and supplies the user identity to the existing publish pipeline.

The auth boundary is implemented in apps/auth/src/routes/token.ts, apps/infra/internal/api/server.go, apps/infra/internal/auth/middleware.go, and control-plane/lib/cli-auth.ts.