Skip to main content

graphban-cli

gban — the client for a human at a terminal.

Five surfaces existed before this and none of them was for a person at a shell prompt: graphban talks to the database from inside the container, gbfleet supervises processes, gbagent is a spawned child, the web app is a browser, and /api/mcp is for agents. Issuing a seat, seeing why an agent is stuck, or re-tasking one meant opening a browser.

Specified by PRD-40.

Install

uv tool install graphban-cli

or, on macOS:

brew install asc-me/tap/gban

The formula carries no resource stanzas — graphban-cli has no runtime dependencies, so there is nothing to vendor and nothing to regenerate when a transitive moves. The supervisor is deliberately not in the tap; see below.

Add the supervisor too if you run waves — a separate package, and gban fleet hands off to it. At a terminal, gban fleet will offer to run this for you when it finds no supervisor; it asks first and never installs on a bare return, because a person who typed a read-only command did not consent to software being installed:

uv tool install graphban-fleet

That also gives you gbagent, the first-party coding agent: it is an entry point of graphban-fleet, not a package of its own, because the supervisor resolves it on PATH like any other vendor binary.

uv tool update-shell once, if uv says the bin directory is not on your PATH. uv tool upgrade --all to move both forward.

Two commands rather than one, and an extra (graphban-cli[fleet]) is deliberately not offered: uv tool install exposes only the REQUESTED package's executables, so an extra installs gbfleet into gban's environment and puts it on no path at all. Measured — with the extra, gban fleet reported "gbfleet is not installed here" while gbfleet sat in the very environment it was running from.

gban pulls nothing: client.py is urllib.request throughout, and the install lands exactly one distribution. gbfleet brings httpx and its transitives, which is why they are separate packages and not one.

With pip instead, into an environment you already have:

pip install graphban-cli

gban looks for gbfleet beside its own interpreter before falling back to PATH, so that shape works with neither on PATH.

To run an unreleased change, install from the repository instead — the same spec the release builds from:

uv tool install "git+https://github.com/asc-me/graphban.git#subdirectory=cli"

Releasing is docs/releasing.md.

gban login --server https://cloud.agentldgr.dev
gban doctor                       # both halves: the ledger, and the local fleet
gban agents                       # the roster, and why an agent is stuck
gban agents role SA-A4 planner    # what used to need a browser
gban seats issue worker worker planner                  # one entry per agent
gban keys                                               # which key is that agent on
gban fleet up --seats-file seats.txt --adapter claude   # hands off to gbfleet

seats issue takes one role per agent, repeats included, because that is the server's own shape: two agents on one seat share a session and cannot review each other. Each code is printed once and written nowhere — a CLI that helpfully saved them would invent a second credential at rest that no route and no test knows about.

agents prints what an agent was last refused, and why. That line is the reason the verb exists: a roster saying "idle worker" for an agent being told no on every call it makes is what made the Super-Arc diagnosis take a database query.

agents role re-tasks a live agent within its credential's ceiling and never past it. A role the key does not permit is the server's refusal, printed in the server's own words; widening a ceiling means minting a different credential, and keeping those two acts apart is the point of having a ceiling. It lands on the agent's next poll.

Enabling delegation on a project

gban login          # once, at a terminal
gban setup          # everything mechanical between that and a delegating agent
gban setup --auto   # …or every project whose repository sits here or beside here

An agent with the delegation skill runs all of this for you except gban login, which it hands back as a ! gban login line to type — it needs a terminal, and no agent has one — and then carries on from where you left it.

The directory names the project. Run setup from inside the repository the work belongs to and it matches that directory against the projects you can read. An explicit --project wins; nothing else does. In particular the default gban login stores is not used here and is named in the refusal when it exists — logging in once inside one project must not quietly mint a credential for it while you are standing in another repository, and a key in the wrong project is not a mistake anybody notices quickly.

setup mints a project-scoped credential, writes the graphban and gbfleet MCP entries into every parent harness that would actually read them, stores that key next to the login session (a gb_sk_…, never the refresh token), installs the supervisor if it is missing, drops the delegation skill into .claude/skills/, and then verifies rather than asserting: it asks the new credential what it can actually see. Restart the session that will call delegate/spawn afterwards — MCP servers are read at startup.

gban fleet and gban doctor's local half then use that minted key. They do not feed the login session to the supervisor. The order is $GBFLEET_API_KEY if you set it, then $GRAPHBAN_API_KEY, then the key gban setup stored — and, for a machine that already ran setup, the one already in the harness dest.

Three properties worth knowing, each of which is a bug this command exists to not have:

  • The credential does not expire. gban keys mint produces a wave key, which lasts a day (FLEET_KEY_DAYS); that is right for a wave and wrong for a project. Only seats expire.
  • It writes where the harness will actually read. There is more than one. Claude Code reads ~/.claude.json's per-project mcpServers (JSON), which outranks a repository .mcp.json — writing the repository file under a stale entry leaves the agent on the old key, and surfaces as a JSON parse error because the harness is parsing a 401 body. Grok reads ~/.grok/config.toml's mcp_servers (TOML, snake_case; mcpServers parses and loads nothing). Writing only Claude's file while Grok holds a different key reports success and leaves delegate unadvertised (GRPH-825). Grok's gbfleet MCP also gets --workspace ~/.grok/gbfleet-wt/<repo>: the sibling default is a path Grok's sandbox cannot write, and spawn then dies as git worktree add 128 (GRPH-826). Claude keeps the sibling default. --scope user is the default because a credential outside the repository cannot be committed. Re-running a working setup still repairs a missing gbfleet entry and a missing --workspace; reuse skips a mint, never a write.
  • --auto matches, and says so. A project carries no repository link — no remote, no path — so --auto compares your project ids and names against this directory, what is in it, and its siblings. One level, never a recursive walk. Two directories answering to one project, or one directory answering to two, are refused rather than guessed: a credential minted into the wrong repository is not a mistake anybody notices quickly.
  • It refuses to write a key into a file git tracks. --scope project on a tracked .mcp.json or .grok/config.toml is refused rather than warned about, because a warning attached to committing a credential still commits it.
  • It gitignores the files it and gbfleet write into the checkout. .mcp.json, .cursor/mcp.json, .grok/config.toml, .gbfleet-*, .swamp/, graphban-swamp/. A warning that said "make sure it is gitignored" still wrote the key; gban setup now adds the lines, and gban doctor FAILs if a credential file would still be committed. It asks git (check-ignore), so an equivalent pattern already in the file is left alone.

Wiring a checkout to Swamp

gban swamp setup

Steps 3 and 5 of the Swamp runbook: repo init, extension source add, vault create, and the gate credential — minted, piped to swamp vault put on stdin, then checked for the scopes it actually came back with. Every step is skipped when already done.

Two things it will not do. It does not install Swamp, because that install pipes a remote script into a shell. And it never writes the gate key into an MCP config: gban setup's agent key must not carry gate, or the agent doing the work attests its own completion — which fails silently, since a gate that always says yes looks exactly like a gate that held.

gban stack map

gban stack map --dry-run
gban stack map --project <id>

Reads this checkout and posts one app-topology record to POST /api/platform/stack/app. --dry-run prints that record and posts nothing; it needs no server and no key. Two runs of the same commit print the same JSON. There is no timestamp in it.

What it reads:

  • Compose files (compose.yml, docker-compose.yml, and the compose.* / docker-compose.* variants): a service with build is a deployable, a service with only image is infrastructure, and depends_on is an edge. environment contributes variable names and, when a value is a literal URL, the bare host.
  • Dockerfiles (Dockerfile, Dockerfile.*, *.Dockerfile), for the deployable they sit in and for a FROM image the table knows.
  • Dependency manifests (package.json, pyproject.toml, requirements*.txt, go.mod, Cargo.toml, Gemfile), matched against the known-SDK table and attributed to the deployable whose directory contains them.
  • .env.example only. A real .env is not a template and is not opened. Source files are not scanned.

The known-SDK table ships at cli/src/gban/data/stack_sdks.toml and is versioned with gban. A checkout may add rows in .graphban/stack-sdks.toml (same shape); those rows are consulted first.

Compose is parsed by a subset, not by PyYAML: block mappings and lists, single-line [...] / {...} flow, and | / > scalars. Anchors and aliases are not expanded (*name stays text). A << merge key is ignored. A file the subset cannot read is skipped with a warning on stderr; the rest of the tree is still mapped.

Env values are never stored and never posted. A host taken from a URL is the bare host (api.stripe.com), because the posted shape rejects any string containing ://. An image the table does not know is infrastructure named after the compose service, kind unclassified image — the image string is not stored. A depends_on, external: true, or extends.file that names something this file does not define becomes an unresolved node. It is not dropped and it is not a failure.

