Skip to content
superagent-aiPublic

About

A universal compute gateway for AI agents

Topics

Resources

Contributing

Security policy

Stars

4 stars

Watchers

0 watching

Forks

Latest commit

 

History

103 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

OpenMetal

OpenMetal

OpenMetal is the universal compute gateway for AI agents. This repository implements a hosted control plane that provisions CPU sandboxes, routes creation across supported compute providers, manages lifecycle operations, and bills managed usage.

Product model

OpenMetal is OpenRouter for cloud sandboxes and GPUs:

  • Managed OpenMetal capacity is the default. OpenMetal owns provider contracts and credentials.
  • Customers have one OpenMetal account and organization-scoped balance.
  • API keys are scoped per project.
  • Every provider resource maps back to one OpenMetal organization and project.
  • Customers pay OpenMetal; OpenMetal reconciles and pays providers.
  • The public API and TypeScript SDK hide provider-specific lifecycle, usage, and billing differences.

The current provider set is Blaxel, Cloudflare, CodeSandbox, Daytona, E2B, Freestyle, Modal, Northflank, Prime Intellect, Runloop, and Vercel. Provider integrations remain behind one capability-oriented OpenMetal API; they are not separate customer-facing products.

Roadmap

OpenMetal is expanding one portable compute interface in stages:

  • Available now: CPU sandboxes, lifecycle operations, processes, files, leased HTTP endpoints, provider routing, managed credits, BYOK, TypeScript SDK, CLI, and Agent Skill.
  • Coming next: GPU and accelerator workloads for training, inference, and computer use.
  • Planned: browser computers and persistent machines through the same API and capability model.

Architecture

apps/web    --Auth/Realtime-->  Supabase
apps/web    --MetalClient---->  apps/api  --Drizzle-->  PostgreSQL
apps/api    --outbox--------->  apps/worker
PostgreSQL  --realtime.send-->  Supabase Realtime  --Broadcast-->  apps/web
  • apps/web is the Next.js dashboard. It uses Supabase only for login, logout, confirmation, session refresh, and authorized private Realtime channels.
  • apps/api is the only public product API. It authenticates Supabase JWTs, authorizes every org/project operation, and writes product state, a durable event, its private Broadcast, and any outbox jobs in one transaction.
  • apps/worker claims outbox jobs with FOR UPDATE SKIP LOCKED and runs provider, billing, and webhook work. Events it records are broadcast the same way.
  • packages/sdk-typescript (@openmetal/sdk) is the only way the dashboard talks to the OpenMetal API.
  • packages/contracts holds runtime Zod schemas shared by the API and SDK.
  • packages/billing holds credit purchase fees, the organization ledger, Stripe Checkout, and automatic top ups.
  • One Supabase project per environment provides Auth, PostgreSQL, and Realtime.

Prepaid billing uses Stripe Checkout, a 5.5% purchase fee with an $0.80 minimum, and pass-through provider usage. See docs/billing.md.

Prerequisites

  • Node.js 22 or later
  • pnpm 11.13.1
  • Bun 1.3.11 (for standalone CLI builds)
  • Docker (for local Supabase)
  • Supabase CLI 2.105.0 or later (supabase --version)

Installation

pnpm install --frozen-lockfile

OpenMetal CLI

The private @openmetal/cli workspace builds the standalone openmetal binary. It supports browser login for control-plane administration; project API keys for sandbox lifecycle, argv-based processes, binary filesystem operations, and leased HTTP endpoints; streamed process output; and --json/--no-input automation. Interactive PTY/terminal sessions and general SSH, WebSocket, or connection APIs remain outside the public surface.

pnpm --filter @openmetal/cli build
apps/cli/dist/openmetal --help

Tagged cli-v* releases publish checksum-verified binaries for macOS, Linux, and Windows plus a bundled Node.js package at @openmetal/cli. See docs/cli.md for installation, authentication, command examples, profiles, npm bootstrap, and CI usage, and docs/runtime.md for the process, filesystem, and endpoint HTTP contracts.

OpenMetal Agent Skill

Install the OpenMetal skill to give compatible coding agents detailed CLI and TypeScript SDK guidance for configuring, provisioning, observing, and managing sandboxes:

npx skills add superagent-ai/openmetal --skill openmetal

The skill follows the open Agent Skills specification and works with OpenCode, Claude Code, Codex, Cursor, and other clients supported by the Skills CLI. Its source and reference material are in skills/openmetal.

