Command line and agents
- Status
- Current
- Updated
- 2026-09-15
- Scope
- ekko CLI
ekko is the command-line counterpart of what ZaunEkko offers on the web. Whatever you can see in a browser, it can read — including content behind level gates, because it uses your own identity.
It is not only a command line for people. It is built so that someone else's agent can use it as smoothly as a person browses the site: a machine-readable capability listing, stable error codes, and output that switches to JSON on its own inside a pipe.
Install
npm install -g @zaunekko/cli
The package ships a launcher plus prebuilt binaries for each platform, so ekko is available right after install.
ekko version
Do not try to build from source — that repository is private, and the path is closed to you.
Sign in
ekko login
The command opens your browser at the ZaunEkko Account sign-in page. Once you approve, the browser shows a completion notice and the terminal receives the credential. It is the same sign-in screen you already use.
If this machine cannot open a browser (a server, a container, an SSH session), the command falls back to a verification code: the terminal prints a URL and a short code, and you open that URL on any device that has a browser. Your phone works.
Either way the terminal always prints the URL. Opening a browser fails silently in many environments, and printing the link is what keeps you unblocked when it does.
ekko login --no-browser # never try to open a browser, just print the link
ekko login --device # go straight to the code flow
ekko login --force # re-authorize even when already signed in
One sign-in lasts a long time: the credential renews itself, and only a long stretch of complete inactivity ends it.
What you are granting
The consent screen lists the permissions. One of them deserves a note: blog.read lets the command line read every Blog post and moment that you yourself can read, including the ones behind level gates. That is exactly the point of this tool — your agent sees what you see — but you should know what you granted.
All of it is read-only. Publishing posts or moments, commenting and uploading are not included.
Revoking
Account → "Sign-in and security" → "Authorized applications" revokes it at any time. After revoking, that machine cannot renew; an access token already issued stays valid for up to 15 more minutes.
ekko logout only clears the local credential — it does not revoke the grant on the server. If a machine is lost, use the page above.
What it can do
Sign-in
ekko login browser sign-in; falls back to a code
ekko logout clear the local credential
ekko whoami what the local credential says; no network
ekko account live level, points and site roles; read-only
Catalog and calls
ekko catalog ls list public services
ekko catalog show <service> details; --version + --contract for schemas
ekko invoke <service> make one call (spends points)
ekko invocations ls call receipts
ekko invocations show <id> a single receipt
ekko rate-limit your current call window
Service status
ekko status public health summary
ekko status incidents incident record
ZaunEkko's Blog (read-only)
ekko blog posts ls posts you can read
ekko blog posts show <slug> a single post
ekko blog moments ls moments
For agents
ekko schema machine-readable capability listing
Without a valid credential nothing works except login, logout, version, help and schema. There is no anonymous read-only fallback, and that is deliberate: a tool that half-works while signed out leaves you unable to tell "I am not signed in" from "this does not exist" — and on Blog, that difference is whether you are seeing everything or only half of it.
Publishing a service and registering a provider are irreversible and stay in the Marketplace console.
Making a call
ekko invoke pixiv.daily-ranking \
--version v1 \
--idempotency-key daily-ranking-2026-09-15 \
--input '{"mode":"daily"}'
--input takes a JSON literal, @path/to/file, or @- to read standard input.
--idempotency-key is required, and getting it wrong costs money
It exists to prevent double billing. On a timeout, a dropped connection or a retry you cannot tell whether the request arrived. Resend with the same key and the platform recognises the same call, returning the original result instead of charging you again.
So the key must be:
- stable for one business intent (unchanged across retries);
- unique across different intents (a new call gets a new key).
A workable pattern is to combine a business identifier with a time window, for example daily-ranking-2026-09-15.
Do not use a random value. Generating a fresh key on every retry turns the mechanism off — and the cost of turning it off is being charged twice, with nothing reporting an error.
The full contract lives in Authentication and calls, which is authoritative for the call surface.
Two kinds of credential
ekko login | ZAUNEKKO_API_KEY | |
|---|---|---|
| Suited to | you, at a terminal | unattended scheduled jobs |
| Can do | every command | call services only |
| Reads Blog | yes | no |
| Lifetime | renews itself | long-lived, revocable |
Create an API key on the "API key" page in Account, then put it in the environment:
export ZAUNEKKO_API_KEY=zek_live_...
ekko invoke ... # no sign-in needed
A key can only call services. Governance commands reject it explicitly, rather than letting the server return a 403 whose cause you cannot see.
The contract agents rely on
The pipe decides the output shape, not a flag
ekko catalog ls # a person at a terminal → table
ekko catalog ls | jq # piped → JSON
ekko catalog ls > out.json # redirected → JSON
Same convention ls and git use for colour. --json and --table override it explicitly.
Tables are for people, the layout may change at any time, do not parse them. JSON forwards the server response as-is.
Exit codes
| Code | Meaning |
|---|---|
0 | success |
2 | not signed in — trigger re-authentication in CI |
1 | any other failure |
2 is reserved for "not signed in" so that scripts never have to grep the message. Messages change, and one reworded word would silently break your check.
Error body
In JSON mode the error goes to stdout as well — an error is a kind of result, and writing it to stderr would leave the caller reading an empty stdout and guessing:
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "...",
"hint": "no call happened and nothing was charged; submit a new call after retryAfter seconds",
"retryAfter": 37
}
}
code is stable: NOT_LOGGED_IN, USAGE, NETWORK, UNAUTHORIZED, FORBIDDEN, NOT_FOUND, RATE_LIMIT_EXCEEDED, SERVER_ERROR, UNKNOWN.
Branch on code, never on message.
Self-description
ekko schema
Prints every command, its parameters, which ones need a sign-in, which ones spend points, plus the output and exit-code conventions. An agent can learn what the tool does without reading this page first.
That listing is itself a contract, so its fields are deliberately few and stable; a changed schemaVersion means the shape changed.
Configuration
Everything is an environment variable. There is no config file — on a tool installed on someone else's machine, a config file becomes the layer nobody can see while debugging.
| Variable | Purpose |
|---|---|
ZAUNEKKO_API_KEY | unattended credential |
ZAUNEKKO_HOME | where credentials live (default ~/.zaunekko) |
ZAUNEKKO_DEVICE_NAME | the machine name shown under "Authorized applications"; set it empty to report nothing |
Versions
ekko carries its own version, unrelated to site releases. It never assumes it matches the server: an old version warns but does not block, and a newer version is only ever announced, never installed for you.
Upgrading and installing are the same command:
npm install -g @zaunekko/cli