Help us shape the future of Go! Take the 2026 Go Developer Survey

weft

command
v0.20.1 Latest Latest
Warning

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

Go to latest
Published: Oct 27, 2026 License: MIT Imports: 42 Imported by: 0

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] [--retain] [--pricing] [--log-level] [--log-format]
weft dev [studio's flags] [--no-watch] [--watch dir] [-- command args…]
weft runs [--agent] [--since] [--failed] [--status] [--limit] [--json]
weft open [run id | public id | session id | trace id] [--open] [--with-token]
weft export <run id> [--wefttest dir [--fixtures-only [--test name]] [--force]] [--format json|jsonl|otlp]
weft doctor
weft vacuum [--db] [--retain]
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; unset, the discovery file's, else http://127.0.0.1:7331), --retain WEFT_RETAIN, --pricing WEFT_PRICING. On weft studio and weft dev, --log-level WEFT_LOG_LEVEL (debug, info, warn, error) and --log-format WEFT_LOG_FORMAT (text, json) pick the server log's slog handler on stderr: the refused ingest batches and the 5xx lines with their request ids.

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 OTel app (traces and runs; the playground, breakpoints and approvals need a runtime link, Go only today):

weft studio                              # serves UI + OTLP; the token is in <db>.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 and, unless --open handed the browser the link, ends "sign in with `weft open --with-token`". 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 the pipeline names no otel.Studio or otel.Local (the README's "When weft/otel reads the file"), 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). Before that, the Studio this user's discovery file names, on whatever port, is asked the same question. One Studio per SQLite file (internal/dblock): before any probe, the start takes <db>.lock — an exclusive OS lock (flock, LockFileEx on Windows) held while it serves and released by the kernel when the process ends, however it ends — so two starts at the same moment, or from directories whose discovery files differ, cannot both serve the file. A start that cannot take it, or that finds a Studio on the file anyway, is refused — "studio already running at http://127.0.0.1:7347 (pid 1234) on …/weft.db" (the lock file and the discovery file name the holder; else "another Studio holds <db>"), exit 1, no database or listener opened (`weft dev` reuses it instead). A ClickHouse database is a shared server and takes no lock. Anything else on the wanted port (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. runs, open, export, doctor and mcp share one resolver: the Studio is --url, else $WEFT_STUDIO_URL, else the discovery file a running weft studio / weft dev wrote (the trust rules otel.Install uses; WEFT_DISCOVERY=off skips it), else http://127.0.0.1:7331; one stderr line names it when the user did not ("weft: Studio http://127.0.0.1:7347 (discovery file …)"). The token is --token, else $WEFT_STUDIO_TOKEN, else the discovery file's when that file names the resolved Studio. `weft runs` prints one row per top-level run (id, agent, status, started with its zone, steps, duration, own cost, the public id when a row has one, the prompt, stopped by — what ended it, a canceled run's cause in parentheses, "-" when none was recorded — and the error: its kind and why, cut to 60 runes), newest first, from GET /api/runs; --agent, --failed and --status (running, succeeded, failed, interrupted, parked or cancelled; a word outside them exits 2) 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 (stopped_by and cancel_cause included when set); stderr names the next step (weft open, weft export). `weft open <id>` tries the id as a run id, a public id, a session id and a trace id in turn (GET /api/runs/{id}, /api/public/{id}, /api/sessions/{id}, /api/traces/{id}) and prints the first one's page — /runs, /conversations, /sessions or /traces — bare; with no id it checks GET /api/meta and prints the Studio root. --with-token prints the #token= fragment too (bare `weft open --with-token` is a terminal's signed-in link to a fresh Studio), --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 writes the run's save-as-test tree into the package directory <dir> — the files of Studio's "save as test" zip, byte for byte: <agent>_replay_test.go beside testdata/TestReplay_<run>/*.json, a test that passes under go test (a replay's composes its source's fixtures for the steps it kept). With --fixtures-only it writes the fixtures alone into <dir>/<name>/, the directory wefttest.Replay(t, dir) reads for a test named <name> (--test, a relative name; default the run id). Either refuses a test file or fixtures already there 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 the Studio the resolver names and prints one line per check, each read from its GET /api/meta (internal/doctor): reachable, token accepted (a missing one names <db>.token and `weft open --with-token`), the discovery file this shell reads against the one the Studio wrote (this Studio, another, none), the database's path and size, the content it stores, the newest run's age and weft version (a warning when it is not this Studio's), the OTLP door (open, walled, off), connected runtimes (and, when none, the --no-playground that turned the link off, or 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.

Retention and weft vacuum

Studio keeps everything by default. --retain (or WEFT_RETAIN; a Go duration, 720h) on weft studio and weft dev is studio.Retain: a sweeper deletes the runs that are not running and were last seen longer ago, with their subagent children, every min(retain/10, 1h) (at least a minute apart), from Studio's own tables only — a SQLite file that also holds the app's thread sessions keeps them untouched. Interrupted runs count as not running and are swept; a run silent for 30 s reads interrupted, so a retention under 1h — which would sweep a live run paused at a breakpoint — is a usage error (exit 2) on studio, dev and vacuum alike. `weft vacuum [--db] [--retain]` is the one-shot form: one sweep when a retention is given, then VACUUM (SQLite: the file shrinks; it waits on the busy timeout while another process writes) or OPTIMIZE TABLE … FINAL on the mutated tables (ClickHouse: merges every part, heavy on a large database). `make vacuum` runs it on $WEFT_DB, else ./.weft/weft.db.

Pricing

Studio prices usage at read time (plan A5) with the table shipped in the studio package (studio/pricing.json, dated). --pricing (or WEFT_PRICING) on weft studio and weft dev names a file of the same shape that replaces it whole — studio.LoadPricing; a file that does not load is a start error (exit 1) naming the path and the field. Nothing is fetched: prices are a plain file.

`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.

Jump to

Keyboard shortcuts

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