Skip to content
Merged
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
13 changes: 13 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM python:3.12-slim

WORKDIR /app

COPY scanner.py cli.py dashboard.py ./

ENV HOST=0.0.0.0
ENV PORT=8080
ENV CLAUDE_USAGE_DB=/data/usage.db

EXPOSE 8080

CMD ["python3", "cli.py", "dashboard", "--no-browser"]
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ cd claude-usage
python cli.py dashboard
```

### Docker
```
git clone https://github.com/phuryn/claude-usage
cd claude-usage
bash scripts/run-docker.sh
```

Opens the dashboard at **http://localhost:9898**.

The script builds the image, then runs the container with:
- `~/.claude` mounted **read-only** — the container can read your transcripts but cannot modify them
- A named Docker volume (`claude-usage-data`) for the SQLite database — persisted across restarts, isolated from your home directory

---

Expand Down Expand Up @@ -155,3 +167,5 @@ See [vscode-extension/README.md](vscode-extension/README.md) for settings, comma
| `cli.py` | `scan`, `today`, `stats`, `dashboard` commands |
| `Formula/claude-usage.rb` | Homebrew formula — install with `brew install --formula <raw-url>` |
| `vscode-extension/` | VS Code extension — embeds the dashboard inside VS Code |
| `Dockerfile` | Container image definition |
| `scripts/run-docker.sh` | Build and run the dashboard in Docker with a read-only `~/.claude` mount |
2 changes: 1 addition & 1 deletion cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

from scanner import VERSION

DB_PATH = Path.home() / ".claude" / "usage.db"
DB_PATH = Path(os.environ.get("CLAUDE_USAGE_DB", Path.home() / ".claude" / "usage.db"))

PRICING = {
# Fable / Mythos — Anthropic's most capable class, priced at 2x Opus.
Expand Down
2 changes: 1 addition & 1 deletion dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from scanner import VERSION

DB_PATH = Path.home() / ".claude" / "usage.db"
DB_PATH = Path(os.environ.get("CLAUDE_USAGE_DB", Path.home() / ".claude" / "usage.db"))

# Which surface is rendering the dashboard: "web" (standalone `cli.py dashboard`)
# or "vscode" (embedded in the extension's sidebar webview). serve() sets this
Expand Down
2 changes: 1 addition & 1 deletion scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

PROJECTS_DIR = Path.home() / ".claude" / "projects"
XCODE_PROJECTS_DIR = Path.home() / "Library" / "Developer" / "Xcode" / "CodingAssistant" / "ClaudeAgentConfig" / "projects"
DB_PATH = Path.home() / ".claude" / "usage.db"
DB_PATH = Path(os.environ.get("CLAUDE_USAGE_DB", Path.home() / ".claude" / "usage.db"))
DEFAULT_PROJECTS_DIRS = [PROJECTS_DIR, XCODE_PROJECTS_DIR]

# Higher number = higher priority when choosing a session's primary model.
Expand Down
40 changes: 40 additions & 0 deletions scripts/run-docker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
set -euo pipefail

REPO_DIR="$(cd "$(dirname "$0")/.." && pwd)"
IMAGE="claude-usage"
CONTAINER="claude-usage"
NETWORK="claude-usage-net"
PORT=9898

echo "▶ Checking for running container..."
if docker ps -q --filter "name=^${CONTAINER}$" | grep -q .; then
echo "⏹ Stopping ${CONTAINER}..."
docker stop "$CONTAINER"
fi

echo "🔗 Ensuring isolated network..."
if ! docker network inspect "$NETWORK" &>/dev/null; then
docker network create \
--opt com.docker.network.bridge.enable_ip_masquerade=false \
"$NETWORK"
fi

echo "⬇ Pulling latest..."
cd "$REPO_DIR"
git pull

echo "🔨 Building image..."
docker build -t "$IMAGE" .

echo "🚀 Starting container..."
docker run --rm -d \
--name "$CONTAINER" \
--network "$NETWORK" \
-p "$PORT:8080" \
-v "$HOME/.claude:/root/.claude:ro" \
-v "${CONTAINER}-data:/data" \
-e HOST=0.0.0.0 \
"$IMAGE"

echo "✅ Running at http://localhost:${PORT}"