Command-Line Tools: ktms and tokenmeter

Two command-line tools replace grpcurl, hand-built curl queries and openssl one-liners: ktms administers Keystone. The Model Switchboard. (KTMS), and tokenmeter reads and writes light-tokenmeter. Both keep keys out of the command line and print a new key only once, and both come with an MCP server for AI assistants.

[!NOTE] The CLIs ship with Keystone 1.2 and light-tokenmeter 1.1. Until those releases, build them from main (see Installation). Their commands, flags and output are Experimental until the next minor release.

Installation

Way ktms tokenmeter
Release archive (darwin/linux, amd64/arm64, signed checksums.txt) ktms_<v>_<os>_<arch>.tar.gz tokenmeter_<v>_<os>_<arch>.tar.gz
Go go install github.com/ai-process/ktms/cmd/ktms@latest go install github.com/ai-process/light-tokenmeter/cmd/tokenmeter@latest
Docker coyl/ktms-cli:<v> coyl/tokenmeter-cli:<v>
Homebrew (once the tap is published) brew install ai-process/tap/ktms brew install ai-process/tap/tokenmeter

Completion: ktms completion zsh > "${fpath[1]}/_ktms", likewise bash and fish, and the same for tokenmeter.

Contexts and keys

A context names a service and where its key lives, so a command needs no flags:

ktms context set prod --addr ktms.example.com:443 --tls --http https://ktms.example.com
ktms context store-key prod             # the admin key, read without echo, into the keychain
ktms context store-key prod --client    # a generate key for ktms chat
ktms context use prod

tokenmeter context set prod --addr tm.example.com:443 --tls --token keychain
tokenmeter context store-token prod
  ktms tokenmeter
Contexts file ~/.config/ktms/config.yaml (KTMS_CONFIG) ~/.config/tokenmeter/config.yaml (TOKENMETER_CONFIG)
Address --addr / KTMS_ADDR (gRPC, default localhost:9090) --addr / TOKENMETER_ADDR (gRPC, default localhost:9090)
HTTP API --http / KTMS_HTTP (default port 8080 of the same host) --http / TOKENMETER_HTTP
TLS --tls / KTMS_TLS --tls / TOKENMETER_TLS
Key --key-file / KTMS_ADMIN_KEY; KTMS_API_KEY for chat --token-file / TOKENMETER_TOKEN
Context --context / KTMS_CONTEXT --context / TOKENMETER_CONTEXT

A key source is env:VAR, file:PATH or keychain (macOS Keychain, or the secret service through secret-tool on Linux); store-key --file and store-token --file keep the key in a 0600 file instead. Flags win over the environment, which wins over the current context; a context named with --context or *_CONTEXT wins over the environment, so its key never goes to an address left in the shell. A key is never accepted as a flag value, and the CLI warns before sending one to a remote address without TLS.

Use the least key that does the job: admin:read for everything in ktms that only reads, admin for changes; a read token for tokenmeter reports, write for event send.

ktms

Command Does
ktms config validate\|plan\|apply\|export\|schema The configuration as a file, see below
ktms models list, ktms rules list The registry and the routing rules
ktms keys list\|mint\|revoke, ktms keys policy set Client keys and their own limits
ktms vendor-keys list\|set\|delete Vendor credentials per client key (set reads the key from stdin)
ktms orgs list, ktms teams list Tenants
ktms spend get key\|user\|team ID Spend against the caps
ktms limits list\|set\|delete Spend limits per key or user, per UTC day or month
ktms route explain KEY Where a request with this key would go, and why each model is kept or dropped
ktms nodes list\|drain, ktms node-keys list\|mint\|revoke Inference nodes and their keys
ktms rotate-encryption Re-encrypt secrets with the active keyring version
ktms chat PROMPT A test call through /v1, then the rule, model and vendor that served it
ktms health /healthz, /readyz and the server’s version
ktms mcp The MCP server
ktms keys mint billing --team acme/search --rpm 120
echo "$OPENAI_KEY" | ktms vendor-keys set billing openai
ktms route explain billing --effort high --attr tier=pro
ktms chat --effort low "Say hi"
ktms limits set key billing day 5
ktms spend get key billing

ktms route explain runs Keystone’s own routing (AdminService/ExplainRoute): the matched rule, every model of its chain kept or dropped with the reason (no_vendor_key, key_policy, team_policy, capability, placement, circuit_open, vendor_paused, node_unavailable, …), and what a call would try. It calls no vendor or judge and spends nothing.

Tables are the default; -o json and -o yaml keep the API’s field names for scripts. Revokes ask on a terminal and need --yes elsewhere.

Configuration as a file

ktms config export -o ktms.yaml      # start from what is running
ktms config plan -f ktms.yaml        # what would change; --detailed-exitcode exits 3 on drift
ktms config apply -f ktms.yaml       # asks first; --yes for scripts

Models, rules, organizations and teams, client keys with policies, vendor keys (from environment variables named in plain_key_env), spend limits and user rate limits. Only the sections in the file are managed, and --prune deletes what a managed section does not list, except admin and admin:read keys. The format is in Configuration as a file and its JSON Schema comes from ktms config schema.

tokenmeter

Command Does
tokenmeter usage summary Requests, tokens and cost over a window, --group-by any dimension, --daily per day
tokenmeter usage top Rankings --by cost\|tokens\|requests over a --dimension
tokenmeter usage by-meta What the events tagged meta key=value used, e.g. a job
tokenmeter usage export Daily rows as CSV or JSON for a sheet or a script
tokenmeter token new\|hash Tokens and their TOKENMETER_TOKENS entries
tokenmeter event send One event, to check that writing works
tokenmeter health /healthz, /readyz and the version
tokenmeter mcp The MCP server
tokenmeter usage summary --last 7d --group-by project
tokenmeter usage top --by cost --dimension action --limit 5
tokenmeter usage by-meta --key job_id --value job-42 --group-by task_id
tokenmeter usage export --last 30d --format csv --out usage.csv
tokenmeter token new --name ktms --scope write

Windows are --last (24h, 7d, 4w) or --from/--to in RFC 3339 or YYYY-MM-DD, in UTC. A window the server caps at 10,000 rows is fetched in smaller parts and added up, so totals are complete. Amounts are USD in tables, exact dollars in -o csv, and cost_nanousd in -o json. pkg/client is the Go client underneath.

Exit codes

Code Meaning
0 Done
1 Failed
2 Bad usage: flags, arguments, a missing subcommand
3 ktms config plan --detailed-exitcode: the plan has changes
4 Not authenticated or not allowed: no key, a wrong key, a key without the scope
5 Not found
6 Rejected as invalid; retrying will not help
7 Unavailable, throttled or timed out: retry later