Environment setup

Shared development secrets live in the committed, encrypted .env. That file is OpenMetal-only: do not mix variables from other repositories. The private decryption key is stored on the superagent-team Dotenvx Armor org, not in git.

Machine-specific values (local Supabase URLs and keys) live in gitignored .env.local.

pnpm exec dotenvx armor login
pnpm supabase:start
pnpm env:local

pnpm env:local writes .env.local and apps/web/.env.local from supabase status. It also copies shared Google and GitHub OAuth values from encrypted .env into supabase/.env. Never commit tokens, database passwords, or secret keys.

To change a shared secret:

pnpm exec dotenvx set STRIPE_SECRET_KEY sk_test_... -f .env

dotenvx set encrypts the value in place. After the first encrypt, store this repo's private key on superagent-team with pnpm env:armor. pnpm dev and the integration test scripts load .env.local over encrypted .env through dotenvx.

Local Supabase lifecycle

pnpm supabase:start
pnpm supabase:reset
pnpm supabase:stop
pnpm db:types

Studio is at http://127.0.0.1:55323. Local mail (magic links) is at http://127.0.0.1:55324. The API gateway is at http://127.0.0.1:55321 and Postgres is at 127.0.0.1:55322. These host ports avoid clashing with another local Supabase stack.

Migration workflow

Supabase migrations in supabase/migrations/ are the canonical schema history. Do not generate a second migration stream with Drizzle Kit.

  1. supabase migration new <name>
  2. Edit the generated SQL
  3. pnpm supabase:reset
  4. Update packages/db/src/schema.ts so it matches the SQL
  5. pnpm db:types and commit the generated packages/db/src/database.types.ts

Auth flow

The dashboard uses @supabase/ssr with a browser client, a server client, and src/proxy.ts session refresh.

  • Magic links land on /auth/confirm, which exchanges token_hash for a cookie session.
  • Google and GitHub OAuth land on /auth/callback, which exchanges the PKCE code for a cookie session.

The dashboard then constructs MetalClient with the current Supabase access token. The API verifies that token with supabase.auth.getClaims() and authorizes from the verified sub claim. It never trusts user_metadata.

OAuth client IDs and secrets belong to Supabase Auth, not the Next.js app. Do not set them as NEXT_PUBLIC_* variables. NEXT_PUBLIC_SITE_URL must be the public app origin so redirectTo points at /auth/callback.

Google and GitHub login

Local Auth reads credentials from supabase/.env via config.toml env() substitution. After changing config.toml, run pnpm supabase:stop then pnpm supabase:start. pnpm supabase:reset is not enough.

Local Auth OAuth

These values live in encrypted .env and are copied to supabase/.env by pnpm env:local.

Variable Source
SUPABASE_AUTH_EXTERNAL_GOOGLE_CLIENT_ID Google Cloud OAuth Web client ID
SUPABASE_AUTH_EXTERNAL_GOOGLE_SECRET Google Cloud client secret
SUPABASE_AUTH_EXTERNAL_GITHUB_CLIENT_ID GitHub OAuth App client ID
SUPABASE_AUTH_EXTERNAL_GITHUB_SECRET GitHub OAuth App client secret

OpenMetal's local Auth callback is http://127.0.0.1:55321/auth/v1/callback, not the CLI callback port 54389.

Google Cloud (Web application)

  1. Create an OAuth client of type Web application.
  2. Authorized JavaScript origins: http://localhost:3100 and the production origin.
  3. Authorized redirect URIs must be the Supabase Auth callback, not the Next.js /auth/callback route:
    • Local: http://127.0.0.1:55321/auth/v1/callback
    • Hosted: https://<project-ref>.supabase.co/auth/v1/callback
  4. Scopes: openid, userinfo.email, userinfo.profile.
  5. Paste the client ID and secret into encrypted .env (pnpm exec dotenvx set ... -f .env) and into hosted Authentication → Sign In / Providers → Google. pnpm env:local copies those values into supabase/.env. Leave Skip nonce check off in hosted; local config.toml sets skip_nonce_check = true.

GitHub OAuth App

GitHub allows one callback URL per app, so use separate apps for local and hosted, or configure only the environment you are testing.

  • Homepage URL: the app origin.
  • Authorization callback URL:
    • Local: http://127.0.0.1:55321/auth/v1/callback
    • Hosted: https://<project-ref>.supabase.co/auth/v1/callback

