The CLI
The omniglass binary is both the server and the client. Its data commands are
generated from the OpenAPI (make gen, via cmd/cligen), so the CLI cannot drift
from the API: a new route is a new command on the next regeneration. A small set of
commands (the run modes and the trusted bootstrap) are hand-written and compose with the
generated tree on the same root.
This page is the mental model and the setup. For the exhaustive, generated list of every command, its flags, and an example, see the CLI reference.
Running the full stack locally
Section titled “Running the full stack locally”make dev brings up the whole stack for a browser session: a dev Postgres (docker
compose, matching the default DSN), the migrations and boot seed, a bootstrapped dev
owner whose token is printed once, a dev example estate (a few campuses with rooms, a few
sign-in-able users, and their grants, so the console is not empty), and the server with
the operator console at http://localhost:8080/web. Ctrl-C stops the server; make down stops Postgres (the
named volume persists data between runs; docker compose down -v wipes it and re-mints a
token next run). make up / make down manage just the database. Tests never touch this
stack: they spin their own ephemeral Postgres via testcontainers.
Connecting
Section titled “Connecting”Every generated command is a client of a running server and takes two shared flags, each with an environment default:
| Flag | Env | Default |
|---|---|---|
--server | OMNIGLASS_SERVER | http://localhost:8080 |
--token | OMNIGLASS_TOKEN | (none) |
The token is a bearer credential (mint the first one with omniglass bootstrap; see
Authentication below). The server enforces the same capability and scope
for the CLI as for any caller: the CLI is just another client, with no privileged path.
export OMNIGLASS_SERVER=https://omniglass.example.comexport OMNIGLASS_TOKEN=ogp_...omniglass location listomniglass location create --name hq --location-type campusomniglass location get hqOutput is JSON. A non-2xx response prints the server’s error body and exits non-zero, so the CLI is safe in scripts.
Authentication
Section titled “Authentication”There are two ways to authenticate, both accepted on every request. A bearer token in
the Authorization header (the --token flag above) is the path for services and the CLI.
A username and password is the path for a human at the web console: the server verifies
it and sets an httpOnly session cookie. The CLI itself always uses a bearer token.
The first owner is created directly against the database with bootstrap (the trusted lane,
no running server needed):
# Mints a bearer credential (the token is printed once) and, with --password, a password# credential (argon2id) so the owner can sign in to the console. This is a trusted direct-DB# lane, so it is exempt from the password policy (unlike the console/API paths). The bootstrap# token expires after --ttl (default 90 days, hard maximum 365 days).omniglass bootstrap ops --password 'set-a-strong-one' --email [email protected] --display-name "Ops Lead"
# Mint a fresh bearer token for an existing user (direct-DB, owner lane). A --description# (required) names what the token is for. Every credential is time-bounded: the token expires# after --ttl (default 90 days, hard maximum 365 days; a --ttl above the cap is an error). A# web-login session cookie has its own, shorter fixed lifetime.omniglass token ops --description 'ci pipeline'omniglass token ops --description 'nightly backup' --ttl 720h # a 30-day token
# A signed-in user can mint its own token over the API (the console Create token action), which# returns the secret once: omniglass auth create-token --description 'my laptop cli'.
# Set or rotate a user's password (direct-DB, owner lane; also policy-exempt as the recovery path).# A break-glass reset also revokes the user's live SESSIONS, so a stolen login stops at once; API# tokens are kept unless --revoke-tokens is given (a full lockout of a compromised account).omniglass set-password ops 'set-a-strong-one'omniglass set-password ops 'set-a-strong-one' --revoke-tokens # full lockout: sessions and tokensOnce a server is running, a signed-in principal manages its own account through the generated self-scoped commands (each edits only the caller’s own profile):
omniglass auth me # your principal, permissions, and grantsomniglass auth update-profile --display-name "Ops Lead"omniglass auth change-password --current-password 'orange-boat-42x' --new-password 'purple-canyon-7'omniglass auth set-avatar --image-base64 "$(base64 -w0 me.jpg)" # set your profile pictureomniglass auth remove-avatar # clear it, falling back to initialsomniglass principal principal avatar list # read your picture back as { image_base64 }Collection commands
Section titled “Collection commands”The collection surface regenerates into four command groups:
node, interface, task, and reachability. They follow the same derivation as every
other resource (POST /interfaces is interface create, GET /tasks/{id} is task get <id>), so nothing here is special-cased. All examples require the matching permission on
the running server.
Register and enroll an edge node (the day-one handshake):
omniglass node listomniglass node create --name edge-hq --display-name "HQ Edge Node" --location hq-west --description "HQ network closet" # needs node:create (all-scope)omniglass node get edge-hqomniglass node update edge-hq --display-name "HQ Edge" --location hq-west # needs node:update; the name is immutableomniglass node delete edge-hq # needs node:delete; decommissions the node (cascades its interfaces, tasks, and enrollment)Every command above is new in practice, not only in the docs: a hand-written node run
mode occupied the same name as the generated node group, so cobra resolved omniglass node list to the daemon and it failed asking for --token. The run mode is now
omniglass node run, a verb beside the others:
omniglass node run --name edge-hq --token ogp_... # the edge daemon: claim, pull the worklist, heartbeatThe node-facing claim exchange is public (the enrollment token is the authentication), so
a node presents its name and token to receive its NATS credential:
omniglass node claim --name edge-hq --token ogp_...Author a reachability check by creating an interface (its poll task is derived automatically):
# An interface owned by a component, placed on a node, with its probe target in params.# It is named by its protocol: --interface-type is the interface_type, there is no --name flag.omniglass interface create \ --interface-type tcp --component disp-1 --node edge-hq \ --params '{"target":"10.0.0.1:22"}' # needs interface:create
omniglass interface listomniglass interface get <id> # interfaces are addressed by idomniglass interface update <id> --node edge-hq --params '{"target":"10.0.0.2:22"}'omniglass interface delete <id> # refused (409) while its task references it
# The poll task is derived from the interface, so the task surface is read-only.omniglass task listomniglass task get <id>The four built interface types are icmp, tcp, ssh, and http, and an interface is
named by its protocol (the --interface-type), unique within its component. An interface update
changes only its node placement and params. A task is derived when its interface is
created, so there is no task create, update, or delete; its placement follows the
interface’s. A node purge cascades its interfaces and their derived tasks.
Read a component’s composed reachability (the verdict, the probe-layer signals, and the recent transitions the availability strip draws):
omniglass component component reachability list disp-1 # needs component:read--image-base64 takes a plain base64 string, not a file path (base64-encode the image
yourself, as the $(base64 …) above does); the server accepts JPEG, PNG, or WebP and
normalizes it to a 256x256 JPEG. An administrator manages any principal’s picture with
omniglass principal setAvatar <id> --image-base64 … and omniglass principal removeAvatar <id>
(gated by principal:set-avatar), reading one back with omniglass principal principal avatar list <id> (gated by
principal:read). A principal with no picture is a 404.
Secrets
Section titled “Secrets”The secret commands are generated like every other resource. secret
covers the encrypted values, secret-type lists the shape registry, and component effective-secret reads the masked cascade onto one component. Output is masked JSON, the same as the console; plaintext lives
behind reveal, which the server audits and which only admin and owner may call.
omniglass secret-type list # the shape registry (snmp-community, basic-auth)omniglass secret list # the all-scope admin directory (masked fields)omniglass secret create --name core-snmp --secret-type snmp-community \ --owner-kind location --owner hq --fields '{"community":"public"}'omniglass secret update <id> --fields '{"community":"s3cret"}' # an omitted field keeps its valueomniglass secret reveal <id> # audited plaintext decrypt (secret:reveal)omniglass secret delete <id>
omniglass component effective-secret list codec-1 # the masked cascade resolved onto a componentThe effective read answers which secret applies to a device and where it comes from, never
what it contains: fields are masked exactly as the directory masks them, and plaintext is only
ever secret reveal. It rides secret:read, which the viewer floor does not carry.
--owner-kind is one of platform | location | system | component; --owner names the owning entity
and is omitted for a platform secret (the install-wide tier, which needs an all-scope grant plus
platform:create). Field maps pass as a JSON object to --fields, validated against the type’s shape.
Variables
Section titled “Variables”The variable commands are generated the same way. variable covers the
plaintext values and component effective-variable reads the cascade onto one component. There is
no reveal: the value is shown in the clear.
omniglass variable list # the all-scope admin directoryomniglass variable create --name poll_interval --value-type int \ --owner-kind system --owner east-auditorium-av --value 30omniglass variable create --name retry --value-type json --owner-kind platform \ --value '{"retries":3,"backoff":"1s"}'omniglass variable update <id> --value 60 # validated against the fixed value_typeomniglass variable delete <id>
omniglass component effective-variable list codec-1 # the cascade resolved onto a componentBoth effective reads return the winner and the candidates it beat, with the tier each came
from, so the answer explains itself rather than just asserting a value. The variable cascade
resolves the system band from the component’s primary membership; resolving against a named
system is offered only by component effective-tag list today.
--value-type is one of string | int | float | bool | json. --value is parsed as JSON, so a
bare 30, true, or {"k":"v"} sends the number, the boolean, or the object; a bare word like HDMI1
falls back to a string, so the common case needs no quoting. A string value that would otherwise parse
as JSON (30, true) is quoted to force a string: --value '"30"'. (secret create --fields parses
the same way.)
generated self-scoped commands (omniglass auth me, auth update-profile,
auth change-password, auth set-avatar / removeAvatar); an administrator manages other
principals, roles, groups, secrets, and variables through the resource commands, all covered
in the admin guide and listed in full in the CLI reference.
The tag commands split along the governance line. The tag resource covers the
key vocabulary (minting, editing, and deleting keys, plus the install-wide platform binding), while binding a
value onto an entity is a custom method on the entity (component set-tag and friends), so it needs
only the write the operator already holds on that entity. effective-tag reads the resolved cascade onto
one component.
omniglass tag list # the governed key vocabularyomniglass tag create --name environment # mint a key (tag:create, admin)omniglass tag create --name rack_position --applies-to '["location"]' --propagates=falseomniglass tag update environment --applies-to '["component","system"]'omniglass tag setPlatform environment --value prod # an install-wide default (tag:update + platform:update)omniglass tag clearPlatform environmentomniglass tag delete environment # cascades its bindings
omniglass component setTag codec-1 --key environment --value dev # component:updateomniglass component listTags codec-1 # the bindings set directly on the componentomniglass component removeTag codec-1 --key environmentomniglass system setTag east-auditorium-av --key environment --value prodomniglass location setTag hq --key environment --value staging
omniglass component component effective-tag list codec-1 # the cascade resolved onto a componentBinding is a custom method on the entity (component setTag), like the principal lifecycle verbs, so it
stays clear of the top-level tag commands. A key name is a normalized lowercase identifier (minting a
bad name is a 422). --applies-to is an entity-kind allow-list passed as a JSON array
('["component","system"]'; empty means universal), checked when a value is bound. --propagates
defaults true (the value cascades to descendants); --propagates=false binds a flat per-entity value
that resolves only on its own entity. Resolving onto a component unions keys and overrides values
most-specific-wins down the cascade.
Component classification catalogs
Section titled “Component classification catalogs”The component-classification catalog
commands cover the vendor, driver, and capability registries: flat, official-vs-custom catalogs on
the same pattern as the type registries. Each resource’s :read sits on the viewer floor; the three
writes (:create, :update, :delete) are admin-gated.
A vendor names an organization, carrying a --kind of manufacturer, integrator, or developer
(default manufacturer):
omniglass vendor list # the vendor registryomniglass vendor create --id barco --display-name Barco --kind manufacturer \ --icon monitor --support-phone "+1-555-0100" --website https://www.barco.comomniglass vendor get barcoomniglass vendor update barco --support-phone "+1-555-0199"omniglass vendor delete barco # refused (422) if officialA driver names the implementation that gets, emits, or sets a product’s signals, with an optional
--version. A capability names what a component can do:
omniglass driver list # the driver registryomniglass driver create --id barco-snmp --display-name "Barco SNMP" --version 1.0.0omniglass driver update barco-snmp --version 1.1.0omniglass driver delete barco-snmp # refused (422) if official
omniglass capability list # the capability registryomniglass capability create --id projector --display-name Projectoromniglass capability delete projector # refused (422) if officialA seed-owned (official) row, for example the crestron vendor or the microphone capability, is
read-only: update and delete both 422. A vendor’s website is validated to an http/https scheme
on write; any other scheme (for example javascript:) is a 422.
Products
Section titled “Products”The product commands cover the product
registry: the concrete SKU that ties the vendor, driver, and capability catalogs together, and the
thing a component points at. product:read sits on the viewer floor; the three writes
(product:create, product:update, product:delete) are admin-gated.
A product names its kind (device, app, service, or vm, default device), optionally its
vendor, driver, and a parent product it is a variant of, and the capabilities it
provides (a JSON array of capability ids):
omniglass product list # the product registryomniglass product create --id barco-ub12 --display-name "Barco UB12" --kind device \ --vendor-id barco --driver-id barco-snmp --capabilities '["projector"]'omniglass product get barco-ub12omniglass product update barco-ub12 --capabilities '["projector","speaker"]' # replaces the whole setomniglass product delete barco-ub12 # 422 if official, 409 if a component points at itA seed-owned (official) product, for example cisco-room-bar, is read-only: update and delete
both 422. A product still referenced by a component (component.product_id) cannot be deleted (409);
an unknown vendor, driver, parent, or capability id is a 422.
Standards
Section titled “Standards”The standard commands cover the
system-side counterpart of a product: the blueprint a system conforms to. standard:read sits on the
viewer floor; standard:create, standard:update, and standard:delete are admin-gated.
omniglass standard list # the standard catalogomniglass standard create --id lecture-hall --display-name "Lecture Hall" \ --parent-standard-id classroom # a variant of an existing standardomniglass standard get lecture-hallomniglass standard delete lecture-hall # 409 if a system still conforms to itUnlike a seeded product, the shipped standards are official: false: they are forked from an in-code
template once, with no inheritance, so they are yours to rename, re-parent, or delete, and the boot seed
installs one only if absent rather than reasserting over your edit. The same holds for the shipped
location types. See the seed model.
Property contracts and values
Section titled “Property contracts and values”A classifier declares which properties its instances carry; an instance sets a value. Both sides are the same three verbs, and the contract commands hang off the classifier that owns them:
omniglass product property list cisco-room-bar # a product's contractomniglass standard property list huddle-room # a standard's contractomniglass location-type property list room # a location type's contractomniglass standard property update huddle-room room_capacity --default-value 6 --required trueomniglass standard property delete huddle-room room_capacity # systems keep any value they set
omniglass component property list dsp-boardroom-3 # the effective readomniglass system property list boardroom # same shape, system sideomniglass location property list east-campus # same shape, location sideomniglass system property update boardroom room_capacity --value 12 # idempotentomniglass system property delete boardroom room_capacity # falls back to the contract defaultThe read resolves the classifier’s contract against the instance’s own values, so a one-off system
(one conforming to no standard) and a productless component still resolve, to their off-contract
values alone. The value commands are scope-injected: an instance outside your read scope is a
non-disclosing 404 on the read and on the write. The registry and its contract share one noun:
omniglass location-type list is the registry, omniglass location-type property list <id> its contract.
Generated versus hand-written
Section titled “Generated versus hand-written”- Generated (
internal/cli/api_gen.go, do not edit): one command per API operation. The resource and verb come from the AIP-style path (POST /locationsislocation create,GET /locations/{name}islocation get <name>, a:verbcustom method is<resource> <verb> <id>, so the principal lifecycle isprincipal disable <id>,principal archive <id>,principal restore <id>, andprincipal purge <id>); path parameters are positional args, the request body is--flags, and OpenAPI query parameters become optional--flags(a set flag is appended to the request query string, an unset one keeps the server default), soprincipal list --include-archived --kind servicefilters the listing. A principal<id>argument accepts either the uuid or a human’s username (omniglass principal archive alice), resolved by the server, so you rarely need to look a uuid up first.--helpplus the example come from the operation’s summary and description. - Hand-written (
internal/cli/api_hooks.goand the run-mode files): the client runtime the generated tree calls, plus commands that are not API operations, theserverandmigraterun modes and the trusted direct-DB owner lane (bootstrap,token,set-password).
To add a hand-written command, write a newXxxCmd() returning a *cobra.Command and add
it in newRoot, exactly as bootstrap does. Regenerating the API commands never touches
it.