tenuo-claude-code 0.2.0__tar.gz → 0.2.1__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 (52) hide show
  1. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/PKG-INFO +3 -5
  2. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/README.md +2 -4
  3. tenuo_claude_code-0.2.1/docs/images/README.md +22 -0
  4. tenuo_claude_code-0.2.1/docs/images/tenuo_claude_code_architecture.png +0 -0
  5. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/pyproject.toml +1 -1
  6. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/src/tenuo_claude_code/__init__.py +1 -1
  7. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/src/tenuo_claude_code/cli.py +34 -33
  8. tenuo_claude_code-0.2.1/tests/test_check.py +74 -0
  9. tenuo_claude_code-0.2.0/docs/images/README.md +0 -9
  10. tenuo_claude_code-0.2.0/tests/test_check.py +0 -31
  11. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/.gitignore +0 -0
  12. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/CONTRIBUTING.md +0 -0
  13. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/LICENSE +0 -0
  14. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/demo/.claude/agents/researcher.md +0 -0
  15. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/demo/.mcp.json +0 -0
  16. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/demo/README.md +0 -0
  17. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/demo/fake-secrets.env +0 -0
  18. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/demo/ops_server.py +0 -0
  19. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/demo/sandbox/incident-report.md +0 -0
  20. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/demo/sandbox/notes.txt +0 -0
  21. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/demo/tenuo.yaml +0 -0
  22. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/demo/tenuo_demo.py +0 -0
  23. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/docs/DETAILS.md +0 -0
  24. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/docs/images/cloud-audit-stream.png +0 -0
  25. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/docs/images/cloud-receipt-approval-detail.png +0 -0
  26. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/docs/images/tenuo_claude_code_architecture.svg +0 -0
  27. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/examples/policies/README.md +0 -0
  28. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/examples/policies/audit-rollout.yaml +0 -0
  29. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/examples/policies/enforce-with-mcp.yaml +0 -0
  30. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/examples/policies/read-only-research.yaml +0 -0
  31. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/src/tenuo_claude_code/admin.py +0 -0
  32. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/src/tenuo_claude_code/authorizer_runtime.py +0 -0
  33. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/src/tenuo_claude_code/data/harness_tools.yaml +0 -0
  34. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/src/tenuo_claude_code/paths.py +0 -0
  35. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/src/tenuo_claude_code/templates/tenuo.yaml.example +0 -0
  36. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/src/tenuo_claude_code/verify.py +0 -0
  37. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/templates/README.md +0 -0
  38. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/templates/admin.env.example +0 -0
  39. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/templates/cloud.env.example +0 -0
  40. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/templates/tenuo.yaml.advanced.example +0 -0
  41. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/templates/tenuo.yaml.cloud.example +0 -0
  42. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/templates/tenuo.yaml.example +0 -0
  43. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/tests/conftest.py +0 -0
  44. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/tests/test_admin.py +0 -0
  45. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/tests/test_approvals.py +0 -0
  46. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/tests/test_authorize_call.py +0 -0
  47. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/tests/test_authorizer_runtime.py +0 -0
  48. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/tests/test_constraints.py +0 -0
  49. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/tests/test_permissions.py +0 -0
  50. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/tests/test_routing.py +0 -0
  51. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/tests/test_scaffold.py +0 -0
  52. {tenuo_claude_code-0.2.0 → tenuo_claude_code-0.2.1}/tests/test_subagents.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tenuo-claude-code
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Tenuo governance for Claude Code — warrants, hooks, MCP proxy, and Cloud lifecycle
5
5
  Project-URL: Homepage, https://tenuo.ai
6
6
  Project-URL: Repository, https://github.com/tenuo-ai/claude-governance
@@ -30,8 +30,6 @@ Description-Content-Type: text/markdown
30
30
 
31
31
  # Tenuo for Claude Code
32
32
 
