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
9 changes: 9 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.git
.github
.idea
.claude
.DS_Store
*.md
captain-hook
hooks
docs
63 changes: 63 additions & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: Build and Push Docker Images

on:
push:
branches: [main]
paths:
- "api/**"
- "frontend/**"
- "Dockerfile*"
- "docker-compose*"
- "Caddyfile"
tags:
- "v*"

env:
REGISTRY: ghcr.io

jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Set image metadata
id: meta
run: |
OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
echo "owner=${OWNER}" >> "$GITHUB_OUTPUT"
echo "sha_short=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT"

- name: Build and push API image
uses: docker/build-push-action@v6
with:
context: ./api
push: true
tags: |
${{ env.REGISTRY }}/${{ steps.meta.outputs.owner }}/claude-code-karma-api:latest
${{ env.REGISTRY }}/${{ steps.meta.outputs.owner }}/claude-code-karma-api:${{ steps.meta.outputs.sha_short }}
cache-from: type=gha,scope=api
cache-to: type=gha,mode=max,scope=api

- name: Build and push Frontend image
uses: docker/build-push-action@v6
with:
context: ./frontend
push: true
tags: |
${{ env.REGISTRY }}/${{ steps.meta.outputs.owner }}/claude-code-karma-frontend:latest
${{ env.REGISTRY }}/${{ steps.meta.outputs.owner }}/claude-code-karma-frontend:${{ steps.meta.outputs.sha_short }}
cache-from: type=gha,scope=frontend
cache-to: type=gha,mode=max,scope=frontend
20 changes: 20 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,26 @@ npm install && npm run dev

Open http://localhost:5173 to view the dashboard.

### Docker Quick Start

```bash
# Production (read-only ~/.claude mount)
make up

# Dev frontend with HMR
make dev

# Health check
make health

# Stop
make down
```

Open http://localhost:8080 to view the dashboard.

See `Makefile` for all available targets.

## Repository Structure

This is a monorepo with all components in a single repository:
Expand Down
9 changes: 9 additions & 0 deletions Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
:8080 {
handle_path /api/* {
reverse_proxy api:8000
}

handle {
reverse_proxy frontend:3000
}
}
50 changes: 50 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
.PHONY: up dev rw down build logs health clean ps

# Production (default) — read-only ~/.claude mount
up:
docker compose up --build -d

# Dev frontend with HMR
dev:
docker compose -f docker-compose.yml -f docker-compose.dev.yml up --build

# Read-write ~/.claude mount
rw:
docker compose -f docker-compose.yml -f docker-compose.rw.yml up --build -d

# Pull pre-built images from GHCR (no local build)
pull:
docker compose pull
docker compose up -d

# Stop all containers
down:
docker compose down

# Build without starting
build:
docker compose build

# Follow logs
logs:
docker compose logs -f

# Follow logs for a specific service (usage: make log-api, make log-frontend, make log-caddy)
log-%:
docker compose logs -f $*

# Health check
health:
@curl -sf http://localhost:8080/api/health | python3 -m json.tool || echo "API not healthy"

# Show running containers
ps:
docker compose ps

# Remove containers, volumes, and orphans
clean:
docker compose down -v --remove-orphans

# Open dashboard in browser
open:
open http://localhost:8080
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,34 @@ Track which skills are invoked across sessions, grouped by plugin or shown indiv

## Quick Start

### Docker (recommended)

```bash
git clone https://github.com/JayantDevkar/claude-code-karma.git
cd claude-code-karma

# Start all services (API + Frontend + Caddy reverse proxy)
make up

# Open the dashboard
open http://localhost:8080
```

Other make targets:

| Command | Description |
|---------|-------------|
| `make up` | Production mode (read-only `~/.claude` mount) |
| `make dev` | Dev mode with frontend HMR |
| `make rw` | Read-write `~/.claude` mount |
| `make health` | Check API health |
| `make logs` | Follow container logs |
| `make down` | Stop all containers |
| `make clean` | Stop and remove volumes |

### Local (without Docker)

```bash
# Clone the repository
git clone https://github.com/JayantDevkar/claude-code-karma.git
cd claude-code-karma

Expand Down
18 changes: 18 additions & 0 deletions api/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.git
.gitignore
.claude
.playwright-mcp
__pycache__
*.pyc
*.pyo
tests/
benchmarks/
docs/
scripts/
*.md
.env
.env.*
.ruff_cache
.pytest_cache
.coverage
htmlcov/
20 changes: 20 additions & 0 deletions api/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM python:3.11-slim

RUN apt-get update && apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

RUN mkdir -p /root/.claude /root/.claude_karma

EXPOSE 8000

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
16 changes: 16 additions & 0 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
services:
frontend:
build:
context: ./frontend
dockerfile: Dockerfile.dev
image: ""
volumes:
- ./frontend:/app
- frontend-node-modules:/app/node_modules
command: sh -c "npm install && npm run dev -- --host 0.0.0.0 --port 3000"
environment:
- INTERNAL_API_URL=http://api:8000
- ORIGIN=http://localhost:8080

volumes:
frontend-node-modules:
5 changes: 5 additions & 0 deletions docker-compose.rw.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
services:
api:
volumes:
- ~/.claude:/root/.claude:rw
- karma-data:/root/.claude_karma
38 changes: 38 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
services:
caddy:
image: caddy:2-alpine
ports:
- "8080:8080"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
depends_on:
api:
condition: service_healthy
frontend:
condition: service_started
restart: unless-stopped

api:
image: ghcr.io/jayantdevkar/claude-code-karma-api:latest
build: ./api
volumes:
- ~/.claude:/root/.claude:ro
- karma-data:/root/.claude_karma
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 5s
start_period: 10s
retries: 3
restart: unless-stopped

frontend:
image: ghcr.io/jayantdevkar/claude-code-karma-frontend:latest
build: ./frontend
environment:
- INTERNAL_API_URL=http://api:8000
- ORIGIN=http://localhost:8080
restart: unless-stopped

volumes:
karma-data:
14 changes: 14 additions & 0 deletions frontend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.git
.gitignore
.claude
.playwright-mcp
node_modules
build
.svelte-kit
.vite
docs/
*.md
.env
.env.*
coverage/
*.tsbuildinfo
25 changes: 25 additions & 0 deletions frontend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
FROM node:22-slim AS build

WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci

COPY . .
RUN npm run build

FROM node:22-slim

WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci --omit=dev

COPY --from=build /app/build ./build

ENV PORT=3000
ENV ORIGIN=http://localhost:8080

EXPOSE 3000

CMD ["node", "build"]
3 changes: 3 additions & 0 deletions frontend/Dockerfile.dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM node:22-slim

WORKDIR /app
28 changes: 22 additions & 6 deletions frontend/src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,39 @@
* Centralized configuration for Claude Code Karma frontend.
*
* API Base URL:
* - Uses PUBLIC_API_URL environment variable if set
* - Falls back to http://localhost:8000 for local development
* - Browser: Uses PUBLIC_API_URL env var, or `/api` (for Caddy reverse proxy)
* - SSR: Uses INTERNAL_API_URL env var (e.g. `http://api:8000` in Docker),
* or falls back to `http://localhost:8000` for local development
*
* To configure in production:
* - Set PUBLIC_API_URL in your .env file
* - Or set it in your deployment environment
* Local dev: Set PUBLIC_API_URL=http://localhost:8000 in .env
* Docker: INTERNAL_API_URL is set via docker-compose environment
*/

/**
* API base URL for all backend requests.
*
* Two contexts need different URLs:
* - Browser: relative `/api` (routed by Caddy) or PUBLIC_API_URL for local dev
* - SSR (Node server): Docker internal `http://api:8000` via INTERNAL_API_URL
*
* @example
* ```ts
* import { API_BASE } from '$lib/config';
* const response = await fetch(`${API_BASE}/projects`);
* ```
*/
export const API_BASE = import.meta.env.PUBLIC_API_URL || 'http://localhost:8000';
const SERVER_API = (() => {
try {
return process.env.INTERNAL_API_URL || 'http://localhost:8000';
} catch {
return 'http://localhost:8000';
}
})();

export const API_BASE =
typeof window !== 'undefined'
? (import.meta.env.PUBLIC_API_URL || `${window.location.origin}/api`)
: SERVER_API;

/**
* API request timeout in milliseconds (default: 30 seconds)
Expand Down