Documentation
¶
Overview ¶
Command weft is the framework's one binary (plan B1): setup B's local Studio and a terminal over the Studio API, for scripts and CI.
go install github.com/weftgo/weft/cmd/weft@latest weft studio [--addr] [--db] [--token] [--rotate-token] [--manifest] [--open] [--no-playground] weft dev [studio's flags] [--no-watch] [--watch dir] [-- command args…] weft runs [--agent] [--since] [--failed] [--limit] [--json] weft open <run id> [--open] [--with-token] weft export <run id> [--wefttest dir [--test name] [--force]] [--format json|jsonl|otlp] weft doctor weft version
Every subcommand is a convenience over what an app or a script can do itself: `weft studio` is studio.New with setup B's options, and runs, open, export and doctor are thin clients of the Studio JSON API (GET /api/runs, /api/runs/{id}, /api/runs/{id}/export, /api/meta). A team that dislikes the CLI loses nothing.
Every connection flag mirrors an environment variable one to one: --addr WEFT_STUDIO_ADDR, --db WEFT_DB, --token WEFT_STUDIO_TOKEN, --manifest WEFT_MANIFEST, --url WEFT_STUDIO_URL (the Studio the API clients talk to, default http://127.0.0.1:7331).
weft studio ¶
Setup B (S4.6, §10.1): the UI, OTLP ingest, SQLite and a dev token on 127.0.0.1:7331 — the zero-infra Studio for any language's app:
weft studio # serves UI + OTLP, prints a dev token WEFT_STUDIO_URL=http://127.0.0.1:7331 ./my-go-app OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:7331 python app.py
The database defaults to the otel local sink's path ($WEFT_DB or ./.weft/weft.db), so the same file serves an in-process app and the binary. --db sqlite://path picks another file; --db clickhouse://user:pass@host:9000/db serves the hosted backend (obsdb/clickhouse). The dev token is stable per database (plan B3): the first start on a SQLite file writes 32 random bytes (base64url) beside it, <db>.token (0600), and every later start serves that token; --rotate-token writes a new one (an action: no environment mirror); --token or WEFT_STUDIO_TOKEN overrides it and leaves the file alone. Like a fixed token it is the panel tokens' signing key, so it is never printed — the banner names its file. A database with no file (":memory:", ClickHouse) gets a token generated for this process, printed at start. Once bound, weft studio writes the discovery file (internal/discovery: studio.json in ./.weft when that exists, else $XDG_RUNTIME_DIR/weft, else the user cache directory; {url, token, db, pid, started, version}, 0600) and removes it on a clean exit — SIGINT, SIGTERM and (unix) SIGHUP all stop it gracefully; a second live Studio's exit puts the first's file back. The writer drops a .gitignore ("*") into ./.weft when it has none. otel.Install and runtime.Install read the file when WEFT_STUDIO_URL is unset (and no otel.Studio is named), trusting it only when its url is loopback, it is fresh and, on unix, it is 0600 and the reader's, so an app joins this Studio with no configuration (WEFT_DISCOVERY=off turns the read off). The playground is on (studio.Playground): inert until an app's runtime (weft/runtime) dials in; --no-playground turns it off. The manifest served at /api/manifest is --manifest (default $WEFT_MANIFEST), else the nearest weft.json from the working directory upward; one line says which file was loaded, or that none was found. --open opens the browser on the UI with the token in the URL fragment (a fragment never reaches a server or a Referer); it is on by default when stdout is a terminal, --open=false turns it off. The link, token included, is the opener's argument (xdg-open, open, rundll32): briefly visible to local users in the process list (/proc/*/cmdline) — acceptable for handing the user's own browser its link, and the reason the token is never printed.
The port policy (plan B2, internal/listen): 127.0.0.1:7331 is the one default. When it is busy, weft studio asks GET /api/meta there (with --token / WEFT_STUDIO_TOKEN as the bearer when set, else the database's stable token — only to an address this user's discovery file names — to loopback only): a Studio serving the same database file is reused — "studio already running at http://127.0.0.1:7331 (pid 1234), reusing", exit 0, no database or listener opened (--open still opens the browser on it; a missing discovery file of that Studio is said in one line) — and anything else (another program, a Studio on another database, a Studio whose meta this token cannot read) moves Studio to the next free port in 7331–7340, said in one line before the banner, which prints the real address. All ten busy is exit 1 naming the range. --addr (or WEFT_STUDIO_ADDR) pins the address: busy is exit 1 with the address in the error, never a probe or another port.
weft runs, weft open, weft export ¶
The API over a terminal. Each takes --url (default $WEFT_STUDIO_URL, else http://127.0.0.1:7331) and --token (default $WEFT_STUDIO_TOKEN). `weft runs` prints one row per top-level run (id, agent, status, started, steps), newest first, from GET /api/runs; --agent and --failed filter on the server, --since (a duration such as 2h, or an RFC 3339 time) stops the paging at the first older run, --limit (default 50, 0 for all) caps the rows and says so on stderr when it hid any, --json prints the rows as Studio serves them. `weft open <id>` checks the run exists and prints its page, <url>/runs/<id>, bare: --with-token prints the #token= fragment too, --open hands the browser the link with the token. `weft export <id>` writes GET /api/runs/<id>/export to stdout (--format json, jsonl or otlp); with --wefttest <dir> it downloads the wefttest fixtures and unzips them into <dir>/<name>/, the directory wefttest.Replay(t, dir) reads for a test named <name> (--test, a relative name; default the run id), refusing a non-empty target without --force. With --force the target's *.json fixtures are replaced, never merged (as wefttest.Record replaces them): a stale one would answer for a request the new run never made.
weft doctor ¶
`weft doctor [--url URL] [--token TOK]` checks a running Studio and prints one line per check, each read from its GET /api/meta (internal/doctor): reachable, token accepted, the database's path and size, the content it stores, connected runtimes (and, when none, what this shell's WEFT_ENV and WEFT_STUDIO_URL say about why), the panel bundle's version and whether weft.json is stale against the latest runs. It exits 1 when a check fails — first of all an unreachable Studio, reported on the first line within a short timeout.
weft dev ¶
`weft dev [flags] [-- command args…]` (plan B1.2) is `weft studio` (in-process: the same flags, the same port policy) plus the app — the command after "--", default `go run .` — run with WEFT_ENV=dev (kept when already set non-empty), WEFT_STUDIO_URL, WEFT_STUDIO_TOKEN and WEFT_DB (the Studio's SQLite file, absolute; unset for ClickHouse) added to the environment, the last three overriding the shell's. Each is a plain variable the app could be given by hand: the command is a convenience, never a requirement. The app runs in its own process group; a .go change under the working directory (--watch dir, repeatable, replaces it; .git, node_modules, vendor, testdata, .weft and dist are skipped) restarts it after 300 ms of quiet — SIGTERM to the group, up to five seconds, SIGKILL, then the command again. A build failure or an app that exits is reported with its exit code and the next save retries; --no-watch turns watching off, and weft dev then exits with the app's code (127 when it cannot start). A directory arriving with .go files in it restarts too; editor lock files (.#name.go) do not. Ctrl-C, SIGTERM and SIGHUP stop the app first (the same signal; SIGHUP as SIGTERM), then Studio; exit 0. A SIGKILL of weft dev cannot be caught: on Linux `go run` gets SIGTERM (Pdeathsig), but the binary it started can be orphaned. Each start prints one line,
studio http://127.0.0.1:7331/ · app pid 4242 · runtime rt_… registered
the token in a #token= fragment only when it was generated for this process (a database with no file: the stable and the fixed token stay out of the log); the runtime is the first one GET /api/runtimes lists that was not there before the app started, waited for up to five seconds, else "no runtime registered yet" and a later line when one registers. An unspecified listen host (0.0.0.0) is handed to the app as 127.0.0.1. Reuse is the port policy's: its probe carries the fixed token, else the database's stable token (only to an address a trusted discovery file names: from another directory with --db on the same file the probe goes out bare and a second Studio starts), so a Studio on the same database is reused and the app gets its URL and token; one walled by another token is skipped for the next port. A Studio weft dev starts writes the discovery file as weft studio does. --watch and --no-watch have no environment mirror: they are the dev loop's, not connection settings.
`weft version` prints the weft version (version.Runtime: the module tag this binary was built from).
Exit codes: 0 success, 1 a failure (printed as "weft: …" on stderr, except doctor, whose lines say it), 2 a usage error.
It is a package of the framework module (ADR 0027) kept apart from the studio library so the library never carries what only the binary needs — it is the one place that imports the clickhouse driver.