cmux Freestyle
Use this skill when a user wants a Freestyle VM snapshot that the cmux backend can boot Cloud VMs from. It lives in its own public repo,
manaflow-ai/cmux-freestyle
, so anyone with a Freestyle API key can run one shell script and get back a
FREESTYLE_SANDBOX_SNAPSHOT
id.
Freestyle snapshots are scoped to the account that created them. A snapshot id from manaflow's account, or from any other user, will not work for a different Freestyle account. Every user has to run the setup script against their own
. There is no shortcut.
When to use
- User asks how to point their own cmux at Freestyle.
- User self-hosts the cmux web backend and needs a snapshot pinned to a known cmux release.
- User wants to rebuild the cmux Cloud VM image on their own Freestyle account.
- User wants to iterate on the standalone repo () instead of the in-repo builder at
repo/web/scripts/build-cloud-vm-images.ts
.
For internal cmux development that pushes a new snapshot to manaflow's Freestyle account and updates
repo/web/services/vms/images/manifest.json
, keep using
repo/web/scripts/build-cloud-vm-images.ts
. This skill is the external-user path.
What it builds
A Freestyle snapshot from a
that mirrors
freestyleBaseDockerfileContent
in the in-repo builder:
- , , the same shell package set, the Python/OpenSSL shim required by the cmux browser proxy.
- Linux/amd64 downloaded from a pinned GitHub release, SHA-256 verified during the image build against
cmuxd-remote-checksums.txt
.
- symlinked to .
- Node.js, Bun, plus pinned coding agent CLIs (Claude Code, OpenCode, Codex, Pi).
- Linux user with passwordless sudo.
- Systemd unit running
cmuxd-remote serve --ws --listen 0.0.0.0:7777 --auth-lease-file ... --rpc-auth-lease-file ...
on Freestyle port .
The image runs the same smoke tests as the in-repo builder, so failures show up at snapshot build time, not later inside a live VM.
How to use it
The repo is self-contained; no cmux checkout required.
bash
git clone https://github.com/manaflow-ai/cmux-freestyle.git
cd cmux-freestyle
export FREESTYLE_API_KEY=fk_...
./setup.sh
Or the one-liner:
bash
FREESTYLE_API_KEY=fk_... \
curl -fsSL https://raw.githubusercontent.com/manaflow-ai/cmux-freestyle/main/install.sh | bash
Useful flags / env on
(the default subcommand):
- or pins the cmuxd-remote release. Default is the latest stable release.
- or
CMUX_FREESTYLE_SNAPSHOT_NAME
sets the Freestyle snapshot name.
- or
CMUX_FREESTYLE_SKIP_CACHE=1
forces a clean Freestyle rebuild.
- , , etc. drop individual agent CLIs.
- emits a machine-readable result; the human path prints
FREESTYLE_SANDBOX_SNAPSHOT=sh-...
at the end.
The build takes 5 to 15 minutes depending on Freestyle's layer cache.
Full self-host bundle
is a dispatcher with four subcommands. Use them together when a user wants more than just the snapshot id:
bash
./setup.sh doctor # validate tools, env, Freestyle key, GitHub release
./setup.sh # mint a snapshot under the user's Freestyle account
./setup.sh web --snapshot sh-xxxxxx # clone manaflow-ai/cmux, write web/.env.local, start Docker Postgres
./setup.sh home --ref feat-ink-rewrite # install cmux-home (Ink TUI) headquarters view
is the Next.js dev env bootstrap: it clones
to
, writes the Freestyle + Cloud VM env keys into
, runs
, and brings up the per-worktree Docker Postgres unless
is set. Stack Auth keys (
,
NEXT_PUBLIC_STACK_PROJECT_ID
,
NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY
) are honoured if exported but optional. The script only needs
and
(plus
for Postgres).
is the "headquarters view": it installs and prepares the Ink/TypeScript port of
at
~/cmux-freestyle-home/ink
. The TUI connects to the local cmux app's Unix socket and gives end users a Node-only dashboard of every workspace plus a Codex/Claude composer. The Ink port currently lives on the
branch; switch the default
once it merges to
. The Rust crate at the cmux-home repo root remains the full-featured upstream.
If
is set in the env when
runs (the
script exports it from the user's
and the shell), cmux-home renders a second panel under the workspace list titled
. Each row shows the VM id, state, snapshot id (the one from
), and age. This makes the TUI a single dashboard for both local cmux workspaces and the user's Freestyle Cloud VMs, with the same selection cursor walking both panels.
Selecting a VM enables the following actions on the row:
- opens the sandbox workflow: creates a new cmux workspace running . The helper script mints a short-lived Freestyle SSH identity, opens an SSH session through with (reverse-forward to the local Subrouter AI gateway on ) plus forwards for common dev ports (, , , ), writes a inside the VM that points at the forwarded subrouter, then drops into a login shell. Codex launched inside the VM in that shell routes through Subrouter on the user's mac. Also opens a browser pane on the right at .
- opens the local-codex workflow: a normal cmux workspace at the TUI's , no SSH, plus a browser pane on the right at
https://<vmId>.vm.freestyle.sh
. Codex/Claude run on the mac against the local checkout; the VM is treated as a remote dev server.
- destroys the VM through the Freestyle SDK.
- on the header creates a new VM from
FREESTYLE_SANDBOX_SNAPSHOT
.
Freestyle gateway constraint. The Freestyle SSH gateway at
rejects
remote port forwarding (
remote port forwarding failed for listen port 31415
). The
flag is therefore opt-in and works only for ordinary Linux/macOS sshd hosts.
Default path for codex inside a Freestyle VM (works today):
mints an ephemeral preauth Tailscale key via
tsadmin api POST /tailnet/-/keys
(tag
, expires in 1h), runs
inside the VM under userspace networking (
--tun=userspace-networking
), enables tailscaled's HTTP proxy + SOCKS5 server on
, writes
/etc/profile.d/cmux-tailnet-proxy.sh
so every login shell exports
/
/
, and writes
with
openai_base_url = "http://subrouter-team.tail41290.ts.net:31415/v1"
. Codex inside the VM then connects to the existing
host on the tailnet through the local proxy, so all OpenAI traffic is routed through Subrouter's account scheduling. The cmux-freestyle snapshot already ships the apt
package, so first-time bring-up is ~6 s end-to-end; subsequent sessions reuse the existing state.
Disable per-call with
, override the subrouter URL with
or
, override the auth-key with
--tailscale-authkey <key>
or
. Today Subrouter only routes Codex, so use Codex inside the VM until Subrouter adds Claude/OpenCode support.
Helper requires
on the mac (
brew install hudochenkov/sshpass/sshpass
) and
on
(
skills/tsadmin/scripts/tsadmin
).
GitHub auth
The snapshot builder talks only to public GitHub endpoints (
/repos/manaflow-ai/cmux/releases/latest
and the release-asset
cmuxd-remote-checksums.txt
). The unauthenticated GitHub API allows 60 requests per hour, which is fine in normal use. If a user hits a 403/429 (shared IP, CI loops, repeated rebuilds), they should set
(or
) and the script forwards it on both the API call and the checksums download. Mention this when guiding a CI integration.
The Freestyle SDK only needs
; no GitHub credentials are required for snapshot creation.
clones
anonymously, so no auth is required there either; only suggest
if a user explicitly wants
against their own fork.
Plugging the snapshot into cmux
After the script prints the snapshot id, hand the user the env block for their cmux backend:
bash
FREESTYLE_API_KEY=fk_...
FREESTYLE_SANDBOX_SNAPSHOT=sh-xxxxxxxxxxxxxxxxxxxx
CMUX_VM_DEFAULT_PROVIDER=freestyle
CMUX_VM_FREESTYLE_ENABLED=1
The
and
FREESTYLE_SANDBOX_SNAPSHOT
must come from the same Freestyle account. Snapshot ids from other accounts will fail to boot.
For ad-hoc smoke testing:
bash
npx -y freestyle vm create --snapshot <snapshotId> --ssh
Inside the VM, confirm everything baked correctly:
bash
cmuxd-remote version
cmux --help
claude --version
codex --version
node --version
bun --version
Iterating on the repo
The whole builder is one TypeScript file at
scripts/build-snapshot.ts
. When updating it, keep it in lockstep with the source of truth in
repo/web/scripts/build-cloud-vm-images.ts
:
- Pinned agent CLI versions ().
- Default Node major and Bun version.
- Dockerfile body (
freestyleBaseDockerfileContent
, , , , ).
- Snapshot create call shape and recovery behavior (
waitForFreestyleSnapshotByName
).
When cmux changes any of those, bump the matching constant in
, ship a release, and update the cmux release tag the script defaults to if anything in the URL/checksum contract changes.
The Freestyle CLI (
) covers VM create / list / ssh / exec / delete but does not expose snapshot create. That's why this script uses the
SDK directly. Do not try to replace it with a CLI-only shell script.
Rules
- Do not promise users that a shared snapshot id will work for them. It will not. Every user runs once with their own Freestyle API key.
- Do not bake provider API keys, R2 credentials, or anything user-specific into the snapshot. The Dockerfile must stay reproducible from public inputs only.
- Do not point the snapshot at an unreleased cmuxd-remote. Use a published release tag so SHA-256 verification has something to anchor on.
- Do not loosen the agent CLI version policy. Specs must be exact semver pins; ranges and are rejected on purpose so each rebuild is reproducible.
- Do not run this against the manaflow Freestyle account when iterating. Use a personal Freestyle account; manaflow's snapshot lifecycle is managed by the in-repo builder and the cmux image manifest.