flats

module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 6, 2026 License: MIT

README

flats caretaker gopher logo

Open-source alternative to sites/artifacts

Flats

Flats lets agents deploy sites and small server apps directly to your own machine: Private by default through local loopback or permitted Tailscale, optionally Public through permitted Portal or Tailscale Funnel, with no hosted Flats control plane.

Your machine runs the code and keeps the versions, SQLite databases, persistent files and secrets. Flats is a single CGO-free Go binary for macOS and Linux, with a CLI, HTTP API, MCP endpoint and web console.

  • Each flat and preview gets its own origin; explicitly permitted Tailscale routes get their own private node.
  • Draft saves preserve Current; approved, successful publishes create immutable numbered versions. Health checks and previews use isolated data.
  • Static sites and sandboxed JavaScript/WASI server apps share the same deploy flow.
  • Publish, activation, rollback and both visibility directions wait for an approval in the web console.
Agent / CLI / web console → Flats on your machine → version + local data
                                    │
                                    ├─ Tailscale → private browser origin
                                    └─ Portal relay → optional public browser origin

Install and try a local flat

On macOS or Linux (amd64 or arm64), run the installer:

curl -fsSL https://raw.githubusercontent.com/gosuda/flats/main/install.sh | sh

It downloads the latest release binary for your platform, verifies its SHA-256 checksum, installs it to ~/.local/bin and runs Flats as a background service: a launchd agent on macOS or a systemd user service on Linux. The service starts at login and restarts if it stops; on Linux the installer also enables lingering when allowed, so Flats keeps running after you log out. It runs flats serve --config with a new config.json in the data directory described below, using the defaults (Local network, Portal off). Configuration and storage lists every setting and where Flats keeps each kind of data.

Open http://127.0.0.1:7878 for the console.

Run the installer again to upgrade: it replaces the binary and restarts the service with its existing settings. A service installed by an earlier release still passes flats serve flags; on its first start the new release moves those flags and the stored console settings into config.json, and the installer prints the flats install command that switches the service to run from that file. Options go after sh -s --: --version v1.2.3 installs a specific release, --dir DIR changes the install directory, --no-service installs only the binary, and flats serve flags after a second -- reinstall the service with those flags written into config.json:

curl -fsSL https://raw.githubusercontent.com/gosuda/flats/main/install.sh | sh -s -- -- --network tailscale

If the service is stopped (not running), the installer only replaces the binary and prints how to start it. Releases include a GitHub build provenance attestation; check an archive with gh attestation verify flats_<os>_<arch>.tar.gz --repo gosuda/flats. The installer does not use sudo or edit shell profiles. flats status shows whether the host answers, and flats uninstall removes the service while keeping your data. The installer does not back up data; see Releases and installation for upgrade backups and how releases are cut. To build from source instead (Go 1.27.1 or newer):

go install github.com/gosuda/flats/cmd/flats@latest   # highest stable release; @main for unreleased changes
# From a checkout:
CGO_ENABLED=0 go build -o flats ./cmd/flats
Container

A Linux image for amd64 and arm64 is published with every release. Its /data volume holds config.json, flat data and materialized docs runtime modules; an empty volume starts with the defaults. On Linux, run it with host networking so the console, CLI and agents reach it on loopback as with a native install:

docker run -d --name flats --restart unless-stopped --network host \
  -v flats-data:/data ghcr.io/gosuda/flats:latest

On macOS or other Docker hosts, publish the same ports on 127.0.0.1 only, or use Tailscale. Never publish the console port to your network: anything that reaches it can approve deploys. See Running Flats in a container for each network mode, configuration, hardening, upgrades and building the image.

Foreground host

Without the service, start a foreground host in one terminal:

flats serve --config ./flats-demo-data/config.json

The first start finds no config.json, so it creates one with the defaults (Local network, Portal off) and an empty database in ./flats-demo-data, and logs that it initialized a new host. It never does that over a directory that already holds Flats data. The console is at http://127.0.0.1:7878.

First flat

Request publication of a minimal static site:

mkdir -p hello
printf '<h1>Hello from Flats</h1>\n' > hello/index.html
flats deploy ./hello --flat hello

The deploy command saves Draft and returns a pending publish request (exit code 3, including with --json). Follow its approval URL and approve the frozen Draft in the console before opening http://hello.localhost:7879; the console is at http://127.0.0.1:7878. Local mode serves loopback development origins and does not join Tailscale. To use other ports, set host.management_addr and host.local_addr with flats config set while the host is stopped, or pass --listen and --local-addr for one run. Each running host needs its own data directory; concurrent hosts sharing one directory are rejected before store or runtime startup.

Private and public deployment

The default network is Local; every other provider is off. Turn a provider on for the host in Settings → Network providers, grouped into Private (Local, Tailscale) and Public (Tailscale Funnel, Portal). That saves network.permitted in config.json and starts the provider's backend without a restart; nothing is served until you also allow the provider for a flat under Networks on the flat's page. Turning a provider off is refused while a flat still allows it. Permitting Tailscale does not permit Funnel. To run the console and private routes on your tailnet as well, set the private backend while the host is stopped:

flats config set network.permitted tailscale
flats config set network.private_backend tailscale

Enable MagicDNS and HTTPS certificates in your tailnet, then follow the node login links in the console, or put a reusable, untagged Tailscale auth key in a protected file and set its path as credentials.tailscale_authkey_file. A host started with --config refuses TS_AUTHKEY and the other TS_* variables, which would bypass config.json. Flats embeds tsnet; permitted flat and preview routes, and the console in Tailscale mode, use separate nodes. Tailnet ACLs decide which devices can reach those origins. Code and data stay on your machine.

Removing a flat’s Tailscale permission stops its Private Tailscale current, preview and redirect routes before saving the revocation; Local stays available, and a separately permitted Public Tailscale Funnel route stays up. An unconfirmed stop keeps permission allowed so the operator can retry. A currently Public flat must complete an approved change to Private before its Public provider can be revoked.

Visibility is Private or Public, independently of publication and connection state. Private routes use Local or permitted Tailscale with existing tailnet ACLs; Public routes use explicitly permitted Portal or Tailscale Funnel. Configuration alone never publishes a version or changes visibility. Public requires a published version and an approved transition with a registered, configured and permitted public route. A route may still be Connecting after approval; open its link only after it reports ready. Legacy listed/unlisted inputs map to Public. Relay availability and Tailscale connectivity are external dependencies.

flats install runs flats serve --config at login using launchd on macOS or a systemd user service on Linux, writing any flags you pass into config.json first; flats uninstall removes the service and keeps data and config.json. The default data directory comes from your OS user configuration directory (~/Library/Application Support/Flats on macOS); --data or FLATS_DATA selects another one, and its config.json sits inside it.

Upgrading an existing host

Back up the data directory and stop the old host before starting the upgrade. The first start of this release moves the old serve flags, network-provider.json and the console settings into config.json, after backing up the database to backups/; see Configuration and storage. Until the service is reinstalled with flats install, later starts check that the flags they are given still match config.json. Published versions keep their numbers; saved-only versions become Private Draft revisions. Historical network configuration does not create provider permissions. Existing Public policy can remain Public while its route is unavailable; do not report a link as reachable until its endpoint is ready and has been checked.

Restore intended host grants explicitly: --network tailscale grants Tailscale, --portal=true grants Portal, or --permit tailscale,tailscale-funnel,portal records only the listed grants. --portal=false disables Portal even if its stored grant remains. A Tailscale grant does not grant Funnel. Configure the relevant backend and allow each desired provider separately under Networks on each flat’s page. Neither a host grant nor a per-flat grant publishes or changes visibility; both Private→Public and Public→Private still require approval. Schema 5 records pre-lifecycle flats as eligible for a one-time Private Tailscale choice. An old default-Tailscale host with historical tsnet state must explicitly choose --network tailscale to preserve eligible flats’ Private Tailscale opt-in, or --network local to consume eligibility as Local-only. Ambiguous startup stops before networking. An already persisted explicit Tailscale backend/grant also preserves eligible flats once. Hosts with neither historical tsnet state nor a persisted Tailscale choice consume eligibility as Local-only on their first upgraded startup; a later host Tailscale grant never opts those flats in. Existing explicit denials win; new flats and later revocations are never opted in by restart. Neither choice grants Funnel or Portal.

Local loopback also binds in Tailscale mode: concurrent hosts need distinct host.management_addr, host.local_addr and data directories.

Flats also supports Markdown documents with type: "docs". The embedded collaborative editor runs on the server-flat runtime with private editing and public reading. Agents read live edits with get_document, save a Draft with save_document, then request operator approval to publish. Agent publications and code rollbacks merge into live Markdown while keeping independent human edits (conflicting lines prefer the target version). Read the content types contract through MCP resource flats://docs/content-types/v1 or get_content_types. Overlapping edits and bounded merge fallback preserve recent pre-activation live text for private recovery (newest 8 records within 2 MiB per document); editors see a notice and view/copy link. Agent reads list conflict metadata rather than full preserved documents. Retrieve preserved text with get_document {slug, conflict: generation}; optional doc selects the file.

Static and server flats

Static builds need an index.html. For a JavaScript server, include flats.json containing {"kind":"server"} and a server.js ES module:

export default {
  async fetch(request, env) {
    env.DB.exec("CREATE TABLE IF NOT EXISTS hits (n INTEGER)");
    env.DB.exec("INSERT INTO hits VALUES (1)");
    const [{ n }] = env.DB.query("SELECT count(*) AS n FROM hits");
    return Response.json({ hits: n });
  }
};

Deploy that directory with the same flats deploy command. JavaScript runs in QuickJS on WebAssembly with SQLite (env.DB), persistent string files (env.FILES), Web Crypto and WebSocket callbacks. It has no Node.js APIs; bundle imports into the uploaded version. Server-side fetch can call exact operator-granted HTTP(S) origins with bounded host enforcement. Browser-side fetch uses normal CORS/CSP protections. See external API setup and examples.

A .wasm server is a WASI preview1 command instantiated afresh per request: request JSON on stdin, response JSON on stdout, only the flat's configured environment variables and secrets, with no inherited host environment, plus clocks and randomness. It has no SQLite/FILES host ABI, filesystem mounts, outbound network or WebSocket API. See the capability table and response format.

Data survives deploys and ordinary rollbacks. flats rollback hello restores code; --restore-data requests a separately frozen restore approval for the pre-deploy DB and FILES snapshot. Historical DB-only snapshots preserve current FILES. Preview data is isolated from live data.

Agent integration

Run flats mcp-config for Claude Code, Codex and Cursor setup. For a host on a custom management port, use flats mcp-config --url http://127.0.0.1:17878. For example:

claude mcp add --transport http flats http://127.0.0.1:7878/mcp
codex mcp add flats --url http://127.0.0.1:7878/mcp

Cursor project .cursor/mcp.json:

{"mcpServers":{"flats":{"url":"http://127.0.0.1:7878/mcp"}}}

Restart/reconnect your client, list tools, then read resource flats://docs/runtime-api/v1 or call get_runtime_reference with {}. The runtime API v1 reference is embedded in the host and available through MCP without an installed skill or source checkout. It contains complete synchronous FILES/DB signatures, text/binary semantics, limits, handler examples and approvals. For a minimal MCP static walkthrough, call save_version with {"slug":"hello","files":[{"path":"index.html", "content":"<h1>Hello</h1>","encoding":"utf8"}],"deploy":true}. For a server, include the flats.json and server.js shown above in the same complete inline file list. Fetch the returned URL and check get_flat/get_logs; a successful save alone does not establish a live site, and requesting deploy still waits for operator approval.

Agents and tools that read llms.txt can start from http://127.0.0.1:7878/llms.txt. The host serves it on the management server with the MCP endpoint and client setup, and links /docs/agent-guide.md (the instructions and tool list the MCP server reports, plus core CLI commands) and /docs/runtime-api-v1.md and /docs/content-types.md; /llms-full.txt concatenates the discovery page and all three references.

Agents connect to the Streamable HTTP endpoint at http://127.0.0.1:7878/mcp on the host, or the console's Tailscale URL plus /mcp from another allowed device. The bundled deployment skill describes the deploy and approval flow.

App environment settings

Ordinary environment variables belong to one flat and are readable by management clients. Use secrets for credentials. Set, inspect and remove ordinary values with:

flats env set hello GREETING 'Hello'
flats env set hello OPTIONAL ''
flats env ls hello
flats env rm hello OPTIONAL

The console's flat Settings page and MCP tools list_env, set_env, delete_env manage the same values. Names match [A-Z_][A-Z0-9_]* and are at most 64 characters; DB, FILES, __PROTO__, PROTOTYPE and CONSTRUCTOR are reserved. Values may be empty, are at most 64 KiB, and must be valid UTF-8 without NUL. A name cannot be both an ordinary variable and a secret; remove the existing setting before switching kinds. New secret writes use the same validation. Existing historical secrets are retained: JavaScript keeps DB/FILES as host bindings, while WASI receives historical values for those names. Other historical names and values retain their runtime behavior.

Server JavaScript reads strings as env.GREETING; WASI receives environment variables. They are never substituted into frontend bundles, static files or builds. Saving does not change running handlers or previews. Approved deployment, redeployment, rollback or standalone data snapshot restoration captures current variables and secrets when activation begins; its health check and live worker share that snapshot. Settings are not pinned to the approval request or code version; writes after capture apply at the next activation. A newly created preview and a Flats host restart also load current settings. Automatic worker restarts reuse the captured snapshot. Redeploy through the existing approval flow to apply changes.

Security and operations

Server code runs in separate worker processes with WebAssembly memory/time limits and restricted host capabilities. The loopback management listener and console node are privileged control surfaces: restrict console access with tailnet ACLs. Approval decisions and provider changes are accepted only on console routes, which require the console header and a same-origin browser request. That is CSRF protection, not authentication: a local process that sends those headers to the loopback listener can decide approvals, so run only trusted agents on the host. A separate operator credential is planned; credentials.operator_file and the --operator-credential-* flags are still accepted but ignored. See the core lifecycle contract for implementation and integration boundaries.

Secret values are operator-managed, encrypted at rest with the local secret.key, and delivered to a flat at its next approved deployment, redeployment, rollback or standalone data snapshot restoration, or Flats host restart. Automatic worker restarts reuse their captured settings. Secret APIs expose names only; ordinary environment-variable APIs expose their values. A flat can read and return its own injected secrets, so deploy code you trust with those values. Anyone who can read the data directory can recover them. Back up the key alongside metadata and flat data; immutable code versions alone are not data backups.

Shutdown attempts every component and reports failures. Portal drains in-flight HTTP before unregistering exposures on normal shutdown. Network teardown is bounded; a timed-out SDK/backend may continue cleanup in the background until process exit. In that case the data-directory lock stays held until exit, so another host cannot race that cleanup.

Read the design and trust model and automated/manual verification gates for manifests, limits, APIs and operational details.

Directories

Path Synopsis
cmd
flats command
Command flats hosts agent-built websites on this machine.
Command flats hosts agent-built websites on this machine.
Package runtimeref contains the runtime contract shipped with the host.
Package runtimeref contains the runtime contract shipped with the host.
internal
api
Package api is the HTTP API shared by agents, the CLI and the web console.
Package api is the HTTP API shared by agents, the CLI and the web console.
app
Package app wires every Flats module into the `flats serve` process.
Package app wires every Flats module into the `flats serve` process.
apps/docs
Package docs embeds the built-in collaborative document editor that runs "docs" content-type flats on the Flats JavaScript runtime.
Package docs embeds the built-in collaborative document editor that runs "docs" content-type flats on the Flats JavaScript runtime.
bundle
Package bundle validates uploaded builds and writes them as immutable version directories.
Package bundle validates uploaded builds and writes them as immutable version directories.
cli
Package cli implements the `flats` command line.
Package cli implements the `flats` command line.
config
Package config reads, validates and writes the host configuration file (config.json).
Package config reads, validates and writes the host configuration file (config.json).
console
Package console serves the operator's web console: a single-page app with no build step whose assets are embedded in the binary.
Package console serves the operator's web console: a single-page app with no build step whose assets are embedded in the binary.
contenttype
Package contenttype names the content adapters supported by the host.
Package contenttype names the content adapters supported by the host.
core
Package core implements Flats: flats, versions, deployments, exposure, previews and approvals.
Package core implements Flats: flats, versions, deployments, exposure, previews and approvals.
egress
Package egress provides a deny-by-default, bounded HTTP client for guest code.
Package egress provides a deny-by-default, bounded HTTP client for guest code.
expose/local
Package local serves flats on one loopback port, routing by Host header (<host>.localhost:<port>).
Package local serves flats on one loopback port, routing by Host header (<host>.localhost:<port>).
expose/portal
Package portal exposes public flats through Portal relays (github.com/gosuda/portal-tunnel).
Package portal exposes public flats through Portal relays (github.com/gosuda/portal-tunnel).
expose/provider
Package provider serves flats on explicitly permitted networks.
Package provider serves flats on explicitly permitted networks.
expose/tsnet
Package tsnet serves flats on the operator's tailnet.
Package tsnet serves flats on the operator's tailnet.
forkwatch
Package forkwatch unwedges child processes stuck between fork and exec; it is only needed on macOS.
Package forkwatch unwedges child processes stuck between fork and exec; it is only needed on macOS.
launchd
Package launchd installs `flats serve` as a per-user launchd agent on macOS.
Package launchd installs `flats serve` as a per-user launchd agent on macOS.
lifecyclecheck/adapter command
Command lifecyclecheck is a test-only HTTP host for deterministic provider faults.
Command lifecyclecheck is a test-only HTTP host for deterministic provider faults.
lifecyclecheck/legacyadapter command
Command legacyadapter seeds historical exposure and approval references.
Command legacyadapter seeds historical exposure and approval references.
mcpx
Package mcpx exposes Flats to coding agents as an MCP server over Streamable HTTP (stateless, JSON responses).
Package mcpx exposes Flats to coding agents as an MCP server over Streamable HTTP (stateless, JSON responses).
qjs
runtime
Package runtime runs server flats.
Package runtime runs server flats.
site
Package site serves the files of one flat version.
Package site serves the files of one flat version.
slug
Package slug validates flat names and builds preview host names.
Package slug validates flat names and builds preview host names.
store
Package store keeps Flats metadata in a single SQLite database.
Package store keeps Flats metadata in a single SQLite database.
systemd
Package systemd installs `flats serve` as a systemd user service on Linux (the counterpart of package launchd on macOS).
Package systemd installs `flats serve` as a systemd user service on Linux (the counterpart of package launchd on macOS).
scripts
tsnet-logout command
Command tsnet-logout logs a stopped tsnet node out of its tailnet, given the node's state directory (for example <data>/tsnet/<host>).
Command tsnet-logout logs a stopped tsnet node out of its tailnet, given the node's state directory (for example <data>/tsnet/<host>).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL