graphban-cli 0.2.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {graphban_cli-0.2.0 → graphban_cli-0.4.0}/PKG-INFO +37 -12
- {graphban_cli-0.2.0 → graphban_cli-0.4.0}/README.md +36 -11
- {graphban_cli-0.2.0 → graphban_cli-0.4.0}/pyproject.toml +1 -1
- {graphban_cli-0.2.0 → graphban_cli-0.4.0}/src/gban/cli.py +61 -10
- {graphban_cli-0.2.0 → graphban_cli-0.4.0}/src/gban/setup.py +313 -53
- {graphban_cli-0.2.0 → graphban_cli-0.4.0}/src/gban/skills/graphban-delegation/SKILL.md +16 -4
- graphban_cli-0.4.0/src/gban/swamp.py +278 -0
- {graphban_cli-0.2.0 → graphban_cli-0.4.0}/tests/test_acts.py +1 -1
- {graphban_cli-0.2.0 → graphban_cli-0.4.0}/tests/test_setup.py +239 -5
- graphban_cli-0.4.0/tests/test_swamp.py +322 -0
- {graphban_cli-0.2.0 → graphban_cli-0.4.0}/.gitignore +0 -0
- {graphban_cli-0.2.0 → graphban_cli-0.4.0}/LICENSE +0 -0
- {graphban_cli-0.2.0 → graphban_cli-0.4.0}/src/gban/__init__.py +0 -0
- {graphban_cli-0.2.0 → graphban_cli-0.4.0}/src/gban/client.py +0 -0
- {graphban_cli-0.2.0 → graphban_cli-0.4.0}/src/gban/config.py +0 -0
- {graphban_cli-0.2.0 → graphban_cli-0.4.0}/src/gban/doctor.py +0 -0
- {graphban_cli-0.2.0 → graphban_cli-0.4.0}/tests/test_doctor.py +0 -0
- {graphban_cli-0.2.0 → graphban_cli-0.4.0}/tests/test_login_projects.py +0 -0
- {graphban_cli-0.2.0 → graphban_cli-0.4.0}/tests/test_packaging.py +0 -0
- {graphban_cli-0.2.0 → graphban_cli-0.4.0}/tests/test_session.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: graphban-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: gban — a Graphban client for the human at a terminal
|
|
5
5
|
Project-URL: Homepage, https://github.com/asc-me/graphban
|
|
6
6
|
Project-URL: Repository, https://github.com/asc-me/graphban
|
|
@@ -127,28 +127,53 @@ is named in the refusal when it exists — logging in once inside one project mu
|
|
|
127
127
|
mint a credential for it while you are standing in another repository, and a key in the wrong
|
|
128
128
|
project is not a mistake anybody notices quickly.
|
|
129
129
|
|
|
130
|
-
`setup` mints a project-scoped credential, writes the `graphban` and `gbfleet` MCP entries
|
|
131
|
-
|
|
132
|
-
and then **verifies** rather than
|
|
133
|
-
|
|
130
|
+
`setup` mints a project-scoped credential, writes the `graphban` and `gbfleet` MCP entries
|
|
131
|
+
into every parent harness that would actually read them, installs the supervisor if it is
|
|
132
|
+
missing, drops the delegation skill into `.claude/skills/`, and then **verifies** rather than
|
|
133
|
+
asserting: it asks the new credential what it can actually see. Restart the session that will
|
|
134
|
+
call `delegate`/`spawn` afterwards — MCP servers are read at startup.
|
|
134
135
|
|
|
135
136
|
Three properties worth knowing, each of which is a bug this command exists to not have:
|
|
136
137
|
|
|
137
138
|
- **The credential does not expire.** `gban keys mint` produces a *wave* key, which lasts a
|
|
138
139
|
day (`FLEET_KEY_DAYS`); that is right for a wave and wrong for a project. Only seats expire.
|
|
139
|
-
- **It writes where the harness will actually read.**
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
140
|
+
- **It writes where the harness will actually read.** There is more than one. Claude Code
|
|
141
|
+
reads `~/.claude.json`'s per-project `mcpServers` (JSON), which outranks a repository
|
|
142
|
+
`.mcp.json` — writing the repository file under a stale entry leaves the agent on the old
|
|
143
|
+
key, and surfaces as a JSON parse error because the harness is parsing a 401 body. Grok
|
|
144
|
+
reads `~/.grok/config.toml`'s `mcp_servers` (TOML, snake_case; `mcpServers` parses and
|
|
145
|
+
loads nothing). Writing only Claude's file while Grok holds a different key reports
|
|
146
|
+
success and leaves `delegate` unadvertised (GRPH-825). Grok's `gbfleet` MCP also
|
|
147
|
+
gets `--workspace ~/.grok/gbfleet-wt/<repo>`: the sibling default is a path Grok's
|
|
148
|
+
sandbox cannot write, and spawn then dies as `git worktree add` 128 (GRPH-826).
|
|
149
|
+
Claude keeps the sibling default. `--scope user` is the default because a credential
|
|
150
|
+
outside the repository cannot be committed. Re-running a working setup still
|
|
151
|
+
*repairs* a missing `gbfleet` entry and a missing `--workspace`; reuse skips a mint,
|
|
152
|
+
never a write.
|
|
144
153
|
- **`--auto` matches, and says so.** A project carries no repository link — no remote, no
|
|
145
154
|
path — so `--auto` compares your project ids and names against this directory, what is in
|
|
146
155
|
it, and its siblings. One level, never a recursive walk. Two directories answering to one
|
|
147
156
|
project, or one directory answering to two, are **refused rather than guessed**: a
|
|
148
157
|
credential minted into the wrong repository is not a mistake anybody notices quickly.
|
|
149
158
|
- **It refuses to write a key into a file git tracks.** `--scope project` on a tracked
|
|
150
|
-
`.mcp.json` is refused rather than warned about, because a warning
|
|
151
|
-
credential still commits it.
|
|
159
|
+
`.mcp.json` or `.grok/config.toml` is refused rather than warned about, because a warning
|
|
160
|
+
attached to committing a credential still commits it.
|
|
161
|
+
|
|
162
|
+
## Wiring a checkout to Swamp
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
gban swamp setup
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Steps 3 and 5 of [the Swamp runbook](https://github.com/asc-me/graphban/blob/main/docs/swamp.md):
|
|
169
|
+
`repo init`, `extension source add`, `vault create`, and the **gate** credential — minted,
|
|
170
|
+
piped to `swamp vault put` on stdin, then checked for the scopes it actually came back with.
|
|
171
|
+
Every step is skipped when already done.
|
|
172
|
+
|
|
173
|
+
Two things it will not do. It does not install Swamp, because that install pipes a remote
|
|
174
|
+
script into a shell. And it never writes the gate key into an MCP config: `gban setup`'s agent
|
|
175
|
+
key must not carry `gate`, or the agent doing the work attests its own completion — which
|
|
176
|
+
fails silently, since a gate that always says yes looks exactly like a gate that held.
|
|
152
177
|
|
|
153
178
|
## `gban login` wants a real terminal
|
|
154
179
|
|
|
@@ -112,28 +112,53 @@ is named in the refusal when it exists — logging in once inside one project mu
|
|
|
112
112
|
mint a credential for it while you are standing in another repository, and a key in the wrong
|
|
113
113
|
project is not a mistake anybody notices quickly.
|
|
114
114
|
|
|
115
|
-
`setup` mints a project-scoped credential, writes the `graphban` and `gbfleet` MCP entries
|
|
116
|
-
|
|
117
|
-
and then **verifies** rather than
|
|
118
|
-
|
|
115
|
+
`setup` mints a project-scoped credential, writes the `graphban` and `gbfleet` MCP entries
|
|
116
|
+
into every parent harness that would actually read them, installs the supervisor if it is
|
|
117
|
+
missing, drops the delegation skill into `.claude/skills/`, and then **verifies** rather than
|
|
118
|
+
asserting: it asks the new credential what it can actually see. Restart the session that will
|
|
119
|
+
call `delegate`/`spawn` afterwards — MCP servers are read at startup.
|
|
119
120
|
|
|
120
121
|
Three properties worth knowing, each of which is a bug this command exists to not have:
|
|
121
122
|
|
|
122
123
|
- **The credential does not expire.** `gban keys mint` produces a *wave* key, which lasts a
|
|
123
124
|
day (`FLEET_KEY_DAYS`); that is right for a wave and wrong for a project. Only seats expire.
|
|
124
|
-
- **It writes where the harness will actually read.**
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
125
|
+
- **It writes where the harness will actually read.** There is more than one. Claude Code
|
|
126
|
+
reads `~/.claude.json`'s per-project `mcpServers` (JSON), which outranks a repository
|
|
127
|
+
`.mcp.json` — writing the repository file under a stale entry leaves the agent on the old
|
|
128
|
+
key, and surfaces as a JSON parse error because the harness is parsing a 401 body. Grok
|
|
129
|
+
reads `~/.grok/config.toml`'s `mcp_servers` (TOML, snake_case; `mcpServers` parses and
|
|
130
|
+
loads nothing). Writing only Claude's file while Grok holds a different key reports
|
|
131
|
+
success and leaves `delegate` unadvertised (GRPH-825). Grok's `gbfleet` MCP also
|
|
132
|
+
gets `--workspace ~/.grok/gbfleet-wt/<repo>`: the sibling default is a path Grok's
|
|
133
|
+
sandbox cannot write, and spawn then dies as `git worktree add` 128 (GRPH-826).
|
|
134
|
+
Claude keeps the sibling default. `--scope user` is the default because a credential
|
|
135
|
+
outside the repository cannot be committed. Re-running a working setup still
|
|
136
|
+
*repairs* a missing `gbfleet` entry and a missing `--workspace`; reuse skips a mint,
|
|
137
|
+
never a write.
|
|
129
138
|
- **`--auto` matches, and says so.** A project carries no repository link — no remote, no
|
|
130
139
|
path — so `--auto` compares your project ids and names against this directory, what is in
|
|
131
140
|
it, and its siblings. One level, never a recursive walk. Two directories answering to one
|
|
132
141
|
project, or one directory answering to two, are **refused rather than guessed**: a
|
|
133
142
|
credential minted into the wrong repository is not a mistake anybody notices quickly.
|
|
134
143
|
- **It refuses to write a key into a file git tracks.** `--scope project` on a tracked
|
|
135
|
-
`.mcp.json` is refused rather than warned about, because a warning
|
|
136
|
-
credential still commits it.
|
|
144
|
+
`.mcp.json` or `.grok/config.toml` is refused rather than warned about, because a warning
|
|
145
|
+
attached to committing a credential still commits it.
|
|
146
|
+
|
|
147
|
+
## Wiring a checkout to Swamp
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
gban swamp setup
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Steps 3 and 5 of [the Swamp runbook](https://github.com/asc-me/graphban/blob/main/docs/swamp.md):
|
|
154
|
+
`repo init`, `extension source add`, `vault create`, and the **gate** credential — minted,
|
|
155
|
+
piped to `swamp vault put` on stdin, then checked for the scopes it actually came back with.
|
|
156
|
+
Every step is skipped when already done.
|
|
157
|
+
|
|
158
|
+
Two things it will not do. It does not install Swamp, because that install pipes a remote
|
|
159
|
+
script into a shell. And it never writes the gate key into an MCP config: `gban setup`'s agent
|
|
160
|
+
key must not carry `gate`, or the agent doing the work attests its own completion — which
|
|
161
|
+
fails silently, since a gate that always says yes looks exactly like a gate that held.
|
|
137
162
|
|
|
138
163
|
## `gban login` wants a real terminal
|
|
139
164
|
|
|
@@ -15,7 +15,7 @@ import subprocess
|
|
|
15
15
|
import sys
|
|
16
16
|
from pathlib import Path
|
|
17
17
|
|
|
18
|
-
from gban import config, doctor as doctor_mod, setup as setup_mod
|
|
18
|
+
from gban import config, doctor as doctor_mod, setup as setup_mod, swamp as swamp_mod
|
|
19
19
|
from gban.client import (EXIT_NO_SESSION, EXIT_NO_SUPERVISOR, EXIT_REFUSED, EXIT_UNREACHABLE,
|
|
20
20
|
Client, NoSession,
|
|
21
21
|
Refused, Unreachable, authenticated, login)
|
|
@@ -56,14 +56,17 @@ def _parser() -> argparse.ArgumentParser:
|
|
|
56
56
|
setup_cmd = sub.add_parser(
|
|
57
57
|
"setup", help="enable delegation on a project",
|
|
58
58
|
description=("Mints a project-scoped credential that does NOT expire, writes the "
|
|
59
|
-
"graphban and gbfleet MCP entries
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
59
|
+
"graphban and gbfleet MCP entries into Claude Code (~/.claude.json) and "
|
|
60
|
+
"Grok (~/.grok/config.toml, TOML mcp_servers) — every file a parent "
|
|
61
|
+
"harness will actually read — and installs the supervisor. Only seats "
|
|
62
|
+
"expire; a credential that died overnight would make 'delegation is set "
|
|
63
|
+
"up' quietly stop being true. Re-running is safe: a working key is "
|
|
64
|
+
"reused, a missing gbfleet entry is still repaired."))
|
|
63
65
|
setup_cmd.add_argument(
|
|
64
66
|
"--scope", choices=["user", "project"], default="user",
|
|
65
|
-
help="user (default) writes ~/.claude.json,
|
|
66
|
-
"
|
|
67
|
+
help="user (default) writes ~/.claude.json and, when it exists, ~/.grok/config.toml; "
|
|
68
|
+
"project writes .mcp.json and .grok/config.toml beside the repo. A user file "
|
|
69
|
+
"outranks the repo copy and cannot be committed")
|
|
67
70
|
setup_cmd.add_argument(
|
|
68
71
|
"--auto", action="store_true",
|
|
69
72
|
help="every project with a repository here: matches this directory, what is in it and "
|
|
@@ -73,6 +76,21 @@ def _parser() -> argparse.ArgumentParser:
|
|
|
73
76
|
setup_cmd.add_argument("--no-install", action="store_true",
|
|
74
77
|
help="do not install the supervisor")
|
|
75
78
|
|
|
79
|
+
swamp_cmd = sub.add_parser(
|
|
80
|
+
"swamp", help="wire this checkout to Swamp's Graphban adapter",
|
|
81
|
+
description=("Mints the GATE credential — the one that attests completion — and puts "
|
|
82
|
+
"it in the Swamp vault and nowhere else. It is deliberately not the "
|
|
83
|
+
"agent key `gban setup` writes into your MCP config: an agent that can "
|
|
84
|
+
"attest its own work turns the completion gate into theatre, and nothing "
|
|
85
|
+
"errors when it does. Does not install Swamp; a remote install script is "
|
|
86
|
+
"for a person to read and run."))
|
|
87
|
+
swamp_do = swamp_cmd.add_subparsers(dest="act")
|
|
88
|
+
swamp_setup = swamp_do.add_parser("setup", help="init, source, vault, and the gate key")
|
|
89
|
+
swamp_setup.add_argument("--adapter", default=swamp_mod.DEFAULT_ADAPTER,
|
|
90
|
+
help=f"the graphban-swamp checkout (default: {swamp_mod.DEFAULT_ADAPTER})")
|
|
91
|
+
swamp_setup.add_argument("--no-init", action="store_true",
|
|
92
|
+
help="refuse rather than run `swamp repo init` here")
|
|
93
|
+
|
|
76
94
|
fleet = sub.add_parser(
|
|
77
95
|
"fleet", help="hand off to gbfleet (the supervisor)",
|
|
78
96
|
description=("Passes everything through to `gbfleet` and returns its exit code "
|
|
@@ -295,7 +313,8 @@ def cmd_setup(args) -> int:
|
|
|
295
313
|
if code == 0:
|
|
296
314
|
human.append(f"\n delegation is enabled on {project}. An agent can now "
|
|
297
315
|
f"`delegate(id=…, lane=…, tier=…, seat=true)` then `spawn`.")
|
|
298
|
-
human.append(
|
|
316
|
+
human.append(" restart Claude Code and/or Grok — whichever session will call "
|
|
317
|
+
"`delegate`/`spawn` — so it reads the new config.")
|
|
299
318
|
_out({"lines": lines, "ok": code == 0, "project": project, **made},
|
|
300
319
|
"\n".join(human), args.as_json)
|
|
301
320
|
return code
|
|
@@ -325,11 +344,43 @@ def _setup_auto(args, url: str, client) -> int:
|
|
|
325
344
|
done[pid], worst = {"repo": str(repo), "ok": code == 0, **made}, max(worst, code)
|
|
326
345
|
human = [doctor_mod.render(lines),
|
|
327
346
|
f"\n {sum(1 for d in done.values() if d['ok'])} of {len(done)} enabled. "
|
|
328
|
-
|
|
347
|
+
"Restart Claude Code and/or Grok — whichever session will call "
|
|
348
|
+
"`delegate`/`spawn` — so it reads the new config."]
|
|
329
349
|
_out({"lines": lines, "ok": worst == 0, "projects": done}, "\n".join(human), args.as_json)
|
|
330
350
|
return worst
|
|
331
351
|
|
|
332
352
|
|
|
353
|
+
def cmd_swamp(args) -> int:
|
|
354
|
+
"""Wire this checkout to Swamp's Graphban adapter (GRPH-796).
|
|
355
|
+
|
|
356
|
+
Mints a SECOND credential — the gate key — and puts it in the vault and nowhere else. The
|
|
357
|
+
agent key `gban setup` writes into the MCP config must never carry `gate`: an agent that
|
|
358
|
+
can attest its own completion turns the gate into theatre, and nothing errors when it does.
|
|
359
|
+
"""
|
|
360
|
+
if args.act != "setup":
|
|
361
|
+
print(f"{PROG}: usage: {PROG} swamp setup", file=sys.stderr)
|
|
362
|
+
return EXIT_REFUSED
|
|
363
|
+
url = _server(args)
|
|
364
|
+
client = authenticated(url, act="swamp setup")
|
|
365
|
+
repo = Path.cwd()
|
|
366
|
+
asked = (args.project or os.environ.get(config.PROJECT_ENV) or "").strip()
|
|
367
|
+
try:
|
|
368
|
+
project, how = setup_mod.resolve_project(
|
|
369
|
+
client, repo, asked, config.settings().get("project", ""))
|
|
370
|
+
except setup_mod.Unresolved as exc:
|
|
371
|
+
print(f"{PROG}: {exc}", file=sys.stderr)
|
|
372
|
+
return EXIT_REFUSED
|
|
373
|
+
lines, code = swamp_mod.setup(client, url, project, repo,
|
|
374
|
+
Path(args.adapter).expanduser(), init=not args.no_init)
|
|
375
|
+
human = [f"{PROG}: {project} — {how}" if how != "named" else f"{PROG}: {project}",
|
|
376
|
+
doctor_mod.render(lines)]
|
|
377
|
+
if code == 0:
|
|
378
|
+
human.append(f"\n The gate key is in the vault and in no MCP config. Keep it that "
|
|
379
|
+
f"way: an agent that can attest its own work is not gated.")
|
|
380
|
+
_out({"lines": lines, "ok": code == 0, "project": project}, "\n".join(human), args.as_json)
|
|
381
|
+
return code
|
|
382
|
+
|
|
383
|
+
|
|
333
384
|
def cmd_fleet(args) -> int:
|
|
334
385
|
"""A subprocess, never an import (D5).
|
|
335
386
|
|
|
@@ -481,7 +532,7 @@ def cmd_keys(args) -> int:
|
|
|
481
532
|
|
|
482
533
|
COMMANDS = {"login": cmd_login, "logout": cmd_logout, "whoami": cmd_whoami,
|
|
483
534
|
"doctor": cmd_doctor, "setup": cmd_setup, "fleet": cmd_fleet, "seats": cmd_seats,
|
|
484
|
-
"agents": cmd_agents, "keys": cmd_keys}
|
|
535
|
+
"agents": cmd_agents, "keys": cmd_keys, "swamp": cmd_swamp}
|
|
485
536
|
|
|
486
537
|
|
|
487
538
|
def main(argv: list[str] | None = None) -> int:
|