Hosted Supabase Dashboard → Authentication → URL Configuration

  • Site URL: production app origin (must match NEXT_PUBLIC_SITE_URL). The dashboard default http://localhost:3000 is wrong for OpenMetal.
  • Redirect URLs: add https://<prod>/auth/callback, https://<prod>/auth/callback**, and keep /auth/confirm for magic links. Add http://localhost:3100/auth/callback and http://localhost:3100/auth/callback** if you test hosted Auth from local web.

Enable Google and GitHub under Providers and paste the same credentials. No schema migration is required.

Hosted Auth email (Resend SMTP)

Production Auth emails (magic links, confirmations, and invites) send through Resend. Local Auth still uses Mailpit at http://127.0.0.1:55324.

Setting Value
Sender OpenMetal <updates@openmetal.sh>
Host smtp.resend.com
Port 465
Username resend
Password RESEND_API_KEY in encrypted .env (also stored in hosted Auth SMTP settings)

Do not run supabase config push after changing hosted SMTP; local config.toml keeps SMTP disabled so developers keep Mailpit.

API and worker division

The API never calls providers. Realtime broadcasts are written by Postgres in the same transaction as their domain event, so they go out only after commit and never for rolled-back work. Duplicate job delivery is safe: domain events are append-only and unique on event_id, and the dashboard deduplicates by event ID and cursor.

SDK usage

import { MetalClient } from "@openmetal/sdk";

const metal = new MetalClient({
  baseUrl: "https://api.openmetal.sh",
  accessToken: async () => currentSupabaseAccessToken,
});

await metal.health();
await metal.organizations.create({ name: "Northwind", slug: "northwind" });
await metal.projects.create(organizationId, { name: "Alpha", slug: "alpha" });
await metal.events.list({ projectId, after: cursor });

The SDK also exposes processes, filesystem, runtimeOperations, and endpoints namespaces for ready sandboxes. The workspace package remains private, while pnpm --filter @openmetal/sdk build:npm produces the self-contained public npm artifact. See packages/sdk-typescript/README.md for SDK usage and publishing details.

Realtime delivery and cursor recovery

Every domain event is broadcast to its private topic, organization:<id> or project:<id>, by calling realtime.send inside the transaction that records it. Supabase Realtime delivers the message only after that transaction commits, so no service needs the Supabase secret key to publish. Clients subscribe with { private: true }. On subscribe or reconnect they call GET /v1/events?project_id=&after=. Realtime is never the only copy of an event.

Testing

pnpm lint
pnpm typecheck
pnpm test
pnpm test:integration
pnpm test:rls
pnpm test:realtime

Integration, RLS, and Realtime tests require local Supabase plus pnpm env:local so .env.local has this machine's keys.

Common local failures

  • API not ready: start Supabase before pnpm dev. GET /ready checks PostgreSQL.
  • 401 from the API: the dashboard session expired. Refresh the page so proxy can rotate cookies.
  • Realtime CHANNEL_ERROR: confirm the user is a member of the project organization and that the channel is private.
  • Magic link missing: open Mailpit at http://127.0.0.1:55324 rather than a real mailbox.
  • OAuth redirect mismatch: provider callback URLs must be http://127.0.0.1:55321/auth/v1/callback locally. The app route is /auth/callback.
  • Port already allocated: pnpm supabase:stop then start again. API uses 4000, web uses 3100.
  • Encrypted .env will not decrypt: run pnpm exec dotenvx armor login as a member of superagent-team. Do not copy keys from other repositories into this .env.
  • supabase start missing private key: use pnpm supabase:start, not bare supabase start. The CLI reads the encrypted root .env and needs dotenvx to decrypt OAuth secrets first.

Root commands

pnpm dev
pnpm build
pnpm lint
pnpm typecheck
pnpm test
pnpm test:integration
pnpm test:rls
pnpm test:realtime
pnpm format
pnpm format:check
pnpm supabase:start
pnpm supabase:stop
pnpm supabase:reset
pnpm env:local
pnpm env:encrypt
pnpm env:armor
pnpm db:types

Contributing

See CONTRIBUTING.md for development setup, validation, and pull request guidelines.

Security

Report vulnerabilities privately through GitHub Security Advisories. See SECURITY.md for the reporting policy.

License

OpenMetal is available under the MIT License.

About

A universal compute gateway for AI agents

Topics

Resources

Contributing

Security policy

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages