CLI

kaja is the terminal client. It talks to the same API the dashboard uses, so the two always agree about what exists.

Install

curl -sfL https://kaja.dev/cli.sh | sh

macOS and Linux, on Intel and ARM. The script verifies the download against the release checksums and installs to /usr/local/bin; set KAJA_INSTALL_DIR to put it somewhere else. Windows binaries are on the release page.

Check it worked:

kaja version

Log in

kaja auth login

This opens your browser and you sign in the way you normally do. The CLI starts a one-shot listener on 127.0.0.1, and the session token is handed back to that local port, so it never travels anywhere else. It is stored in ~/.kaja/config.yaml with 0600 permissions.

There are no API tokens to create or rotate. kaja auth logout revokes the session, and kaja auth whoami shows who you are signed in as.

If you belong to more than one organization, pick one once:

kaja org ls
kaja org use acme

That choice is saved, so no later command needs --org.

Everyday commands

Almost everything is scoped to a project, passed as --project or -p. The cluster is worked out from the project, the same way the dashboard hides it.

kaja project ls
kaja app ls -p demo
kaja app get api -p demo
kaja logs api -p demo

kaja logs streams from the running pods and colours each pod differently, so a rollout with two replicas is readable. --tail sets how much history to show first, --no-follow prints that history and exits, and --timestamps includes the container runtime's own timestamps.

Deploy an image

kaja app deploy api -p demo --image ghcr.io/acme/api:v2

This does what the dashboard does: it writes a draft and then publishes it. If someone has an unpublished draft open in the console, the deploy builds on top of that draft rather than discarding their edit, and it says so. --no-publish stops after the draft.

Secrets

kaja secret ls -p demo
kaja secret set app-config API_URL=https://api.example.com -p demo
kaja secret set app-config --unset OLD_KEY -p demo

Keys you do not mention are preserved. Values are never printed back, by any command, including --json.

Two refusals worth knowing about. Secrets that are not Opaque, such as registry credentials, are managed from the console form that owns their structure. And a secret whose values came back redacted is refused rather than written, because writing redacted values back would blank every key in it.

Domains

kaja domain add app.example.com -p demo
kaja domain verify app.example.com -p demo
kaja domain ls -p demo

domain add prints the exact DNS records the server checks for, rather than guessing at them, because the routing target depends on how your cluster is published. Once the records resolve, binding the domain to an app's port is done in the console.

Open a terminal

kaja shell api -p demo

You get an interactive shell in the app's running container, with your local window size and resizing as you resize the terminal. It is the same session machinery as the console's terminal tab, which means the same rules: every session is recorded, sessions count against your organization's limits, and they end on the same idle and duration timeouts.

The shell that runs is chosen by the server, bash falling back to sh. It is not a command you pass in, because the argv is what gets written to the audit record.

Managed services are reachable the same way, which is the quickest route to a psql prompt against your own database:

kaja service ls -p demo
kaja shell postgres-main -p demo --service

Pick a specific replica with --pod, or a sidecar with --container. For a distroless image, or a container that is crash-looping and therefore has no shell to enter, --debug attaches an ephemeral debug container instead:

kaja shell api -p demo --debug

Kubernetes cannot remove an ephemeral container once it is added. It stays in the pod's spec until the pod is replaced.

Scripting

--json works on any read command and prints the decoded payload instead of a table. Empty lists come back as [] rather than null, so a jq pipeline does not break on an organization with nothing in it.

kaja app ls -p demo --json | jq -r '.[].metadata.name'

kaja shell exits with the remote command's exit code, so it composes with && and set -e like any other command.

Tab completion comes from kaja completion <shell> for bash, zsh, fish and PowerShell. On top of the command names, project, app, secret, service, domain and organization names complete by querying the API, along with pod and container names for kaja shell. Each lookup gives up after three seconds, so a shell never hangs on TAB.

Serve it to an AI agent

kaja mcp exposes the same read-only view over the Model Context Protocol, so an agent can answer questions about what you have deployed:

claude mcp add kaja -- kaja mcp

It reuses this CLI's session and cannot write anything. See MCP server.

Configuration

Settings resolve in this order: flag, then environment variable, then ~/.kaja/config.yaml, then the built-in default.

FlagEnvironmentDefault
API--endpointKAJA_ENDPOINThttps://server.kaja.dev
Console--web-urlKAJA_WEB_URLhttps://console.kaja.dev
Organization--orgKAJA_ORGwhatever kaja org use saved
SessionKAJA_TOKENthe stored login

These are two different hosts. The API endpoint serves the GraphQL API, and the console URL is only used to open the browser at login. Pointing --endpoint at the console returns a web page instead of JSON, and the CLI says so rather than failing obscurely.

Next steps