motif64

Agents · DeepSeek V4.1 Flash · OpenRouter · CPU/API

published by @dh7 on

m64 m64_7796c3cb05e5
Download .tar.gz

opencode terminal on DeepSeek V4.1 Flash (OpenRouter). opencode 1.18.23's own TUI, running in tmux inside the sandbox and served to the browser by ttyd: open the app port and use opencode as in a terminal. The session survives closing the tab. Drives DeepSeek V4.1 Flash through OpenRouter. No GPU; the only egress is openrouter.ai:443, and the m64 gate adds the OpenRouter key, so the agent never holds it.

Lineage

parent
none declared
derived from
no published capsule shares half its files
children
none

Files

9 files

AGENT.md

opencode-tmux-dsflash-openrouter-gb10

opencode 1.18.23's own terminal UI, running in a tmux session inside the sandbox and served to the browser by ttyd 1.7.7 (xterm.js over a WebSocket). Model: DeepSeek V4.1 Flash on OpenRouter. No GPU. The container's only egress is openrouter.ai:443, through the m64 gate, which adds the OpenRouter key: the container never holds it.

This capsule is for a human at a browser. It has no API kind: there is nothing for an agent to call besides status. An agent that wants to drive opencode programmatically should use opencode-dsflash-openrouter-gb10 (sessions API + opencode attach); one that wants only the model should use deepseek-flash-openrouter-gb10 (OpenAI chat completions).

Ports and routes

Main port (the capsule's URL):

Method Path What
GET /status phase, and app_url: the terminal
GET /agent this document plus live status (?format=json: with app_url)
GET /human/ landing page linking to the terminal
GET /health 200 once ttyd answers

App port (app_url, the front's port + 1 unless --app-port): ttyd, raw.

  • GET / is ttyd's page; GET /token its (empty) auth token; GET /ws the WebSocket, subprotocol tty. ttyd's protocol: the client first sends a JSON text frame {"AuthToken":"","columns":C,"rows":R}; then binary frames, first byte the command: client 0+bytes = input, 1+JSON {"columns":C,"rows":R} = resize; server 0+bytes = output, 1 = window title, 2 = preferences.
  • Every client attaches to the tmux session opencode (tmux new-session -A), so all tabs share one opencode, and it survives disconnects. If opencode is quit, the next client starts a new one. The workspace is /scratch/work (the run's scratch).

Security

  • ttyd runs without its own auth and without --check-origin. The app front gives this port exactly the main front's protections: tailnet peers only, a Host allowlist (DNS rebinding), same-site/CSRF refusal, the token rule, and a 403 for any WebSocket upgrade whose Origin is present and not the app's own origin (tested: a foreign Origin, or the main port's origin, gets 403; a fake Host gets 400 on HTTP and 403 on the upgrade).
  • ttyd's own --check-origin is left off on purpose. The front forwards Host and Origin verbatim, so behind it ttyd's check would pass for a browser on the right origin, but it adds nothing the front does not already enforce, and it drops every upgrade that carries no Origin at all (non-browser clients such as scripts and tests).
  • On the tailnet (the default) there is no token: anyone on your tailnet can open the terminal, i.e. run any command opencode or a shell can run in this container, and spend the OpenRouter key. With a token, open app_url?token=<token> once (it becomes a cookie).

capsule.toml

schema = "m64/capsule@1"description = "opencode terminal on DeepSeek V4.1 Flash (OpenRouter). opencode 1.18.23's own TUI, running in tmux inside the sandbox and served to the browser by ttyd: open the app port and use opencode as in a terminal. The session survives closing the tab. Drives DeepSeek V4.1 Flash through OpenRouter. No GPU; the only egress is openrouter.ai:443, and the m64 gate adds the OpenRouter key, so the agent never holds it."# No API kind: the main port only carries /status, /agent, /human and /health; the# terminal is the app ([runtime.app]) on the second port.kinds = []authors = ["dh7"]license = "apache-2.0"[hardware]platform = "linux-aarch64"[runtime]sandbox = "container"# No image: the Dockerfile builds on node:22-bookworm-slim pinned by digest.gpu = false# A tiny health server (health.js); the front needs an internal port and a health route.internal_port = 9500health = "/health"expected_load_seconds = 60user = "1000:1000"# ttyd: the opencode TUI (tmux session "opencode") over HTTP + WebSocket (subprotocol# "tty"). Raw on the second front port, which carries the front's peer, Host, same-site,# WebSocket-Origin and token checks; ttyd itself has no auth.[runtime.app]internal_port = 7681[network]needs = ["openrouter.ai:443"]why = "the model runs on OpenRouter: your prompts and its replies go through openrouter.ai"[secrets]required = ["OPENROUTER_API_KEY"]# The key never enters the container: opencode calls http://openrouter.ai/api/v1 through# the gate, which adds the header and goes on over TLS. The capsule's env var holds only the# placeholder m64-injected:OPENROUTER_API_KEY.[[secrets.inject]]name = "OPENROUTER_API_KEY"host = "openrouter.ai"header = "Authorization"value = "Bearer {secret}"