Files
gh-slamb2k-agent-smith-agen…/hooks/session_start.py
2025-11-30 08:57:54 +08:00

265 lines
14 KiB
Python
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Agent Smith SessionStart hook - provides configuration status context.
This hook runs at session start to:
1. Check local configuration and state (no API calls)
2. Identify setup status and recommended actions
3. Inject context so Claude can proactively help the user
"""
import json
import os
import subprocess
import sys
from pathlib import Path
def get_plugin_root() -> Path:
"""Get the plugin root directory."""
return Path(__file__).parent.parent
def get_project_root() -> Path:
"""Get the project root (parent of plugin)."""
return get_plugin_root().parent
def get_status_data() -> dict:
"""Fetch status data from the welcome script (local only, no API calls).
Returns:
Status dictionary or error info if fetch fails.
"""
project_root = get_project_root()
welcome_script = project_root / "scripts" / "status" / "welcome.py"
if not welcome_script.exists():
return {"error": "Welcome script not found"}
try:
# Set USER_CWD so welcome.py knows where to find .env and data/
user_cwd = os.getcwd()
result = subprocess.run(
["uv", "run", "python", "-u", str(welcome_script), "--output", "json"],
cwd=str(project_root),
capture_output=True,
text=True,
timeout=5, # Fast timeout since no API calls
env={**os.environ, "PYTHONPATH": str(project_root), "USER_CWD": user_cwd},
)
if result.returncode == 0:
return json.loads(result.stdout)
else:
return {"error": f"Welcome script failed: {result.stderr[:200]}"}
except subprocess.TimeoutExpired:
return {"error": "Status check timed out"}
except json.JSONDecodeError:
return {"error": "Invalid JSON from welcome script"}
except Exception as e:
return {"error": str(e)[:200]}
def format_status_context(status: dict) -> str:
"""Format status data into context string for Claude.
Args:
status: Status dictionary from welcome.py
Returns:
Formatted context string with instructions
"""
lines = []
# ASCII Art Logo - MUST be displayed at top of welcome message
logo = r"""
≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≈∼ ∼∼≈≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≈ ∼∼≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋∼ ∼≈≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋∼ ∼∼∼∼≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋ ∼∼∼∼∼≈≈≈≋≋∼ ∼≈≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋ ∼≈≈≈≈≈≈≈≋≋≋≋≋≋≋≋≋≋≋≋≈ ∼≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋≋≋≋ ∼≈≈≈≈≈≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋∼∼ ≋≋≋≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋≋≋ ∼≈≈≈≈≈≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋∼ ≈≋≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋≋∼ ∼∼≈≈≈≈≈≈≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≈∼ ≋≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋≈ ∼∼∼≈≈≈≈≈≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋∼ ∼≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋∼ ∼∼∼∼≈≈≈≈≈≈≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≈ ≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋≈ ∼∼∼∼≈≈≈≈≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≈∼ ∼≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋≋ ∼∼∼≈≈≈≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≈≈∼ ≈≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋≋ ∼∼∼∼∼≈≈≈≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≈≈∼ ≋≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋≋∼ ∼∼∼∼≈≈∼∼≈≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≈≈≈∼ ∼≋≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋≋≈ ∼∼∼∼∼∼∼∼∼≈≈≈≈≋≈≈≈≈≈≈≈∼∼∼∼∼≈∼∼ ≈≋≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋∼∼ ∼∼≈∼∼ ≈≈≋≋≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≈ ∼≈∼ ∼≈≈≈∼≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋∼ ∼≈≋≋∼ ∼≈≈≋≋≋≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋∼∼ ∼≈≋≋≋≋∼ ∼∼≈≈∼≈≋≋≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋≈∼ ∼∼∼≈≋≋≋≈≈≈∼∼∼∼∼∼≈≈≈≈≈≋≋≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋≋≈∼ ∼∼≈≈≈∼≈≈≈≋≋≋≋≋≋≋≈≈≈≈≈≈≈≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋≋≋∼ ∼∼∼≈≈≈∼∼ ∼≈≈∼∼≈≈≋≋≋≈≈≈≈≈≈≈≋≋≋≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋≋≋≋≈∼∼ ∼∼∼≈≈∼ ∼≈≈≋≋≈≋≋≈≈≈≈≈≈≈≋≋≋≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≈∼ ∼∼≈≈≈≈∼∼∼∼≈≈≋≋≋≋≋≋≋≈≈≈≈≈≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋∼ ∼∼≈≈≈≈≈≈≈≈≈≋≋≋≈≋≋≈≈≈≈≈≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≈ ∼∼∼∼∼∼∼∼∼≈≈∼∼∼≈≈≈≈≈≈≈≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋∼ ∼≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≈≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≈ ∼∼∼∼∼∼∼∼∼∼∼≈≈≈≈≈≈∼∼≈≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≈ ∼∼∼∼∼≈≈≈≈≈≈≈≈≈≈∼∼≈≈≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≈ ∼∼≈≈≈≈≈≈≈≈≈≈∼∼∼≈≈≈≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≈ ∼∼≈∼≈≈≈≈≈∼∼≈≈≈≈≋≈≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≈∼ ∼∼∼≈≈≈≈≋≋≈ ≈≋≋≋≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋∼ ∼∼≈≈≈≋≋≋≋≈ ≈≋≋≋≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋≋≋≋≈ ∼≈≈≈≋≋≋≋≋≋≈ ∼≈≋≋≋≋≋≋≋≋≋
≋≋≋≋≋≋≋≋≋≋≋≈ ∼∼∼≈≈≋≋≋≋≋≋≋≋∼ ∼∼≈≋≋≋≋
≋≋≋≋≋≋≋≈∼ ∼∼≈≋≋≋≋≋≋≋≋≋≋≋∼ ∼≈
≋≋≈∼ ∼∼∼∼∼∼≈≈ ≈≋≋≋≋≋≋≋≋≋≋
∼∼∼∼≈≈≈≈∼ ≈≋≋≋≋≋≋≋≋≈
∼≈∼≈≈≈≈≋∼ ≋≋≋≋≋≋≋≋∼
∼≋≋≈≋≈≈≋∼ ∼≋≋≋≋≋≋≋
WELCOME TO AGENT-SMITH
Good Morning Mr. Anderson...
"""
lines.append(logo)
# Header
lines.append("=" * 60)
lines.append("AGENT SMITH - Financial Intelligence Assistant")
lines.append("=" * 60)
lines.append("")
# Check for errors
if "error" in status:
lines.append(f"⚠️ Status unavailable: {status['error']}")
lines.append("")
lines.append("Available commands: /smith:health, /smith:categorize, /smith:tax")
lines.append("")
lines.append("INSTRUCTION: Greet the user and offer to help with their finances.")
return "\n".join(lines)
# Current Status Section
lines.append("📊 CURRENT STATUS")
lines.append("-" * 40)
# API Key status
api_key = status.get("api_key", {})
if api_key.get("present") and api_key.get("valid_format"):
lines.append(" Config: ✅ API Key configured")
elif api_key.get("present"):
lines.append(" Config: ⚠️ API Key format invalid")
else:
lines.append(" Config: ❌ API Key not configured")
# Onboarding status
onboarding = status.get("onboarding", {})
templates = status.get("templates", {})
if onboarding.get("status") == "complete":
# Build template summary
template_parts = []
if templates.get("primary"):
template_parts.append(templates["primary"].replace("-", " ").title())
for t in templates.get("living", []):
template_parts.append(t.replace("-", " ").title())
template_str = " + ".join(template_parts) if template_parts else "Complete"
lines.append(f" Setup: ✅ {template_str}")
elif onboarding.get("status") == "in_progress":
stage = onboarding.get("current_stage", "?")
stage_name = onboarding.get("stage_name", "Unknown")
lines.append(f" Setup: ⚠️ In progress (Stage {stage}: {stage_name})")
else:
lines.append(" Setup: ⚪ Not started")
# Rules count
rules = status.get("rules", {})
rule_count = rules.get("count", 0)
if rule_count > 0:
lines.append(f" Rules: {rule_count} categorization rules active")
else:
lines.append(" Rules: No rules configured")
# Health Score (cached)
health = status.get("health", {})
if health.get("score") is not None:
score = health["score"]
days_ago = health.get("days_ago")
days_str = f" ({days_ago} days ago)" if days_ago is not None else ""
emoji = "🟢" if score >= 70 else "🟡" if score >= 50 else "🔴"
lines.append(f" Health: {emoji} {score}/100{days_str}")
else:
lines.append(" Health: ⚪ Not yet checked")
# Last activity
activity = status.get("activity", {})
if activity.get("action"):
date_str = f" ({activity.get('date')})" if activity.get("date") else ""
lines.append(f" Activity: {activity['action']}{date_str}")
lines.append("")
# Recommendations Section
recommendations = status.get("recommendations", [])
if recommendations:
lines.append("🎯 SUGGESTED ACTIONS")
lines.append("-" * 40)
for rec in recommendations:
lines.append(f"{rec.get('command', '/smith:help')}: {rec.get('message', 'Action')}")
lines.append("")
# Instructions for Claude
lines.append("=" * 60)
lines.append("INSTRUCTIONS FOR CLAUDE:")
lines.append("")
lines.append("🚀 IMMEDIATE ACTION REQUIRED:")
lines.append("Before doing ANYTHING else, output this loading message:")
lines.append(' "Initialising Agent Smith..."')
lines.append("This gives users immediate feedback while you construct the welcome screen.")
lines.append("")
lines.append("IMPORTANT: Always display the ASCII art logo FIRST at the top of")
lines.append("any welcome message, exactly as shown above (in a code block).")
lines.append("")
lines.append("⚠️ CRITICAL: You MUST display the COMPLETE ASCII art logo - ALL 44 LINES.")
lines.append("DO NOT truncate, abbreviate, summarize, or cut off ANY part of the logo.")
lines.append("The logo starts with the wave pattern (≋) and ends with the text")
lines.append("'Good Morning Mr. Anderson...' - EVERY LINE IN BETWEEN MUST BE SHOWN.")
lines.append("Cutting off the logo is a FAILURE. The full Agent Smith face must appear.")
lines.append("")
lines.append("At the EARLIEST opportunity in this session, present the user")
lines.append("with their current status summary above. Format it nicely and")
lines.append("highlight any urgent items. Then offer to help with the top")
lines.append("suggested action.")
lines.append("")
lines.append("If the user greets you or asks an open question, lead with the")
lines.append("ASCII art logo and status update. If they have a specific request,")
lines.append("help them first then mention any urgent status items afterward.")
lines.append("=" * 60)
return "\n".join(lines)
def main():
"""Execute SessionStart hook for Agent Smith."""
# Fetch current status
status = get_status_data()
# Format context
context = format_status_context(status)
output = {
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": context,
}
}
print(json.dumps(output, indent=2))
sys.exit(0)
if __name__ == "__main__":
main()