bwg

BandwagonHost / KiwiVM fleet control for humans and AI agents.

CLI · Go SDK · MCP server · read-only enforced in the client, not by a prompt · MIT

$ bwg ls
SERVER  HOSTNAME           PLAN      LOCATION         BANDWIDTH   USED  RESETS  STATE
osaka   osaka.example.com  speed-2g  JP, Osaka        █████████░   94%  6d 3h   
tokyo   tokyo.example.com  micro128  JP, Tokyo        █████░░░░░   47%  6d 3h   ok
la      la.example.com     kvm-2g    US, Los Angeles  ██░░░░░░░░   19%  6d 3h   ok

Total: 1.8 TiB of 3.9 TiB across 3 servers (45%)

! osaka: bandwidth at 94%

Real output, pinned by a test so it cannot drift from what the tool prints. KiwiVM shows you one VPS per page; this is the whole account in one sweep.

Install

curl -fsSL https://raw.githubusercontent.com/lroolle/bwg-cli/main/install.sh | bash

# or
go install github.com/lroolle/bwg-cli/cmd/bwg@latest

60 seconds

Two values per VPS, both from the KiwiVM panel: the VEID in the panel URL after ?veid=, and the API key under the API tab.

export BWG_VEID=1347645
export BWG_API_KEY=private_xxxxxxxx

bwg ls          # the fleet
bwg info        # plan, addresses, quota
bwg ssh         # ssh in, port resolved from the API

A fleet lives in ~/.config/bwg/config.yaml (mode 0600): bwg server add, or bwg server import keys.csv from the billing portal's export, then bwg server check to verify every pair.

Why it exists

Read-only is a capability, not a flag

c := kiwivm.New(veid, key, kiwivm.ReadOnly())
c.Restart(ctx)     // *ReadOnlyError — no HTTP request is made
c.ServiceInfo(ctx) // fine

Every endpoint is classified in one registry — 45 endpoints, 29 of them mutating — and the gate sits in the SDK in front of the HTTP client. The CLI, the MCP server and any Go program importing the package inherit it. A test reflects over every client method and asserts a read-only client refuses all 29 without touching the network, so a method added later is covered automatically.

bwg --read-only ls    # or: export BWG_READ_ONLY=1
bwg api ops           # how every endpoint is classified

For agents

Every command speaks --json and --jq. Data on stdout, diagnostics on stderr. Exit codes carry the reason: 2 configuration, 3 refused, 4 credentials rejected. Errors name the command that fixes them.

claude mcp add bwg -- bwg mcp --read-only

In read-only mode the mutating MCP tools are not advertised at all — an agent is never offered a tool that is certain to be refused. MCP has no confirmation channel, so serve read-only unless the host has an approval layer you trust.

When to skip it