A real post needs a git commit (there is nothing to send for a repo with no HEAD) and a project-write API key: $GBFLEET_API_KEY, then $GRAPHBAN_API_KEY, then the key gban setup stored for this project. The login session is a different credential and is not sent. Pass --project (or $GRAPHBAN_PROJECT) so the record names the project; without one, --dry-run still prints and a post is refused.

gban login wants a real terminal

It refuses without one, rather than prompting. getpass falls back to a plain echoing read when it cannot turn echo off — it warns, but the warning arrives after the person has decided to type — so a login through a pipe, a heredoc or an editor's command runner would put the password in the scrollback. There is no non-interactive login yet (PRD-40 open question 2: an API key cannot reach the JWT routes, so CI would need a service session).

Why not gb

Because gb is already git branch on a large share of developer machines, and an alias beats a binary on PATH. The deployed walk hit it on the very first command and got git's usage text; nothing inside the process can detect that, because by the time gb would have run, the alias did not.

It is not one alias but a whole namespace. oh-my-zsh's git plugin — which is where most of these come from — defines sixteen gb* aliases and ten grb*, so gb, gba, grb and gbl are all spoken for. gban is outside it, still short, and still says which product it belongs to.

Licence — Apache-2.0, deliberately not the repository's FSL-1.1

The repository is FSL-1.1-Apache-2.0. This directory is Apache-2.0, for the reasons PRD-22 §8 gives for fleet/ — every one of which applies here identically. gban is inert without a Graphban server and holds no authority of its own, so FSL's Competing Use clause protects the server and protects nothing here. It is a laptop-installed developer CLI, which is exactly the kind of dependency that has to clear a corporate licence policy scanner.

Why it is in this repository

Not a second repository, for the reason fleet/README.md gives for the supervisor, with more force: the client↔server contract has no schema anywhere, and a cross-repo break would present as absence reading clean — gban still runs, nothing errors, the verb quietly stops meaning what it said. The evidence is recent and specific: ROLES lost reviewer in one PR while another added a test naming it, and CI caught the pair inside seventeen minutes because both lived in one repository. Split across two, that lands as a bug report from somebody whose gban agents role ... reviewer started refusing.

Not inside backend/, because graphban-api pulls fastapi, sqlalchemy, pgvector, psycopg, alembic, redis and cryptography, and this installs on a laptop. tests/test_packaging.py derives its forbidden set from the backend's own dependency list rather than a denylist somebody maintains.

What it is not

It is not a second web app: no board, no PRD editor, no search. Every verb is either something a human currently opens a browser for, or a diagnosis nothing else gives.

It is not graphban, which talks to the local database from inside the container and stays exactly as it is. Mixing "against the DB in the container" and "over HTTP from a laptop" into one command is an ambiguity that ends with somebody purging the wrong instance.

It holds no state the server does not and computes nothing the server computes. Every verb is one endpoint, called once (PRD-40 D11); an ordering between two calls would be a rule, and a rule in the client is a second definition of something the server already enforces.

Metadata

Release files for graphban-cli 0.7.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for graphban-cli 0.7.1
File Size Uploaded Provenance
graphban_cli-0.7.1.tar.gz 113.0 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for graphban-cli 0.7.1
File Interpreter ABI Platform
graphban_cli-0.7.1-py3-none-any.whl Python 3 none any Details

Total release size: 189.9 kB

Release files / graphban_cli-0.7.1.tar.gz

Download URL graphban_cli-0.7.1.tar.gz
Size 113.0 kB
Tags Source
SHA-256 checksum
How to use checksums
d1234d5a9bc803c5e6bda0b60a2b81c3fef95ec169d8403be0240b0546da4418
BLAKE2b-256 checksum
How to use checksums
d06ccd026ffa70f0b4c4e911e1d0cbcfcba3c9942641f33b77f54213fdad6060
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.

Transparency log

Release files / graphban_cli-0.7.1-py3-none-any.whl

Download URL graphban_cli-0.7.1-py3-none-any.whl
Size 76.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
dbf62540126fc4da1c14a89ba724dd830ac2f1ba9e4b4ed3f150a10ef904b11a
BLAKE2b-256 checksum
How to use checksums
4347a1c2e8cadf41fbc896b64bcf3a4ec96446c24204328a64de4d900668a0a9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.

Transparency log

Security for graphban-cli 0.7.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for graphban_cli-0.7.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page