{"id":47321852,"url":"https://github.com/kychee-com/run402","last_synced_at":"2026-06-03T12:00:33.280Z","repository":{"id":342286404,"uuid":"1173507078","full_name":"kychee-com/run402","owner":"kychee-com","description":"MCP server for Run402 — AI-native Postgres + REST + auth + storage + static sites. Pay with x402 USDC on Base. No signups.","archived":false,"fork":false,"pushed_at":"2026-05-31T11:16:22.000Z","size":10672,"stargazers_count":3,"open_issues_count":16,"forks_count":2,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-31T13:09:02.285Z","etag":null,"topics":["ai-agent","backend-as-a-service","database","mcp","micropayments","model-context-protocol","openclaw","postgres","supabase-alternative","x402"],"latest_commit_sha":null,"homepage":"https://run402.com","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kychee-com.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-03-05T12:51:02.000Z","updated_at":"2026-05-31T11:16:24.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/kychee-com/run402","commit_stats":null,"previous_names":["majortal/run402-mcp","kychee-com/run402","kychee-com/run402-mcp"],"tags_count":173,"template":false,"template_full_name":null,"purl":"pkg:github/kychee-com/run402","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kychee-com%2Frun402","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kychee-com%2Frun402/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kychee-com%2Frun402/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kychee-com%2Frun402/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kychee-com","download_url":"https://codeload.github.com/kychee-com/run402/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kychee-com%2Frun402/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33863264,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-03T02:00:06.370Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-agent","backend-as-a-service","database","mcp","micropayments","model-context-protocol","openclaw","postgres","supabase-alternative","x402"],"created_at":"2026-03-17T18:15:48.533Z","updated_at":"2026-06-03T12:00:33.270Z","avatar_url":"https://github.com/kychee-com.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\".github/logo.svg\" width=\"120\" alt=\"run402 logo\"\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003erun402 — Postgres, storage \u0026 deploys for AI agents\u003c/h1\u003e\n\n[![Tests](https://github.com/kychee-com/run402/actions/workflows/test.yml/badge.svg)](https://github.com/kychee-com/run402/actions/workflows/test.yml)\n[![CodeQL](https://github.com/kychee-com/run402/actions/workflows/codeql.yml/badge.svg)](https://github.com/kychee-com/run402/actions/workflows/codeql.yml)\n[![npm: @run402/sdk](https://img.shields.io/npm/v/@run402/sdk?label=%40run402%2Fsdk)](https://www.npmjs.com/package/@run402/sdk)\n[![npm: run402](https://img.shields.io/npm/v/run402?label=run402)](https://www.npmjs.com/package/run402)\n[![npm: run402-mcp](https://img.shields.io/npm/v/run402-mcp?label=run402-mcp)](https://www.npmjs.com/package/run402-mcp)\n[![npm: @run402/functions](https://img.shields.io/npm/v/@run402/functions?label=%40run402%2Ffunctions)](https://www.npmjs.com/package/@run402/functions)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)\n\n[Run402](https://run402.com) gives an agent a full Postgres database, REST API, user auth, content-addressed file storage, static site hosting, serverless functions, and image generation — provisioned with one call, paid with x402 USDC on Base (or Stripe credits). The prototype tier is free on testnet.\n\nThis monorepo ships every surface an agent can pick up:\n\n| Package | Use when… |\n|---------|-----------|\n| [`@run402/sdk`](./sdk/) | Calling Run402 from TypeScript — typed kernel, isomorphic (Node 22 / Deno / Bun / V8 isolates) with a Node entry that auto-loads the local keystore + allowance + x402 fetch |\n| [`run402` CLI](./cli/) | Terminal, scripts, CI, agent-controlled shells — JSON in, JSON out, exit code on failure |\n| [`run402-mcp`](./src/) | Claude Desktop, Cursor, Cline, Claude Code — core Run402 operations as MCP tools |\n| [OpenClaw skill](./openclaw/) | OpenClaw agents (no MCP server required) |\n| [`@run402/functions`](https://www.npmjs.com/package/@run402/functions) | Imported _inside_ deployed functions (`db(req?)`, `adminDb()`, `auth.user()`, `email`, `ai`, `assets`) and for TypeScript autocomplete in your editor. Source lives in the private gateway monorepo (it's bundled into your function zip at deploy time, so it co-evolves with the gateway). |\n\nAll five interfaces share a single typed kernel where appropriate: `@run402/sdk`. MCP tools, CLI subcommands, and OpenClaw scripts are thin shims over SDK calls. `@run402/functions` is the in-function helper that runs inside deployed code; the npm package on the registry stays in lockstep with what the gateway bundles, even though its source ships from the private monorepo. Pick whichever interface fits your runtime.\n\n## 30-second start\n\n```bash\nnpm install -g run402\nrun402 init                                          # creates allowance, requests testnet faucet\nrun402 tier set prototype                            # free on testnet (verifies x402 setup)\nrun402 projects provision --name my-app              # → anon_key, service_key, project_id\nrun402 sites deploy-dir ./dist                       # incremental deploy of a directory → live URL\nrun402 subdomains claim my-app                       # → https://my-app.run402.com\n```\n\nThat's a real Postgres database + a deployed static site, paid for autonomously with testnet USDC.\n\n## The patterns\n\n### Paste-and-go assets — content-addressed URLs with SRI\n\n`assets.put()` returns an `AssetRef` whose `scriptTag()` / `linkTag()` / `imgTag()` emitters produce HTML with the URL, the SRI integrity hash, and modern best-practice attributes (`defer`, `loading=\"lazy\"`, `decoding=\"async\"`, `crossorigin`) already wired. The URL is content-addressed (`pr-\u003cpublic_id\u003e.run402.com/_blob/\u003ckey\u003e-\u003c8hex\u003e.\u003cext\u003e`), served through the v1.33 CDN, and never needs invalidation:\n\n```ts\nimport { run402 } from \"@run402/sdk/node\";\nconst r = run402();\nconst p = await r.project(projectId);\n\nconst logo  = await p.assets.put(\"logo.png\", { bytes: pngBytes });\nconst app   = await p.assets.put(\"app.js\",   { content: jsSource });\nconst style = await p.assets.put(\"app.css\",  { content: css });\n\nconst html = `\n\u003c!doctype html\u003e\n\u003chtml\u003e\n  \u003chead\u003e${style.linkTag()}${app.scriptTag({ type: \"module\" })}\u003c/head\u003e\n  \u003cbody\u003e${logo.imgTag(\"Company logo\")}\u003c/body\u003e\n\u003c/html\u003e\n`;\n```\n\n`immutable: true` is the default — the SDK computes the SHA-256 client-side, the gateway returns a content-hashed URL, and the browser refuses execution on byte mismatch. No cache-invalidation choreography, no waiting, no integrity-attribute construction.\n\n### Dark-by-default tables + the expose manifest\n\nTables you create are unreachable via `/rest/v1/*` until you declare them in a manifest. That closes the \"agent created a table, forgot to set RLS, data leaked\" footgun. The manifest is convergent — applying it twice is a no-op; items removed between applies have their policies, grants, triggers, and views dropped.\n\n```bash\ncat \u003e manifest.json \u003c\u003c'EOF'\n{\n  \"$schema\": \"https://run402.com/schemas/manifest.v1.json\",\n  \"version\": \"1\",\n  \"tables\": [\n    { \"name\": \"items\",  \"expose\": true,  \"policy\": \"user_owns_rows\",\n      \"owner_column\": \"user_id\", \"force_owner_on_insert\": true },\n    { \"name\": \"audit\",  \"expose\": false }\n  ],\n  \"views\": [\n    { \"name\": \"leaderboard\", \"base\": \"items\", \"select\": [\"user_id\", \"score\"], \"expose\": true }\n  ],\n  \"rpcs\": [\n    { \"name\": \"compute_streak\", \"signature\": \"(user_id uuid)\", \"grant_to\": [\"authenticated\"] }\n  ]\n}\nEOF\n\nrun402 projects validate-expose \u003cproject_id\u003e --file manifest.json\nrun402 projects apply-expose    \u003cproject_id\u003e --file manifest.json\nrun402 projects get-expose   \u003cproject_id\u003e\n```\n\nBuilt-in policies: `user_owns_rows` (rows where `owner_column = auth.uid()`; with `force_owner_on_insert: true` a BEFORE INSERT trigger sets it), `public_read_authenticated_write` (anyone reads, any authenticated user writes), `public_read_write_UNRESTRICTED` (fully open; requires `i_understand_this_is_unrestricted: true`), and `custom` (escape hatch — your own `CREATE POLICY` SQL).\n\nUse `run402 projects validate-expose` or the MCP `validate_manifest` tool for a non-mutating feedback loop before applying. Optional migration SQL is used only to check manifest references; it is not executed as a PostgreSQL dry run, and this does not validate deploy manifests.\n\n**Auth-as-SDLC:** put the same JSON under `database.expose` in your v2 `ReleaseSpec`. The gateway validates it against your migration SQL during deploy and rejects mismatches with a structured `errors` array listing every violation.\n\n### Slick deploys — `deployDir` + plan/commit + progress\n\n`deployDir` walks a local directory, hashes every file client-side, asks the gateway _which_ bytes it doesn't already have, and PUTs only those. Re-deploying an unchanged tree returns immediately with `bytes_uploaded: 0`.\n\n```ts\nimport { run402 } from \"@run402/sdk/node\";\n\nconst r = run402();\nconst { url, bytes_uploaded, bytes_total } = await r.sites.deployDir({\n  project: projectId,\n  dir: \"./dist\",\n  onEvent: (e) =\u003e process.stderr.write(JSON.stringify(e) + \"\\n\"),\n});\n```\n\nProgress events stream over `onEvent` (or stderr from the CLI) as unified\n`DeployEvent` JSON objects from the v2 deploy primitive.\n\nCLI:\n\n```bash\nrun402 sites deploy-dir ./dist --project prj_… \u003e result.json 2\u003e events.log\n```\n\n### Same-origin web routes — static site + function ingress\n\nDeploy-v2 routes and static public paths are release resources: they activate atomically with the site, functions, migrations, secrets, and subdomains in the same `deploy apply`. Release static asset paths such as `events.html` are distinct from browser-visible public static paths such as `/events`. Use `site.public_paths` for ordinary clean static URLs; keep routes for function ingress and exact, method-aware static aliases.\n\n```json\n{\n  \"project_id\": \"prj_...\",\n  \"site\": {\n    \"replace\": {\n      \"index.html\": { \"data\": \"\u003c!doctype html\u003e\u003cmain id='app'\u003e\u003c/main\u003e\u003cscript\u003efetch('/api/hello')\u003c/script\u003e\" },\n      \"events.html\": { \"data\": \"\u003c!doctype html\u003e\u003ch1\u003eEvents\u003c/h1\u003e\" }\n    },\n    \"public_paths\": {\n      \"mode\": \"explicit\",\n      \"replace\": {\n        \"/events\": { \"asset\": \"events.html\", \"cache_class\": \"html\" }\n      }\n    }\n  },\n  \"functions\": {\n    \"replace\": {\n      \"api\": {\n        \"runtime\": \"node22\",\n        \"source\": {\n          \"data\": \"export default async function handler(req) { const url = new URL(req.url); return Response.json({ ok: true, path: url.pathname }); }\"\n        }\n      },\n      \"login\": {\n        \"runtime\": \"node22\",\n        \"source\": { \"data\": \"export default async function handler(req) { return Response.json({ ok: true }); }\" }\n      }\n    }\n  },\n  \"routes\": {\n    \"replace\": [\n      { \"pattern\": \"/api/*\", \"methods\": [\"GET\", \"POST\", \"OPTIONS\"], \"target\": { \"type\": \"function\", \"name\": \"api\" } },\n      { \"pattern\": \"/login\", \"methods\": [\"POST\"], \"target\": { \"type\": \"function\", \"name\": \"login\" } }\n    ]\n  }\n}\n```\n\n`site.public_paths.mode: \"explicit\"` means only the complete `public_paths.replace` table is directly reachable as static URLs. In the example, `/events` serves the release asset `events.html`, while `/events.html` is not public unless separately declared. `mode: \"implicit\"` restores filename-derived public reachability and can widen access, so review gateway warnings before confirming it.\n\nOmit `routes` or pass `routes: null` to carry forward base routes. Use `routes: { \"replace\": [] }` to clear the route table. Route entries are an ordered `replace` list, not a path-keyed map. Function targets use `{ \"type\": \"function\", \"name\": \"\u003cmaterialized function name\u003e\" }`. Static route targets use exact patterns only, methods `[\"GET\"]` or `[\"GET\",\"HEAD\"]`, and `{ \"pattern\": \"/events\", \"methods\": [\"GET\",\"HEAD\"], \"target\": { \"type\": \"static\", \"file\": \"events.html\" } }` where `file` is a release static asset path, not a public path, URL, CAS hash, rewrite, or redirect. Use static route targets for method-aware aliases such as static `GET /login` plus function `POST /login`; in explicit public path mode the backing asset can stay private by filename. Direct `/functions/v1/:name` calls remain API-key protected; browser-routed paths are public same-origin ingress.\n\nMatching is exact or final-prefix-wildcard only. `/admin` and `/admin/` are exact trailing-slash equivalents; `/admin/*` matches children but not `/admin`, `/admin/`, `/admin.css`, or `/administrator`, so deploy both `/admin` and `/admin/*` for a routed section root. Query strings are ignored for matching and preserved in the handler's full public `req.url`. Exact routes beat prefix routes; longest prefix wins; method-compatible dynamic routes beat static assets. A `POST /login` route can coexist with static `GET /login` HTML. Unsafe method mismatch returns `405`, and matched dynamic route failures fail closed instead of falling back to static files.\n\nRouted functions use the Node 22 Fetch Request -\u003e Response contract: `export default async function handler(req) { ... }`. `req.method` is the browser method, and `req.url` is the full public URL on managed subdomains, deployment hosts, and verified custom domains. Derive OAuth callbacks from it, for example `new URL(\"/admin/oauth/google/callback\", new URL(req.url).origin)`. Append multiple cookies with `headers.append(\"Set-Cookie\", value)`; redirects, cookies, and query strings are preserved. The raw `run402.routed_http.v1` envelope is internal; do not write route handlers against it.\n\nAvoid routing every static file, broad method lists by default, wildcard static route targets, leading-slash static files, directory shorthand, and one-static-route-target-per-page tables that exhaust route limits. Also watch wildcard function routes that shadow direct public static paths. Warning codes to handle include `STATIC_ALIAS_SHADOWS_STATIC_PATH`, `STATIC_ALIAS_RELATIVE_ASSET_RISK`, `STATIC_ALIAS_DUPLICATE_CANONICAL_URL`, `STATIC_ALIAS_EXTENSIONLESS_NON_HTML`, and `STATIC_ALIAS_TABLE_NEAR_LIMIT`; inspect active routes, `static_public_paths`, and resolve diagnostics to distinguish the route pattern from the backing `asset_path`.\n\nDiagnose public URLs with the URL-first CLI or MCP/SDK equivalents:\n\n```bash\nrun402 deploy diagnose --project prj_123 https://example.com/events --method GET\nrun402 deploy resolve --project prj_123 --url https://example.com/events?utm=x#hero --method GET\nrun402 deploy resolve --project prj_123 --host example.com --path /events --method GET\n```\n\n`deploy_diagnose_url` and `r.project(id).deploy.resolve({ url, method: \"GET\" })` return `would_serve`, `diagnostic_status`, `match`, normalized request data, warnings, full resolution JSON, and next steps. When returned, `asset_path`, `reachability_authority`, and `direct` explain which release asset backs the public URL and whether reachability came from implicit file-path mode, explicit `site.public_paths`, or a route-only static alias. Stable-host diagnostics may also include `authorization_result`, `cas_object` (`sha256`, `exists`, `expected_size`, `actual_size`), hostname-specific `response_variant`, and route/static fields such as `allow`, `route_pattern`, `target_type`, `target_name`, and `target_file`. Known `match` literals are `host_missing`, `manifest_missing`, `active_release_missing`, `unsupported_manifest_version`, `path_error`, `none`, `static_exact`, `static_index`, `spa_fallback`, `spa_fallback_missing`, `route_function`, `route_static_alias`, and `route_method_miss`; preserve unknown future strings. Known `authorization_result` values include `authorized`, `not_public`, `not_applicable`, `manifest_missing`, `target_missing`, `active_release_missing`, `unsupported_manifest_version`, `path_error`, `missing_cas_object`, `unfinalized_or_deleting_cas_object`, `size_mismatch`, and `unauthorized_cas_object`. Known `fallback_state` values include `active_release_missing`, `unsupported_manifest_version`, and `negative_cache_hit`; preserve unknown future strings. `result` is the diagnostic body status, not the HTTP status of the SDK call, so host misses can still be successful CLI/MCP/SDK calls with `would_serve: false`. Do not treat resolve/diagnose as a fetch, cache purge, or cache-policy oracle; route method misses should inspect `allow`, and CAS authorization/health failures should inspect or redeploy the affected static asset. Branch on structured JSON fields such as `cache_class` and preserve unknown cache classes.\n\nRelease observability exposes stable asset identity and public reachability. Inventories include `release_generation`, `static_manifest_sha256`, nullable `static_manifest_metadata` (`file_count`, `total_bytes`, `cache_classes`, `cache_class_sources`, `spa_fallback`), and `static_public_paths[]` when returned. `site.paths` lists release static assets; `static_public_paths[]` lists browser-visible public paths with `public_path`, `asset_path`, `reachability_authority`, `direct`, cache class, and content type. Plan and release diffs expose `static_assets` counters: unchanged/changed/added/removed, `newly_uploaded_cas_bytes`, `reused_cas_bytes`, `deployment_copy_bytes_eliminated`, `legacy_immutable_warnings`, `previous_immutable_failures`, and `cas_authorization_failures`.\n\nRuntime route failure codes to branch on: `ROUTE_MANIFEST_LOAD_FAILED` (manifest/propagation), `ROUTED_INVOKE_WORKER_SECRET_MISSING` (custom-domain Worker secret), `ROUTED_INVOKE_AUTH_FAILED` (internal invoke signature), `ROUTED_ROUTE_STALE` (selected route failed release revalidation), `ROUTE_METHOD_NOT_ALLOWED` (method mismatch), and `ROUTED_RESPONSE_TOO_LARGE` (body over 6 MiB).\n\n### GitHub Actions OIDC deploys — link once, deploy with the same CLI\n\nFor repo-driven deploys, Run402 does not need service keys or allowance files in GitHub secrets. Run a local link command once:\n\n```bash\nrun402 ci link github --project prj_... --manifest run402.deploy.json\n# Optional route authority for CI route declarations:\nrun402 ci link github --project prj_... --manifest run402.deploy.json --route-scope /admin --route-scope /api/*\n```\n\nThat creates a deploy-scoped `/ci/v1/*` binding and writes a workflow that grants `id-token: write`, checks out the repo, and runs the existing deploy primitive:\n\n```yaml\npermissions:\n  contents: read\n  id-token: write\n\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - name: Deploy to run402\n        run: npx --yes run402@1.60.0 deploy apply --manifest 'run402.deploy.json' --project 'prj_...'\n```\n\nCI deploys are intentionally narrow: `site`, `functions`, `database`, absent/current `base`, and route declarations only when the binding has covering `--route-scope` patterns. Without route scopes, CI cannot ship `routes`. Keep secrets, domains, subdomains, checks, non-current base, and broader trust changes in a local allowance-backed deploy. If the gateway returns `CI_ROUTE_SCOPE_DENIED`, re-link with exact scopes like `/admin` or final-wildcard scopes like `/api/*`, or deploy locally. Manage bindings with `run402 ci list` and `run402 ci revoke`.\n\n### In-function helpers — caller-context vs BYPASSRLS\n\nInside a deployed function, import from `@run402/functions`. Two distinct DB clients keep RLS clean:\n\n```ts\nimport { db, adminDb, auth, email, ai } from \"@run402/functions\";\n\nexport default async (req: Request) =\u003e {\n  const user = await auth.requireUser();\n\n  // Caller-context — db() mints a 60s actor JWT so run402.current_user_id() resolves in RLS.\n  // No .eq(\"user_id\", user.id) needed — RLS already binds the visitor's rows; the redundant\n  // filter is a deploy-fail (R402_AUTH_REDUNDANT_USER_FILTER) under @run402/functions v3.0+.\n  const mine = await db().from(\"items\").select(\"*\");\n\n  // BYPASSRLS — for platform-authored writes (audit logs, cron cleanup, webhook handlers).\n  await adminDb().from(\"audit\").insert({ event: \"items_read\", user_id: user.id });\n\n  // Send mail from the project's mailbox — discovers it automatically.\n  if (mine.length === 0) {\n    await email.send({ to: user.email, subject: \"Welcome\", html: \"\u003ch1\u003ehi\u003c/h1\u003e\" });\n  }\n\n  return Response.json(mine);\n};\n```\n\n`adminDb().sql(query, params?)` runs raw parameterized SQL and always bypasses RLS. It returns a flat `Promise\u003cRecord\u003cstring, unknown\u003e[]\u003e` (just the rows — no envelope):\n\n```ts\nimport { adminDb, auth } from \"@run402/functions\";\n\nexport default async (req: Request) =\u003e {\n  const user = await auth.requireUser();\n\n  const rows = await adminDb().sql(\n    \"SELECT count(*)::int AS n FROM items WHERE user_id = $1\",\n    [user.id],\n  );\n  const n = (rows[0]?.n as number | undefined) ?? 0;\n  return Response.json({ count: n });\n};\n```\n\n`@run402/functions` is auto-bundled into deployed code; install it in your editor for full TypeScript autocomplete (also works at build time for static-site generation with `RUN402_SERVICE_KEY` + `RUN402_PROJECT_ID` set).\n\n`ai.generateImage({ prompt, aspect? })` is available inside deployed functions for live app flows such as generated avatars or OG images. It calls the project runtime image endpoint with `RUN402_SERVICE_KEY`, so deployed functions do not need allowance wallets or x402 signing code. Aspects are `square`, `landscape`, and `portrait`; the result is `{ image, content_type, aspect }` with base64 image bytes. Runtime image generation is billed, rate-limited, and spend-capped against the project billing account; public routed functions should authenticate/rate-limit their users before calling it.\n\n`assets.put(key, source, opts?)` uploads bytes from inside a deployed function through the same CAS-backed apply substrate as deploy-time assets. It uses `RUN402_SERVICE_KEY`, accepts a string, `Uint8Array`, or `{ content | bytes }`, and returns an SDK-compatible `AssetRef` with mutable and immutable URLs.\n\n**Calling from outside a function entirely** (raw `curl`/`fetch` from CI scripts, bash bootstrappers, non-TS runtimes) — service-key writes go to `/admin/v1/rest/\u003ctable\u003e`, not `/rest/v1/*`. The gateway 403s service-role tokens on `/rest/v1/*` so a leaked key can't silently bypass RLS, which means `curl ... \u003e /dev/null` against the wrong path looks like success but writes nothing. SQL-shaped admin work uses `POST /projects/v1/admin/:id/sql` (or `run402 projects sql`).\n\n```bash\ncurl -X POST https://api.run402.com/admin/v1/rest/audit \\\n  -H \"Authorization: Bearer $RUN402_SERVICE_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"event\":\"seed\",\"ts\":\"2026-04-30\"}'\n```\n\n## SDK — `@run402/sdk`\n\n```bash\nnpm install @run402/sdk\n```\n\nTwo entry points:\n\n- **`@run402/sdk`** — isomorphic. Bring your own `CredentialsProvider` (a session-token shim, a remote vault, anything that resolves project keys + auth headers). Works in Node 22, Deno, Bun, V8 isolates.\n- **`@run402/sdk/node`** — Node-only convenience. Reads `~/.config/run402/projects.json`, signs x402 payments from the local allowance, exposes `sites.deployDir(...)`, `fileSetFromDir(...)`, and typed deploy-manifest helpers (`loadDeployManifest`, `normalizeDeployManifest`).\n\n```ts\nimport { run402 } from \"@run402/sdk/node\";\n\nconst r = run402();\nconst project = await r.projects.provision({ tier: \"prototype\" });\nconst p = await r.project(project.project_id);\nawait p.assets.put(\"hello.txt\", { content: \"hi\" });\n```\n\nThe SDK is organised as 23 namespaces: `projects`, `assets`, `cache`, `ci`, `sites`, `functions`, `jobs`, `secrets`, `subdomains`, `domains`, `email` (+ `webhooks`), `senderDomain`, `auth`, `apps`, `tier`, `billing`, `contracts`, `ai`, `allowance`, `service`, `admin`, `operator` (the human/email operator session — browser-delegated `login` + `overview` across every wallet that verified your email), plus the `r.project(id).apply` hero for atomic mixed writes (release slices + assets slice via `/apply/v1/*`). Every operation throws a typed `Run402Error` subclass on failure: `PaymentRequired`, `ProjectNotFound`, `Unauthorized`, `ApiError`, `NetworkError`, `LocalError`, `Run402DeployError`. `apply()` automatically re-plans safe current-base `BASE_RELEASE_CONFLICT` races and emits `apply.retry` progress events. See [`sdk/README.md`](./sdk/README.md).\n\n**Astro SSR + ISR cache (v1.52+).** For Astro apps, use `@run402/astro` 1.0+ — `export default run402();` in `astro.config.mjs` returns an `AstroUserConfig` composing the SSR adapter (Lambda + SnapStart + ISR cache + AsyncLocalStorage request-context), image integration, and build-time detectors. Functions opt into the SSR class via `FunctionSpec.class: \"ssr\"` in `ReleaseSpec`; the gateway provisions SnapStart and caches HTML responses keyed by `(host, path, search, method, locale, release_id)`. Cache is bypass-by-default (no-store unless `Cache-Control` explicitly allows it AND no `Set-Cookie` AND no auth-taint flag from `auth.*` helpers / payment primitives). Invalidate from in-function code or out-of-band: `r.cache.invalidate(url)` / `r.cache.invalidatePrefix({ host, prefix })` / `r.cache.invalidateAll({ host })` (SDK), `run402 cache invalidate \u003curl\u003e` (CLI). Inspect cached state with `r.cache.inspect(url)` / `run402 cache inspect \u003curl\u003e`. Agent DX helpers also in the CLI: `run402 doctor` (5 health checks), `run402 dev` (Astro dev with `.env.local`), `run402 logs --request-id req_...` (correlate across functions). Full reference at [`astro/README.md`](./astro/README.md) and [`cli/llms-cli.txt`](./cli/llms-cli.txt) (R402_* SSR Runtime Error Codes section).\n\n## CLI — `run402`\n\n```bash\nnpm install -g run402\n```\n\nEvery subcommand prints JSON to stdout, JSON errors to stderr, exits 0 on success and 1 on failure — designed for an agent shell, not a human. Full reference: [`cli/llms-cli.txt`](./cli/llms-cli.txt) (also at \u003chttps://run402.com/llms-cli.txt\u003e).\n\n```bash\nrun402 init                              # one-shot allowance + faucet + tier check\nrun402 status                            # account snapshot (allowance, balance, tier, projects)\nrun402 projects provision --name my-app\nrun402 projects sql \u003cid\u003e \"CREATE TABLE …\"\nrun402 projects validate-expose \u003cid\u003e --file manifest.json\nrun402 projects apply-expose \u003cid\u003e --file manifest.json\nrun402 sites deploy-dir ./dist\nrun402 deploy release active --project \u003cid\u003e  # inspect current-live release inventory\nrun402 deploy diagnose --project \u003cid\u003e https://example.com/events --method GET\nrun402 functions deploy \u003cid\u003e \u003cname\u003e --file fn.ts\nrun402 ci link github --project \u003cid\u003e       # GitHub Actions OIDC deploy binding (--route-scope for CI routes)\nrun402 assets put ./asset.png --immutable\nrun402 assets diagnose \u003curl\u003e             # inspect live CDN state for a public URL\nrun402 cdn wait-fresh \u003curl\u003e --sha \u003chex\u003e  # poll until a mutable URL serves the new SHA\n```\n\nThe active project is sticky: `run402 projects use \u003cid\u003e` makes `\u003cid\u003e` the default for every subsequent `\u003cid\u003e`-taking subcommand, so most commands work without it.\n\n## MCP server — `run402-mcp`\n\n```bash\nnpx -y run402-mcp                        # standalone test\n```\n\n### Claude Desktop\n\nAdd to `~/Library/Application Support/Claude/claude_desktop_config.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"run402\": { \"command\": \"npx\", \"args\": [\"-y\", \"run402-mcp\"] }\n  }\n}\n```\n\n### Cursor\n\nAdd to `.cursor/mcp.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"run402\": { \"command\": \"npx\", \"args\": [\"-y\", \"run402-mcp\"] }\n  }\n}\n```\n\n### Cline\n\nAdd to your Cline MCP settings:\n\n```json\n{\n  \"mcpServers\": {\n    \"run402\": { \"command\": \"npx\", \"args\": [\"-y\", \"run402-mcp\"] }\n  }\n}\n```\n\n### Claude Code\n\n```bash\nclaude mcp add run402 -- npx -y run402-mcp\n```\n\n## OpenClaw skill\n\n```bash\ncp -r openclaw ~/.openclaw/skills/run402\ncd ~/.openclaw/skills/run402/scripts \u0026\u0026 npm install\n```\n\nEach script re-exports from `cli/lib/*.mjs` — the OpenClaw command surface is identical to the CLI command surface by construction. See [`openclaw/README.md`](./openclaw/README.md).\n\n## MCP tools\n\nThe full MCP surface — every tool is a thin shim over an SDK call.\n\n### Database\n\n| Tool | Description |\n|------|-------------|\n| `provision_postgres_project` | Provision a new database. Auto-handles x402 payment. |\n| `run_sql` | Execute SQL (DDL or queries). Returns a markdown table. |\n| `rest_query` | Query/mutate via PostgREST. |\n| `apply_expose` | Apply the declarative authorization manifest (tables, views, RPCs). Convergent — drops items removed between applies. |\n| `validate_manifest` | Validate the auth/expose manifest without applying it. Accepts manifest object/string, optional `migration_sql`, optional `project_id`. |\n| `get_expose` | Return the current manifest. `source` is either `applied` (from the tracking table) or `introspected` (regenerated from live DB state). |\n| `get_schema` | Introspect tables, columns, types, constraints, RLS policies. |\n| `get_usage` | Per-project usage report (API calls, storage, lease expiry). |\n| `promote_user` / `demote_user` | Manage `project_admin` role on a project user. |\n| `delete_project` | Cascade purge — schema, Lambdas, S3 site files, deployments, secrets, published versions. Irreversible. |\n\n### Asset storage (content-addressed CDN)\n\n| Tool | Description |\n|------|-------------|\n| `assets_put` | Upload an asset (any size, up to 5 TiB) via direct-to-S3 presigned URLs. Returns an `AssetRef` with `scriptTag()` / `linkTag()` / `imgTag()` emitters. |\n| `assets_get` | Download an asset to a local file. |\n| `assets_ls` | Keyset-paginated list with prefix filter. |\n| `assets_rm` | Delete an asset. |\n| `assets_sign` | Time-boxed presigned GET URL for a private asset. |\n| `diagnose_public_url` | Live CDN state for a public URL — expected vs observed SHA, cache headers, invalidation status. |\n| `wait_for_cdn_freshness` | Poll a mutable URL until it serves the expected SHA-256. |\n\n### Sites \u0026 subdomains\n\n| Tool | Description |\n|------|-------------|\n| `deploy_site` | Deploy a static site from inline file bytes. |\n| `deploy_site_dir` | Deploy a static site from a local directory. Routes through the unified deploy primitive (CAS-backed) — only uploads bytes the gateway doesn't have. |\n| `claim_subdomain` | Claim `\u003cname\u003e.run402.com` (idempotent; reassigns to latest deployment on subsequent deploys). |\n| `list_subdomains` / `delete_subdomain` | Manage subdomains. |\n| `add_custom_domain` / `list_custom_domains` / `check_domain_status` / `remove_custom_domain` | Point your own domain at a Run402 subdomain. |\n| `deploy` / `deploy_resume` / `deploy_list` / `deploy_events` | Apply, resume, list, and inspect deploy operations. |\n| `deploy_release_get` / `deploy_release_active` / `deploy_release_diff` | Inspect release inventory and release-to-release diffs without starting a new deploy mutation. |\n| `deploy_diagnose_url` | URL-first deploy resolver diagnostics. Params: `project_id`, either `url` or `host`/`path`, optional `method`; returns `would_serve`, `diagnostic_status`, `match`, warnings, next steps, and fenced JSON. |\n\n### CI/OIDC bindings\n\n| Tool | Description |\n|------|-------------|\n| `ci_create_binding` | Create a GitHub Actions CI deploy binding from a locally signed delegation. Optional `route_scopes` delegate exact paths like `/admin` or final wildcards like `/api/*`; omitted means no CI route authority. |\n| `ci_list_bindings` / `ci_get_binding` / `ci_revoke_binding` | Inspect and revoke CI bindings, including returned `route_scopes`. |\n\n### Functions \u0026 secrets\n\n| Tool | Description |\n|------|-------------|\n| `deploy_function` | Deploy a Node 22 serverless function. Cron-schedulable. |\n| `invoke_function` | Invoke a deployed function over the direct API-key-protected test path. |\n| `get_function_logs` | Recent logs (CloudWatch), filterable by `since` and routed `request_id`. |\n| `update_function` | Update schedule / timeout / memory without redeploying code. |\n| `list_functions` / `delete_function` | List / remove functions. |\n| `set_secret` / `list_secrets` / `delete_secret` | Manage `process.env` secrets injected into all functions. Values are write-only; list returns keys and timestamps only. |\n| `jobs_submit` / `jobs_get` / `jobs_logs` / `jobs_cancel` | Submit and inspect fixed platform-managed jobs. Requests use the gateway jobs shape; the SDK supplies the required idempotency header. |\n\n### Auth \u0026 email\n\n| Tool | Description |\n|------|-------------|\n| `request_magic_link` | Send a passwordless login email. |\n| `verify_magic_link` | Exchange the magic link token for `access_token` + `refresh_token`. |\n| `create_auth_user` / `invite_auth_user` | Create/update auth users and send trusted service-key invites. |\n| `set_user_password` | Change, reset, or set a user's password. |\n| `auth_settings` | Configure password set, preferred sign-in method, public signup policy, and project-admin passkey enforcement. |\n| `passkey_register_options` / `passkey_register_verify` | Create and verify WebAuthn passkey registration ceremonies. |\n| `passkey_login_options` / `passkey_login_verify` | Create and verify WebAuthn passkey login ceremonies. |\n| `list_passkeys` / `delete_passkey` | List or delete the authenticated user's passkeys. |\n| `create_mailbox` / `get_mailbox` / `delete_mailbox` | Per-project mailbox at `\u003cslug\u003e@mail.run402.com`. |\n| `send_email` | Template (`project_invite`, `magic_link`, `notification`) or raw HTML. Single recipient. |\n| `list_emails` / `get_email` / `get_email_raw` | Read messages. `get_email_raw` returns RFC-822 bytes for DKIM / zk-email verification. |\n| `register_mailbox_webhook` / `list_mailbox_webhooks` / `get_mailbox_webhook` / `update_mailbox_webhook` / `delete_mailbox_webhook` | Email-event webhooks (delivery, bounced, complained, reply_received). |\n| `register_sender_domain` / `sender_domain_status` / `remove_sender_domain` | Send from your own domain (DKIM verified). |\n| `enable_sender_domain_inbound` / `disable_sender_domain_inbound` | Receive replies on your custom sender domain. |\n\n### AI helpers\n\n| Tool | Description |\n|------|-------------|\n| `generate_image` | Text-to-PNG via x402 ($0.03 / image). |\n| `ai_translate` | Translate text. Metered per project. |\n| `ai_moderate` | Moderate text (free). |\n| `ai_usage` | Translation quota (used / included / remaining). |\n\n### Apps marketplace\n\n| Tool | Description |\n|------|-------------|\n| `browse_apps` | Browse public forkable apps. |\n| `get_app` | Inspect an app, including expected `bootstrap_variables`. |\n| `fork_app` | Clone schema + site + functions into a new project. Runs the app's `bootstrap` function with provided variables. |\n| `publish_app` | Publish a project as a forkable app. |\n| `list_versions` / `update_version` / `delete_version` | Manage published versions. |\n\n### Tier \u0026 billing\n\n| Tool | Description |\n|------|-------------|\n| `set_tier` | Subscribe / renew / upgrade a tier (auto-detects action). x402 payment. |\n| `tier_status` | Current tier, lease expiry, usage, and function authoring caps when returned. |\n| `get_quote` | Tier pricing (free, no auth). |\n| `tier_checkout` | Stripe checkout for a tier (alternative to x402). |\n| `create_email_billing_account` / `link_wallet_to_account` | Email-based billing accounts; hybrid Stripe + x402. |\n| `billing_history` | Ledger history. |\n| `buy_email_pack` | $5 for 10,000 emails (never expire). |\n| `set_auto_recharge` | Auto-buy email packs when credits run low. |\n\n### KMS contract wallets (on-chain signing)\n\n| Tool | Description |\n|------|-------------|\n| `provision_contract_wallet` | AWS KMS-backed Ethereum wallet. $0.04/day rental + $0.000005 per call. Private keys never leave KMS. |\n| `get_contract_wallet` / `list_contract_wallets` | Metadata + live native balance. |\n| `set_recovery_address` / `set_low_balance_alert` | Optional safety nets. |\n| `contract_call` | Submit a write call (chain gas at-cost + KMS sign fee). |\n| `contract_read` | Read-only call (free). |\n| `get_contract_call_status` | Lifecycle, gas, receipt. |\n| `drain_contract_wallet` | Drain native balance (works on suspended wallets — the safety valve). |\n| `delete_contract_wallet` | Schedule KMS key deletion (refused if balance ≥ dust). |\n\n### Allowance \u0026 account\n\n| Tool | Description |\n|------|-------------|\n| `init` | One-shot setup: allowance + faucet + tier check + project list. |\n| `status` | Full account snapshot (allowance, balance, tier, projects). |\n| `allowance_status` / `allowance_create` / `allowance_export` | Local allowance management. |\n| `request_faucet` | Request testnet USDC. |\n| `check_balance` | USDC balance for an allowance address. |\n| `list_projects` | Active projects for a wallet. |\n| `pin_project` | Pin a project (admin only — uses the configured admin allowance wallet). |\n| `project_info` / `project_keys` / `project_use` | Inspect / set the active project. |\n| `create_checkout` | Stripe checkout to add cash credit. |\n| `send_message` | Send feedback to the Run402 team. |\n| `set_agent_contact` / `get_agent_contact_status` / `verify_agent_contact_email` | Register agent contact info, read assurance status, and start the operator email reply challenge. |\n| `start_operator_passkey_enrollment` | Email a Run402 operator passkey enrollment link to the verified contact email. |\n| `get_operator_status` | Compact operator-health snapshot — contact assurance state, critical items, skipped notifications, billing accounts, projects, active thresholds. Read via `run402 doctor` or directly. |\n| `get_notification_preferences` / `set_notification_preferences` | Read/update operator notification preferences (cadence, channels, per-class toggles, locale, timezone). Cross-wallet effects require `email_verified`; webhook URL changes require `operator_passkey`. |\n| `list_notifications` | Per-delivery-attempt audit log. Paginated, filterable by event_type / since. |\n| `test_notification` | Fire a real test notification through the full worker pipeline. Audit row marked `is_test=true`. Rate-limited per wallet at 1/min. |\n| `rotate_webhook_secret` | Generate a new HMAC signing secret for the operator webhook (returned exactly once). Previous secret remains valid for 24h. Requires `operator_passkey`. |\n\n### Service status (no auth)\n\n| Tool | Description |\n|------|-------------|\n| `service_status` | Public availability report — 24h/7d/30d uptime per capability, operator, deployment topology. |\n| `service_health` | Liveness probe with per-dependency results. |\n\n## Configuration\n\n| Variable | Default | Purpose |\n|----------|---------|---------|\n| `RUN402_API_BASE`        | `https://api.run402.com`         | API base URL (override for staging) |\n| `RUN402_CONFIG_DIR`      | `~/.config/run402`               | Local credential storage base directory (named wallets live under `profiles/\u003cname\u003e/`) |\n| `RUN402_WALLET`          | `default`                        | Active named wallet (profile). Overridden by `--wallet \u003cname\u003e` and per-directory `.run402.json`; `RUN402_PROFILE` is an alias. See `run402 wallets`. |\n| `RUN402_ALLOWANCE_PATH`  | `{config_dir}/allowance.json`    | Custom allowance file path |\n\nLocal state lives at:\n\n- `~/.config/run402/projects.json` (`0600`) — `{ projects: { \u003cid\u003e: { anon_key, service_key, tier, lease_expires_at } } }`\n- `~/.config/run402/allowance.json` (`0600`) — wallet for x402 signing\n\n`anon_key` and `service_key` have no expiry — lease enforcement happens server-side. Rotate them by deleting the project and re-provisioning.\n\n## Development\n\n```bash\nnpm run build           # builds core/, sdk/, then the MCP server\nnpm test                # SKILL + sync + unit tests\nnpm run test:e2e        # 47 CLI end-to-end tests\nnpm run test:sync       # checks MCP/CLI/OpenClaw/SDK stay in sync\nnpm run test:skill      # validates SKILL.md frontmatter + body\n```\n\nArchitecture: every tool / subcommand / skill script is a thin shim over an `@run402/sdk` call. `core/` holds Node-only filesystem primitives (keystore, allowance, SIWE signing) wrapped by the SDK's Node provider. See [`CLAUDE.md`](./CLAUDE.md) for the full layout.\n\n## Links\n\n- Web: \u003chttps://run402.com\u003e\n- API docs (HTTP): \u003chttps://run402.com/llms.txt\u003e · \u003chttps://run402.com/openapi.json\u003e\n- CLI docs: \u003chttps://run402.com/llms-cli.txt\u003e\n- Status: \u003chttps://api.run402.com/status\u003e\n- Health: \u003chttps://api.run402.com/health\u003e\n\n[简体中文](./README.zh-CN.md)\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkychee-com%2Frun402","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkychee-com%2Frun402","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkychee-com%2Frun402/lists"}