Initial commit
This commit is contained in:
342
skills/nav-start/SKILL.md
Normal file
342
skills/nav-start/SKILL.md
Normal file
@@ -0,0 +1,342 @@
|
||||
---
|
||||
name: nav-start
|
||||
description: Load Navigator documentation navigator when starting development session, resuming work, or beginning new feature. Use when user mentions starting work, beginning session, resuming after break, or checking project status.
|
||||
allowed-tools: Read, Bash
|
||||
version: 1.0.0
|
||||
---
|
||||
|
||||
# Navigator Navigator Skill
|
||||
|
||||
Load the Navigator documentation navigator to start your development session with optimized context.
|
||||
|
||||
## When to Invoke
|
||||
|
||||
Invoke this skill when the user:
|
||||
- Says "start my session", "begin work", "start working"
|
||||
- Says "load the navigator", "show me the docs"
|
||||
- Asks "what should I work on?"
|
||||
- Mentions "resume work", "continue from where I left off"
|
||||
- Asks about project structure or current tasks
|
||||
|
||||
**DO NOT invoke** if:
|
||||
- User already ran `/nav:start` command this conversation
|
||||
- Navigator already loaded (check conversation history)
|
||||
- User is in middle of implementation (only invoke at session start)
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### Step 1: Check Navigator Version
|
||||
|
||||
Check if user is running latest Navigator version:
|
||||
|
||||
```bash
|
||||
# Run version checker (optional - doesn't block session start)
|
||||
if [ -f "scripts/check-version.sh" ]; then
|
||||
bash scripts/check-version.sh
|
||||
|
||||
# Note: Exit code 1 means update available, but don't block session
|
||||
# Exit code 0 means up to date
|
||||
# Exit code 2 means cannot check (network issue)
|
||||
fi
|
||||
```
|
||||
|
||||
**Version check behavior**:
|
||||
- If update available: Show notification, continue session
|
||||
- If up to date: Show ✅, continue session
|
||||
- If cannot check: Skip silently, continue session
|
||||
|
||||
**Never block session start** due to version check.
|
||||
|
||||
### Step 2: Check Navigator Initialization
|
||||
|
||||
Check if `.agent/DEVELOPMENT-README.md` exists:
|
||||
|
||||
```bash
|
||||
if [ ! -f ".agent/DEVELOPMENT-README.md" ]; then
|
||||
echo "❌ Navigator not initialized in this project"
|
||||
echo ""
|
||||
echo "Run /nav:init to set up Navigator structure first."
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
If not found, inform user to run `/nav:init` first.
|
||||
|
||||
### Step 3: Load Documentation Navigator
|
||||
|
||||
Read the navigator file:
|
||||
|
||||
```
|
||||
Read(
|
||||
file_path: ".agent/DEVELOPMENT-README.md"
|
||||
)
|
||||
```
|
||||
|
||||
This is the lightweight index (~2k tokens) that tells you:
|
||||
- What documentation exists
|
||||
- When to load specific docs
|
||||
- Current task focus
|
||||
- Project structure overview
|
||||
|
||||
### Step 4: Check for Active Context Marker
|
||||
|
||||
Check if there's an active marker from previous `/nav:compact`:
|
||||
|
||||
```bash
|
||||
if [ -f ".agent/.context-markers/.active" ]; then
|
||||
marker_file=$(cat .agent/.context-markers/.active)
|
||||
echo "🔄 Active context marker detected!"
|
||||
echo ""
|
||||
echo "Marker: $marker_file"
|
||||
echo ""
|
||||
echo "This marker was saved during your last /nav:compact."
|
||||
echo "Load it to continue where you left off?"
|
||||
echo ""
|
||||
echo "[Y/n]:"
|
||||
fi
|
||||
```
|
||||
|
||||
If user confirms (Y or Enter):
|
||||
- Read the marker file: `Read(file_path: ".agent/.context-markers/{marker_file}")`
|
||||
- Delete `.active` file: `rm .agent/.context-markers/.active`
|
||||
- Show confirmation: "✅ Context restored from marker!"
|
||||
|
||||
If user declines (n):
|
||||
- Delete `.active` file
|
||||
- Show: "Skipping marker load. You can load it later with /nav:markers"
|
||||
|
||||
### Step 5: Load Navigator Configuration
|
||||
|
||||
Read configuration:
|
||||
|
||||
```
|
||||
Read(
|
||||
file_path: ".agent/.nav-config.json"
|
||||
)
|
||||
```
|
||||
|
||||
Parse:
|
||||
- `project_management`: Which PM tool (linear, github, jira, none)
|
||||
- `task_prefix`: Task ID format (TASK, GH, LIN, etc.)
|
||||
- `team_chat`: Team notifications (slack, discord, none)
|
||||
|
||||
### Step 6: Check PM Tool for Assigned Tasks
|
||||
|
||||
**If PM tool is Linear**:
|
||||
```bash
|
||||
# Check if Linear MCP available
|
||||
# Try to list assigned issues
|
||||
```
|
||||
|
||||
**If PM tool is GitHub**:
|
||||
```bash
|
||||
gh issue list --assignee @me --limit 10 2>/dev/null
|
||||
```
|
||||
|
||||
**If PM tool is none**:
|
||||
Skip task checking.
|
||||
|
||||
### Step 7: Display Session Statistics (OpenTelemetry)
|
||||
|
||||
Run the OpenTelemetry session statistics script:
|
||||
|
||||
```bash
|
||||
# Get the skill's base directory (passed via SKILL_BASE_DIR)
|
||||
SKILL_DIR="${SKILL_BASE_DIR:-$HOME/.claude/plugins/marketplaces/jitd-marketplace/skills/nav-start}"
|
||||
python3 "$SKILL_DIR/scripts/otel_session_stats.py"
|
||||
```
|
||||
|
||||
This script:
|
||||
- **If OTel enabled**: Shows real-time metrics from Claude Code
|
||||
- Real token usage (input/output/cache)
|
||||
- Cache hit rate (CLAUDE.md caching performance)
|
||||
- Session cost (actual USD spent)
|
||||
- Active time (seconds of work)
|
||||
- Context availability
|
||||
- **If OTel disabled**: Shows setup instructions
|
||||
- **If no metrics yet**: Shows "waiting for export" message
|
||||
|
||||
**Benefits of OTel integration**:
|
||||
- Real data (not file-size estimates)
|
||||
- Cache performance validation
|
||||
- Cost tracking for ROI measurement
|
||||
- Official API (won't break on updates)
|
||||
|
||||
### Step 8: Display Session Summary
|
||||
|
||||
Show this formatted summary:
|
||||
|
||||
```
|
||||
╔══════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ 🚀 Navigator Session Started ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════╝
|
||||
|
||||
📖 Documentation Navigator: Loaded
|
||||
🎯 Project Management: [PM tool or "Manual"]
|
||||
✅ Token Optimization: Active
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
📊 DOCUMENTATION LOADED (MEASURED)
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Navigator (.agent/DEVELOPMENT-README.md):
|
||||
Size: [nav_bytes] bytes = [nav_tokens] tokens
|
||||
|
||||
CLAUDE.md (auto-loaded):
|
||||
Size: [claude_bytes] bytes = [claude_tokens] tokens
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Total documentation: [total_tokens] tokens
|
||||
Available for work: [available] tokens ([percent]%)
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
💡 On-demand loading strategy:
|
||||
Load task doc when needed: +3-5k tokens
|
||||
Load system doc if needed: +4-6k tokens
|
||||
Load SOP if helpful: +2-3k tokens
|
||||
|
||||
Total with all docs: ~[total + 15]k tokens
|
||||
|
||||
vs Traditional (all upfront): ~150k tokens
|
||||
Savings: ~[150 - total - 15]k tokens
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
🔹 Navigator WORKFLOW REMINDER
|
||||
|
||||
1. Navigator-first loading
|
||||
- ✅ Loaded: .agent/DEVELOPMENT-README.md
|
||||
- Next: Load ONLY relevant task/system docs
|
||||
|
||||
2. Use agents for research
|
||||
- Multi-file searches: Use Task agent (saves 60-80% tokens)
|
||||
- Code exploration: Use Explore agent
|
||||
- NOT manual Read of many files
|
||||
|
||||
3. Task documentation
|
||||
- After features: Use nav-task-manager skill
|
||||
- After bugs: Use nav-sop-creator skill
|
||||
|
||||
4. Context management
|
||||
- Run nav-compact skill after isolated sub-tasks
|
||||
- Context markers save your progress
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
[MULTI-CLAUDE WORKFLOWS CHECK - v4.3.0+]
|
||||
|
||||
Check if multi-Claude workflows installed:
|
||||
```bash
|
||||
if ! command -v navigator-multi-claude.sh &> /dev/null; then
|
||||
echo ""
|
||||
echo "⚡ Multi-Claude Workflows Available (v4.3.0+)"
|
||||
echo ""
|
||||
echo " Enable parallel AI execution for complex tasks."
|
||||
echo " Status: Not installed"
|
||||
echo ""
|
||||
echo " Install: 'Install multi-Claude workflows'"
|
||||
echo " Learn more: See RELEASE-NOTES-v4.3.0.md"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
fi
|
||||
```
|
||||
|
||||
Only show this prompt:
|
||||
- If plugin version >= 4.3.0
|
||||
- If scripts not installed
|
||||
- Once per session (set flag in memory)
|
||||
|
||||
Do NOT show if:
|
||||
- Scripts already installed
|
||||
- Plugin version < 4.3.0
|
||||
- User explicitly dismissed before
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
[If tasks found from PM tool, list them here]
|
||||
|
||||
[If no tasks found:]
|
||||
No active tasks found. What would you like to work on?
|
||||
```
|
||||
|
||||
## Predefined Functions
|
||||
|
||||
### scripts/otel_session_stats.py
|
||||
|
||||
**Purpose**: Display real-time session statistics via OpenTelemetry
|
||||
|
||||
**When to call**: After loading navigator, before presenting session summary
|
||||
|
||||
**Requirements**:
|
||||
- CLAUDE_CODE_ENABLE_TELEMETRY=1 (optional - shows setup if disabled)
|
||||
- Metrics available from current session (shows waiting message if not)
|
||||
|
||||
**Execution**:
|
||||
```bash
|
||||
SKILL_DIR="${SKILL_BASE_DIR:-$HOME/.claude/plugins/marketplaces/jitd-marketplace/skills/nav-start}"
|
||||
python3 "$SKILL_DIR/scripts/otel_session_stats.py"
|
||||
```
|
||||
|
||||
**Output**: Formatted statistics with:
|
||||
- Token usage breakdown (input/output/cache)
|
||||
- Cache hit rate percentage
|
||||
- Session cost in USD
|
||||
- Active time
|
||||
- Context availability
|
||||
|
||||
**Error Handling**:
|
||||
- If OTel not enabled: Shows setup instructions
|
||||
- If no metrics yet: Shows "waiting for export" message
|
||||
- Never crashes - always displays helpful guidance
|
||||
|
||||
## Reference Files
|
||||
|
||||
This skill uses:
|
||||
- **otel_session_stats.py**: Real-time session stats via OpenTelemetry
|
||||
- **.agent/DEVELOPMENT-README.md**: Navigator content
|
||||
- **.agent/.nav-config.json**: Configuration
|
||||
- **.agent/.context-markers/.active**: Active marker check
|
||||
|
||||
## Error Handling
|
||||
|
||||
**Navigator not found**:
|
||||
```
|
||||
❌ Navigator not initialized
|
||||
|
||||
Run /nav:init to create .agent/ structure first.
|
||||
```
|
||||
|
||||
**PM tool configured but not working**:
|
||||
```
|
||||
⚠️ [PM Tool] configured but not accessible
|
||||
|
||||
Check authentication or run setup guide.
|
||||
```
|
||||
|
||||
**Config file malformed**:
|
||||
```
|
||||
⚠️ .agent/.nav-config.json is invalid JSON
|
||||
|
||||
Fix syntax or run /nav:init to regenerate.
|
||||
```
|
||||
|
||||
## Success Criteria
|
||||
|
||||
Session start is successful when:
|
||||
- [ ] Navigator loaded successfully
|
||||
- [ ] Token usage calculated and displayed
|
||||
- [ ] PM tool status checked (if configured)
|
||||
- [ ] User knows what to work on next
|
||||
- [ ] Navigator workflow context set
|
||||
|
||||
## Notes
|
||||
|
||||
This skill provides the same functionality as `/nav:start` command but with:
|
||||
- Natural language invocation (no need to remember `/` syntax)
|
||||
- Auto-detection based on user intent
|
||||
- Composable with other Navigator skills
|
||||
|
||||
If user prefers manual invocation, they can still use `/nav:start` command (both work in hybrid mode).
|
||||
447
skills/nav-start/scripts/otel_session_stats.py
Executable file
447
skills/nav-start/scripts/otel_session_stats.py
Executable file
@@ -0,0 +1,447 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Navigator Session Statistics (OpenTelemetry-powered)
|
||||
|
||||
Queries real token usage from Claude Code OpenTelemetry metrics.
|
||||
Requires CLAUDE_CODE_ENABLE_TELEMETRY=1
|
||||
|
||||
Usage:
|
||||
python3 otel_session_stats.py
|
||||
|
||||
Environment Variables Required:
|
||||
CLAUDE_CODE_ENABLE_TELEMETRY=1
|
||||
OTEL_METRICS_EXPORTER=console (or otlp)
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
from typing import Dict, Optional
|
||||
|
||||
|
||||
def check_otel_enabled() -> bool:
|
||||
"""Check if Claude Code telemetry is enabled."""
|
||||
return os.getenv("CLAUDE_CODE_ENABLE_TELEMETRY") == "1"
|
||||
|
||||
|
||||
def get_otel_metrics() -> Optional[Dict]:
|
||||
"""
|
||||
Get OpenTelemetry metrics from Claude Code.
|
||||
|
||||
Strategy: Read from OpenTelemetry SDK's metric reader if available.
|
||||
|
||||
Returns:
|
||||
Dict with raw metrics data or None if unavailable
|
||||
"""
|
||||
# Try to access metrics from OpenTelemetry SDK
|
||||
try:
|
||||
from opentelemetry import metrics as otel_metrics
|
||||
|
||||
# Get the global meter provider
|
||||
meter_provider = otel_metrics.get_meter_provider()
|
||||
|
||||
# Check if metrics are available
|
||||
if hasattr(meter_provider, '_sdk_config'):
|
||||
# This would contain the metrics if SDK is properly configured
|
||||
# For now, we don't have direct access to metric values
|
||||
# They're exported to console/OTLP but not easily queryable
|
||||
pass
|
||||
|
||||
except ImportError:
|
||||
# OpenTelemetry SDK not installed - expected in most cases
|
||||
pass
|
||||
|
||||
# Alternative: Check if Prometheus exporter is running
|
||||
exporter_type = os.getenv("OTEL_METRICS_EXPORTER", "")
|
||||
|
||||
if exporter_type == "prometheus":
|
||||
# Try to query Prometheus endpoint
|
||||
try:
|
||||
import urllib.request
|
||||
response = urllib.request.urlopen("http://localhost:9464/metrics", timeout=1)
|
||||
prometheus_data = response.read().decode('utf-8')
|
||||
return {"source": "prometheus", "data": prometheus_data}
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# For console exporter, metrics go to stderr and aren't easily captured
|
||||
# In a real implementation, we'd need to:
|
||||
# 1. Store metrics in a shared location
|
||||
# 2. Use a metrics backend (Prometheus/OTLP collector)
|
||||
# 3. Query from Claude Code's internal metrics store
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def parse_prometheus_metrics(prometheus_data: str) -> Optional[Dict]:
|
||||
"""
|
||||
Parse Prometheus format metrics from Claude Code.
|
||||
|
||||
Args:
|
||||
prometheus_data: Raw Prometheus metrics text
|
||||
|
||||
Returns:
|
||||
Parsed metrics dictionary or None
|
||||
"""
|
||||
# First, find the most recent session_id
|
||||
current_session_id = None
|
||||
session_count_max = 0
|
||||
|
||||
for line in prometheus_data.split('\n'):
|
||||
# Skip comments
|
||||
if line.startswith('#'):
|
||||
continue
|
||||
|
||||
if 'claude_code_session_count_total' in line:
|
||||
parts = line.split()
|
||||
if len(parts) >= 2:
|
||||
try:
|
||||
count = float(parts[-1])
|
||||
if count >= session_count_max:
|
||||
session_count_max = count
|
||||
# Extract session_id from labels
|
||||
if 'session_id="' in line:
|
||||
session_start = line.find('session_id="') + 12
|
||||
session_end = line.find('"', session_start)
|
||||
if session_end > session_start:
|
||||
current_session_id = line[session_start:session_end]
|
||||
except ValueError:
|
||||
# Skip lines that don't have numeric values
|
||||
continue
|
||||
|
||||
if not current_session_id:
|
||||
# Fallback: use any session if we can't determine current
|
||||
pass
|
||||
|
||||
metrics = {
|
||||
"input_tokens": 0,
|
||||
"output_tokens": 0,
|
||||
"cache_read_tokens": 0,
|
||||
"cache_creation_tokens": 0,
|
||||
"cost_usd": 0.0,
|
||||
"active_time_seconds": 0,
|
||||
"model": "unknown",
|
||||
"session_id": current_session_id or "unknown"
|
||||
}
|
||||
|
||||
try:
|
||||
for line in prometheus_data.split('\n'):
|
||||
# Skip comments and empty lines
|
||||
if line.startswith('#') or not line.strip():
|
||||
continue
|
||||
|
||||
# Filter by current session_id for accurate stats
|
||||
if current_session_id and f'session_id="{current_session_id}"' not in line:
|
||||
continue
|
||||
|
||||
# Parse token usage metrics
|
||||
if 'claude_code_token_usage' in line and not line.startswith('#'):
|
||||
parts = line.split()
|
||||
if len(parts) >= 2:
|
||||
value = float(parts[-1])
|
||||
|
||||
if 'type="input"' in line:
|
||||
metrics["input_tokens"] += int(value)
|
||||
elif 'type="output"' in line:
|
||||
metrics["output_tokens"] += int(value)
|
||||
elif 'type="cacheRead"' in line:
|
||||
metrics["cache_read_tokens"] += int(value)
|
||||
elif 'type="cacheCreation"' in line:
|
||||
metrics["cache_creation_tokens"] += int(value)
|
||||
|
||||
# Extract model
|
||||
if 'model="' in line:
|
||||
model_start = line.find('model="') + 7
|
||||
model_end = line.find('"', model_start)
|
||||
if model_end > model_start:
|
||||
metrics["model"] = line[model_start:model_end]
|
||||
|
||||
# Parse cost metrics
|
||||
elif 'claude_code_cost_usage' in line:
|
||||
parts = line.split()
|
||||
if len(parts) >= 2:
|
||||
metrics["cost_usd"] += float(parts[-1])
|
||||
|
||||
# Parse active time
|
||||
elif 'claude_code_active_time_total' in line:
|
||||
parts = line.split()
|
||||
if len(parts) >= 2:
|
||||
metrics["active_time_seconds"] = int(float(parts[-1]))
|
||||
|
||||
# Return metrics only if we have actual data
|
||||
if metrics["input_tokens"] > 0 or metrics["output_tokens"] > 0:
|
||||
return metrics
|
||||
|
||||
# If current session has no data, try without session filter (most recent data)
|
||||
if current_session_id:
|
||||
# Retry without session filter
|
||||
metrics_fallback = {
|
||||
"input_tokens": 0,
|
||||
"output_tokens": 0,
|
||||
"cache_read_tokens": 0,
|
||||
"cache_creation_tokens": 0,
|
||||
"cost_usd": 0.0,
|
||||
"active_time_seconds": 0,
|
||||
"model": "unknown",
|
||||
"session_id": None # No specific session (aggregate)
|
||||
}
|
||||
|
||||
for line in prometheus_data.split('\n'):
|
||||
if line.startswith('#') or not line.strip():
|
||||
continue
|
||||
|
||||
# No session filtering - aggregate all
|
||||
if 'claude_code_token_usage' in line:
|
||||
parts = line.split()
|
||||
if len(parts) >= 2:
|
||||
value = float(parts[-1])
|
||||
if 'type="input"' in line:
|
||||
metrics_fallback["input_tokens"] += int(value)
|
||||
elif 'type="output"' in line:
|
||||
metrics_fallback["output_tokens"] += int(value)
|
||||
elif 'type="cacheRead"' in line:
|
||||
metrics_fallback["cache_read_tokens"] += int(value)
|
||||
elif 'type="cacheCreation"' in line:
|
||||
metrics_fallback["cache_creation_tokens"] += int(value)
|
||||
|
||||
if 'model="' in line:
|
||||
model_start = line.find('model="') + 7
|
||||
model_end = line.find('"', model_start)
|
||||
if model_end > model_start:
|
||||
metrics_fallback["model"] = line[model_start:model_end]
|
||||
|
||||
elif 'claude_code_cost_usage' in line:
|
||||
parts = line.split()
|
||||
if len(parts) >= 2:
|
||||
metrics_fallback["cost_usd"] += float(parts[-1])
|
||||
|
||||
elif 'claude_code_active_time_total' in line:
|
||||
parts = line.split()
|
||||
if len(parts) >= 2:
|
||||
metrics_fallback["active_time_seconds"] = int(float(parts[-1]))
|
||||
|
||||
if metrics_fallback["input_tokens"] > 0 or metrics_fallback["output_tokens"] > 0:
|
||||
return metrics_fallback
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error parsing Prometheus metrics: {e}", file=sys.stderr)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def query_session_metrics() -> Optional[Dict]:
|
||||
"""
|
||||
Query current session metrics from OpenTelemetry.
|
||||
|
||||
Returns:
|
||||
{
|
||||
"input_tokens": int,
|
||||
"output_tokens": int,
|
||||
"cache_read_tokens": int,
|
||||
"cache_creation_tokens": int,
|
||||
"cost_usd": float,
|
||||
"active_time_seconds": int,
|
||||
"model": str
|
||||
}
|
||||
or None if metrics unavailable
|
||||
"""
|
||||
metrics_data = get_otel_metrics()
|
||||
|
||||
if not metrics_data:
|
||||
return None
|
||||
|
||||
# Parse based on source
|
||||
if metrics_data.get("source") == "prometheus":
|
||||
return parse_prometheus_metrics(metrics_data.get("data", ""))
|
||||
|
||||
# For console exporter, we'd need to implement JSON parsing
|
||||
# This is more complex as it requires capturing stderr output
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def display_setup_instructions():
|
||||
"""Display setup instructions when OTel is not configured."""
|
||||
print("⚠️ OpenTelemetry Not Enabled")
|
||||
print()
|
||||
print("Navigator can show real-time session statistics with OpenTelemetry.")
|
||||
print()
|
||||
print("Quick Setup:")
|
||||
print("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
||||
print()
|
||||
print(" # Add to ~/.zshrc or ~/.bashrc:")
|
||||
print(" export CLAUDE_CODE_ENABLE_TELEMETRY=1")
|
||||
print(" export OTEL_METRICS_EXPORTER=console")
|
||||
print()
|
||||
print(" # Then restart your shell:")
|
||||
print(" source ~/.zshrc")
|
||||
print()
|
||||
print("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
||||
print()
|
||||
print("What you'll get:")
|
||||
print(" • Real token usage (not estimates)")
|
||||
print(" • Cache hit rates (CLAUDE.md caching performance)")
|
||||
print(" • Session costs (actual USD spent)")
|
||||
print(" • Active time tracking")
|
||||
print()
|
||||
print("For detailed setup: .agent/sops/integrations/opentelemetry-setup.md")
|
||||
print()
|
||||
|
||||
|
||||
def display_no_metrics_message():
|
||||
"""Display message when OTel is enabled but no metrics available yet."""
|
||||
exporter = os.getenv("OTEL_METRICS_EXPORTER", "console")
|
||||
|
||||
print("📊 OpenTelemetry Enabled")
|
||||
print()
|
||||
|
||||
if exporter == "console":
|
||||
print("⚠️ Console exporter detected")
|
||||
print()
|
||||
print("Console exporter writes metrics to stderr (not queryable by this script).")
|
||||
print()
|
||||
print("To see formatted metrics, switch to Prometheus exporter:")
|
||||
print()
|
||||
print(" 1. Update ~/.zshrc:")
|
||||
print(" export OTEL_METRICS_EXPORTER=prometheus")
|
||||
print()
|
||||
print(" 2. Restart terminal:")
|
||||
print(" exec zsh")
|
||||
print()
|
||||
print(" 3. Start Claude Code:")
|
||||
print(" claude")
|
||||
print()
|
||||
print(" 4. Run this script again:")
|
||||
print(" python3 scripts/otel_session_stats.py")
|
||||
print()
|
||||
print("Prometheus metrics will be available at: http://localhost:9464/metrics")
|
||||
else:
|
||||
print(f"Exporter: {exporter}")
|
||||
print()
|
||||
print("Metrics export every 60 seconds by default.")
|
||||
print("Continue working - stats will appear after first export.")
|
||||
print()
|
||||
print("For faster metrics (development):")
|
||||
print(" export OTEL_METRIC_EXPORT_INTERVAL=10000 # 10 seconds")
|
||||
print()
|
||||
|
||||
|
||||
def display_navigator_stats(metrics: Dict):
|
||||
"""
|
||||
Display Navigator-optimized session statistics.
|
||||
|
||||
Args:
|
||||
metrics: Dictionary with session metrics from OTel
|
||||
"""
|
||||
print("📊 Navigator Session Statistics (Real-time via OTel)")
|
||||
print("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
||||
print()
|
||||
if metrics.get("session_id"):
|
||||
print(f"Session: {metrics['session_id'][:8]}...")
|
||||
else:
|
||||
print("⚠️ Showing cumulative stats across all recent sessions")
|
||||
print()
|
||||
|
||||
# Token usage breakdown
|
||||
input_tokens = metrics["input_tokens"]
|
||||
output_tokens = metrics["output_tokens"]
|
||||
cache_read = metrics["cache_read_tokens"]
|
||||
cache_creation = metrics["cache_creation_tokens"]
|
||||
|
||||
# Calculate totals
|
||||
total_tokens = input_tokens + output_tokens + cache_read + cache_creation
|
||||
charged_tokens = input_tokens + output_tokens
|
||||
|
||||
# Visual token distribution bar
|
||||
bar_width = 50
|
||||
if total_tokens > 0:
|
||||
input_bars = int((input_tokens / total_tokens) * bar_width)
|
||||
output_bars = int((output_tokens / total_tokens) * bar_width)
|
||||
cache_read_bars = int((cache_read / total_tokens) * bar_width)
|
||||
cache_creation_bars = bar_width - input_bars - output_bars - cache_read_bars
|
||||
|
||||
print("Token Distribution:")
|
||||
print("┌" + "─" * bar_width + "┐")
|
||||
bar_content = ("🟦" * input_bars +
|
||||
"🟩" * output_bars +
|
||||
"🟨" * cache_read_bars +
|
||||
"🟧" * cache_creation_bars)
|
||||
print(f"│{bar_content}│")
|
||||
print("└" + "─" * bar_width + "┘")
|
||||
print(" 🟦 Input 🟩 Output 🟨 Cache Read (free) 🟧 Cache Creation")
|
||||
print()
|
||||
|
||||
print(f"📥 Input: {input_tokens:,}")
|
||||
print(f"📤 Output: {output_tokens:,}")
|
||||
print(f"💾 Cache Read: {cache_read:,} (free)")
|
||||
print(f"🔧 Cache Creation: {cache_creation:,}")
|
||||
print()
|
||||
|
||||
print(f"📊 Total Tokens: {total_tokens:,}")
|
||||
print(f" ├─ Charged: {charged_tokens:,}")
|
||||
print(f" └─ Free (cache): {cache_read:,}")
|
||||
print()
|
||||
|
||||
# Cache efficiency (if cache was used)
|
||||
if cache_read > 0:
|
||||
cache_percentage = (cache_read / total_tokens) * 100
|
||||
print(f"⚡ Cache Efficiency: {cache_percentage:.1f}% of total tokens")
|
||||
print()
|
||||
|
||||
# Cost and efficiency analysis
|
||||
active_seconds = metrics['active_time_seconds']
|
||||
minutes = active_seconds // 60
|
||||
seconds = active_seconds % 60
|
||||
|
||||
print(f"💰 Session Cost: ${metrics['cost_usd']:.4f}")
|
||||
print(f"⏱️ Active Time: {minutes}m {seconds}s")
|
||||
|
||||
# Calculate efficiency metrics
|
||||
if active_seconds > 0:
|
||||
cost_per_min = (metrics['cost_usd'] / active_seconds) * 60
|
||||
tokens_per_min = (total_tokens / active_seconds) * 60
|
||||
print(f"📈 Cost Rate: ${cost_per_min:.4f}/min")
|
||||
print(f"⚡ Token Rate: {int(tokens_per_min):,} tokens/min")
|
||||
print()
|
||||
|
||||
# Context availability (only charged tokens count toward window)
|
||||
context_used = charged_tokens
|
||||
total_context = 200000
|
||||
available = total_context - context_used
|
||||
percent_available = int((available / total_context) * 100)
|
||||
|
||||
print(f"📦 Context Window:")
|
||||
print(f" ├─ Used: {context_used:,} tokens")
|
||||
print(f" └─ Available: {available:,} tokens ({percent_available}%)")
|
||||
print()
|
||||
|
||||
print(f"🤖 Model: {metrics['model']}")
|
||||
print()
|
||||
print("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
||||
print()
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point for session statistics."""
|
||||
|
||||
# Check if OTel is enabled
|
||||
if not check_otel_enabled():
|
||||
display_setup_instructions()
|
||||
return 0
|
||||
|
||||
# Try to query metrics
|
||||
metrics = query_session_metrics()
|
||||
|
||||
if not metrics:
|
||||
# OTel enabled but no metrics exported yet
|
||||
display_no_metrics_message()
|
||||
return 0
|
||||
|
||||
# Display real statistics
|
||||
display_navigator_stats(metrics)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user