costcut 0.0.9__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.
@@ -0,0 +1,56 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .venv/
6
+ venv/
7
+ .pytest_cache/
8
+ .ruff_cache/
9
+ .mypy_cache/
10
+ .coverage
11
+ htmlcov/
12
+ dist/
13
+ build/
14
+
15
+ # Node
16
+ node_modules/
17
+ .next/
18
+ out/
19
+ .turbo/
20
+ *.tsbuildinfo
21
+
22
+ # Playwright
23
+ frontend/test-results/
24
+ frontend/playwright-report/
25
+
26
+ # IDE
27
+ .idea/
28
+ .vscode/
29
+ *.swp
30
+ .DS_Store
31
+
32
+ # Env / secrets (only encrypted files are committed)
33
+ .env
34
+ .env.local
35
+ .env.*.local
36
+ !.env.example
37
+ !*.encrypted
38
+
39
+ # Sops decrypted artifacts
40
+ .decrypted/
41
+
42
+ # Logs
43
+ *.log
44
+ logs/
45
+
46
+ # Local Docker volumes
47
+ postgres-data/
48
+ loki-data/
49
+ grafana-data/
50
+
51
+ # Placeholders
52
+ __placeholder__
53
+
54
+
55
+ # MkDocs build output
56
+ docs/site/
costcut-0.0.9/PKG-INFO ADDED
@@ -0,0 +1,132 @@
1
+ Metadata-Version: 2.4
2
+ Name: costcut
3
+ Version: 0.0.9
4
+ Summary: Claude Code observability + cost optimization CLI
5
+ Project-URL: Homepage, https://costcut.dev
6
+ Project-URL: Repository, https://github.com/kuznetsov-ai/cost-guard
7
+ Author-email: Eugene Kuznetsov <eugene@costcut.dev>
8
+ License: MIT
9
+ Keywords: claude-code,cost,llm,observability,tokens
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Topic :: Software Development
14
+ Requires-Python: >=3.11
15
+ Requires-Dist: httpx>=0.27.0
16
+ Requires-Dist: pydantic>=2.7.0
17
+ Requires-Dist: rich>=13.7.0
18
+ Requires-Dist: typer>=0.12.0
19
+ Provides-Extra: dev
20
+ Requires-Dist: mypy>=1.10.0; extra == 'dev'
21
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
22
+ Requires-Dist: ruff>=0.4.0; extra == 'dev'
23
+ Description-Content-Type: text/markdown
24
+
25
+ # cost-guard CLI
26
+
27
+ OSS CLI for Cost Guard. Tracks Claude Code session tokens, cost, hook compliance.
28
+
29
+ ## Install (Week 6+)
30
+
31
+ ```bash
32
+ pip install cost-guard # PyPI (Week 6 release)
33
+ brew install cost-guard # Homebrew tap (Week 6 release)
34
+ ```
35
+
36
+ ## Dev install
37
+
38
+ ```bash
39
+ cd cli
40
+ python -m venv .venv && source .venv/bin/activate
41
+ pip install -e ".[dev]"
42
+ pytest
43
+ cost-guard --version
44
+ ```
45
+
46
+ ## Commands
47
+
48
+ ### `cost-guard init` — install hooks bundle (Week 3 ✅)
49
+
50
+ Installs 5 bundled hook scripts + shared library into `.claude/hooks/`, registers them in `.claude/settings.json`.
51
+
52
+ ```bash
53
+ cost-guard init # install to .claude/hooks (default)
54
+ cost-guard init --target ~/.claude/hooks # explicit target path
55
+ cost-guard init --dry-run # preview changes, write nothing
56
+ cost-guard init --force # overwrite existing hooks
57
+ ```
58
+
59
+ Installs:
60
+ - **Hooks:** `parallel_agents_guard.sh`, `test_before_push_guard.sh`, `large_read_warning.sh`, `token_alert.sh`, `session_cost_check.sh`
61
+ - **Library:** `_lib/cost.sh` (shared utilities)
62
+
63
+ Registers hooks in `.claude/settings.json` with event + matcher + command path. Idempotent: running twice adds no duplicates.
64
+
65
+ Exit code 0 on success, 1 on error.
66
+
67
+ ### `cost-guard report` — session analytics (Week 2 ✅)
68
+
69
+ Scans Claude Code JSONL transcripts in `~/.claude/projects/`, aggregates tokens + cost per session, renders a Rich table.
70
+
71
+ ```bash
72
+ cost-guard report # default: last 20 sessions for cwd
73
+ cost-guard report --project ~/Projects/MyApp # specific project
74
+ cost-guard report --since 2026-05-01 # only sessions since date
75
+ cost-guard report --last 5 # top 5 most recent
76
+ cost-guard report --all # no limit
77
+ ```
78
+
79
+ Output columns: `Started | Duration | Model | Msgs | In | Out | Cache | Cost | Session`. Final row = TOTAL.
80
+
81
+ ### `cost-guard auth` — token management (Week 7 ✅)
82
+
83
+ Manages authentication with backend API.
84
+
85
+ ```bash
86
+ cost-guard auth set <api_key> # save API key (must start with cg_live_)
87
+ cost-guard auth status # check login status + show masked token
88
+ cost-guard auth logout # remove stored token
89
+ ```
90
+
91
+ Token storage: `~/.config/cost-guard/token` (0o600 permissions). Env override: `COST_GUARD_TOKEN=cg_live_...` takes precedence. Backend: `GET /v1/auth/me` (Bearer JWT).
92
+
93
+ ### `cost-guard push` — upload sessions to backend (Week 7 ✅)
94
+
95
+ Aggregates sessions from local JSONL and POSTs to backend API.
96
+
97
+ ```bash
98
+ cost-guard push # default: last 30 days
99
+ cost-guard push --project ~/Projects/MyApp # specific project
100
+ cost-guard push --since 7 # only last 7 days
101
+ cost-guard push --dry-run # preview without HTTP
102
+ ```
103
+
104
+ Requires authenticated token. Filters by `--since` (days). POSTs `{id, project, primary_model, started_at, ended_at, input_tokens, output_tokens, cost_usd}`. Continues on errors, returns 1 if any failed.
105
+
106
+ ### Stubs (later weeks)
107
+ <!-- delegation-guard: ok -->
108
+
109
+ - `cost-guard live` — real-time TUI dashboard (Week 2.5)
110
+ - `cost-guard alert --budget 5` — daily spend cap (Week 3)
111
+
112
+ ## Pricing
113
+
114
+ Per-million-token rates (verified 2026-05-23 against https://docs.claude.com/en/docs/about-claude/models):
115
+
116
+ | Model | Input | Output | Cache read | Cache write 5m | Cache write 1h |
117
+ |---|---|---|---|---|---|
118
+ | `claude-opus-4-7` / `4-6` | $15 | $75 | $1.50 | $18.75 | $30 |
119
+ | `claude-sonnet-4-6` / `4-5` | $3 | $15 | $0.30 | $3.75 | $6 |
120
+ | `claude-haiku-4-5` | $1 | $5 | $0.10 | $1.25 | $2 |
121
+
122
+ Unknown models return cost=0 (not None) — see `cost_guard.pricing.compute_cost_usd`.
123
+
124
+ ## Tests
125
+
126
+ ```bash
127
+ cd cli
128
+ pytest # 50 tests, ~1s
129
+ COSTGUARD_REAL_SMOKE=1 pytest tests/test_real_data_smoke.py # opt-in: scans real ~/.claude/projects
130
+ ```
131
+
132
+ <!-- delegation-guard: ok -->
@@ -0,0 +1,108 @@
1
+ # cost-guard CLI
2
+
3
+ OSS CLI for Cost Guard. Tracks Claude Code session tokens, cost, hook compliance.
4
+
5
+ ## Install (Week 6+)
6
+
7
+ ```bash
8
+ pip install cost-guard # PyPI (Week 6 release)
9
+ brew install cost-guard # Homebrew tap (Week 6 release)
10
+ ```
11
+
12
+ ## Dev install
13
+
14
+ ```bash
15
+ cd cli
16
+ python -m venv .venv && source .venv/bin/activate
17
+ pip install -e ".[dev]"
18
+ pytest
19
+ cost-guard --version
20
+ ```
21
+
22
+ ## Commands
23
+
24
+ ### `cost-guard init` — install hooks bundle (Week 3 ✅)
25
+
26
+ Installs 5 bundled hook scripts + shared library into `.claude/hooks/`, registers them in `.claude/settings.json`.
27
+
28
+ ```bash
29
+ cost-guard init # install to .claude/hooks (default)
30
+ cost-guard init --target ~/.claude/hooks # explicit target path
31
+ cost-guard init --dry-run # preview changes, write nothing
32
+ cost-guard init --force # overwrite existing hooks
33
+ ```
34
+
35
+ Installs:
36
+ - **Hooks:** `parallel_agents_guard.sh`, `test_before_push_guard.sh`, `large_read_warning.sh`, `token_alert.sh`, `session_cost_check.sh`
37
+ - **Library:** `_lib/cost.sh` (shared utilities)
38
+
39
+ Registers hooks in `.claude/settings.json` with event + matcher + command path. Idempotent: running twice adds no duplicates.
40
+
41
+ Exit code 0 on success, 1 on error.
42
+
43
+ ### `cost-guard report` — session analytics (Week 2 ✅)
44
+
45
+ Scans Claude Code JSONL transcripts in `~/.claude/projects/`, aggregates tokens + cost per session, renders a Rich table.
46
+
47
+ ```bash
48
+ cost-guard report # default: last 20 sessions for cwd
49
+ cost-guard report --project ~/Projects/MyApp # specific project
50
+ cost-guard report --since 2026-05-01 # only sessions since date
51
+ cost-guard report --last 5 # top 5 most recent
52
+ cost-guard report --all # no limit
53
+ ```
54
+
55
+ Output columns: `Started | Duration | Model | Msgs | In | Out | Cache | Cost | Session`. Final row = TOTAL.
56
+
57
+ ### `cost-guard auth` — token management (Week 7 ✅)
58
+
59
+ Manages authentication with backend API.
60
+
61
+ ```bash
62
+ cost-guard auth set <api_key> # save API key (must start with cg_live_)
63
+ cost-guard auth status # check login status + show masked token
64
+ cost-guard auth logout # remove stored token
65
+ ```
66
+
67
+ Token storage: `~/.config/cost-guard/token` (0o600 permissions). Env override: `COST_GUARD_TOKEN=cg_live_...` takes precedence. Backend: `GET /v1/auth/me` (Bearer JWT).
68
+
69
+ ### `cost-guard push` — upload sessions to backend (Week 7 ✅)
70
+
71
+ Aggregates sessions from local JSONL and POSTs to backend API.
72
+
73
+ ```bash
74
+ cost-guard push # default: last 30 days
75
+ cost-guard push --project ~/Projects/MyApp # specific project
76
+ cost-guard push --since 7 # only last 7 days
77
+ cost-guard push --dry-run # preview without HTTP
78
+ ```
79
+
80
+ Requires authenticated token. Filters by `--since` (days). POSTs `{id, project, primary_model, started_at, ended_at, input_tokens, output_tokens, cost_usd}`. Continues on errors, returns 1 if any failed.
81
+
82
+ ### Stubs (later weeks)
83
+ <!-- delegation-guard: ok -->
84
+
85
+ - `cost-guard live` — real-time TUI dashboard (Week 2.5)
86
+ - `cost-guard alert --budget 5` — daily spend cap (Week 3)
87
+
88
+ ## Pricing
89
+
90
+ Per-million-token rates (verified 2026-05-23 against https://docs.claude.com/en/docs/about-claude/models):
91
+
92
+ | Model | Input | Output | Cache read | Cache write 5m | Cache write 1h |
93
+ |---|---|---|---|---|---|
94
+ | `claude-opus-4-7` / `4-6` | $15 | $75 | $1.50 | $18.75 | $30 |
95
+ | `claude-sonnet-4-6` / `4-5` | $3 | $15 | $0.30 | $3.75 | $6 |
96
+ | `claude-haiku-4-5` | $1 | $5 | $0.10 | $1.25 | $2 |
97
+
98
+ Unknown models return cost=0 (not None) — see `cost_guard.pricing.compute_cost_usd`.
99
+
100
+ ## Tests
101
+
102
+ ```bash
103
+ cd cli
104
+ pytest # 50 tests, ~1s
105
+ COSTGUARD_REAL_SMOKE=1 pytest tests/test_real_data_smoke.py # opt-in: scans real ~/.claude/projects
106
+ ```
107
+
108
+ <!-- delegation-guard: ok -->
@@ -0,0 +1,51 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "costcut"
7
+ version = "0.0.9"
8
+ description = "Claude Code observability + cost optimization CLI"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Eugene Kuznetsov", email = "eugene@costcut.dev" }]
13
+ keywords = ["claude-code", "observability", "tokens", "cost", "llm"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Programming Language :: Python :: 3.12",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Topic :: Software Development",
19
+ ]
20
+ dependencies = [
21
+ "typer>=0.12.0",
22
+ "httpx>=0.27.0",
23
+ "pydantic>=2.7.0",
24
+ "rich>=13.7.0",
25
+ ]
26
+
27
+ [project.optional-dependencies]
28
+ dev = [
29
+ "pytest>=8.0.0",
30
+ "ruff>=0.4.0",
31
+ "mypy>=1.10.0",
32
+ ]
33
+
34
+ [project.scripts]
35
+ costcut = "cost_guard.cli:app"
36
+ cost-guard = "cost_guard.cli:app"
37
+
38
+ [project.urls]
39
+ Homepage = "https://costcut.dev"
40
+ Repository = "https://github.com/kuznetsov-ai/cost-guard"
41
+
42
+ [tool.hatch.build.targets.wheel]
43
+ packages = ["src/cost_guard"]
44
+
45
+ [tool.ruff]
46
+ line-length = 100
47
+ target-version = "py312"
48
+
49
+ [tool.pytest.ini_options]
50
+ testpaths = ["tests"]
51
+ pythonpath = ["src"]
@@ -0,0 +1 @@
1
+ __version__ = "0.0.9"
@@ -0,0 +1,75 @@
1
+ # delegation-guard: ok
2
+ """Authentication and token management for cost-guard."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import os
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+ import httpx
11
+
12
+
13
+ def _get_token_path() -> Path:
14
+ """Get the path to the token file."""
15
+ config_dir = Path.home() / ".config" / "cost-guard"
16
+ config_dir.mkdir(parents=True, exist_ok=True)
17
+ return config_dir / "token"
18
+
19
+
20
+ def load_token() -> Optional[str]:
21
+ """Load token from env or file."""
22
+ # Env takes precedence
23
+ if env_token := os.getenv("COST_GUARD_TOKEN"):
24
+ return env_token
25
+
26
+ # Then file
27
+ token_path = _get_token_path()
28
+ if token_path.exists():
29
+ return token_path.read_text().strip()
30
+
31
+ return None
32
+
33
+
34
+ def save_token(token: str) -> Path:
35
+ """Save token to file with restricted permissions."""
36
+ token_path = _get_token_path()
37
+ token_path.write_text(token)
38
+ token_path.chmod(0o600)
39
+ return token_path
40
+
41
+
42
+ def clear_token() -> bool:
43
+ """Remove token file. Returns True if file existed."""
44
+ token_path = _get_token_path()
45
+ if token_path.exists():
46
+ token_path.unlink()
47
+ return True
48
+ return False
49
+
50
+
51
+ def get_api_url() -> str:
52
+ """Get the API URL from env or default."""
53
+ return os.getenv("COST_GUARD_API_URL", "https://api.costcut.dev")
54
+
55
+
56
+ async def verify_token(token: str) -> dict | None:
57
+ """Verify token by calling GET /v1/auth/me. Returns user dict or None."""
58
+ url = get_api_url()
59
+ headers = {"Authorization": f"Bearer {token}"}
60
+
61
+ try:
62
+ async with httpx.AsyncClient(timeout=30.0) as client:
63
+ resp = await client.get(f"{url}/v1/auth/me", headers=headers)
64
+ if resp.status_code == 200:
65
+ return resp.json()
66
+ return None
67
+ except Exception:
68
+ return None
69
+
70
+
71
+ def mask_token(token: str) -> str:
72
+ """Mask token for display."""
73
+ if len(token) <= 8:
74
+ return "***"
75
+ return f"{token[:8]}{'*' * 4}"
@@ -0,0 +1,139 @@
1
+ # delegation-guard: ok
2
+ """cost-guard CLI entry point."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import asyncio
7
+ from typing import Optional
8
+
9
+ import typer
10
+
11
+ from cost_guard import __version__
12
+ from cost_guard.auth import clear_token, load_token, mask_token, save_token, verify_token
13
+ from cost_guard.init_cmd import run_init
14
+ from cost_guard.push import run_push
15
+ from cost_guard.report import run_report
16
+
17
+ app = typer.Typer(
18
+ name="cost-guard",
19
+ help="Claude Code observability + cost optimization.",
20
+ no_args_is_help=True,
21
+ )
22
+ auth_app = typer.Typer(help="Authentication management")
23
+
24
+
25
+ def _version_callback(value: bool) -> None:
26
+ if value:
27
+ typer.echo(f"cost-guard {__version__}")
28
+ raise typer.Exit()
29
+
30
+
31
+ @app.callback()
32
+ def main(
33
+ version: bool = typer.Option(
34
+ False,
35
+ "--version",
36
+ callback=_version_callback,
37
+ is_eager=True,
38
+ help="Show version and exit.",
39
+ ),
40
+ ) -> None:
41
+ """Cost Guard CLI."""
42
+
43
+
44
+ @app.command()
45
+ def init(
46
+ target: str = typer.Option(".claude/hooks", help="Hook install path"),
47
+ force: bool = typer.Option(False, help="Overwrite existing hook files"),
48
+ dry_run: bool = typer.Option(False, help="Print what would be done, write nothing"),
49
+ ) -> None:
50
+ """Install hooks bundle into target directory."""
51
+ exit_code = run_init(target=target, force=force, dry_run=dry_run)
52
+ raise typer.Exit(exit_code)
53
+
54
+
55
+ @app.command()
56
+ def report(
57
+ project: str = typer.Option(".", help="Project path to scan"),
58
+ since: Optional[str] = typer.Option(None, help="Only include sessions since YYYY-MM-DD"),
59
+ last: Optional[int] = typer.Option(None, help="Show only last N sessions (default 20)"),
60
+ all: bool = typer.Option(False, help="Show all sessions"),
61
+ ) -> None:
62
+ """Show session analytics report."""
63
+ exit_code = run_report(project=project, since=since, last=last, all=all)
64
+ raise typer.Exit(exit_code)
65
+
66
+
67
+ @auth_app.command()
68
+ def set(api_key: str = typer.Argument(..., help="API key (starts with cg_live_)")) -> None:
69
+ """Set API token for authentication."""
70
+ if not api_key.startswith("cg_live_"):
71
+ typer.echo("Error: API key must start with 'cg_live_'", err=True)
72
+ raise typer.Exit(1)
73
+
74
+ save_token(api_key)
75
+ typer.echo(f"Token saved. Masked: {mask_token(api_key)}")
76
+
77
+
78
+ @auth_app.command()
79
+ def status() -> None:
80
+ """Check authentication status."""
81
+ token = load_token()
82
+ if not token:
83
+ typer.echo("Not logged in.", err=True)
84
+ raise typer.Exit(1)
85
+
86
+ user = asyncio.run(verify_token(token))
87
+ if user is None:
88
+ typer.echo("Error: Invalid token (401 or connection failed)", err=True)
89
+ raise typer.Exit(1)
90
+
91
+ email = user.get("email", "unknown")
92
+ plan = user.get("plan", "unknown")
93
+ typer.echo(f"Logged in as: {email} (plan: {plan})")
94
+ typer.echo(f"Token: {mask_token(token)}")
95
+
96
+
97
+ @auth_app.command()
98
+ def logout() -> None:
99
+ """Remove stored authentication token."""
100
+ if clear_token():
101
+ typer.echo("Logged out.")
102
+ else:
103
+ typer.echo("Not logged in (no token file found).")
104
+
105
+
106
+ app.add_typer(auth_app, name="auth")
107
+
108
+
109
+ @app.command()
110
+ def push(
111
+ project: str = typer.Option(".", help="Project path to scan"),
112
+ since: Optional[int] = typer.Option(30, help="Only push sessions from last N days"),
113
+ dry_run: bool = typer.Option(False, help="Print what would be pushed without HTTP"),
114
+ ) -> None:
115
+ """Push session data to backend."""
116
+ exit_code = run_push(project=project, since=since, dry_run=dry_run)
117
+ raise typer.Exit(exit_code)
118
+
119
+
120
+ @app.command()
121
+ def live() -> None:
122
+ """Live TUI dashboard of current session (Week 2 implementation)."""
123
+ typer.echo("live (stub; Week 2)")
124
+
125
+
126
+ @app.command()
127
+ def alert(budget: float = typer.Option(5.0, help="USD daily cap")) -> None:
128
+ """Set daily spend cap; warn or block when exceeded (Week 3)."""
129
+ typer.echo(f"alert budget=${budget:.2f} (stub; Week 3)")
130
+
131
+
132
+ @app.command()
133
+ def sync() -> None:
134
+ """Push session data to cloud (Week 4 implementation)."""
135
+ typer.echo("sync (stub; Week 4)")
136
+
137
+
138
+ if __name__ == "__main__":
139
+ app()