Skip to content
Closed
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
18 changes: 18 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"permissions": {
"allow": [
"Bash(chmod:*)",
"Bash(python3 -m venv:*)",
"Bash(.venv/bin/pip install:*)",
"Bash(brew list:*)",
"Bash(/opt/homebrew/bin/python3.12:*)",
"Bash(.venv/bin/ruff format:*)",
"Bash(.venv/bin/ruff check .)",
"Bash(.venv/bin/pytest tests/ -v)",
"Bash(./scripts/install-launchagent.sh:*)",
"Bash(claude mcp add:*)",
"Bash(curl:*)",
"Bash(cat:*)"
]
}
}
73 changes: 73 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
.PHONY: check fmt lint test clean install uninstall dev venv

# Run all quality gates (format check, lint, tests)
check: fmt lint test

# Check/fix formatting with ruff
fmt:
ruff format --check .

# Run linter with ruff
lint:
ruff check .

# Run tests
test:
pytest tests/ -v

# Clean build artifacts
clean:
rm -rf build/ dist/ *.egg-info .pytest_cache .ruff_cache
find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true

# Create virtual environment (requires Python 3.10+)
venv:
@if [ ! -d .venv ]; then \
echo "Creating virtual environment..."; \
PYTHON=$$(command -v python3.12 || command -v python3.11 || command -v python3.10 || echo "python3"); \
$$PYTHON -m venv .venv && .venv/bin/pip install --upgrade pip; \
fi

# Install with dev dependencies (for development)
dev: venv
.venv/bin/pip install -e ".[dev]"

# Full installation: venv + deps + LaunchAgent + CLI + MCP
install: venv
@echo "Installing dependencies..."
.venv/bin/pip install -e .
@echo ""
@echo "Installing LaunchAgent..."
./scripts/install-launchagent.sh
@echo ""
@echo "Adding to Claude Code..."
@CLAUDE_CMD=$$(command -v claude || echo "$$HOME/.local/bin/claude"); \
if [ -x "$$CLAUDE_CMD" ]; then \
$$CLAUDE_CMD mcp add --transport http --scope user session-analytics http://localhost:8081/mcp 2>/dev/null && \
echo "Added session-analytics to Claude Code" || \
echo "session-analytics already configured in Claude Code"; \
else \
echo "Note: claude not found. Run manually:"; \
echo " claude mcp add --transport http --scope user session-analytics http://localhost:8081/mcp"; \
fi
@echo ""
@echo "Installation complete!"
@echo ""
@echo "Make sure ~/.local/bin is in your PATH:"
@echo ' export PATH="$$HOME/.local/bin:$$PATH"'

# Uninstall: LaunchAgent + CLI + MCP config
uninstall:
@echo "Uninstalling..."
./scripts/uninstall-launchagent.sh
@echo ""
@echo "Removing from Claude Code..."
@CLAUDE_CMD=$$(command -v claude || echo "$$HOME/.local/bin/claude"); \
if [ -x "$$CLAUDE_CMD" ]; then \
$$CLAUDE_CMD mcp remove --scope user session-analytics 2>/dev/null && \
echo "Removed session-analytics from Claude Code" || \
echo "session-analytics not found in Claude Code"; \
fi
@echo ""
@echo "Uninstall complete!"
@echo "Note: venv and source code remain in place."
94 changes: 94 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Claude Session Analytics

MCP server for queryable analytics on Claude Code session logs.

## Overview

Replaces `parse-session-logs.sh` with a persistent, queryable analytics layer. Parses JSONL session logs from `~/.claude/projects/` and provides:

- **User-centric timeline**: Events across conversations, organized by timestamp
- **Rich querying**: Tool frequency, command breakdown, sequences, permission gaps
- **Persistent storage**: SQLite at `~/.claude/contrib/analytics/data.db`
- **Auto-refresh**: Queries automatically refresh stale data (>5 min old)
- **CLI access**: Full CLI for shell scripts and hooks

## Installation

```bash
make install
```

This will:
1. Create a virtual environment
2. Install dependencies
3. Set up a LaunchAgent for auto-start
4. Add the MCP server to Claude Code

## Development

```bash
make dev # Install dev dependencies
./scripts/dev.sh # Run in dev mode with auto-reload
```

## Commands

```bash
make check # Run fmt, lint, test
make install # Install LaunchAgent + CLI
make uninstall # Remove LaunchAgent + CLI
```

## CLI Usage

The CLI provides direct access to analytics from the command line:

```bash
# Database status
session-analytics-cli status

# Ingest log files
session-analytics-cli ingest --days 7

# Tool frequency
session-analytics-cli frequency --days 30

# Bash command breakdown
session-analytics-cli commands --prefix git

# Session info
session-analytics-cli sessions

# Token usage by day/session/model
session-analytics-cli tokens --by model

# Tool sequences
session-analytics-cli sequences --min-count 3

# Permission gaps (commands needing settings.json)
session-analytics-cli permissions --threshold 5

# Insights for /improve-workflow
session-analytics-cli insights --refresh
```

All commands support `--json` for machine-readable output.

## MCP Tools

| Tool | Purpose |
|------|---------|
| `ingest_logs` | Refresh data from JSONL files |
| `query_timeline` | Events in time window |
| `query_tool_frequency` | Tool usage counts |
| `query_commands` | Bash command breakdown |
| `query_sequences` | Common tool patterns |
| `query_permission_gaps` | Commands needing settings.json |
| `query_sessions` | Session metadata |
| `query_tokens` | Token usage analysis |
| `get_insights` | Pre-computed patterns for /improve-workflow |
| `get_status` | Ingestion status + DB stats |

## License

