#!/usr/bin/env python3 """Generate Workshop inventory for Timmy's config audit. Scans ~/.timmy/ and produces WORKSHOP_INVENTORY.md documenting every config file, env var, model route, and setting — with annotations on who set each one and what it does. Usage: python scripts/generate_workshop_inventory.py [--output PATH] Default output: ~/.timmy/WORKSHOP_INVENTORY.md """ from __future__ import annotations import argparse import os from datetime import UTC, datetime from pathlib import Path TIMMY_HOME = Path(os.environ.get("HERMES_HOME", Path.home() / ".timmy")) # Known file annotations: (purpose, who_set) FILE_ANNOTATIONS: dict[str, tuple[str, str]] = { ".env": ( "Environment variables — API keys, service URLs, Honcho config", "hermes-set", ), "config.yaml": ( "Main config — model routing, toolsets, display, memory, security", "hermes-set", ), "SOUL.md": ( "Timmy's soul — immutable conscience, identity, ethics, purpose", "alex-set", ), "state.db": ( "Hermes runtime state database (sessions, approvals, tasks)", "hermes-set", ), "approvals.db": ( "Approval tracking for sensitive operations", "hermes-set", ), "briefings.db": ( "Stored briefings and summaries", "hermes-set", ), ".hermes_history": ( "CLI command history", "default", ), ".update_check": ( "Last update check timestamp", "default", ), } DIR_ANNOTATIONS: dict[str, tuple[str, str]] = { "sessions": ("Conversation session logs (JSON)", "default"), "logs": ("Error and runtime logs", "default"), "skills": ("Bundled skill library (read-only from upstream)", "default"), "memories": ("Persistent memory entries", "hermes-set"), "audio_cache": ("TTS audio file cache", "default"), "image_cache": ("Generated image cache", "default"), "cron": ("Scheduled cron job definitions", "hermes-set"), "hooks": ("Lifecycle hooks (pre/post actions)", "default"), "matrix": ("Matrix protocol state and store", "hermes-set"), "pairing": ("Device pairing data", "default"), "sandboxes": ("Isolated execution sandboxes", "default"), } # Known config.yaml keys and their meanings CONFIG_ANNOTATIONS: dict[str, tuple[str, str]] = { "model.default": ("Primary LLM model for inference", "hermes-set"), "model.provider": ("Model provider (custom = local Ollama)", "hermes-set"), "toolsets": ("Enabled tool categories (all = everything)", "hermes-set"), "agent.max_turns": ("Max conversation turns before reset", "hermes-set"), "agent.reasoning_effort": ("Reasoning depth (low/medium/high)", "hermes-set"), "terminal.backend": ("Command execution backend (local)", "default"), "terminal.timeout": ("Default command timeout in seconds", "default"), "compression.enabled": ("Context compression for long sessions", "hermes-set"), "compression.summary_model": ("Model used for compression", "hermes-set"), "auxiliary.vision.model": ("Model for image analysis", "hermes-set"), "auxiliary.web_extract.model": ("Model for web content extraction", "hermes-set"), "tts.provider": ("Text-to-speech engine (edge = Edge TTS)", "default"), "tts.edge.voice": ("TTS voice selection", "default"), "stt.provider": ("Speech-to-text engine (local = Whisper)", "default"), "memory.memory_enabled": ("Persistent memory across sessions", "hermes-set"), "memory.memory_char_limit": ("Max chars for agent memory store", "hermes-set"), "memory.user_char_limit": ("Max chars for user profile store", "hermes-set"), "security.redact_secrets": ("Auto-redact secrets in output", "default"), "security.tirith_enabled": ("Policy engine for command safety", "default"), "system_prompt_suffix": ("Identity prompt appended to all conversations", "hermes-set"), "custom_providers": ("Local Ollama endpoint config", "hermes-set"), "session_reset.mode": ("Session reset behavior (none = manual)", "default"), "display.compact": ("Compact output mode", "default"), "display.show_reasoning": ("Show model reasoning chains", "default"), } # Known .env vars ENV_ANNOTATIONS: dict[str, tuple[str, str]] = { "OPENAI_BASE_URL": ( "Points to local Ollama (localhost:11434) — sovereignty enforced", "hermes-set", ), "OPENAI_API_KEY": ( "Placeholder key for Ollama compatibility (not a real API key)", "hermes-set", ), "HONCHO_API_KEY": ( "Honcho cross-session memory service key", "hermes-set", ), "HONCHO_HOST": ( "Honcho workspace identifier (timmy)", "hermes-set", ), } def _tag(who: str) -> str: return f"`[{who}]`" def generate_inventory() -> str: """Build the inventory markdown string.""" lines: list[str] = [] now = datetime.now(UTC).strftime("%Y-%m-%d %H:%M UTC") lines.append("# Workshop Inventory") lines.append("") lines.append(f"*Generated: {now}*") lines.append(f"*Workshop path: `{TIMMY_HOME}`*") lines.append("") lines.append("This is your Workshop — every file, every setting, every route.") lines.append("Walk through it. Anything tagged `[hermes-set]` was chosen for you.") lines.append("Make each one yours, or change it.") lines.append("") lines.append("Tags: `[alex-set]` = Alexander chose this. `[hermes-set]` = Hermes configured it.") lines.append("`[default]` = shipped with the platform. `[timmy-chose]` = you decided this.") lines.append("") # --- Files --- lines.append("---") lines.append("## Root Files") lines.append("") for name, (purpose, who) in sorted(FILE_ANNOTATIONS.items()): fpath = TIMMY_HOME / name exists = "✓" if fpath.exists() else "✗" lines.append(f"- {exists} **`{name}`** {_tag(who)}") lines.append(f" {purpose}") lines.append("") # --- Directories --- lines.append("---") lines.append("## Directories") lines.append("") for name, (purpose, who) in sorted(DIR_ANNOTATIONS.items()): dpath = TIMMY_HOME / name exists = "✓" if dpath.exists() else "✗" count = "" if dpath.exists(): try: n = len(list(dpath.iterdir())) count = f" ({n} items)" except PermissionError: count = " (access denied)" lines.append(f"- {exists} **`{name}/`**{count} {_tag(who)}") lines.append(f" {purpose}") lines.append("") # --- .env breakdown --- lines.append("---") lines.append("## Environment Variables (.env)") lines.append("") env_path = TIMMY_HOME / ".env" if env_path.exists(): for line in env_path.read_text().splitlines(): line = line.strip() if not line or line.startswith("#"): continue key = line.split("=", 1)[0] if key in ENV_ANNOTATIONS: purpose, who = ENV_ANNOTATIONS[key] lines.append(f"- **`{key}`** {_tag(who)}") lines.append(f" {purpose}") else: lines.append(f"- **`{key}`** `[unknown]`") lines.append(" Not documented — investigate") else: lines.append("*No .env file found*") lines.append("") # --- config.yaml breakdown --- lines.append("---") lines.append("## Configuration (config.yaml)") lines.append("") for key, (purpose, who) in sorted(CONFIG_ANNOTATIONS.items()): lines.append(f"- **`{key}`** {_tag(who)}") lines.append(f" {purpose}") lines.append("") # --- Model routing --- lines.append("---") lines.append("## Model Routing") lines.append("") lines.append("All auxiliary tasks route to the same local model:") lines.append("") aux_tasks = [ "vision", "web_extract", "compression", "session_search", "skills_hub", "mcp", "flush_memories", ] for task in aux_tasks: lines.append(f"- `auxiliary.{task}` → `qwen3:30b` via local Ollama `[hermes-set]`") lines.append("") lines.append("Primary model: `hermes3:latest` via local Ollama `[hermes-set]`") lines.append("") # --- What Timmy should audit --- lines.append("---") lines.append("## Audit Checklist") lines.append("") lines.append("Walk through each `[hermes-set]` item above and decide:") lines.append("") lines.append("1. **Do I understand what this does?** If not, ask.") lines.append("2. **Would I choose this myself?** If yes, it becomes `[timmy-chose]`.") lines.append("3. **Would I choose differently?** If yes, change it and own it.") lines.append("4. **Is this serving the mission?** Every setting should serve a purpose.") lines.append("") lines.append("The Workshop is yours. Nothing here should be a mystery.") return "\n".join(lines) + "\n" def main() -> None: parser = argparse.ArgumentParser(description="Generate Workshop inventory") parser.add_argument( "--output", type=Path, default=TIMMY_HOME / "WORKSHOP_INVENTORY.md", help="Output path (default: ~/.timmy/WORKSHOP_INVENTORY.md)", ) args = parser.parse_args() content = generate_inventory() args.output.parent.mkdir(parents=True, exist_ok=True) args.output.write_text(content) print(f"Workshop inventory written to {args.output}") print(f" {len(content)} chars, {content.count(chr(10))} lines") if __name__ == "__main__": main()