This commit was merged in pull request #348.
This commit is contained in:
254
scripts/generate_workshop_inventory.py
Normal file
254
scripts/generate_workshop_inventory.py
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
#!/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()
|
||||||
Reference in New Issue
Block a user