Skip to content

Commit 94d9dfc

Browse files
ccfclaude
andauthored
feat: multi-agent support, unified CLI, and extractors (#37)
* feat: add multi-agent data model with OpenAI and Google pricing Introduce agent_type dimension across the data model to support Codex CLI and Gemini CLI alongside Claude Code. Rename claude_version → agent_version and claude_helpfulness → agent_helpfulness with backward-compatible Pydantic validators. Add pricing for 9 OpenAI/Google models and agent_type filter to analytics queries. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add Codex CLI and Gemini CLI extractors with registry-based discovery Introduce a SessionExtractor protocol and three implementations (Claude Code, Codex CLI, Gemini CLI) behind an extractor registry. list_local_sessions() and sync now dispatch through the registry, enabling multi-agent session discovery and ingestion from ~/.codex/ and ~/.gemini/ alongside ~/.claude/. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add unified `primer` CLI with init, server, hook, mcp, and doctor commands Replaces ~8 manual steps (python -m, scripts/, raw uvicorn, manual settings edits) with a single `pip install . && primer init && primer server start` workflow. Adds click-based CLI with commands: init, setup, server {start,stop,status,logs}, hook {install,uninstall,status}, mcp {install,uninstall,serve}, sync, doctor, and configure {get,set,list}. Includes config.toml bridge, launchd/systemd/pidfile server management, and refactored hook installer. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: increase CLI test coverage to 95% (was ~55%) Add 40+ tests across server_manager, setup, server, sync, doctor, configure, and config modules using monkeypatch — no real infrastructure. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: prevent PRIMER_ADMIN_API_KEY env leak between tests The init test's load_config_into_env() set env vars without monkeypatch, causing test_admin_headers to see a stale admin key. Fix both the source (delenv in init test) and the victim (patch ADMIN_API_KEY in mcp fixture). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: sort imports in init.py to satisfy ruff I001 in CI Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address bugbot review comments - Fix invalid systemd directive `StandardErrorOutput` → `StandardError` - Close log file descriptor after spawning background server process - Key cumulative token tracker per model to avoid cross-model deltas - Only count `ExecCommandBegin` events (not paired End) for tool calls - Populate `agent_type_counts` in overview stats from session data Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address second round of bugbot comments - Escape special chars in TOML string serializer (`\`, `"`) - Read engineer ID from nested `engineer` key in setup response - Escape XML special chars in launchd plist env values - Fix operator precedence in Gemini extractor usage metadata access - Remove dead `_project_path_to_dir_name` and `_find_transcript` from reader Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add Astro marketing website with landing, docs, blog, and deployment Implements the marketing website plan using Astro 5 with Tailwind v4, React islands for interactive components, and MDX content collections for the blog. Includes dark hero, feature grid, comparison table, pricing page, and GitHub Pages deployment workflow. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address third round of bugbot comments - Preserve comments in config.toml by doing line-level replacement in set_value instead of full round-trip through tomllib - Coerce string values to int/float/bool before storing in TOML - Mask sensitive values in `configure set` output (consistent with get/list) - Scope S603 suppression to cli/ and hook/ via per-file-ignores - Use exact session_id match in Gemini telemetry lookup instead of substring matching on raw log lines Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add agent_type_counts to frontend OverviewStats type Aligns the TypeScript interface with the backend schema that now returns agent type distribution data. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address fourth round of bugbot comments - Merge _extract_session_id and _extract_project_path into single _extract_session_meta to avoid reading each rollout file twice - Use check=False in systemd stop to handle gracefully when server isn't running instead of crashing with CalledProcessError Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address fifth round of bugbot comments - Preserve leading whitespace in config line replacement - Guard API key truncation for short keys in doctor command - XML-escape log path in launchd plist generation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add Codex CLI and Gemini CLI sessions to seed script Extend seed_data.py with multi-agent support so the dashboard shows realistic Codex/Gemini data alongside Claude Code. Also bump the ingest rate limit from 120/min to 300/min to avoid 429s during seeding. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: mask short sensitive values in configure output Short sensitive values (<=12 chars) were displayed in full by `configure get`, `configure set`, and `configure list`. Now they show "***" consistent with the doctor command. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ce80d89 commit 94d9dfc

File tree

83 files changed

+15541
-216
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+15541
-216
lines changed

.github/workflows/website.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
name: Deploy Website
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- "website/**"
8+
- "brand/**"
9+
workflow_dispatch:
10+
11+
permissions:
12+
contents: read
13+
pages: write
14+
id-token: write
15+
16+
concurrency:
17+
group: "pages"
18+
cancel-in-progress: false
19+
20+
jobs:
21+
build:
22+
runs-on: ubuntu-latest
23+
steps:
24+
- uses: actions/checkout@v4
25+
26+
- uses: actions/setup-node@v4
27+
with:
28+
node-version: "22"
29+
cache: "npm"
30+
cache-dependency-path: website/package-lock.json
31+
32+
- name: Install dependencies
33+
run: cd website && npm ci
34+
35+
- name: Build website
36+
run: cd website && npm run build
37+
38+
- name: Upload artifact
39+
uses: actions/upload-pages-artifact@v3
40+
with:
41+
path: website/dist
42+
43+
deploy:
44+
environment:
45+
name: github-pages
46+
url: ${{ steps.deployment.outputs.page_url }}
47+
runs-on: ubuntu-latest
48+
needs: build
49+
steps:
50+
- name: Deploy to GitHub Pages
51+
id: deployment
52+
uses: actions/deploy-pages@v4
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
"""add agent_type, rename claude_version and claude_helpfulness
2+
3+
Revision ID: a1b2c3d4e5f6
4+
Revises: 7dda515efd1d
5+
Create Date: 2026-02-27 00:00:00.000000
6+
7+
"""
8+
9+
from typing import Sequence, Union
10+
11+
import sqlalchemy as sa
12+
from alembic import op
13+
14+
# revision identifiers, used by Alembic.
15+
revision: str = "a1b2c3d4e5f6"
16+
down_revision: Union[str, None] = "7dda515efd1d"
17+
branch_labels: Union[str, Sequence[str], None] = None
18+
depends_on: Union[str, Sequence[str], None] = None
19+
20+
21+
def upgrade() -> None:
22+
# --- sessions table ---
23+
with op.batch_alter_table("sessions") as batch_op:
24+
batch_op.add_column(
25+
sa.Column("agent_type", sa.String(30), nullable=False, server_default="claude_code")
26+
)
27+
batch_op.alter_column("claude_version", new_column_name="agent_version")
28+
batch_op.create_index("ix_sessions_agent_type", ["agent_type"])
29+
30+
# --- session_facets table ---
31+
with op.batch_alter_table("session_facets") as batch_op:
32+
batch_op.alter_column("claude_helpfulness", new_column_name="agent_helpfulness")
33+
34+
# --- daily_stats table ---
35+
with op.batch_alter_table("daily_stats") as batch_op:
36+
batch_op.add_column(
37+
sa.Column("agent_type", sa.String(30), nullable=False, server_default="claude_code")
38+
)
39+
batch_op.drop_constraint("uq_daily_stats_engineer_date")
40+
batch_op.create_unique_constraint(
41+
"uq_daily_stats_engineer_date", ["engineer_id", "date", "agent_type"]
42+
)
43+
44+
45+
def downgrade() -> None:
46+
# --- daily_stats table ---
47+
with op.batch_alter_table("daily_stats") as batch_op:
48+
batch_op.drop_constraint("uq_daily_stats_engineer_date")
49+
batch_op.create_unique_constraint("uq_daily_stats_engineer_date", ["engineer_id", "date"])
50+
batch_op.drop_column("agent_type")
51+
52+
# --- session_facets table ---
53+
with op.batch_alter_table("session_facets") as batch_op:
54+
batch_op.alter_column("agent_helpfulness", new_column_name="claude_helpfulness")
55+
56+
# --- sessions table ---
57+
with op.batch_alter_table("sessions") as batch_op:
58+
batch_op.drop_index("ix_sessions_agent_type")
59+
batch_op.alter_column("agent_version", new_column_name="claude_version")
60+
batch_op.drop_column("agent_type")

brand/tokens.css

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/* Primer Design Tokens — shared across website/ and frontend/ */
2+
:root {
3+
/* Brand */
4+
--primer-brand: #6366F1;
5+
--primer-foreground: #4338CA;
6+
--primer-surface: #EEF2FF;
7+
--primer-border: #C7D2FE;
8+
--primer-dark: #0F0B2A;
9+
10+
/* Extended Palette */
11+
--primer-50: #EEF2FF;
12+
--primer-100: #E0E7FF;
13+
--primer-200: #C7D2FE;
14+
--primer-400: #818CF8;
15+
--primer-500: #6366F1;
16+
--primer-600: #4F46E5;
17+
--primer-700: #4338CA;
18+
--primer-900: #312E81;
19+
20+
/* Semantic */
21+
--primer-success: #16A34A;
22+
--primer-warning: #D97706;
23+
--primer-critical: #DC2626;
24+
--primer-info: #2563EB;
25+
}
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
# Installation Simplification Plan
2+
3+
## Context
4+
Primer currently requires ~8 manual steps across Python, Node.js, and shell. This plan introduces a `primer` CLI, a `curl | sh` installer for personal use, and enhanced Docker/Helm for enterprise.
5+
6+
## Current Pain Points
7+
- No single entry point or CLI — everything is `python -m`, `python scripts/`, or raw curl
8+
- No `[project.scripts]` in pyproject.toml
9+
- No daemonization (server runs in foreground terminal)
10+
- Frontend requires Node.js + separate dev server
11+
- Docker Compose is minimal (no frontend, no health checks)
12+
- No Helm chart
13+
- MCP registration and API key distribution are manual
14+
15+
## 1. Unified `primer` CLI
16+
17+
### Entry Point
18+
```toml
19+
# pyproject.toml
20+
[project.scripts]
21+
primer = "primer.cli:main"
22+
```
23+
24+
### Command Tree
25+
```
26+
primer init # Create ~/.primer/, run migrations, generate config
27+
primer setup # Create identity (name, email from git config)
28+
primer server start [--fg] # Start server (background default, foreground for Docker)
29+
primer server stop # Stop background server
30+
primer server status # Show running state, port, uptime
31+
primer server logs # Tail server logs
32+
primer hook install # Install SessionEnd hook in Claude Code
33+
primer hook uninstall # Remove hook
34+
primer hook status # Check hook configuration
35+
primer mcp install # Register MCP sidecar
36+
primer mcp uninstall # Remove MCP registration
37+
primer mcp serve # Run MCP server (used by the registration)
38+
primer sync # Sync local sessions to server
39+
primer doctor # Diagnose issues (connectivity, hook, MCP)
40+
primer version # Show version
41+
primer configure [set|get] # View/edit config
42+
```
43+
44+
### Config Precedence
45+
1. CLI flags → 2. Environment variables → 3. `~/.primer/config.toml` → 4. PrimerSettings defaults
46+
47+
### Data Directory
48+
```
49+
~/.primer/
50+
config.toml # API keys, server URL, port
51+
primer.db # SQLite database
52+
frontend-dist/ # Pre-built React app
53+
logs/server.log # Server output
54+
server.pid # PID file for background server
55+
```
56+
57+
### Server Management
58+
- macOS: launchd plist at `~/Library/LaunchAgents/dev.primer.server.plist`
59+
- Linux: systemd user unit at `~/.config/systemd/user/primer.service`
60+
- Fallback: `nohup` + PID file
61+
62+
## 2. Personal Use: `curl | sh` Installer
63+
64+
```bash
65+
curl -fsSL https://primer.dev/install.sh | sh
66+
```
67+
68+
### Steps
69+
1. **Detect environment**: OS, arch, Python 3.12+, pipx
70+
2. **Install package**: `pipx install primer` (or `pip install --user`)
71+
3. **Initialize**: `primer init` — creates ~/.primer/, runs Alembic, generates keys
72+
4. **Setup identity**: `primer setup --auto` — reads name/email from `git config`
73+
5. **Install hook**: `primer hook install` — writes to `~/.claude/settings.json`
74+
6. **Register MCP**: `primer mcp install` — adds MCP sidecar entry
75+
7. **Download frontend**: Pre-built tarball from GitHub release → `~/.primer/frontend-dist/`
76+
8. **Start server**: Create launchd plist or systemd unit, start service
77+
9. **Verify**: Health check + print summary with URLs and key location
78+
79+
### Config File
80+
```toml
81+
[server]
82+
host = "127.0.0.1"
83+
port = 8000
84+
database_url = "sqlite:///~/.primer/primer.db"
85+
admin_api_key = "primer-admin-xxxxxxxx"
86+
87+
[identity]
88+
api_key = "primer_xxxxxxxx"
89+
name = "Your Name"
90+
email = "you@example.com"
91+
```
92+
93+
## 3. Enterprise: Docker Compose
94+
95+
### New `Dockerfile.frontend`
96+
Multi-stage: `node:20-alpine` build → `nginx:alpine` serve
97+
98+
### Enhanced `docker-compose.yml`
99+
- PostgreSQL with health check
100+
- Server with health check + required env var validation (`${VAR:?message}`)
101+
- Frontend container (nginx)
102+
- `.env.docker.example` template
103+
104+
### Enterprise Setup Script
105+
```bash
106+
curl -fsSL https://primer.dev/enterprise-setup.sh | sh
107+
```
108+
Checks Docker, prompts for PostgreSQL, generates keys, writes .env, runs `docker compose up -d`.
109+
110+
## 4. Enterprise: Kubernetes / Helm
111+
112+
### Chart Structure
113+
```
114+
deploy/helm/primer/
115+
Chart.yaml, values.yaml
116+
templates/
117+
deployment-server.yaml # With HPA
118+
deployment-frontend.yaml
119+
service-*.yaml
120+
ingress.yaml # With TLS
121+
configmap.yaml, secret.yaml
122+
migration-job.yaml # Helm pre-install/pre-upgrade hook
123+
hpa.yaml, pdb.yaml
124+
```
125+
126+
### Key values.yaml
127+
- Server: 2 replicas, autoscaling 2-10 at 70% CPU
128+
- External PostgreSQL recommended (with `existingSecret` support)
129+
- Ingress with TLS
130+
- Migration job runs before server starts
131+
132+
## 5. Code Changes Required
133+
134+
### `pyproject.toml`
135+
- Add `[project.scripts]` entry
136+
- Use `tomllib` (stdlib in 3.12+) for config parsing
137+
138+
### `src/primer/common/config.py`
139+
- Read `~/.primer/config.toml` as fallback
140+
- Add `data_dir` property
141+
142+
### `src/primer/server/app.py`
143+
- Check `~/.primer/frontend-dist/` as fallback for static files
144+
- Log configuration on startup
145+
146+
### `scripts/install_hook.py`
147+
- Refactor core logic into `src/primer/hook/installer.py`
148+
- Script becomes thin wrapper
149+
150+
### `Dockerfile.server`
151+
- Add `HEALTHCHECK`
152+
- Use `primer server start --fg` as entry point
153+
154+
## Implementation Sequence
155+
156+
| Phase | Work |
157+
|---|---|
158+
| **1** | `primer` CLI core — init, setup, server, hook, mcp, sync, doctor |
159+
| **2** | `install.sh` script — OS detection, pipx install, launchd/systemd |
160+
| **3** | Docker improvements — frontend Dockerfile, compose health checks, .env |
161+
| **4** | Helm chart — deployments, ingress, migration job, HPA |
162+
| **5** | Documentation — getting-started, deployment, CLI reference |
163+
164+
## Risks
165+
- PyPI publication needed for `pipx install primer`
166+
- Pre-built frontend requires CI release pipeline
167+
- Port 8000 conflicts — auto-detect free port
168+
- Python 3.12+ requirement — document pyenv/brew install
169+
- Database migrations on upgrade — `primer server start` should auto-migrate

0 commit comments

Comments
 (0)