How it works
Nine walkthroughs: the shape of the tool, how you get anywhere in it, how changes are batched and reviewed, how it behaves at fifty thousand resources, and how it works with the AI agent you already run.
Five commands, one surface
The UI is the product. The other four exist because a UI cannot be piped, scheduled, or called by an agent.
zn the UI. everything, keyboard and mouse
│
├── zn auth login · logout · token new · org use
├── zn run <op> [flags] any of the 119 operations, non-interactively
│ └── --raw the 27 prefixes with no catalogue entry
├── zn doctor what works, what does not, and why
└── zn mcp serve --stdio the agent bridge
THE BRIDGE BETWEEN THEM
every action in the UI displays its `zn run …` equivalent
y copies it
so: you never memorise a command,
and you never have to leave the UI to find one.Stage, review, apply
The change set is why a TUI beats a CLI here. Cost work is inherently multi-resource, and applying 40 changes as 40 commands is how mistakes happen.
Resources ──┐
Recs ──┼──▶ select across views ──▶ staged change set
Cost ──┘ (space · drag · ctrl-a on a filter) │
│
an AGENT can stage into the same set ────────────────────────┤
"zn:stage_schedule_attach" → shows up under A │
▼
┌────────────────────────────┐
│ A · review │
│ grouped by action + tier │
│ total saving computed │
│ T3 items called out │
└─────────────┬──────────────┘
│
┌─────────────────────────────┴──────────┐
│ any T3 in the batch? │
│ yes → type that resource's name once │
│ (or x to drop it and apply 6) │
│ no → [y/N] │
└─────────────────────────────┬──────────┘
▼
applied as ONE batched request,
permission-checked per resource,
with one entry in your history
One request, one entry in your history, one thing to undo — instead
of forty commands and forty chances to get one of them wrong.A view over 51,204 rows
The estate never enters this process. Every list is a window, every filter is a query, and the refresh budget is shared with the web app.
viewport 40 rows ┌───────────────────────────────────┐
│ │ GET /resources?limit=50&page=N │
▼ │ + server-side filters │
fetch page N ─────────────┤ + a stable order, so nothing │
prefetch N+1 └───────────────────────────────────┘
│
├── / search ──▶ a QUERY, never a client-side filter
├── sort ──▶ a QUERY (allowlisted columns only)
└── ctrl-a ──▶ selects the FILTER, not the rows
"all 1,088 running aws resources"
so a 40k-row selection is one predicate,
not 40k ids in memory
REFRESH BUDGET shared with the web app, so zn is deliberately
overview 30s conservative and tells you when it is waiting
lists manual · r to refresh
k8s table 10s cached
k8s live on select onlyThe mouse, and what it costs
Capturing the mouse takes away the gesture developers use most. This is the whole mitigation, and it is on screen permanently rather than in a man page.
WHILE THE UI RUNS mouse captured · SGR 1006 + DECSET 1002
click select row / tab / sidebar
drag range-select rows
wheel scroll
click header sort
THE COST native drag-to-select is gone
THE THREE WAYS BACK all three visible in the footer
shift+drag your terminal's own selection, untouched
y yank the row over OSC 52 — works through SSH,
which native selection does NOT
--no-mouse release capture entirely for the session
RELEASED AUTOMATICALLY TERM=dumb · NO_COLOR · CI=true · no tty
and whenever the UI shells out
OUTSIDE THE UI zn run and zn doctor never touch the
mouse and use OSC 8 hyperlinks insteadA question, routed to your agent
zn gives you the chat surface. Your agent supplies the intelligence, on your subscription. Nothing here needs a model, a key, or a bill.
you type in zn the answer comes back INTO zn
│ ▲
▼ │
zn context ─ org · view · filter · selection ids
│
▼
the connector decides HOW to invoke it
claude claude -p "{prompt}" --output-format stream-json
codex codex exec "{prompt}"
aider aider --message "{prompt}" (shell mode)
yours six lines of TOML
│
▼
the agent works — and calls back into zn for data
│
│ ┌──────────────────────────────────────────────────┐
└──▶│ zn IS the MCP server it is calling. │
│ So it does not parse prose to learn what │
│ happened — it SERVED the calls, and renders │
│ them as first-class rows even when the agent's │
│ own output is opaque or unstructured. │
└──────────────────────────────────────────────────┘
│
▼
writes become PROPOSALS in the change set, not actions
│
▼
⊕ staged 2 changes · A to review · you approve
TWO DEPTHS
a / ⏎ quick ask — headless, rendered inside zn, keeps the thread
C full session — hand over the terminal (tmux split, or
suspend and resume) when you want the agent's own UIBring your own agent
zn ships no model, stores no API key, and pays for no inference. It makes itself the best possible tool for the agent you already run.
WHAT zn DOES NOT DO
no model selection · no API key · no prompt UI · no chat history
no inference bill · no vendor lock
WHAT IT DOES INSTEAD $ZN_AGENT is to zn
┌──────────────────────────────┐ what $EDITOR is to git
│ zn mcp serve --stdio │
│ --propose-only │ one line registers it:
└──────────────┬───────────────┘ zn mcp install
│ (detects claude, cursor,
┌───────────┴───────────┐ codex, aider on PATH)
▼ ▼
claude · cursor any agent with a shell
codex · aider zn run --list
(MCP clients) zn run <op> --stage
INSIDE THE UI press c
$TMUX set ──▶ vertical split: zn left, your agent right
no tmux ──▶ suspend, release the mouse, hand over the
terminal, resume when the agent exits
WHAT IT HANDS OVER `zn context` — six lines
org · view · filter · selection ids · staged count · tool access
NEVER a data dump. The agent pulls what it needs through the tools.The agent proposes, you dispose
Two independent layers, so neither one being wrong is enough. This is also where the PAT-scope finding stops mattering.
LAYER 1 — the token LAYER 2 — the bridge
mint a read-scoped PAT for the agent zn mcp serve --propose-only
│ │
▼ ▼
the server refuses a write a write tool call becomes
before it reaches anything — a STAGED PROPOSAL, never
not zn's discretion, the rule an executed action
│ │
└────────────────┬───────────────────────┘
▼
proposals land in the change set
│
▼
a human opens zn, presses A, and sees
FROM YOU · 2 changes
PROPOSED BY … · 2 changes, with the agent's reasoning
│
▼
the tier ladder applies to the batch
a T3 member still needs its name typed
The agent does the analysis. You keep the authority. That split is
the whole design, and it is enforced in two places rather than one.What is generated, and what is not
Every screen, form, gate and help string is generated from the same catalogue the platform already uses, so a new capability appears here the day it ships.
the operation catalogue — one reviewed source of truth
│
└── generated ──────▶ the app, at build time
│
┌───────────────────────────────┼───────────────────────────────┐
▼ ▼ ▼
PALETTE ENTRY ACTION FORM zn run
name · short · tier from the JSON Schema flags
fuzzy-searchable scalar → text field + the
enum → picker same
default → prefilled gate
pattern → validation
object → editor pane
Of 119 operations, 110 take nothing but simple values, so their
forms are exact. The remaining nine get a structured editor.
Add a capability to the catalogue and it appears here as a palette
entry, a form, a confirmation gate and a scriptable command — all of
it, on the same day, with nothing to keep in sync by hand.Keys
Everything is reachable by keyboard, by mouse, and by the palette. The tier is read from the registry, never decided by the app.
Every key
Fifteen bindings is the whole surface. The palette covers everything else.
Exit codes
zn run and zn doctor are the scriptable surface, so their codes are a contract. 77 is “go get a role”; 4 is “you are not allowed”.
| 0 | success |
| 1 | the operation failed |
| 2 | usage error, or a refused unbounded fetch |
| 3 | not authenticated, or the token was rejected |
| 4 | denied by policy |
| 75 | UNKNOWN — a write whose outcome cannot be determined |
| 77 | you lack the capability (it names which) |
| 78 | a T3 gate was not satisfied non-interactively |
The risk ladder
Applied once per batch rather than once per resource — the change set is what makes that safe, because you see everything it covers before you confirm.
| Tier | What it is | In the UI | Count |
|---|---|---|---|
| T0 | read | opens | 85 |
| T1 | bookkeeping, idempotent | runs | 6 |
| T2 | state change | [y/N] once, per batch | 17 |
| T3 | irreversible | type the name · --confirm | 11 |
Every screen
Twenty screens, drawn against an estate of fifty thousand resources.
zn
No subcommand. The whole product opens, and the first screen answers the question people actually have: what is this costing, what is broken, what changed.
$ zn ┌─ zn · Acme · acme-prod ───────────────────────────────────── $18,204/mo ▲6.2% · 3 alerts ─┐ ├────────────────┬──────────────────────────────────────────────────┬──────────────────────────┤ │ 1 Overview │ SPEND SAVEABLE RESOURCES │ TOP MOVERS │ │ 2 Resources │ $18,204 $2,840 51,204 │ │ │ 3 Cost │ │ aks-9920 │ │ 4 Recs │ ALERTS │ platform-eu ▲ $312 │ │ 5 Schedules │ ● budget q3-platform at 94% │ db-3f10bc │ │ 6 Deploys │ ● 3 pods CrashLoopBackOff · platform-eu │ orders-pg ▲ $141 │ │ 7 Kubernetes │ ● discovery stale 4h · gcp-prod │ i-0abc123 │ │ 8 Govern │ │ api-prod ▼ $38 │ │ 9 Settings │ RECENT │ │ │ │ 14:02 deploy checkout → staging ok │ COVERAGE │ │ staged │ 11:40 bulk stop 12 resources ok │ scheduled 38% │ │ 0 changes │ 09:15 schedule nights created ok │ tagged 91% │ │ │ │ │ │ │ │ │ ├────────────────┴──────────────────────────────────────────────────┴──────────────────────────┤ │ ctrl-k palette · / search · ? keys · shift+drag select text · q quit │ └──────────────────────────────────────────────────────────────────────────────────────────────┘
Resources
Virtualised against the server, never held in memory. Multi-select with the keyboard or a mouse drag; the inspector follows the cursor.
┌─ zn · Resources ─────────────────────────── 51,204 · filter: aws · running · 3 selected ─┐ ├────────────────┬──────────────────────────────────────────────────┬──────────────────────────┤ │ 1 Overview │ ID NAME TYPE COST SCH │ i-0abc123 │ │ 2 Resources │▸◉ i-0abc123 api-prod ec2 $412 — │ api-prod │ │ 3 Cost │ ◉ i-0def456 worker-batch ec2 $286 nights│ │ │ 4 Recs │ ◯ db-3f10bc orders-pg rds $884 — │ cpu p95 3.1% │ │ 5 Schedules │ ◉ vol-7712aa orders-pg-dataebs $96 — │ net 0.4 MB/s │ │ 6 Deploys │ ◯ fn-checkout checkout lambda $18 — │ uptime 42d │ │ 7 Kubernetes │ ◯ i-0aa9911 ci-runner-3 ec2 $204 — │ cost $412/mo │ │ 8 Govern │ ◯ aks-9920 platform-eu aks $1,204 wknd │ │ │ 9 Settings │ │ RECOMMENDED │ │ │ … 51,197 more · scroll or / to narrow │ ec2-idle-30d │ │ staged │ │ saves $412/mo │ │ 3 changes │ │ │ │ │ │ a actions on 3 │ │ │ │ A review staged │ ├────────────────┴──────────────────────────────────────────────────┴──────────────────────────┤ │ space select · drag range · a actions · ⏎ open · / search server-side · y yank │ └──────────────────────────────────────────────────────────────────────────────────────────────┘
ctrl-k
The answer to “I don’t want to remember commands”. Fuzzy over all 119 operations and every view — and it shows the scriptable form of whatever you highlight.
┌─ zn · Resources ──────────────────────────────────────────────────────────────────── 51,204 ─┐ ├──────────────────────────────────────────────────────────────────────────────────────────────┤ │ ctrl-k sched▌ 119 operations │ ├──────────────────────────────────────────────────────────────────────────────────────────────┤ │ ▸ Attach selection to a schedule schedule attach T2 │ │ Create a schedule schedule create T2 │ │ List schedules schedules list T0 │ │ Detach a resource from a schedule schedule detach T2 │ │ Go to Schedules view view · 5 │ │ Schedule success report reports schedule-success T0 │ ├──────────────────────────────────────────────────────────────────────────────────────────────┤ │ runs non-interactively as zn run schedule-attach --schedule=nights --resource=i-0abc123 │ │ y copies that line. You never memorise a command; the palette │ │ hands you the script when you need one. │ ├──────────────────────────────────────────────────────────────────────────────────────────────┤ │ ⏎ run · ↑↓ move · y copy as script · esc close │ └──────────────────────────────────────────────────────────────────────────────────────────────┘
an action form
Built from the tool’s JSON Schema, not hand-authored. 92% of arguments are plain scalars; enums become pickers, patterns become validation, defaults are prefilled.
┌─ zn · schedule create ─────────────────────────────── generated from the tool's JSON Schema ─┐ ├──────────────────────────────────────────────────────────────────────────────────────────────┤ │ ▸ name nights string · required │ │ timezone UTC enum · 11 values ← ⇆ to cycle │ │ crons[0] 0 19 * * 1-5 · stop array<object> · required │ │ crons[1] 0 8 * * 1-5 · start + to add a row │ │ resource_ids 3 selected from your selection e to edit │ │ description string · optional │ ├──────────────────────────────────────────────────────────────────────────────────────────────┤ │ T2 · reversible 3 resources will start and stop on this timetable │ │ idempotency key minted automatically · never surfaced │ ├──────────────────────────────────────────────────────────────────────────────────────────────┤ │ tab next · ⏎ create · y copy as zn run · esc cancel │ └──────────────────────────────────────────────────────────────────────────────────────────────┘
A · review
The feature a CLI cannot have. Stage changes across views, see the total saving, then apply once as a single bulk action — with the T3 gate applied to the batch rather than seven times.
┌─ zn · Review staged changes ───────────────────────────────── 7 changes · saves $1,986/mo ─┐ ├──────────────────────────────────────────────────────────────────────────────────────────────┤ │ ATTACH TO SCHEDULE nights T2 │ │ i-0abc123 api-prod saves $412 │ │ i-0def456 worker-batch saves $286 │ │ vol-7712aa orders-pg-data saves $96 │ │ │ │ STOP NOW T3 │ │ i-0aa9911 ci-runner-3 saves $204 │ │ │ │ RESIZE T2 │ │ aks-9920 platform-eu saves $604 │ │ fn-checkout checkout saves $122 │ │ db-3f10bc orders-pg saves $262 │ │ │ ├──────────────────────────────────────────────────────────────────────────────────────────────┤ │ 1 change is T3 and irreversible. Type ci-runner-3 to include it, │ │ or press x to drop it and apply the other 6. │ │ › ▌ │ ├──────────────────────────────────────────────────────────────────────────────────────────────┤ │ a apply · x drop · d diff · esc back · executes as one bulk action │ └──────────────────────────────────────────────────────────────────────────────────────────────┘
Cost
Dimensions on tab, drill on enter. It states its own limits: USD, UTC, and that the last day or two reads low because the rollup runs at 19:55 UTC.
┌─ zn · Cost ─────────────────────────────────── 1–30 Jul 2026 UTC · $18,204 ▲6.2% · USD ─┐ ├────────────────┬──────────────────────────────────────────────────┬──────────────────────────┤ │ 1 Overview │ by provider (tab: team · tag · resource) │ DRILL │ │ 2 Resources │ │ │ │ 3 Cost │ aws ████████████████████████████ $11,880 │ platform │ │ 4 Recs │ gcp ███████████ $4,410 │ $6,204 ▲9% │ │ 5 Schedules │ azure ████ $1,914 │ data │ │ 6 Deploys │ │ $5,110 ▲4% │ │ 7 Kubernetes │ 30-day trend │ web │ │ 8 Govern │ ▁▂▂▃▃▄▄▅▅▅▆▆▆▇▇▇███▇▇▆▆▅▅▄▄▃▂▁ │ $3,880 ▼2% │ │ 9 Settings │ │ untagged │ │ │ data through 29 Jul — the last 1–2 days read low│ $3,010 ▲8% │ │ │ every figure is USD, every window is UTC │ │ │ │ │ ⏎ drill in │ │ │ │ e export CSV │ │ │ │ CSV │ ├────────────────┴──────────────────────────────────────────────────┴──────────────────────────┤ │ tab dimension · ⏎ drill · e export CSV · / filter │ └──────────────────────────────────────────────────────────────────────────────────────────────┘
Kubernetes
The table comes from the cheap reports endpoint; the expensive live call fires only for the focused row, because the live endpoints scan the whole org inventory to find one.
┌─ zn · Kubernetes · platform-eu ──────────────────────────────── 41 pods · 3 unhealthy · aks ─┐ ├────────────────┬──────────────────────────────────────────────────┬──────────────────────────┤ │ 1 Overview │ pods deployments events nodes │ checkout-7d9f │ │ 2 Resources │ │ prod · Running │ │ 3 Cost │ NAME READY RST CPU MEM │ │ │ 4 Recs │▸ checkout-7d9f-x2k1 1/1 0 120m 412Mi │ LOGS l │ │ 5 Schedules │ checkout-7d9f-p81q 1/1 0 98m 388Mi │ 14:02 INFO │ │ 6 Deploys │ worker-5b8c-mm40 0/1 14 — — │ 14:02 WARN │ │ 7 Kubernetes │ etl-2c1a-77zz 1/1 0 1.2 2.1Gi │ 14:02 ERROR │ │ 8 Govern │ api-gw-9f2e-k0d1 1/1 2 340m 701Mi │ │ │ 9 Settings │ redis-0 1/1 0 40m 120Mi │ d describe │ │ │ │ y yaml │ │ │ │ r restart T3 │ │ │ │ │ │ │ │ cached table │ │ │ │ live on select │ ├────────────────┴──────────────────────────────────────────────────┴──────────────────────────┤ │ l logs -f · d describe · r restart T3 · live data is fetched only for the focused row │ └──────────────────────────────────────────────────────────────────────────────────────────────┘
/
A server query every time. 51,204 rows do not fit in this process, and pulling them here to grep them would be the bug.
┌─ zn · Resources ────────────────────────────────────────────── searching 51,204 server-side ─┐ ├──────────────────────────────────────────────────────────────────────────────────────────────┤ │ / orders▌ 14 matches · 40ms │ ├──────────────────────────────────────────────────────────────────────────────────────────────┤ │ db-3f10bc orders-pg rds aws name │ │ vol-7712aa orders-pg-data ebs aws name │ │ i-0bb4410 orders-worker ec2 aws name │ │ sql-77aa orders-archive azuresql azure name │ │ bkt-orders orders-exports gcs gcp tag: team=orders │ │ … 9 more │ ├──────────────────────────────────────────────────────────────────────────────────────────────┤ │ Search is a server query, never a client filter — the estate does not │ │ fit in this process and pulling it here to grep it would be a bug. │ │ Name, id and tags are all matched, and the result comes back │ │ in tens of milliseconds against fifty thousand rows. │ ├──────────────────────────────────────────────────────────────────────────────────────────────┤ │ ⏎ open · space select · esc clear │ └──────────────────────────────────────────────────────────────────────────────────────────────┘
?
Everything reachable two ways. The text-selection escape hatches are on this screen and in the footer, permanently.
┌─ zn · Keys ───────────────────────────────────────────────────────────────────── ? to close ─┐ ├──────────────────────────────────────────────────────────────────────────────────────────────┤ │ MOVE SELECT ACT │ │ j k ↑ ↓ row space toggle row ctrl-k palette │ │ ctrl-d/u page drag range a actions │ │ wheel scroll ctrl-a all matching ⏎ open │ │ g G top / end x clear / search │ │ 1–9 · click view A review staged e edit │ ├──────────────────────────────────────────────────────────────────────────────────────────────┤ │ TEXT SELECTION │ │ shift+drag your terminal's own selection, untouched │ │ y yank the row over OSC 52 — works through SSH, │ │ which native selection does not │ │ zn --no-mouse release the mouse entirely │ ├──────────────────────────────────────────────────────────────────────────────────────────────┤ │ Mouse is captured while the UI runs. That costs native drag-select, │ │ so the two ways to get it back are on screen at all times. │ └──────────────────────────────────────────────────────────────────────────────────────────────┘
zn doctor
Everything that decides whether the tool works, including three things the platform will not tell you on its own.
$ zn doctor ✓ binary zn 0.1.0 · darwin/arm64 ✓ spec 119 ops · generated 2026-08-18 ✓ service api.zop.dev · 41ms ✓ spec skew none — embedded matches live tools/list ✓ auth dev@acme.io · token expires in 88d ✓ org acme-prod Acme · 51,204 resources ✓ clock local is IST · all requests sent UTC ! rate budget shared across your organisation zn paces itself so it never crowds the dashboard. ! agent cursor detected, never connected open it once, then zn connect terminal ✓ truecolor yes ✓ mouse SGR 1006 yes ✓ OSC 8 links yes ✓ OSC 52 clipboard yes ✓ box drawing yes ✓ size 142×48 3 warnings, 0 errors. zn doctor --verbose for the checks behind each.
zn auth
Paste a token. No device-code flow exists and building one is auth-service work, so this is honest about what it is.
$ zn auth dev@acme.io acme-prod Acme token zn_pat_…9f3c expires in 88 days login paste a token from Developer Settings logout forget it token new mint another org useswitch $ zn auth login Create one at https://app.zop.dev/settings/tokens Paste token › •••••••••••••••••••• ✓ dev@acme.io stored ~/.config/zn/credentials (0600) ! A token carries your own permissions. Mint a narrower one for CI or for an agent — zn auth token new. Which org? 1 Acme 51,204 2 Northwind 8,910 › 1 ✓ ready. Run zn.
zn run
One command for all 119 operations, plus --raw for the 27 prefixes with no catalogue entry. This is what the palette copies.
# the palette handed you this line. it is the whole scripting surface. $ zn run resources-list --provider=aws --state=running --limit=50 {"items":[{"id":"i-0abc123",…}],"total":1088,"hasMore":true} $ zn run --list | head -4 resources-list T0 List resources in this org, one page at a time resource-stop T3 Stop a running resource schedule-create T2 Start and stop things on a timetable service-deploy T2 Ship a revision to an environment $ zn run resource-stop --resource=db-3f10bc --confirm=orders-pg ✓ stopped orders-pg $ zn run --raw GET event-readiness {"items":[{"id":"er-14","name":"diwali-sale"}]} --raw reaches anything the catalogue does not cover yet. $ zn run resources-list --json | jq -r '.items[].id' i-0abc123 i-0def456 db-3f10bc Stdout is data, stderr is everything else, so every command pipes.
zn mcp serve
The same operations, the same permissions, the same trail — and the agent stages into the change set you review, rather than acting behind you.
$ claude mcp add zn -- zn mcp serve --stdio ✓ zn 119 tools, filtered to what this token may call $ claude > which aws resources cost the most and are doing nothing? ● zn:get_recommendations (rule=idle, provider=aws, limit=10) ↳ 10 of 47 · $2,840/mo · did not enumerate 51,204 resources ● zn:get_resource_overview (id=i-0abc123) ↳ cpu p95 3.1% · net 0.4 MB/s Three EC2 instances are under 5% CPU. A nights-and-weekends schedule saves $1,240/mo. Want me to stage it? The agent stages into the SAME change set the UI shows. You open zn, press A, and review what it proposed before anything applies.
the ask pane
Type a question; it goes to whichever agent you have connected. Because zn served the tool calls, it renders the answer with its OWN components — that table is the real one, selectable and actionable, not markdown the agent printed.
┌─ zn · Ask ─────────────────────────────────────── claude · connected · 3 resources attached ─┐ │ ask resources cost recs k8s │ ├──────────────────────────────────────────────────────────────────────────────────────────────┤ │ you │ │ which of these are safe to stop overnight? │ │ │ │ claude │ │ ● get_recommendations rule=idle provider=aws 10 of 47 │ │ ● get_resource_overview i-0abc123 cpu p95 3.1% │ │ ● get_blast_radius aks-9920 2 dependents │ │ │ │ Two are safe. aks-9920 backs a prod ingress, so I left it out. │ │ │ │ ID NAME CPU p95 IDLE SAVES │ │ ◉ i-0abc123 api-prod 3.1% 30d $412 │ │ ◉ i-0def456 worker-batch 1.4% 30d $286 │ │ ◯ aks-9920 platform-eu 44% — $604 │ │ ↑ the same table as the Resources view — space select · ⏎ open │ │ │ │ ⊕ staged 2 changes attach to nights saves $698/mo A review │ │ │ │ › ▌ │ ├──────────────────────────────────────────────────────────────────────────────────────────────┤ │ ⏎ send · @ attach selection · C full session · ^p switch agent · nothing here is markdown │ └──────────────────────────────────────────────────────────────────────────────────────────────┘
zn connect
Every agent you have, and whether the wiring actually works. A config file containing the right line is not proof of anything.
$ zn connect agent status tools transport last used ────────────────────────────────────────────────────────────────────── claude ✓ connected 119 stream-json 2h ago default codex ✓ connected 119 text yesterday cursor ! never opened — — — aider · shell mode — zn run — cursor — config written but it has never connected. Open Cursor once; a config file with the right line in it is not proof the wiring works. Add your own in ~/.config/zn/config.toml — six lines, no plugin to install: [connector.mine] detect = "my-agent" ask = ["my-agent", "-p", "{prompt}"] register = "my-agent mcp add zn -- zn mcp serve --stdio" speaks = "mcp" # or "shell" — then it gets zn run instead of tools No token is written to any of their config files. They hold a command; zn holds the credential. Rotating it is zn auth login, once.
zn mcp install
zn ships no model and stores no API key. It finds the agents already on your machine and registers itself as a tool provider for them.
$ zn mcp install looking for agents on this machine… ✓ claude Claude Code 2.1.4 ~/.claude.json ✓ cursor Cursor 0.48 ~/.cursor/mcp.json · codex not installed · aider not installed Register zn with both? [Y/n] › y ✓ claude zn mcp serve --stdio --propose-only ✓ cursor zn mcp serve --stdio --propose-only Both now see every operation your token is allowed to call. Writes are proposals, not actions — they land in your change set. zn ships no model, stores no API key, and pays for no inference. Your agent, your subscription, your context window.
c · ask your agent
The $EDITOR pattern. zn hands over the terminal — or opens a tmux split — and your own CLI does the talking, with zn as its hands.
# inside zn, press c — $ZN_AGENT is to zn what $EDITOR is to git ┌──────────────────────────────────────────────┬───────────────────────────────────────────────┐ │ zn · Recommendations │ claude (your CLI, your subscription) │ │ filter rule=idle provider=aws │ │ │ │ zn handed me: │ │ ◉ rec-8821 i-0abc123 $412 │ org acme-prod · Recommendations │ │ ◉ rec-8830 vol-7712aa $186 │ 3 selected · rule=idle │ │ ◉ rec-8844 aks-9920 $604 │ 119 tools via MCP │ │ ◯ rec-8851 fn-checkout $122 │ │ │ │ > are these safe to stop at night? │ │ 3 selected · $1,202/mo │ │ │ │ ● zn:get_resource_overview x3 │ │ c ask your agent │ ↳ all three idle 30d, no traffic │ │ a actions │ │ │ A review staged │ Two are safe. aks-9920 backs a │ │ │ prod ingress — I staged the two │ │ │ and left it out. │ │ │ │ └──────────────────────────────────────────────┴───────────────────────────────────────────────┘ In tmux, c opens a split. Without tmux, zn suspends, releases the mouse, and hands over the terminal — then resumes when the agent exits.
the agent proposes
Two layers stop an agent acting alone: a read-only token the platform enforces, and a bridge that turns every write into a proposal.
┌─ zn · Review staged changes ──────────────────────────── 5 changes · 2 proposed by claude ─┐ ├──────────────────────────────────────────────────────────────────────────────────────────────┤ │ FROM YOU │ │ ATTACH TO nights T2 │ │ i-0def456 worker-batch saves $286 │ │ vol-7712aa orders-pg-data saves $96 │ │ │ │ PROPOSED BY claude 14:06 │ │ ATTACH TO nights T2 │ │ i-0abc123 api-prod saves $412 idle 30d, no traffic │ │ rec-8830 orders-pg-data saves $186 gp2→gp3, restore-reversible │ │ note left aks-9920 out — it backs a prod ingress │ │ │ ├──────────────────────────────────────────────────────────────────────────────────────────────┤ │ The agent could not apply any of this. It has a read-scoped token │ │ and the bridge runs --propose-only, so a write is physically a │ │ proposal. You are the only thing that can press a. │ ├──────────────────────────────────────────────────────────────────────────────────────────────┤ │ a apply all 4 · x drop one · d why? · esc back │ └──────────────────────────────────────────────────────────────────────────────────────────────┘
any agent, six lines
The contract is a command to run and a context block to read. Claude, Codex, Cursor, aider or a shell script — zn does not care which.
# any agent. zn only needs a command to run. ~/.config/zn/config.toml [agent] command = "claude" args = ["--continue"] # or just: ZN_AGENT=codex zn # or aider, cursor-agent, opencode, a shell script — zn does not care # what the agent is handed. ids and shape, never a data dump. $ zn context org acme-prod Acme · 51,204 resources view recommendations · filter rule=idle provider=aws selected 3 rec-8821 rec-8830 rec-8844 staged 2 changes tools mcp: registered (propose-only) · shell: zn run --list budget rate limit 100/min, org-wide — prefer summaries to enumeration Six lines, not six thousand rows. The agent asks for what it needs through the tools; zn does not pre-load its context window.
failure states
A terminal app has to draw its own bad news. Each of these is a state zn will really be in, not a hypothetical.
# the states a real terminal app has to draw ✗ cannot reach zop.dev last good 14:02 · showing cached view, read-only retrying in 8s · r to retry now ! slowing down backing off 4s · 12 of 22 pages · the budget is shared with everyone in your organisation ! token rejected expired, revoked, or the service is briefly unavailable zn auth login · or zn doctor to tell them apart ✗ write outcome unknown the connection dropped after the request was sent. redeploy is not idempotent — zn will not retry it for you. key 9f2c-4410-b8 · ⏎ to open the deploy list and check
What lands when
Reads first, then actions and the change set, then the operations views, then your own agent.
| When | What lands | |
|---|---|---|
| Now | The view: overview, resources, cost and recommendations. Search, filters, and all of it scriptable. | read |
| Next | Actions and generated forms. The staged change set, with the risk ladder applied to the batch. | act |
| Then | Kubernetes, deploys and governance. A real log tail. | operate |
| Soon | Connectors: register zn with Claude, Codex, Cursor or your own agent. | connect |
| After | The ask pane: a question from inside zn, answered by your agent, rendered in zn’s own components. | ask |
What it deliberately is not
The decisions we made by leaving things out.
No API key to store, no inference to pay for, no vendor to be locked into. You bring the agent you already run.
Connectors put a command in your agent’s config, never a credential. Rotating is one login, once.
Writes from an agent land in the change set as proposals. Only a person can apply them.
Costs are labelled, windows stated, and where a figure is still settling the screen says so.
Asked for more than it can safely return, zn stops and names the command that answers the question properly.
When an outcome is genuinely unknown, zn says unknown and hands you the command to check.
Every action has both, and the palette is the safety net for whatever you have not learned yet.