w17ctl

w17ctl — the wandering-compiler developer CLI

w17ctl is the single client and entrypoint for a w17 project. Everything a developer or a pipeline does — scaffolding, change requests, codegen, applying migrations, running the local stack — goes through it.

w17ctl <command> --help     # context-sensitive help for any command

Install

curl -fsSL https://get.w17.dev/install.sh | sh -s -- --pre

Installs into /usr/local/bin, verifying the download against the release’s SHA256SUMS first — always, with no flag to skip it.

--pre is needed while w17ctl is in release candidates: latest means the newest STABLE release, and there is not one yet. Drop it once v0.1.0 ships.

curl -fsSL https://get.w17.dev/install.sh | sh -s -- --version v0.1.0-rc.1 --dir ~/bin

Or take a .tar.gz straight from Releases — linux and macOS, amd64 and arm64. There is nothing to configure after unpacking: w17ctl is a single static binary and keeps its state in ~/.w17/.

w17ctl version     # what you have, and which console it defaults to
w17ctl login       # point it at a console

w17ctl version prints the console address compiled into the binary. That is only the LAST resort in the resolution order — --console, then the console you are logged into, then this default — so logging in is what decides where commands go.

Start here — you + a coding agent

w17 is built to be driven by a coding agent (Claude Code, …). Two steps:

1. Log in once:

w17ctl login

2. Paste this to your coding agent ONCE to onboard it (it’s context, not a task — the point is that the agent learns w17 and records it, so every later session already knows the drill and you can just ask for features):

This project is built with w17 (wandering-compiler): a declarative compiler where
you declare schemas, DB models, queries, mutations, events, and APIs as proto +
annotations, and it generates the gRPC binaries, DB layer, SQL migrations,
gateways, and clients — the only hand-written code is the business-handler bodies.

Do this now to learn how it works:
1. Run `w17ctl guide`. It writes AGENTS.md (how to drive the w17ctl CLI) and
   w17/specs/ (the platform reference: the annotation catalog, the generation
   model, the event system, the directory structure).
2. Read AGENTS.md and ALL of w17/specs/.
3. If it isn't already recorded there, note the essentials in this project's
   CLAUDE.md / AGENTS.md and in your long-term memory, so future sessions start
   from it: this is a w17 project driven via w17ctl; you change behaviour by
   editing proto/ and running `w17ctl codegen`, NEVER by hand-editing generated
   files; only business-handler bodies are hand-written; the full platform
   reference is w17/specs/ (refresh with `w17ctl guide`).

From now on I'll just ask you to implement features. Always follow the w17 way:
declare then generate, hand-write only handler bodies, and re-read w17/specs/ when
unsure about an annotation or the architecture. Do not invent DB or gRPC plumbing
by hand.

That’s it — the agent learns w17 once, records it, and from then on you just describe features. The platform reference is regenerated by the compiler, so it always matches your w17 version. Everything below is reference detail you’ll rarely need to read.

Mental model — what w17ctl is (and isn’t)

w17ctl is deliberately dumb. It holds no compiler know-how. Every real decision — compiling the proto IR, planning SQL migrations, running the compatibility engine, signing the lock — happens on the console (the backend), reached over a gRPC API. w17ctl’s whole job is three things:

  1. Transport — talk to the console’s gRPC API (always over TLS).
  2. Local disk — read/write the project’s files: the signed w17/lock.yaml, generated code, snapshots, and the dev-machine config under ~/.w17/.
  3. Docker — drive the local compose stack.
   you / CI  ──▶  w17ctl  ──▶  console gRPC API  ──▶  the compiler core
              (transport +      (all the know-how)     (private IP)
               disk + docker)
        ◀──  finished artifacts the client writes verbatim  ──

So when a command “generates code” or “plans a migration”, w17ctl is uploading your inputs and applying what the console sends back — it never compiles anything itself. This is why the client is small, publishable, and safe to run in CI: it can’t produce a broken or forged artifact, only apply a console-signed one.

w17ctl finds your project by walking parent directories to the nearest w17/lock.yaml, so you can run it from anywhere inside the tree.

State it keeps

Location What Notes
w17/lock.yaml the signed project lock — connections, targets, generated-code config, pinned schema edited by scaffolding commands; signed by the console
~/.w17/auth.yaml console credentials (bearer + org memberships) per console written by login
~/.w17/config.yaml dev-machine project registry + per-project host-port assignments + presets written by init / project
w17/certs/, w17/snapshots/ local dev PKI + branch schema snapshots certs / db / initiative

Command reference

Getting started

Command What it does · under the hood
guide Write AGENTS.md (how to DRIVE w17ctl — golden rules + workflow + task→command cheat-sheet; client-embedded, works offline) and fetch w17/specs/ (how the w17 TECHNOLOGY works — the annotation catalog + architecture primer, generated server-side so it always matches the compiler). This is how a coding agent is onboarded: it reads AGENTS.md automatically, which points it at w17/specs/. --stdout prints the usage guide; --force refreshes AGENTS.md; --no-specs skips the server fetch. The w17/specs/ fetch needs a reachable console (run login first).

Auth & organizations

Talk to a console and pick your org scope. Login is a single gRPC call to the console (the same endpoint every other command dials) — email + password in, bearer out.

