graphban-cli 0.8.9__tar.gz → 0.8.10__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.
Files changed (43) hide show
  1. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/PKG-INFO +1 -1
  2. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/pyproject.toml +1 -1
  3. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/src/gban/cli.py +28 -0
  4. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/src/gban/setup.py +10 -3
  5. graphban_cli-0.8.10/tests/test_fleet_until.py +54 -0
  6. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/tests/test_setup.py +27 -1
  7. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/uv.lock +1 -1
  8. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/.gitignore +0 -0
  9. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/LICENSE +0 -0
  10. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/README.md +0 -0
  11. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/src/gban/__init__.py +0 -0
  12. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/src/gban/client.py +0 -0
  13. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/src/gban/config.py +0 -0
  14. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/src/gban/data/stack_sdks.toml +0 -0
  15. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/src/gban/doctor.py +0 -0
  16. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/src/gban/gitignore.py +0 -0
  17. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/src/gban/machine.py +0 -0
  18. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/src/gban/playbooks.py +0 -0
  19. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/src/gban/skills/graphban-answer/SKILL.md +0 -0
  20. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/src/gban/skills/graphban-delegation/SKILL.md +0 -0
  21. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/src/gban/skills/graphban-grill/SKILL.md +0 -0
  22. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/src/gban/skills/graphban-orient/SKILL.md +0 -0
  23. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/src/gban/skills/graphban-prd/SKILL.md +0 -0
  24. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/src/gban/skills/graphban-prove/SKILL.md +0 -0
  25. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/src/gban/skills/graphban-review/SKILL.md +0 -0
  26. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/src/gban/skills/graphban-scan/SKILL.md +0 -0
  27. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/src/gban/skills/graphban-ship/SKILL.md +0 -0
  28. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/src/gban/skills/graphban-unstick/SKILL.md +0 -0
  29. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/src/gban/skills/graphban-wave/SKILL.md +0 -0
  30. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/src/gban/skills/graphban-work/SKILL.md +0 -0
  31. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/src/gban/skills/watch-wave/SKILL.md +0 -0
  32. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/src/gban/stackmap.py +0 -0
  33. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/src/gban/swamp.py +0 -0
  34. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/tests/test_acts.py +0 -0
  35. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/tests/test_doctor.py +0 -0
  36. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/tests/test_login_pair.py +0 -0
  37. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/tests/test_login_projects.py +0 -0
  38. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/tests/test_machine_report.py +0 -0
  39. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/tests/test_packaging.py +0 -0
  40. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/tests/test_playbooks.py +0 -0
  41. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/tests/test_session.py +0 -0
  42. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/tests/test_stackmap.py +0 -0
  43. {graphban_cli-0.8.9 → graphban_cli-0.8.10}/tests/test_swamp.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: graphban-cli
3
- Version: 0.8.9
3
+ Version: 0.8.10
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "graphban-cli"
3
- version = "0.8.9"
3
+ version = "0.8.10"
4
4
  description = "gban — a Graphban client for the human at a terminal"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -551,6 +551,30 @@ def cmd_stack(args) -> int:
551
551
  return 0
552
552
 
553
553
 
554
+ ADAPTER_ENV = "GBAN_ADAPTER"
555
+
556
+
557
+ def _until_with_adapter(rest: list[str]) -> list[str] | None:
558
+ """`gbfleet until` requires --adapter; Triage's drain note shows the command without it.
559
+
560
+ Fill it from the flag (already there), $GBAN_ADAPTER, the settings file's `adapter`, or
561
+ `claude` when that binary is on PATH. Otherwise say which value is missing and how to set
562
+ it, rather than letting argparse print usage. Only args before a `--` are ours to read.
563
+ """
564
+ own = rest[:rest.index("--")] if "--" in rest else rest
565
+ if any(a == "--adapter" or a.startswith("--adapter=") for a in own):
566
+ return rest
567
+ adapter = (os.environ.get(ADAPTER_ENV) or config.settings().get("adapter") or "").strip()
568
+ if not adapter and shutil.which("claude"):
569
+ adapter = "claude"
570
+ if not adapter:
571
+ print(f"{PROG}: `{PROG} fleet until` needs --adapter (the agent vendor to run, e.g. "
572
+ f"claude) and none is configured. Pass --adapter <vendor>, set ${ADAPTER_ENV}, "
573
+ f"or add \"adapter\" to {config.home() / config.SETTINGS_FILE}.", file=sys.stderr)
574
+ return None
575
+ return [rest[0], "--adapter", adapter, *rest[1:]]
576
+
577
+
554
578
  def cmd_fleet(args) -> int:
