A command-line interface for Rossoctl, built with Cobra.
This project follows the standard Go CLI layout:
.
├── main.go # Thin entry point; calls cmd.Execute()
├── cmd/ # Cobra command tree (grouped by command)
│ ├── root.go # Root command + Execute() + persistent flags
│ ├── version.go # `rossoctl version`
│ ├── unimplemented.go # newGroup/newLeaf helpers + UNIMPLEMENTED stub
│ ├── toplevel.go # apply, install, status, uninstall
│ ├── login.go # `rossoctl login` (--token or OAuth device flow)
│ ├── agents.go # `rossoctl agents ...` (`list` fetches GET /agents)
│ ├── authconfig.go # `rossoctl auth-config` (shows server auth config)
│ ├── config.go # `rossoctl config ...` (context management)
│ ├── gateway.go # `rossoctl gateway ...`
│ ├── images.go # `rossoctl images ...`
│ ├── namespaces.go # `rossoctl namespaces ...` (`list` fetches GET /namespaces)
│ ├── skills.go # `rossoctl skills ...`
│ ├── tools.go # `rossoctl tools ...` (list/delete/import, mirrors agents)
│ └── ui.go # `rossoctl ui ...`
├── internal/ # Private application logic (not importable externally)
│ ├── apiclient/ # HTTP client for the Rossoctl backend API
│ ├── buildinfo/ # Version metadata formatting
│ ├── config/ # ~/.rossoctl/config.yaml context persistence
│ └── deviceflow/ # OAuth 2.0 device authorization grant (Keycloak)
├── Makefile
└── go.mod
Design principles:
main.gostays trivial — it only callscmd.Execute().cmd/handles the CLI surface — flag parsing, help text, and wiring. Each command lives in its own file and registers itself withrootCmdininit().internal/holds the real logic — packages there are free of Cobra and of I/O, so they can be unit-tested directly.internal/also prevents other modules from importing this code.
Each GitHub release ships prebuilt binaries (see .github/workflows/release.yml).
Download the tarball for your platform, where OSEXT is uname and OS_ARCH
is uname -m:
ROSSOCTL_CLI_VERSION=v0.1.0
OSEXT=$(uname) # Linux or Darwin
OS_ARCH=$(uname -m) # x86_64 or arm64
# Assets label arm64 as "arm64" on both OSes; Linux uname -m reports aarch64.
[ "$OS_ARCH" = "aarch64" ] && OS_ARCH=arm64
url="https://github.com/kagenti/rossoctl-cli/releases/download/${ROSSOCTL_CLI_VERSION}/rossoctl-${ROSSOCTL_CLI_VERSION}-${OSEXT}-${OS_ARCH}.tar.gz"
curl -fsSL "$url" | tar -xz # extracts ./rossoctlmake build # -> ./bin/rossoctl (version info injected via -ldflags)
make install # install into $GOBIN
make test # go test ./...rossoctl --help
rossoctl version
rossoctl agents --help
# Manage contexts (persisted in ~/.rossoctl/config.yaml, kubectl-style)
rossoctl config get-contexts # created + seeded on first use
rossoctl config create-context --name dev \
--server http://my-host:8080/api/v1/ --namespace team1 --bearer-token <token> # becomes current
rossoctl config use-context dev
rossoctl config set-context --namespace team1 # set namespace on current context (warns if unknown to server)
rossoctl config set-context --namespace team1 --server http://other:8080/api/v1/ # also replace the server
rossoctl config set-context --name prod # rename the current context (updates the current reference)
rossoctl login --token <token> # set the token on the current context directly
rossoctl login # or: OAuth device flow against the server's Keycloak
rossoctl login --server http://host:8080/api/v1/ --token <token> # target the context for that host (create if absent), make it current
# Show the server's auth configuration (GET <server>/auth/config)
rossoctl auth-config
rossoctl auth-config --json
rossoctl --server http://my-host:8080/api/v1/ auth-config
# List agents (GET <server>/agents)
rossoctl agents list # single namespace: agents --namespace, else current context
rossoctl agents --namespace team2 list # list one specific namespace
rossoctl agents list --all-namespaces # -A: discover via GET /namespaces, list across all
rossoctl agents list --all-namespaces --json # each namespace's response, separated by ---
# Show one agent (GET <server>/agents/<namespace>/<name>)
rossoctl agents get orders # single-column text, laid out like the web detail page
rossoctl agents get orders --json # raw JSON
# Delete an agent (DELETE <server>/agents/<namespace>/<name>)
rossoctl agents delete orders
# Import an agent from a container image (POST <server>/agents)
rossoctl agents import from-image --name orders --containerImage ghcr.io/x/y:latest
rossoctl agents import --deployment-type sandbox from-image \
--name orders --containerImage ghcr.io/x/y:latest --imagePullSecret regcred \
--envVarsURL https://example.com/orders.env # newline-separated key=value
# `agents --namespace` overrides the context's namespace for any agents subcommand
rossoctl agents --namespace team2 get orders # -> GET /agents/team2/orders
# `agents --context` uses a named context instead of the current one (its server, token, namespace)
rossoctl agents --context prod get orders
rossoctl agents --context prod --namespace teamX list # --namespace still overrides the context's namespace
# Tools mirror the agents commands, against the /tools endpoint.
# --namespace, --context, --all-namespaces (-A), and --json behave as for agents.
rossoctl tools list # single namespace (context, or --namespace)
rossoctl tools list --all-namespaces # discover and list across all
rossoctl tools --namespace team2 list --json
rossoctl tools delete weather-mcp # DELETE /tools/<namespace>/weather-mcp
rossoctl tools import from-image --name weather-mcp --containerImage ghcr.io/x/y:latest # POST /tools
rossoctl tools import --deployment-type statefulset from-image \
--name weather-mcp --containerImage ghcr.io/x/y:latest --envVarsURL https://example.com/tool.env
# --ports sets service ports as name:port:targetPort[:protocol] (default http:9090:9090:TCP); a bare "port" = http:port:port:TCP
rossoctl tools import from-image --name weather-mcp --containerImage ghcr.io/x/y:latest --ports grpc:9000:9001:TCP,8080
# List namespaces (GET <server>/namespaces)
rossoctl namespaces list
rossoctl namespaces list --all # include non-kagenti-enabled namespaces
rossoctl namespaces list --json
# Log the underlying REST requests to stderr
rossoctl -v agents listContexts are persisted in ~/.rossoctl/config.yaml (directory 0700, file
0600). Each context has a name, a server URI, an optional namespace, and an
optional bearer token.
The file is created lazily — the first command that needs it seeds a context
from the default server (http://kagenti-ui.localtest.me:8080/api/v1/) and
makes it current. Creating a context makes it current.
The server a command talks to is resolved as: an explicit --server flag wins
(and no bearer token is sent); otherwise the current context supplies both the
server URI and its bearer token. The global --server and --verbose/-v
flags must appear before the subcommand; -v logs each REST request (method,
URL, status, timing) to stderr.
rossoctl login --token <token> stores a token on a context. With --server,
the token is stored on the context named after that server's hostname (created
if none exists), which becomes current; without --server, it is stored on the
current context. rossoctl login (no --token) runs the OAuth 2.0 device
authorization grant (RFC 8628): it reads keycloak_url, realm, and
client_id from GET <server>/auth/config, requests a device code from
Keycloak, prints a verification URL and one-time code (and best-effort opens a
browser), polls until you authorize, and saves the resulting bearer token on
the target context.
The command tree mirrors the subcommands referenced in the Rossoctl docs
(agents, config, gateway, images, namespaces, skills, tools, ui,
plus auth-config and the top-level apply, install, login, status,
uninstall). The
config context commands, login, auth-config, agents list,
agents get, agents delete, agents import from-image, tools list,
tools delete, tools import from-image, and namespaces list are
implemented; other leaf commands currently print
UNIMPLEMENTED as a placeholder.