A single binary — yggdrasil — drives the entire control-plane lifecycle: bootstrap, manifests, runs, deploy, catalog and collaborators. It does not re-implement business logic: every subcommand shapes an HTTP call to the core engine's API, which is the authority over schema, versions and execution.
Philosophy
The CLI is deliberately thin. The core engine validates the manifest, writes the version, dispatches the workflow and enforces auth; the CLI just reads the file, makes the POST and prints the response. That makes it scriptable (any command runs in CI without a TUI, via flags), versionable (you keep YAML manifests in git and apply them with apply) and auditable (every action becomes an API call recorded by the core).
Nearly every command needs an active context: a core engine to talk to. The context lives in ~/.yggdrasil/config.yaml — server, token and collaborator — in the kubectl style. You create one with init or login, switch with config use-context, and any command accepts --server/--token to override the context for a single call.
The workspace dev commands (integrations, surfaces) are the exception: they don't talk to the core — they manipulate a local monorepo checkout. Everything else just needs a reachable core.
Command reference
The subcommands are organized by the platform lifecycle — from the first init all the way to building your own plugins.
Bootstrap & context
Command
What it does
init [--dir <path>]
Standalone bootstrap in one command: brings up Postgres, the core engine and adapters via docker-compose. With --server <url> it just connects to an existing core engine.
login --server <url> --username <slug>
Trades credentials for a session token and saves a context. For accounts with MFA, pass --totp <code> or --recovery-code <code> (in an interactive terminal the code is prompted automatically).
status
Shows the active context, the target server and a health check (healthz/readyz) of the core.
version
Prints the CLI version (and the build hash, when available).
update
Downloads and installs the latest release for your OS/architecture, verifying the checksum. --check only reports.
config use-context <name>
Switches the active server context. The config family also has get-contexts, current-context, set-context, delete-context and view.
Manifests
Command
What it does
apply -f <file> [--dry-run]
Creates a new manifest version from a file (multiple documents separated by --- are supported; -f - reads from stdin). With --dry-run it shows the diff against the active version without applying.
get <kind> [<name>]
Lists all manifests of a kind, or fetches one by name. -o table|yaml|json picks the format; -n scopes by namespace.
describe <kind> <name>
Prints a manifest as YAML — a convenient shortcut over get … -o yaml.
diff -f <file>
Compares, on the client, the manifests in the file against the active versions on the server, line by line.
delete <kind> <name> [--soft]
Removes a manifest (hard by default, asking for confirmation). --soft just flips active=false and preserves the history.
rollback <kind> <name> --to <ver>
Re-applies the contents of an old version as a new version — the history stays intact, so you can roll forward again later.
Runs
Command
What it does
run <workflow> [--input k=v] [--async]
Triggers a workflow execution by name. Synchronous by default (prints progress step by step); --input k=v is repeatable; --async returns a run id immediately.
ops list | get | retry | abort | replay
The operational side of runs: ops list filters and lists, ops get <run-id> inspects a run, and retry/abort/replay <run-id> manage runs in flight or finished.
logs <run-id>
Follows a run in real time, printing each step's status transitions until the terminal state.
Control plane
Command
What it does
deploy control-plane -f cp.yaml
Applies a control_plane manifest and then triggers the deploy workflow against it — sugar over apply + run with the bootstrap-topology defaults. --no-run applies without executing.
Lists the configured OAuth/OIDC providers (name, type, status).
auth provider apply -f <file>
Registers or updates a provider from YAML/JSON, and prints the login URL to verify it on the spot. There's also auth provider get and delete.
Catalog
Command
What it does
install <repo_ref>
Quickstart install of an integration on a remote core engine: fetches the repo's yggdrasil-quickstart.yaml, walks through the declared inputs (TUI by default, --non-interactive for CI) and dispatches. Accepts GitHub refs (owner/repo[@ref][:path]) and OCI refs (oci://…); --dry-run compiles without dispatching.
Build
Command
What it does
new integration <name> --owner <org>
Scaffolds a new integration adapter from the official template, rewriting the module paths. The template compiles out of the box (go test ./...).
new surface <name> --owner <org>
Scaffolds a new surface (UI / edge). Shared flags: --module, --dir, --template, --no-git-init.
Workspace dev
Command
What it does
integrations <subcommand>
Helper for contributors working in the monorepo: list, install, remove, installed and a tui. Operates on the local checkout, not on a remote core.
surfaces <subcommand>
Sibling helper for surfaces in the workspace: list, active, install, remove, installed, scaffold, activate, deactivate.
Auto-update
The CLI keeps itself current on its own. After each command, it checks for a new release at most once per interval (caching the result in the config) and, if the binary is behind, prints a discreet hint on stderr suggesting yggdrasil update. update downloads the latest release artifact for your OS/architecture, checks the checksum against the release's checksums.txt and swaps the running executable atomically. Development builds never overwrite themselves by accident — they only report what the latest release is.
A typical session
Login with MFA, a diff preview before applying, dispatching the workflow and inspecting the run:
terminal
$yggdrasil login --server https://ygg.acme.internal --username gio --totp 481920✓ logged in as gio → context "ygg-acme-internal" saved to ~/.yggdrasil/config.yaml$yggdrasil apply -f workflows/nightly-sync.yaml --dry-run
~ workflow global/nightly-sync (dry-run, not applied)
schedule: "0 3 * * *"+ retries: 3$yggdrasil apply -f workflows/nightly-sync.yaml
created workflow global/nightly-sync (version 7)$yggdrasil run nightly-sync --input target=staging --async
✓ dispatched global/nightly-sync (run r-2f9c, status running)follow: yggdrasil logs r-2f9c | yggdrasil ops get r-2f9c$yggdrasil ops get r-2f9crun_id: r-2f9cstatus: succeededworkflow: nightly-sync
No context yet? Start with Self-hosting & first steps: one yggdrasil init brings up the core and writes your first context — then it's just status, apply and run.