555
579
  """A subprocess, never an import (D5).
556
580
 
@@ -576,6 +600,10 @@ def cmd_fleet(args) -> int:
576
600
  rest = list(args.rest)
577
601
  if rest[:1] == ["--"]:
578
602
  rest = rest[1:]
603
+ if rest[:1] == ["until"]:
604
+ rest = _until_with_adapter(rest)
605
+ if rest is None:
606
+ return EXIT_REFUSED
579
607
  argv = [binary, *rest]
580
608
  url = config.resolve(args.server, config.URL_ENV, "url")
581
609
  if url and "--server" not in argv:
@@ -425,6 +425,12 @@ def verify(url: str, key: str, project: str) -> list[dict]:
425
425
  f"{project} is writable" if project in writable else
426
426
  f"the key cannot write {project} — you can read it but not write it, so "
427
427
  "an agent on this key can claim nothing"))
428
+ if "key_project_id" in ctx and ctx["key_project_id"] != project:
429
+ # `gbfleet until` refuses a key with no project to bind its minted-by check to
430
+ # (GRPH-1658), so a reused cross-project key fails the Triage drain command.
431
+ out.append(_line("ledger", FAIL, "scope",
432
+ "the key is not scoped to this project, and `gban fleet until` "
433
+ "refuses an unscoped key"))
428
434
  missing = {_tier_id(t) for t in (ctx.get("missing_tiers") or [])}
429
435
  out.append(_line("ledger", PASS if "fleet" not in missing else FAIL, "fleet tools",
430
436
  "delegate and mint_enrolment are in the manifest" if "fleet" not in missing
@@ -495,9 +501,10 @@ def run(client: Client, url: str, project: str, repo: Path, *, scope: str = "use
495
501
  # A key that cannot do the job is replaced, never patched: `tool_tiers` are fixed at
496
502
  # mint and there is no route that changes them.
497
503
  lines.append(_line("config", UNKNOWN, "credential",
498
- "the configured credential no longer works (revoked, or it cannot "
499
- "delegate), so a new one is being minted; if the old one still "
500
- "works, remove it in Settings → API keys"))
504
+ "the configured credential no longer works (revoked, cannot "
505
+ "delegate, or not scoped to this project), so a project-scoped "
506
+ "one is being minted; if the old one still works, remove it in "
507
+ "Settings → API keys"))
501
508
  if not key:
502
509
  try:
503
510
  minted = mint(client, project)
@@ -0,0 +1,54 @@
1
+ """GRPH-1656 — the command Triage's drain note shows must start a drain as copied."""
2
+ from __future__ import annotations
3
+
4
+ import shlex
5
+
6
+ import pytest
7
+
8
+ from gban import config
9
+ from gban.cli import main
10
+
11
+
12
+ @pytest.fixture()
13
+ def seen(tmp_path, monkeypatch):
14
+ monkeypatch.setenv(config.HOME_ENV, str(tmp_path))
15
+ monkeypatch.delenv("GBAN_ADAPTER", raising=False)
16
+ monkeypatch.delenv("GBFLEET_API_KEY", raising=False)
17
+ monkeypatch.setattr("gban.cli.doctor_mod.find_supervisor", lambda: "/usr/bin/gbfleet")
18
+ out = {}
19
+
20
+ class Done:
21
+ returncode = 0
22
+
23
+ monkeypatch.setattr("gban.cli.subprocess.run",
24
+ lambda argv, **kw: (out.update(argv=argv), Done())[1])
25
+ return out
26
+
27
+
28
+ def test_drain_note_command_reaches_until_with_an_adapter(seen, monkeypatch):
29
+ monkeypatch.setenv("GBAN_ADAPTER", "claude")
30
+ cmd = "gban fleet until --prd P75W-P1" # the string waits.drain_note emits
31
+ assert main(shlex.split(cmd)[1:]) == 0
32
+ argv = seen["argv"]
33
+ assert argv[1] == "until" and argv[argv.index("--adapter") + 1] == "claude"
34
+ assert "--prd" in argv
35
+
36
+
37
+ def test_adapter_falls_back_to_claude_on_path(seen, monkeypatch):
38
+ monkeypatch.setattr("gban.cli.shutil.which", lambda n: "/bin/claude" if n == "claude" else None)
39
+ assert main(["fleet", "until", "--prd", "X"]) == 0
40
+ assert "claude" in seen["argv"]
41
+
42
+
43
+ def test_explicit_adapter_is_left_alone(seen, monkeypatch):
44
+ monkeypatch.setenv("GBAN_ADAPTER", "claude")
45
+ assert main(["fleet", "until", "--adapter", "grok", "--prd", "X"]) == 0
46
+ assert seen["argv"].count("--adapter") == 1 and "grok" in seen["argv"]
47
+
48
+
49
+ def test_unknown_adapter_names_what_to_set(seen, monkeypatch, capsys):
50
+ monkeypatch.setattr("gban.cli.shutil.which", lambda n: None)
51
+ assert main(["fleet", "until", "--prd", "X"]) != 0
52
+ err = capsys.readouterr().err
53
+ assert "--adapter" in err and "GBAN_ADAPTER" in err
54
+ assert "argv" not in seen
@@ -59,7 +59,8 @@ class Server:
59
59
  """Mints, and answers `get_context` as the deployed one does."""
