OSYRA CLI reference
osyra is the pre-release OSYRA control-plane CLI source tree. It is designed
to turn a terminal into an authenticated OSYRA client and to build as a single
static Go binary (CGO_ENABLED=0).
Authentication is browser-delegated (RFC 8252 loopback + PKCE S256) using the
public client osyra-cli — there is no embedded client secret. Public-client
refresh fails closed until the CLI, edge, and IAM implement ADR-134 DPoP sender
binding. A device-authorization grant (RFC 8628) covers headless / remote hosts.
This page documents the command contract implemented in the current source.
There is no publicly shipped binary yet. The detailed sections below cover the
authentication and binary-lifecycle verbs plus the internal eng approval
workflow; the complete registered command inventory is called out separately so
the narrower prose below is not mistaken for the whole CLI.
Installation
There is currently no supported public installation command. In particular, do
not rely on the planned https://get.osyra.ai/install.sh,
osyra-ai/tap/osyra, or Scoop package names until AP00020 publishes a signed
release and those distribution channels exist.
For source-only evaluation, contributors with repository access can build the current checkout:
cd AP00020-osyra-cli
mkdir -p dist
go build -o ./dist/osyra .
./dist/osyra --helpThe release pipeline is intended to publish checksums, a cosign keyless signature, and an SBOM, and the installer/update implementation is designed to verify those artifacts. No current release artifact exists to verify or update to.
Verify a source build:
./dist/osyra versionCurrent source command inventory
The current Cobra root registers the following commands. This inventory is about source availability; commands that call OSYRA services still require a compatible, explicitly configured deployment.
| Command | Current source status |
|---|---|
| login, logout, whoami | Auth client commands; detailed below. |
| version, update | Binary metadata and signed-update implementation. update has no release artifact to consume today. |
| verify <receipt> | Offline receipt verification against a caller-pinned root. |
| verify-export <partition-dir> | Offline warehouse-export partition verification against a caller-pinned platform anchor. |
| connect <claude-code\|codex\|cursor> | Local agent-configuration writer/dry-run/undo surface. |
| receipt list\|get\|inspect | Receipt read/inspect surface; network operations require a compatible edge. |
| policy get | Signed policy-bundle metadata read; requires a compatible edge. |
| memory list | Read-only verified-memory receipt query; fails honestly with OSY-VERIFIED-MEMORY-NOT-WIRED until the edge receipt store is wired. |
| capture hook\|otlp-receiver | Inline-capture carrier commands. There is no registered capture install subcommand in the current source. |
| eng sign-jws\|compose-bundle\|register-approval-key | Restricted engineering/security-ops approval workflow; detailed below. |
| completion, help | Cobra-generated shell completion and help. |
The current source-emitted osyra <command> --help text is the flag-level
authority for the newer verify, verify-export, connect, receipt,
policy, memory, and capture groups until this page grows dedicated
sections for each one.
Global flags
These persistent flags apply to every command. Configuration precedence is
flag › env (OSYRA_*) › config file › built-in default.
| Flag | Env | Default | Description |
|---|---|---|---|
| --host | OSYRA_HOST | https://api.osyra.ai | Protected-resource API origin. |
| --web-url | OSYRA_WEB_URL | Resolved profile issuer | Human browser origin serving /oauth/cli-authorize. Override only when the browser UI uses a third origin. |
| --profile | OSYRA_PROFILE | default | Named credential/config profile. |
| --config-dir | OSYRA_CONFIG_DIR | OS-standard (e.g. ~/.config/osyra) | Override the config + credential-file directory. |
| --cred-store | OSYRA_CRED_STORE | auto | Credential backend: auto | keychain | file (auto prefers keychain, falls back to file). |
| --json | — | off | Machine-readable JSON output on stdout. |
| --quiet, -q | — | off | Suppress non-essential output. Never silences warnings, errors, or the cred-store fallback disclosure. |
| --verbose, -v | — | off | Increase operational detail. Never prints secrets. |
| --no-color | — | off | Disable ANSI color in output. |
| --no-browser | — | off | Do not auto-open a browser; print the authorize URL to open yourself on the same machine. |
| --insecure-localhost | — | off | Dev-only escape: permit an http://127.0.0.1[:port] / http://localhost[:port] origin (and nothing else over http). |
The CLI keeps three origins explicit:
hostis the protected-resource API origin, including/api/v1/iam/users/me.- The profile
issueris both the exact JWT issuer pin and the OAuth/OIDC origin for discovery, token, revocation, and device authorization. web_urlis the human browser entry and defaults to the resolvedissuer.
OSYRA Cloud therefore uses host=https://api.osyra.ai and
issuer=web_url=https://auth.osyra.ai; OAuth credentials are never posted to
the protected-resource host. A private deployment with no profile issuer
remains single-origin. Each value must be a bare origin with no credentials,
path, query, or fragment. HTTPS is required outside the explicit
--insecure-localhost loopback escape.
Configuration file
Non-secret config lives in config.toml in the config directory; tokens never
go in config — they are stored separately in the credential store.
default_profile = "default"
[profiles.default]
host = "https://api.osyra.ai"
issuer = "https://auth.osyra.ai"
# web_url defaults to issuer. Set it only when the browser UI delegates elsewhere.
# web_url = "https://console.private.example"
[profiles.staging]
host = "https://api.staging.osyra.ai"
issuer = "https://auth.staging.osyra.ai"
web_url = "https://auth.staging.osyra.ai" # browser entry; API remains on hostToken storage
The short-lived access token is stored in the OS keychain (macOS Keychain /
Windows Credential Manager / Linux Secret Service) via a pure-Go keyring
library, under the keychain service name ai.osyra.cli, keyed per
(host, profile). A 0600-permission file fallback is used on headless boxes
with no Secret Service. The active backend is always disclosed by
osyra version and on first login.
Legacy credentials written by the incomplete bearer-refresh implementation are
scrubbed on read. New writes never persist refresh_token; refresh can return
only after ADR-134 DPoP proof generation and edge/IAM key binding are complete.
Other environment variables
| Env | Effect |
|---|---|
| OSYRA_NO_UPDATE_CHECK | Set to any non-empty value to disable the passive update check entirely. |
| PGURL | Default Postgres DSN used by osyra eng register-approval-key when --dsn is not passed. |
The passive update check runs at most once per 24h, caches its result on disk
(update-check.json) in the config dir, and never self-updates the binary.
Exit codes
Scripts and CI can branch on these stable exit codes.
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Generic / uncategorized internal error |
| 2 | Usage (bad flags/args, unknown command) |
| 3 | Auth required / token invalid — run osyra login |
| 4 | OAuth protocol error (edge/IAM error body) |
| 5 | Security-validation failure (state/iss mismatch, malformed callback) |
| 6 | Network / TLS / DNS / timeout |
| 7 | Login-flow (loopback callback) timeout |
| 8 | Credential-store failure (keychain + file fallback both failed) |
osyra login
Authenticate via the browser and store an access token.
Synopsis
osyra login [--scope <s1,s2>] [--timeout <duration>] [--force] [--device]
[--no-browser] [--host <url>] [--web-url <url>] [--json]Description
Runs the browser-delegated PKCE loopback flow:
- Generates a PKCE verifier and
state(CSPRNG, in-memory only). - Binds an ephemeral
127.0.0.1:<port>single-use callback listener. - Opens the
…/oauth/cli-authorizepage (on theweb_urlorigin) in your system browser; you sign in and approve. - The page redirects to
http://127.0.0.1:<port>/callback?code&state&iss. - The CLI validates
statetheniss(constant-time), then exchanges the code at{issuer}/oauth/token(no client secret — public + PKCE). - Confirms the token with a real
whoamicall, then stores it.
If a valid token already exists for the (host, profile), login short-circuits
and reports "already signed in" unless --force is set.
Use --device for the RFC 8628 device-authorization grant on headless or remote
hosts (SSH, containers, CI): the CLI prints a short user code and verification URL
that you approve from any browser (even on another machine), then polls until you
approve. The device-grant path does not mint a refresh token.
Flags
| Flag | Description |
|---|---|
| --scope <s1,s2> | Narrow the requested scope. May only narrow the registered least-privilege set. |
| --timeout <duration> | Loopback wait timeout, e.g. 3m (default 3m). Must be positive. For --device, only honored when set explicitly (otherwise bounded by the server's device-code expiry). |
| --force | Re-run the flow even if a valid token already exists. |
| --device | Use the RFC 8628 device-authorization grant (no local browser). |
| --no-browser | Do not auto-open a browser; print the authorize URL to open in a browser on the same machine. |
(Plus the global flags — --host, --web-url, --profile,
--json, etc.)
Example
# Standard browser login against the default host
osyra login
# Headless host (no local browser): device-authorization grant
osyra login --device
# Print the authorize URL instead of opening a browser (same-machine only)
osyra login --no-browser
# Log in to a named staging profile, JSON output
osyra login --profile staging --jsonosyra whoami
Print the authenticated identity.
Synopsis
osyra whoami [--host <url>] [--profile <name>] [--json]Description
Calls the real GET /api/v1/iam/users/me with the stored access token and prints
your identity (email, display name, status, MFA, and — when the edge supplies them
— org, workspace, roles, and scopes).
On a 401, the CLI exits 3 with the hint to run osyra login. It does not
replay a bearer refresh token: ADR-134 requires sender-constrained DPoP refresh,
and that cross-service proof contract is not yet implemented.
With --json, the raw server body is emitted verbatim on stdout.
Flags
This command takes only the global flags (commonly --host,
--profile, --json).
Example
# Human-readable identity summary
osyra whoami
# Raw JSON identity (verbatim server body)
osyra whoami --jsonosyra logout
Revoke (best-effort) and delete the stored credentials.
Synopsis
osyra logout [--all] [--host <url>] [--profile <name>] [--json]Description
Always deletes the local credential. It first attempts a best-effort, secretless RFC 7009 server-side revoke and reports the honest outcome:
revoked: true— the server revoked the token.revoked: unavailable— the server could not revoke it (the token then expires on its own).revoked: false— no token was present to revoke.
It never falsely claims "revoked"; the local credential is wiped unconditionally either way. Logout is idempotent — a missing credential is not an error.
--all wipes credentials for every profile. The file backend wipes all profiles
atomically; the OS keychain cannot be portably enumerated, so --all on a
keychain backend wipes only the active profile and warns you to re-run per
profile (or use --cred-store=file).
Flags
| Flag | Description |
|---|---|
| --all | Wipe credentials for every profile (see keychain caveat above). |
(Plus the global flags.)
Example
# Log out of the active profile
osyra logout
# Wipe every profile (file store wipes all; keychain wipes the active profile)
osyra logout --all
# Machine-readable result
osyra logout --jsonosyra version
Print version, build info, and the credential-store backend.
Synopsis
osyra version [--json]Description
Prints the CLI version, build information, and the credential-store backend that
would be used for the current configuration. The source also implements a
passive, opt-out, ≤once/24h-cached release check; it prints a one-line note only
when a newer release exists. AP00020 currently has no release, so there is no
published update for this check to report. The check never blocks or fails the
command. Disable it with OSYRA_NO_UPDATE_CHECK.
Flags
This command takes only the global flags (commonly --json).
Example
osyra version
# Machine-readable version + build info
osyra version --jsonosyra update (source implemented; no release available)
Update osyra to a signed release once AP00020 begins publishing releases
(verify-everything, anti-downgrade).
Synopsis
osyra update [--check] [--version <tag>] [--yes] [--json]Description
The source implements self-update verification: cosign keyless signature pinned
to the OSYRA release-workflow identity, SHA-256 checksum, a
strictly-newer-than-running anti-downgrade check, and atomic replacement with
rollback. It is fail-closed: any verification failure leaves the current
binary untouched (and exits with security-class code 5). Today this command
cannot select a real update because AP00020 has no published release.
Before replacing the binary, update prompts for confirmation. --yes skips the
prompt; a non-interactive (non-TTY) run must pass --yes, otherwise it exits
2 (usage).
Flags
| Flag | Description |
|---|---|
| --check | Report whether a newer version exists; never replace the binary. |
| --version <tag> | Update to a specific forward-only release tag after releases exist. |
| --yes | Skip the confirmation prompt (required for non-interactive / CI use). |
(Plus the global flags, commonly --json.)
Example
# Future use after AP00020 publishes signed releases:
# report whether an update is available, without changing anything
osyra update --check
# Interactive self-update to the latest release
osyra update
# Non-interactive update in CI
osyra update --yes
# Pin a specific forward-only release
osyra update --version "$FUTURE_TAG" --yesosyra eng
Internal engineering / ops tooling (restricted workflows).
osyra eng groups internal engineering and security-ops tooling that is not
part of the day-to-day authenticated control-plane flow. Its subcommands implement
the ENG-937 2-of-3 JWS storage-reclamation approval workflow.
Running osyra eng with no subcommand prints help. The three subcommands are
sign-jws, compose-bundle,
and register-approval-key.
osyra eng sign-jws
Sign a canonical-JSON approval payload (ES256, software EC key).
Synopsis
osyra eng sign-jws --kid <kid> --key <key.pem> [--payload <file>] [--json]Description
Signs the ENG-937 reclamation approval payload with a software EC key (ES256).
Reads the canonical-JSON payload from --payload (a file) or stdin, computes the
RFC 7515 §3.1 JWS Signing Input, signs it with the supplied PKCS#8/SEC1 EC private
key, and emits a single-signer General JWS JSON bundle on stdout. Combine signers
later with compose-bundle.
The private key never leaves the process and is never logged. The payload bytes are signed verbatim; the billing verifier re-derives the canonical payload server-side, so any tampering fails every signature. Hardware/PKCS#11 and WebAuthn signers are not implemented in the current source; the implemented fallback is the software ES256 signer.
Flags
| Flag | Description |
|---|---|
| --kid <kid> | JWS key id of the registered approver key. Required. |
| --key <path> | Path to the approver's EC private key PEM (PKCS#8 or SEC1). Required. |
| --payload <path> | Path to the canonical-JSON payload file. Defaults to stdin (- also means stdin). |
Example
# Sign a payload file, writing the single-signer bundle to disk
osyra eng sign-jws \
--kid approver-operator-1 \
--key ./operator.key.pem \
--payload ./reclaim-payload.json > operator.bundle.json
# Pipe the payload in via stdin
cat ./reclaim-payload.json | osyra eng sign-jws --kid approver-operator-1 --key ./operator.key.pemosyra eng compose-bundle
Merge per-signer bundles into one General JWS approval bundle.
Synopsis
osyra eng compose-bundle --in <bundle1> --in <bundle2> [--in <bundleN> ...] [--json]Description
Combines the single-signer General JWS bundles produced by
sign-jws (one per approver, typically created on separate
machines) into one multi-signer General JWS JSON envelope (RFC 7515 §7.2.1), ready
for the gRPC approval_jws_bundle field. The merged bundle JSON is written to
stdout.
All inputs must carry the same payload; mismatched payloads are refused. The
2-of-3 slot policy itself (operator plus at least one of cso_proxy / legal_cfo)
is enforced server-side by the billing verifier — compose-bundle only assembles
the wire bytes.
Flags
| Flag | Description |
|---|---|
| --in <path> | Path to a single-signer bundle. Repeat for each approver (minimum 2 for a valid 2-of-3). At least one --in is required. |
Example
osyra eng compose-bundle \
--in operator.bundle.json \
--in cso.bundle.json > approval.bundle.jsonosyra eng register-approval-key
Register an approver public key in forensic.approval_keys (cso_audit role).
Synopsis
osyra eng register-approval-key \
--kid <kid> --public-key <pub.pem> --role <role> \
--approver-user-id <uuid> --registered-by-user-id <uuid> \
[--algorithm ES256|EdDSA] [--dsn <postgres-dsn>] [--print-sql] [--json]Description
Inserts an approver's public key into forensic.approval_keys for the ENG-937
2-of-3 JWS workflow. The runtime billing_service role has SELECT-only on this
table; registration is an ops/security responsibility, so connect with a
cso_audit (prod) or billing_migration (non-prod) DSN via --dsn or $PGURL.
The public key is validated against the table CHECK constraints and its declared algorithm/curve (ES256 ⇒ EC P-256, EdDSA ⇒ Ed25519) before the insert, so a key the verifier could never accept is rejected client-side.
Use --print-sql for air-gapped operation: it prints the exact parameterized
INSERT statement and the bound values for a DBA to run, without connecting to
any database.
Flags
| Flag | Default | Description |
|---|---|---|
| --kid <kid> | — | JWS key id (primary key). Required. |
| --public-key <path> | — | Path to the approver's public key PEM (PKIX). Required. |
| --role <role> | — | One of operator | cso_proxy | legal_cfo. Required. |
| --approver-user-id <uuid> | — | UUID of the human approver. Required. |
| --registered-by-user-id <uuid> | — | UUID of the ops/cso user performing the registration. Required. |
| --algorithm <alg> | ES256 | ES256 or EdDSA. |
| --dsn <dsn> | $PGURL | Postgres DSN for the cso_audit / billing_migration role. |
| --print-sql | off | Print the parameterized INSERT + bound values and exit; no DB connection. |
Example
Resolve both identities from real IAM user records before running either path. The shell guards below fail closed if the operator has not exported those real UUIDs; do not invent placeholder identities for an approval-key record.
: "${APPROVER_USER_ID:?export the approver UUID returned by IAM}"
: "${REGISTERED_BY_USER_ID:?export the registering operator UUID returned by IAM}"
# Air-gapped: print the exact parameterized INSERT for a DBA to run
osyra eng register-approval-key \
--kid approver-operator-1 \
--public-key ./operator.pub.pem \
--role operator \
--approver-user-id "$APPROVER_USER_ID" \
--registered-by-user-id "$REGISTERED_BY_USER_ID" \
--print-sql
# Connected: register directly via a cso_audit DSN
osyra eng register-approval-key \
--kid approver-operator-1 \
--public-key ./operator.pub.pem \
--role operator \
--approver-user-id "$APPROVER_USER_ID" \
--registered-by-user-id "$REGISTERED_BY_USER_ID" \
--dsn "$CSO_AUDIT_DSN"Help
Every command supports --help (and -h) for inline usage:
osyra --help
osyra login --help
osyra eng sign-jws --help