Command What it does · under the hood
login [<host>] Sign in over gRPC (AuthService.SignIn) and store the bearer + org memberships in ~/.w17/auth.yaml. Prompts interactively for anything not given (email → password (hidden) → host); --email + --password (or W17_PASSWORD) keep it non-interactive for CI.
logout [<url>] Drops a console’s stored credential from ~/.w17/auth.yaml.
whoami Prints the stored identity + orgs for the active console (--all = every logged-in console). Pure local read.
org list / org use <slug> List orgs you belong to (server read) and pick the default; the default org scopes subsequent commands.

Project setup & scaffolding

These edit the signed lock and write starter proto files. No codegen — they prepare inputs.

Command What it does · under the hood
init Wizard: prompts for project name + generated-code paths, registers the project with the console, writes a fresh signed w17/lock.yaml, and auto-registers it in ~/.w17/config.yaml. Refuses if a lock exists.
connection <cmd> Wizard to add a DB/store connection to the lock (identity = connection name). Edits the signed lock.
domain <cmd> / module <cmd> Scaffold the proto/domains/<domain>[/<module>]/w17.proto cascade sentinel; --with-example seeds the four-layer (queries / mutations / business / types) shape. Pure local file writes.
template [<surface>] Prints a commented example .proto for one surface (domain, module, types, queries, mutations, business, rest, admin, events, subscribers, rpc, mcp, cli). A “how do I annotate X” reference; --list to enumerate.
target <cmd> Declare what codegen emits + how it deploys — the lock’s generated_code entries: client (FE clients), grpc-client (Go client pkg), business, binary (composed binaries), ci, scale (prod replicas). All edit the signed lock.
plugin <cmd> List embedded + installed plugins; install one (e.g. auth) into the project — stages its proto tree + records activation.
secrets <cmd> Production-secrets (age tier): init mints a project age keypair; encrypt turns a plain .secrets into a committable .secrets.age. Runtime decrypts when a key is present, reads plain otherwise — seamless dev, strong devops. Local crypto only.
certs Fill a directory with a local dev PKI (self-signed CA + CA-signed leaf) for the internal-mesh TLS switch (W17_INTERNAL_TLS=on). Idempotent — never overwrites.

Codegen & schema

The compiler-driven commands. Each uploads your protos to the console and writes back what it returns.

Command What it does · under the hood
codegen Generate all derived code: Storage gRPC handlers + the ACL / eventbus / MCP bundles. Uploads every .proto under proto/ to the console, which compiles the IR + emits the files; w17ctl writes them under <root>/<gen_dir>/.
verify Re-checks that every committed generated lock (ACL + eventbus) still matches the current proto — the CI drift hook paired with codegen. Offline (no console); non-zero exit on drift.
push --proto=… Push everything (schema + fixtures + future artifact types) to the console in one call; the server diffs + stores. Idempotent — safe to re-run whenever inputs change.
migrate generate Compile + push the schema; the console plans the SQL migrations (initial create vs. revision diff, auto-detected) and pins the lock target. migrate list shows history; migrate fetch downloads the planned .up.sql; migrate push-raw is the hand-authored-body escape hatch.
fixtures <cmd> Apply console-rendered fixture seeds (parameterized upserts) to a connection’s target store (DSN via W17_TARGET_<CONN> env).
clean Remove codegen output (pb stubs / FE clients / languages / generated bundle files) while preserving hand-written packages.

Schema lifecycle — the review flow (PR analog)

Snapshots + a compatibility engine + a review/merge gate. Git branch → initiative (main/master → trunk).

Command What it does · under the hood
initiative <cmd> Initiatives + snapshots: current / list / show / materialize / push. A git branch maps to an initiative; the first push lazily materializes it. Schema state lives in the console’s generated storage.
compat <cmd> Compatibility report between two snapshots — DB / WIRE / API findings + severity — with a dev/prod policy gate. report --base --head, or against-trunk. The console’s compat engine does the analysis; w17ctl renders the verdict.
review <cmd> open / show / approve / merge a set of schema changes. Merging into a prod-mode env runs compat and blocks BREAKING changes (exit non-zero); dev warns + proceeds.
env <cmd> Deployment environments + mode (dev/prod). A prod-mode env turns the compat engine’s BREAKING verdict into a hard merge block.

Local development

Docker + local project ergonomics. This is where w17ctl drives compose.

Command What it does · under the hood
stack <cmd> Local docker-compose lifecycle: build / up / down / logs / ps / restart. Injects the project’s unique host ports from ~/.w17/config.yaml.
project <cmd> Dev-machine project registry: list / import / remove / ports / ps + run presets. Keeps unique host ports across all installed projects (no env-file juggling) in ~/.w17/config.yaml.
db <cmd> Local dev DB snapshots scoped to the current initiative: save / list / activate / delete. Branch-scoped checkpoints for fast switching.
test Run the generated e2e suite against a deployed gateway. Locates the e2erunner module and runs it in Docker against --target — a pure HTTP/MCP client that never touches a DB.
ui <cmd> Serve the local web app — a human-facing view + controller over the project registry, presets, and Docker stacks (the GUI counterpart to project / stack). On-demand, 127.0.0.1 only; no daemon, no image.

Why the split matters (design invariant)

The compiler’s IP and all trust-bearing crypto (lock signing) live on the console, never in the client. w17ctl ships as a standalone public tool and imports zero private compiler code — only the public sdk/go module + the console’s gRPC contracts. That’s what lets the same minimal client both scaffold a project on your laptop and gate a production release in CI: it verifies + applies console-signed artifacts, and can’t forge one.