Wingsmith
CLI and console

hgr CLI Reference

Command structure, noun-verb pattern, output modes, authentication, config file, fake backend.

hgr is Wingsmith’s command-line interface. One Go binary that talks to the control plane’s HTTP API. It never holds a kubeconfig, never dials a cluster directly, and never talks to the hub.

Command structure

Every command follows a noun-verb pattern:

hgr <noun> <verb> [args]

Examples:

hgr cluster ls
hgr cluster get iad-1
hgr workload create
hgr operation create node.drain iad-1-n01 --reason firmware --wait

Nouns

Commands are organized by the object model:

GroupNounsType
Supplycluster, node, device, device-class, topology-domain, poolObserved, except Cluster and Pool (declared halves too)
Demandartifact, workload, deployment, endpoint, discoveredDeclared, except Discovered (observed)
Bindingtenant, project, quota, serving-quota, priority-class, policyDeclared
Machineryoperation, allocation, component, event, benchmark, alert-thresholdImperative, observed, and mixed

Local commands: login, logout, context, config, whoami, explain, version.

Verbs

All nouns have ls and get. Verbs that change something:

NounVerbs
workloadcreate, scale, pause, resume, drain, rm
operationcreate, cancel
serving-quotacreate, set, rm
alert-thresholdcreate, set, rm
benchmarkrun, request create
discoveredtrack, ignore, promote, rm, restore

Operations are created with hgr operation create <type> <target>. Available types: node.drain, node.cordon, node.uncordon, and workload operations like workload.scale.

Declared state, observed state, and operations stay separate. Draining a node is an operation (hgr operation create node.drain ...), not a flag on node, so there’s a record of who asked, why, and how it ended.

Output modes

Set with --output / -o, HANGAR_OUTPUT, or output: in the config file.

ModeDescription
auto (default)Human table on a TTY, json when piped
human / wideTable; wide reveals columns marked wide (id, operator version)
jsonStable machine contract, indented
jsonlOne object per line, for streaming consumers
yamlSame data, round-tripped through JSON tags
tsvSame columns the table shows, tab-separated
nameBare identifiers, for xargs
template=...Go text/template over the JSON
jsonpath=...Dotted path with [n] and [*] projection

The JSON contract for a list is the API’s own envelope, an object not a bare array:

{
  "items": [...],
  "count": 50,
  "next_cursor": "MTAwMQ"
}

count is the size of this page. A listing with next_cursor is not the whole collection. Use --cursor to get the next page or --all to follow cursors to the end.

Agent mode

--agent or HANGAR_AGENT=1 wraps everything in one envelope: schema_version, ok, command, data, error, warnings, next_actions, meta. Prompts and animation are off, and meta.simulated is true when the fake backend answered.

Authentication

Human access to Wingsmith is a single admin login. hgr login reads the admin password and stores access and refresh tokens in the active context.

hgr login                                # prompts for password
hgr login --password-stdin              # reads from stdin
hgr logout                               # revokes tokens
hgr whoami                               # shows current context

Password sources (in order):

  1. --password-stdin
  2. HANGAR_ADMIN_PASSWORD
  3. No-echo prompt

The access token is short-lived. hgr renews it with the refresh token when it expires. AUTH_EXPIRED means the refresh token was refused.

For CI, log in as admin and let the session expire with the job:

printf '%s' "$HANGAR_ADMIN_PASSWORD" | hgr login --server "$HANGAR_SERVER" --password-stdin

Do not give CI the operator or gateway service credentials. Those are narrow service principals with no admin rights. Use HANGAR_TOKEN or --token to supply an access token without storing it.

Tokens are never printed in any output mode and are redacted from re-run commands suggested in errors.

Configuration

Config file: ~/.config/hangar/config.yaml (or HANGAR_CONFIG, or $XDG_CONFIG_HOME/hangar/config.yaml), written mode 0600.

current_context: prod
contexts:
  prod:
    server: https://plane.example.com
    token: ...          # written by hgr login
    refresh_token: ...  # written by hgr login
    project: default
output: auto
fake: false

Precedence: flag → environment → config file → default.

FlagEnvironmentConfig key
--contextHANGAR_CONTEXTcurrent_context
--serverHANGAR_SERVERcontext.server
--tokenHANGAR_TOKENcontext.token
--projectHANGAR_PROJECTcontext.project
--outputHANGAR_OUTPUToutput
--fakeHANGAR_FAKEfake
--agentHANGAR_AGENTnone
--timeoutHANGAR_TIMEOUTnone
--wait-timeoutHANGAR_WAIT_TIMEOUTnone

There is no default server. Until one is set, every command that needs the plane fails with USAGE and says how to set it. config, context, explain, and version work without a server or token.

Fake backend

hgr --fake runs against an offline in-memory plane for demos, tests, and CI without a server.

  • Opt-in: selected by --fake, HANGAR_FAKE=1, or fake: true in the config file. A command never answers from the fake when you expect a real plane.
  • Shaped like the server: field names, envelope, cursor behavior, and error vocabulary match the plane.
  • Seeded, not random: four clusters in every phase, including an unreachable one and one that never connected, plus a cordoned node, an unhealthy device, an allocation holding fewer devices than it asked for, and a quota over its limit.
  • Persistent state: HANGAR_FAKE_STATE=/path/to/state.json pins the state to a file and persists mutations, so create-then-poll workflows work offline.
  • Failure injection: HANGAR_FAKE_FAIL=NOT_FOUND makes every call fail with that code, for testing error paths.

Examples:

hgr --fake cluster ls
hgr --fake node ls --cluster iad-1 --cordoned
hgr --fake device ls --cluster iad-1 --allocatable --group-by domain
hgr --fake operation create node.drain iad-1-n02 --reason firmware --wait

login and logout never touch the network under --fake.

Error codes

Every failure is an error with a stable code. hgr explain lists them; hgr explain NOT_FOUND describes one.

ExitCodes
0success
1INTERNAL
2USAGE, INPUT_REQUIRED
3AUTH_REQUIRED, AUTH_EXPIRED, FORBIDDEN
4NOT_FOUND
5CONFLICT, INVALID_STATE, AMBIGUOUS, CONFIRMATION_REQUIRED
8TIMEOUT
9RATE_LIMITED
10SERVER_ERROR, NETWORK
11OPERATION_FAILED (a waited operation or benchmark ended failed or cancelled)
130INTERRUPTED

Human failures print the message, cause, runnable fixes, and code:

✗ no control plane URL configured for context "default"
  cause  looked in --server, HANGAR_SERVER, and ~/.config/hangar/config.yaml
  try    hgr config set context.server http://localhost:8000  Set one
         hgr cluster ls --fake  Or work offline
  code   USAGE · hgr explain USAGE

Special commands

hgr endpoint sla <endpoint> shows an endpoint’s service level for the current month, a --month YYYY-MM, or a --from/--to range, with the verdict against the success-rate objective its serving quota declares.

GET /v1/models is counted under the reserved endpoint _catalog. A request naming a model Wingsmith does not serve is counted under _unknown.