33
- PyPI package: [`tenuo-claude-code`](https://pypi.org/project/tenuo-claude-code/) · CLI: `tenuo-claude`, `tenuo-admin`
34
-
35
33
  [![PyPI](https://img.shields.io/pypi/v/tenuo-claude-code)](https://pypi.org/project/tenuo-claude-code/)
36
34
  [![Python](https://img.shields.io/pypi/pyversions/tenuo-claude-code)](https://pypi.org/project/tenuo-claude-code/)
37
35
  [![CI](https://github.com/tenuo-ai/claude-governance/actions/workflows/ci.yml/badge.svg)](https://github.com/tenuo-ai/claude-governance/actions/workflows/ci.yml)
@@ -71,7 +69,7 @@ No Docker, or prefer native? Run `tenuo-claude install-authorizer` once, then `b
71
69
 
72
70
  ## How it works
73
71
 
74
- ![Architecture](docs/images/tenuo_claude_code_architecture.svg)
72
+ ![Architecture](https://raw.githubusercontent.com/tenuo-ai/claude-governance/main/docs/images/tenuo_claude_code_architecture.png)
75
73
 
76
74
  ```
77
75
  tenuo.yaml → init/up → warrant + authorizer + hooks + MCP proxy
@@ -483,7 +481,7 @@ In `mode: audit`, denials show as `WOULD-DENY` in `audit` output (`shadow: true`
483
481
 
484
482
  These are the non-repudiable receipts for compliance and fleet audit, not the local JSONL file.
485
483
 
486
- ![Authorization receipts in Tenuo Cloud](docs/images/cloud-audit-stream.png)
484
+ ![Authorization receipts in Tenuo Cloud](https://raw.githubusercontent.com/tenuo-ai/claude-governance/main/docs/images/cloud-audit-stream.png)
487
485
 
488
486
  More: [docs/DETAILS.md § Receipts](docs/DETAILS.md#receipts).
489
487
 
@@ -1,7 +1,5 @@
1
1
  # Tenuo for Claude Code
2
2
 
3
- PyPI package: [`tenuo-claude-code`](https://pypi.org/project/tenuo-claude-code/) · CLI: `tenuo-claude`, `tenuo-admin`
4
-
5
3
  [![PyPI](https://img.shields.io/pypi/v/tenuo-claude-code)](https://pypi.org/project/tenuo-claude-code/)
6
4
  [![Python](https://img.shields.io/pypi/pyversions/tenuo-claude-code)](https://pypi.org/project/tenuo-claude-code/)
7
5
  [![CI](https://github.com/tenuo-ai/claude-governance/actions/workflows/ci.yml/badge.svg)](https://github.com/tenuo-ai/claude-governance/actions/workflows/ci.yml)
@@ -41,7 +39,7 @@ No Docker, or prefer native? Run `tenuo-claude install-authorizer` once, then `b
41
39
 
42
40
  ## How it works
43
41
 
44
- ![Architecture](docs/images/tenuo_claude_code_architecture.svg)
42
+ ![Architecture](https://raw.githubusercontent.com/tenuo-ai/claude-governance/main/docs/images/tenuo_claude_code_architecture.png)
45
43
 
46
44
  ```
47
45
  tenuo.yaml → init/up → warrant + authorizer + hooks + MCP proxy
@@ -453,7 +451,7 @@ In `mode: audit`, denials show as `WOULD-DENY` in `audit` output (`shadow: true`
453
451
 
454
452
  These are the non-repudiable receipts for compliance and fleet audit, not the local JSONL file.
455
453
 
456
- ![Authorization receipts in Tenuo Cloud](docs/images/cloud-audit-stream.png)
454
+ ![Authorization receipts in Tenuo Cloud](https://raw.githubusercontent.com/tenuo-ai/claude-governance/main/docs/images/cloud-audit-stream.png)
457
455
 
458
456
  More: [docs/DETAILS.md § Receipts](docs/DETAILS.md#receipts).
459
457
 
@@ -0,0 +1,22 @@
1
+ # Images for docs
2
+
3
+ | File | Used in | Shows |
4
+ |------|---------|--------|
5
+ | `tenuo_claude_code_architecture.png` | README (PyPI + GitHub) | Architecture diagram (export from `.svg`) |
6
+ | `tenuo_claude_code_architecture.svg` | Source | Editable diagram |
7
+ | `cloud-audit-stream.png` | README | Cloud receipt list (allow/deny/approved) |
8
+ | `cloud-receipt-approval-detail.png` | PRESENTATION | Drill-down: human approval + request hash |
9
+
10
+ **PyPI:** the project README uses absolute `raw.githubusercontent.com` URLs and PNG
11
+ (raster) assets. PyPI does not resolve relative paths and does not render SVG in
12
+ project descriptions.
13
+
14
+ Regenerate the architecture PNG after editing the SVG:
15
+
16
+ ```bash
17
+ rsvg-convert -w 1280 docs/images/tenuo_claude_code_architecture.svg \
18
+ -o docs/images/tenuo_claude_code_architecture.png
19
+ ```
20
+
21
+ Captured from [cloud.tenuo.ai](https://cloud.tenuo.ai) after a governed WebFetch
22
+ with approval configured. Do not commit credentials or secrets in replacements.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tenuo-claude-code"
3
- version = "0.2.0"
3
+ version = "0.2.1"
4
4
  description = "Tenuo governance for Claude Code — warrants, hooks, MCP proxy, and Cloud lifecycle"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -1,3 +1,3 @@
1
1
  """Tenuo governance for Claude Code — CLI, hooks, and MCP proxy."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.2.1"
@@ -1931,6 +1931,40 @@ def cmd_onboard(args) -> None:
1931
1931
 
1932
1932
  created = scaffold_example_policy(DEMO_DIR, no_scaffold=getattr(args, "no_scaffold", False))
1933
1933
 
1934
+ admin_key = None
1935
+ if cloud:
1936
+ token = getattr(args, "connect_token", None) or os.environ.get("TENUO_CONNECT_TOKEN")
1937
+ if not token and CLOUD_ENV.exists():
1938
+ token = runtime_env().get("TENUO_CONNECT_TOKEN")
1939
+ if not token and not getattr(args, "yes", False):
1940
+ token = _prompt("Paste Quick Connect token (tenuo_ct_…)")
1941
+ if not token:
1942
+ raise SystemExit("Cloud onboarding needs TENUO_CONNECT_TOKEN (Quick Connect → Authorizer Only).")
1943
+ write_cloud_env(token)
1944
+
1945
+ url = _parse_connect_token(token.strip())["url"]
1946
+ write_cloud_profile(url=url)
1947
+ print(f"Wrote {CLOUD_PROFILE.name}")
1948
+
1949
+ if getattr(args, "advanced", False) or getattr(args, "demo", False):
1950
+ approver = getattr(args, "approver", None)
1951
+ approver_id = getattr(args, "approver_id", None)
1952
+ if not approver and not approver_id and not getattr(args, "yes", False):
1953
+ approver = _prompt("Approver display name (must exist in Cloud)")
1954
+ if not approver and not approver_id:
1955
+ raise SystemExit("--advanced requires --approver-id or --approver.")
1956
+ write_advanced_profile(approver=approver, approver_id=approver_id)
1957
+ print(f"Wrote {ADVANCED_PROFILE.name} (advanced — re-run `tenuo-admin setup`)")
1958
+
1959
+ admin_key = getattr(args, "admin_key", None) or os.environ.get("TENUO_ADMIN_KEY")
1960
+ if not admin_key and ADMIN_ENV.exists():
1961
+ admin_key = read_env_file(ADMIN_ENV).get("TENUO_ADMIN_KEY")
1962
+ if not admin_key and not getattr(args, "yes", False):
1963
+ admin_key = _prompt("Paste tenant-admin key for one-time setup (blank = skip)", "")
1964
+ if admin_key:
1965
+ write_admin_env(admin_key)
1966
+ print(f"Wrote {ADMIN_ENV}")
1967
+
1934
1968
  if not created:
1935
1969
  print("\nRunning preflight…")
1936
1970
  try:
@@ -1955,39 +1989,6 @@ def cmd_onboard(args) -> None:
1955
1989
  print("\nOnboard complete (local). Open Claude Code in this directory.")
1956
1990
  return
1957
1991
 
1958
- # Cloud path
1959
- token = getattr(args, "connect_token", None) or os.environ.get("TENUO_CONNECT_TOKEN")
1960
- if not token and CLOUD_ENV.exists():
1961
- token = runtime_env().get("TENUO_CONNECT_TOKEN")
1962
- if not token and not getattr(args, "yes", False):
1963
- token = _prompt("Paste Quick Connect token (tenuo_ct_…)")
1964
- if not token:
1965
- raise SystemExit("Cloud onboarding needs TENUO_CONNECT_TOKEN (Quick Connect → Authorizer Only).")
1966
- write_cloud_env(token)
1967
-
1968
- url = _parse_connect_token(token.strip())["url"]
1969
- write_cloud_profile(url=url)
1970
- print(f"Wrote {CLOUD_PROFILE.name}")
1971
-
1972
- if getattr(args, "advanced", False) or getattr(args, "demo", False):
1973
- approver = getattr(args, "approver", None)
1974
- approver_id = getattr(args, "approver_id", None)
1975
- if not approver and not approver_id and not getattr(args, "yes", False):
1976
- approver = _prompt("Approver display name (must exist in Cloud)")
1977
- if not approver and not approver_id:
1978
- raise SystemExit("--advanced requires --approver-id or --approver.")
1979
- write_advanced_profile(approver=approver, approver_id=approver_id)
1980
- print(f"Wrote {ADVANCED_PROFILE.name} (advanced — re-run `tenuo-admin setup`)")
1981
-
1982
- admin_key = getattr(args, "admin_key", None) or os.environ.get("TENUO_ADMIN_KEY")
1983
- if not admin_key and ADMIN_ENV.exists():
1984
- admin_key = read_env_file(ADMIN_ENV).get("TENUO_ADMIN_KEY")
1985
- if not admin_key and not getattr(args, "yes", False):
1986
- admin_key = _prompt("Paste tenant-admin key for one-time setup (blank = skip)", "")
1987
- if admin_key:
1988
- write_admin_env(admin_key)
1989
- print(f"Wrote {ADMIN_ENV}")
1990
-
1991
1992
  cmd_init(argparse.Namespace(cloud=False, local=False))
1992
1993
 
1993
1994
  if admin_key:
@@ -0,0 +1,74 @@
1
+ """Preflight / wiring checks (PyPI layout — no repo ``bin/tenuo-claude``)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+
7
+ from tenuo_claude_code import cli
8
+
9
+
10
+ def test_check_wiring_ok_without_launcher(monkeypatch, tmp_path):
11
+ """Missing ``./bin/tenuo-claude`` must not fail preflight (PyPI installs use PATH)."""
12
+ proj = tmp_path / "proj"
13
+ proj.mkdir()
14
+ monkeypatch.setattr(cli, "LAUNCHER", proj / "bin" / "tenuo-claude", raising=False)
15
+ monkeypatch.setattr(cli, "DEMO_DIR", proj, raising=False)
16
+ ok = cli._check_wiring({"mcp": {}}, True)
17
+ assert ok is True
18
+
19
+
20
+ def test_probe_runtime_creds_accepts_tenant_without_srl(monkeypatch):
21
+ monkeypatch.setattr(
22
+ cli,
23
+ "cloud_api",
24
+ lambda method, url, key, path: (
25
+ 404,
26
+ {"error": {"code": "srl_not_found", "message": "no SRL exists for this tenant"}},
27
+ ),
28
+ )
29
+
30
+ ok, msg = cli.probe_runtime_creds({"url": "https://api.example", "api_key": "rt"})
31
+
32
+ assert ok is True
33
+ assert "no SRL yet" in msg
34
+
35
+
36
+ def test_cloud_onboard_writes_profile_before_preflight(monkeypatch, tmp_path):
37
+ state = tmp_path / ".state"
38
+ monkeypatch.setattr(cli, "DEMO_DIR", tmp_path, raising=False)
39
+ monkeypatch.setattr(cli, "STATE", state, raising=False)
40
+ monkeypatch.setattr(cli, "CONFIG_FILE", tmp_path / "tenuo.yaml", raising=False)
41
+ monkeypatch.setattr(cli, "CLOUD_ENV", state / "cloud.env", raising=False)
42
+ monkeypatch.setattr(cli, "CLOUD_STATE", state / "cloud_state.json", raising=False)
43
+ monkeypatch.setattr(cli, "CLOUD_PROFILE", tmp_path / "tenuo.cloud.yaml", raising=False)
44
+ monkeypatch.setattr(cli, "ADVANCED_PROFILE", tmp_path / "tenuo.advanced.yaml", raising=False)
45
+ monkeypatch.setattr(cli, "ADMIN_ENV", tmp_path / "home" / ".tenuo" / "admin.env", raising=False)
46
+ monkeypatch.setattr(cli, "scaffold_example_policy", lambda *a, **k: False)
47
+ monkeypatch.setattr(cli, "_parse_connect_token",
48
+ lambda token: {"url": "https://api.example", "api_key": "rt"})
49
+
50
+ seen = {}
51
+
52
+ def fake_check(_args):
53
+ seen["mode"] = cli.intended_mode({})
54
+ raise SystemExit(0)
55
+
56
+ monkeypatch.setattr(cli, "cmd_check", fake_check)
57
+ monkeypatch.setattr(cli, "cmd_init", lambda _args: None)
58
+ monkeypatch.setattr(cli, "cmd_up", lambda _args: None)
59
+ monkeypatch.setattr(cli, "cmd_verify", lambda _args: None)
60
+
61
+ cli.cmd_onboard(argparse.Namespace(
62
+ cloud=True,
63
+ local=False,
64
+ yes=True,
65
+ no_scaffold=False,
66
+ connect_token="tenuo_ct_fake",
67
+ admin_key=None,
68
+ advanced=False,
69
+ demo=False,
70
+ approver=None,
71
+ approver_id=None,
72
+ ))
73
+
74
+ assert seen["mode"] == "cloud"
@@ -1,9 +0,0 @@
1
- # Images for docs
2
-
3
- | File | Used in | Shows |
4
- |------|---------|--------|
5
- | `cloud-audit-stream.png` | README | Cloud receipt list (allow/deny/approved) |
6
- | `cloud-receipt-approval-detail.png` | README | Drill-down: human approval + request hash |
7
-
8
- Captured from [cloud.tenuo.ai](https://cloud.tenuo.ai) after a governed WebFetch
9
- with approval configured. Do not commit credentials or secrets in replacements.
@@ -1,31 +0,0 @@
1
- """Preflight / wiring checks (PyPI layout — no repo ``bin/tenuo-claude``)."""
2
-
3
- from __future__ import annotations
4
-
5
- from tenuo_claude_code import cli
6
-
7
-
8
- def test_check_wiring_ok_without_launcher(monkeypatch, tmp_path):
9
- """Missing ``./bin/tenuo-claude`` must not fail preflight (PyPI installs use PATH)."""
10
- proj = tmp_path / "proj"
11
- proj.mkdir()
12
- monkeypatch.setattr(cli, "LAUNCHER", proj / "bin" / "tenuo-claude", raising=False)
13
- monkeypatch.setattr(cli, "DEMO_DIR", proj, raising=False)
14
- ok = cli._check_wiring({"mcp": {}}, True)
15
- assert ok is True
16
-
17
-
18
- def test_probe_runtime_creds_accepts_tenant_without_srl(monkeypatch):
19
- monkeypatch.setattr(
20
- cli,
21
- "cloud_api",
22
- lambda method, url, key, path: (
23
- 404,
24
- {"error": {"code": "srl_not_found", "message": "no SRL exists for this tenant"}},
25
- ),
26
- )
27
-
28
- ok, msg = cli.probe_runtime_creds({"url": "https://api.example", "api_key": "rt"})
29
-
30
- assert ok is True
31
- assert "no SRL yet" in msg