60
60
 
61
61
  def __init__(self, *, writable=("core",), missing=(), refuse=None, issued=(),
62
- missing_for=None):
62
+ missing_for=None, scope_for=None):
63
+ self.scope_for = dict(scope_for or {})
63
64
  self.writable, self.missing, self.refuse = list(writable), list(missing), refuse
64
65
  self.missing_for = dict(missing_for or {})
65
66
  self.minted = []
@@ -92,6 +93,7 @@ def _context(server):
92
93
  raise Refused(401, "invalid api key", "")
93
94
  missing = server.missing_for.get(self.api_key, server.missing)
94
95
  ctx = {"project_id": "core", "writable_projects": server.writable,
96
+ "key_project_id": server.scope_for.get(self.api_key, "core"),
95
97
  "missing_tiers": [{"tier": t} for t in missing]}
96
98
  return {"result": {"content": [{"text": json.dumps(ctx)}]}}
97
99
  return call
@@ -1455,3 +1457,27 @@ def test_the_image_manifest_lists_the_shipped_skills():
1455
1457
  manifest = json.loads((root / "backend/scripts/gban_skills.json").read_text())
1456
1458
  shipped = {p.name for p in (root / "cli/src/gban/skills").iterdir() if p.is_dir()}
1457
1459
  assert set(manifest["skills"]) == shipped
1460
+
1461
+
1462
+ def test_unscoped_configured_key_is_replaced_by_a_project_scoped_mint(tmp_path, wired):
1463
+ """GRPH-1658: `until` refuses an unscoped key, so setup must not reuse one."""
1464
+ repo, home = tmp_path / "repo", tmp_path / ".claude.json"
1465
+ repo.mkdir()
1466
+ server = Server(issued={"gb_sk_old"}, scope_for={"gb_sk_old": None})
1467
+ home.write_text(json.dumps({"projects": {str(repo.resolve()): {"mcpServers": {
1468
+ "graphban": {"type": "http", "url": URL, "headers": {"X-API-Key": "gb_sk_old"}}}}}}))
1469
+ lines, code, made = _run(server, repo, home, wired=wired)
1470
+ assert code == 0, lines
1471
+ assert len(server.minted) == 1 and server.minted[0]["project_id"] == "core"
1472
+ assert config.stored_supervisor_key("core") == "gb_sk_new1"
1473
+
1474
+
1475
+ def test_project_scoped_configured_key_is_still_reused(tmp_path, wired):
1476
+ repo, home = tmp_path / "repo", tmp_path / ".claude.json"
1477
+ repo.mkdir()
1478
+ server = Server(issued={"gb_sk_old"})
1479
+ home.write_text(json.dumps({"projects": {str(repo.resolve()): {"mcpServers": {
1480
+ "graphban": {"type": "http", "url": URL, "headers": {"X-API-Key": "gb_sk_old"}}}}}}))
1481
+ lines, code, made = _run(server, repo, home, wired=wired)
1482
+ assert code == 0, lines
1483
+ assert server.minted == [] and made.get("reused")
@@ -124,7 +124,7 @@ wheels = [
124
124
 
125
125
  [[package]]
126
126
  name = "graphban-cli"
127
- version = "0.8.9"
127
+ version = "0.8.10"
128
128
  source = { editable = "." }
129
129
 
130
130
  [package.optional-dependencies]
File without changes
File without changes
File without changes