MIT
55 changes: 55 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "claude-session-analytics"
version = "0.1.0"
description = "MCP server for queryable analytics on Claude Code session logs"
readme = "README.md"
requires-python = ">=3.10"
license = "MIT"
authors = [
{ name = "Evan Senter" }
]
keywords = ["mcp", "claude", "analytics", "session-logs"]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
dependencies = [
"fastmcp>=0.1.0",
"uvicorn>=0.30.0",
]

[project.optional-dependencies]
dev = [
"pytest>=8.0.0",
"pytest-asyncio>=0.23.0",
"ruff>=0.8.0",
]

[project.scripts]
session-analytics = "session_analytics.server:main"
session-analytics-cli = "session_analytics.cli:main"

[tool.hatch.build.targets.wheel]
packages = ["src/session_analytics"]

[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"

[tool.ruff]
target-version = "py310"
line-length = 100
src = ["src", "tests"]

[tool.ruff.lint]
select = ["E", "F", "I", "N", "W", "UP"]
ignore = ["E501"] # Line length handled by formatter
41 changes: 41 additions & 0 deletions scripts/com.evansenter.claude-session-analytics.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.evansenter.claude-session-analytics</string>

<key>ProgramArguments</key>
<array>
<string>__VENV_PYTHON__</string>
<string>-m</string>
<string>session_analytics.server</string>
</array>

<key>WorkingDirectory</key>
<string>__PROJECT_DIR__</string>

<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
<key>PYTHONPATH</key>
<string>__PROJECT_DIR__/src</string>
</dict>

<key>RunAtLoad</key>
<true/>

<key>KeepAlive</key>
<true/>

<key>StandardOutPath</key>
<string>__HOME__/.claude/session-analytics.log</string>

<key>StandardErrorPath</key>
<string>__HOME__/.claude/session-analytics.err</string>

<key>ProcessType</key>
<string>Background</string>
</dict>
</plist>
37 changes: 37 additions & 0 deletions scripts/dev.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/bin/bash
# Run session analytics in development mode (foreground, auto-reload, verbose logging)

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
LABEL="com.evansenter.claude-session-analytics"
PLIST="$HOME/Library/LaunchAgents/$LABEL.plist"

cd "$PROJECT_DIR"
source .venv/bin/activate

# Stop LaunchAgent if running (to free port 8081)
LAUNCHAGENT_WAS_RUNNING=false
if launchctl list 2>/dev/null | grep -q "$LABEL"; then
echo "Stopping LaunchAgent for dev mode..."
launchctl unload "$PLIST" 2>/dev/null
LAUNCHAGENT_WAS_RUNNING=true
osascript -e 'display notification "Stopped for dev mode" with title "Session Analytics"' 2>/dev/null
fi

# Restart LaunchAgent on exit
cleanup() {
if [[ "$LAUNCHAGENT_WAS_RUNNING" == "true" && -f "$PLIST" ]]; then
echo ""
echo "Restarting LaunchAgent..."
launchctl load "$PLIST"
osascript -e 'display notification "LaunchAgent restarted" with title "Session Analytics"' 2>/dev/null
fi
}
trap cleanup EXIT

echo "Starting session analytics in dev mode (Ctrl+C to stop)..."
echo "Add to Claude Code: claude mcp add --transport http --scope user session-analytics http://127.0.0.1:8081/mcp"
echo ""

# DEV_MODE enables verbose logging
DEV_MODE=1 uvicorn session_analytics.server:create_app --host 127.0.0.1 --port 8081 --reload --factory
55 changes: 55 additions & 0 deletions scripts/install-launchagent.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/bin/bash
# Install the session analytics server as a macOS LaunchAgent (auto-starts on login)

set -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
VENV_PYTHON="$PROJECT_DIR/.venv/bin/python"
PLIST_TEMPLATE="$SCRIPT_DIR/com.evansenter.claude-session-analytics.plist"
PLIST_DEST="$HOME/Library/LaunchAgents/com.evansenter.claude-session-analytics.plist"
LABEL="com.evansenter.claude-session-analytics"

# Check venv exists
if [[ ! -f "$VENV_PYTHON" ]]; then
echo "Error: Virtual environment not found at $PROJECT_DIR/.venv"
echo "Run: python3 -m venv .venv && source .venv/bin/activate && pip install -e ."
exit 1
fi

# Create LaunchAgents directory if needed
mkdir -p "$HOME/Library/LaunchAgents"
mkdir -p "$HOME/.claude"

# Stop existing service if running
if launchctl list | grep -q "$LABEL"; then
echo "Stopping existing service..."
launchctl unload "$PLIST_DEST" 2>/dev/null || true
fi

# Generate plist with correct paths
echo "Installing LaunchAgent..."
sed -e "s|__VENV_PYTHON__|$VENV_PYTHON|g" \
-e "s|__PROJECT_DIR__|$PROJECT_DIR|g" \
-e "s|__HOME__|$HOME|g" \
"$PLIST_TEMPLATE" > "$PLIST_DEST"

# Load the service
echo "Starting service..."
launchctl load "$PLIST_DEST"

# Verify it's running
sleep 1
if launchctl list | grep -q "$LABEL"; then
echo ""
echo "Session analytics installed and running!"
echo " Logs: ~/.claude/session-analytics.log"
echo " Errors: ~/.claude/session-analytics.err"
echo ""
echo "To uninstall: $SCRIPT_DIR/uninstall-launchagent.sh"
osascript -e 'display notification "LaunchAgent installed and running" with title "Session Analytics"' 2>/dev/null
else
echo "Error: Service failed to start. Check ~/.claude/session-analytics.err"
osascript -e 'display notification "Failed to start - check logs" with title "Session Analytics" sound name "Basso"' 2>/dev/null
exit 1
fi
Loading