Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.git/
.github/
.claude/
tests/
docs/
*.md
LICENSE
__pycache__/
*.pyc
.DS_Store
server.log
14 changes: 14 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM python:3.12-slim

WORKDIR /app
COPY cli.py dashboard.py scanner.py ./

ENV HOST=0.0.0.0 \
PORT=8080 \
PYTHONUNBUFFERED=1

EXPOSE 8080

# Run scan once at startup, then serve. Bypasses cli.py:cmd_dashboard
# to avoid the webbrowser.open call (irrelevant inside a container).
CMD ["python3", "-c", "from scanner import scan; scan(); from dashboard import serve; serve()"]
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,24 @@ cd claude-usage
python3 cli.py dashboard
```

### Docker

```bash
docker compose up -d
# Open http://localhost:8080
```

Mounts `~/.claude` into the container, so the SQLite DB at `~/.claude/usage.db`
persists on the host and CLI commands (`python3 cli.py today`) keep working
alongside the container.

To run single-shot CLI commands without starting the server:

Comment on lines +63 to +67
```bash
docker compose run --rm claude-usage python3 cli.py today
docker compose run --rm claude-usage python3 cli.py stats
```

---

## Usage
Expand Down
12 changes: 12 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
services:
claude-usage:
build: .
container_name: claude-usage
ports:
- "8080:8080"
volumes:
# JSONL logs (read) + usage.db (read/write)
- ${HOME:?HOME must be set}/.claude:/root/.claude
# Optional: Xcode Claude integration logs (macOS only)
# - ${HOME}/Library/Developer/Xcode/CodingAssistant/ClaudeAgentConfig/projects:/root/Library/Developer/Xcode/CodingAssistant/ClaudeAgentConfig/projects:ro
Comment on lines +5 to +11
Comment on lines +5 to +11
restart: unless-stopped
29 changes: 29 additions & 0 deletions tests/test_docker_setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Static checks for Docker setup (no docker build required — stdlib only)."""

import re
import unittest
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent


class TestDockerSetup(unittest.TestCase):
def test_dockerfile_exists_and_uses_python(self):
df = (ROOT / "Dockerfile").read_text()
self.assertRegex(df, r"(?m)^FROM\s+python:",
"Dockerfile must use a Python base image")

def test_compose_exposes_dashboard_port(self):
compose = (ROOT / "compose.yaml").read_text()
self.assertIn("8080", compose)

def test_compose_mounts_claude_home(self):
compose = (ROOT / "compose.yaml").read_text()
self.assertTrue(
re.search(r"(~|\$HOME|\$\{HOME\})/\.claude", compose),
f"compose.yaml must bind-mount ~/.claude; got:\n{compose}",
)


if __name__ == "__main__":
unittest.main()
Loading