The command-line interface for Utos workflows.
The CLI resolves and validates workflows and talks to a daemon — the Docker split, where the client builds and the daemon executes. It is the only component that reads authored source, resolves dependencies and contacts registries; the daemon receives a fully-resolved bundle and runs it.
Grab the binary for your platform from the latest release
and put it on your PATH. There is no runtime to install — the binaries are self-contained.
| Platform | Asset |
|---|---|
| Windows x64 | utos-<version>-win-x64.zip |
| Linux x64 / arm64 | utos-<version>-linux-x64.tar.gz, …-linux-arm64.tar.gz |
| macOS Apple silicon / Intel | utos-<version>-osx-arm64.tar.gz, …-osx-x64.tar.gz |
SHA256SUMS is published alongside them:
sha256sum -c SHA256SUMS --ignore-missing| Command | Does |
|---|---|
utos validate <file> |
Resolve a workflow and its dependencies, then check the bundle against the spec rules |
utos inspect <file> |
Show the resolved dependency graph, canonical identities and content digest |
utos context create|use|ls|rm |
Manage the daemons this CLI knows about |
utos version |
CLI version, and the daemon's if one can be reached |
utos load <file> |
Resolve, validate and load a workflow onto a daemon |
utos run <file|reference> |
Schedule an execution, optionally loading the file first |
utos logs <execution-id> |
Stream an execution's events |
utos workflow ls |
List the workflows loaded on a daemon |
utos workflow rm <reference> |
Remove a loaded workflow; without a version, every loaded version |
utos execution ls |
List executions, newest first, optionally --workflow <reference> |
utos execution output <execution-id> |
Show the values and result an execution produced |
utos context create local http://localhost:5164
utos run ./hello.yaml --start greet \
--input '{"name":"world"}' \
--env API_BASE=https://api.dev \
--followA command picks its daemon from --host, then --context, then UTOS_HOST, then the current
context. Configuration lives in ~/.utos/config.json, relocatable with UTOS_CONFIG.
Output is coloured on an interactive terminal and plain when redirected. NO_COLOR turns colour
off everywhere; FORCE_COLOR or CLICOLOR_FORCE turn it on for redirected output, such as a CI log
that renders ANSI or a terminal recording.
Workflows are validated before the daemon is contacted, so a broken one fails the same way whether or not a daemon is running.
There is deliberately no utos build. A WorkflowBundle is a wire payload, not a
distributable artifact — what gets published to a registry is the source graph, and the flattened
bundle only ever exists on its way to a daemon. Resolution is a pipeline stage that validate and
load both drive; inspect is the window onto it.
Workflows follow the Kubernetes manifest convention — apiVersion, kind, metadata, spec,
and nothing else at the top level. Each activity names its kind with type:
apiVersion: utos.io/v1
kind: Workflow
metadata:
name: hello
version: "1.0.0"
spec:
activities:
greet:
type: http
method: GET
url: "{{ env.API_BASE }}/hello/{{ input.name }}"
onSuccess:
- condition: output.ok
return: { greeted: "{{ output.name }}" }
- error: { code: NOT_GREETED, message: "{{ output.error }}" }Conditions are bare JavaScript expressions and {{ }} interpolates one into text. A rule ends
the run with return — with a value, or bare to end with none — fails it with error, or
transitions to another activity; there are no end/error targets. The legal type values
are derived from the protobuf descriptor rather than hard-coded, so a new activity kind in the
spec becomes authorable as soon as the SDK package is bumped. See
workflow-source-format.md
for the normative mapping (return is result on the wire), and
template-expressions.md
for the language.
Source problems carry UTOS-S### codes and point at a line; bundle rule violations carry the
UTOS-* codes from
workflow-validation.md and
an addressable path:
UTOS-T003 workflows["acme/greet:1.0.0"].spec.activities["send"].onSuccess[0].transition.name
Transition target 'notify' is not an activity in this workflow.
--json on validate emits the same information for scripts.
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Unclassified error |
| 2 | Usage error |
| 3 | The workflow was read but is invalid |
| 4 | The daemon could not be reached, or refused the request |
| 5 | The workflow ran and failed |
Requires the .NET 10 SDK. NativeAOT publishing additionally needs a C++ toolchain — on Windows,
Visual Studio with the MSVC workload, with vswhere.exe reachable
(C:\Program Files (x86)\Microsoft Visual Studio\Installer on PATH).
dotnet build Utos.Cli.slnx -c Release
dotnet test Utos.Cli.slnx -c Release
dotnet publish src/Utos.Cli/Utos.Cli.csproj -c Release -r win-x64The CLI ships as NativeAOT single-file binaries, which constrains its dependencies: anything
resolving types by reflection at run time is out. That is why parsing uses System.CommandLine
rather than Spectre.Console.Cli, why configuration is JSON with source-generated serialization,
and why console output is a few dozen lines of ANSI rather than a rendering library. The
constraint reaches the SDK packages too, which are AOT-analysed on every build so a regression
here surfaces there rather than at this binary's publish.
Neither reading a document nor judging a bundle is implemented here. Both are defined
normatively by the spec, so both live in packages every tool shares: Utos.Workflow.Source turns
authored YAML into a Workflow and reports what only a document can get wrong (UTOS-S###), and
Utos.Workflow.Validation judges the resulting bundle. Both are driven by the conformance
fixtures in utos/api, so a workflow one tool accepts cannot be rejected by another — and the
registry's upload path reads documents through the same code this does.
There is no utos build. A bundle is a wire payload rather than a distributable artifact, so
resolution is a stage inside validate and load, and inspect shows the result — nothing writes
a bundle to disk.
src/Utos.Cli owns the console — commands, output, exit codes — and src/Utos.Cli.Core owns the
pipeline and the daemon client and knows nothing about it. The split is load-bearing rather than
tidy: everything in Core has to be callable from a test with no terminal, so it returns values
and throws instead of writing to Console.
Two things that will bite a contributor otherwise. The protobuf C# namespace is
Utos.Workflows.V1 — plural — while the wire package is utos.workflow.v1, singular. A
singular namespace would shadow the Workflow message type for anything under Utos.*, and a
using-alias cannot fix it, because C# resolves simple names through enclosing namespaces before
using-directives. And a test project must not reference Utos.Daemon.Server alongside
Utos.Daemon.Client: the two define the same gRPC service types and cannot coexist in one
assembly.