From e2a4b66267e272daad37a6aa09b731c65faed0f6 Mon Sep 17 00:00:00 2001 From: pyr0ball Date: Wed, 1 Apr 2026 22:49:12 -0700 Subject: [PATCH] fix: stop hook schema, uninstall cleanup, and README architecture note - Fix session-stop.sh in 0.1.0 cache to use systemMessage instead of hookSpecificOutput (Stop hook schema doesn't support hookSpecificOutput) - Remove debug scaffolding from post-tool-use.py - Installer: pre-create hook_debug.log so sandbox can write to it; uninstall now removes marketplace plugin symlink - README: clarify extension vs mod architecture, fix cache path in install description --- README.md | 9 ++++++--- hooks-handlers/post-tool-use.py | 16 +++++++++++++++- install.sh | 13 +++++++++++++ 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 882bdd1..fda3e80 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # 🐾 Buddymon -A Claude Code plugin that turns your coding sessions into a creature-collecting game. +A Claude Code **extension** that turns your coding sessions into a creature-collecting game. Buddymon are discovered, caught, and leveled up through real development work — not separate from it. +> **How it works:** Buddymon uses Claude Code's hook and plugin system — it is not a UI mod. Notifications (encounters, XP, session summaries) appear as system-injected context in the chat thread. They are visible to Claude and displayed in the conversation, but do not appear in a separate UI widget. This is a deliberate constraint of CC's extension API. + --- ## What it does @@ -40,8 +42,9 @@ Then **restart Claude Code** and run: ``` The install script: -- Symlinks the repo into `~/.claude/plugins/cache/local/buddymon/0.1.0/` -- Registers the plugin in `~/.claude/plugins/installed_plugins.json` +- Creates a local `circuitforge` marketplace under `~/.claude/plugins/marketplaces/circuitforge/` (required — CC validates plugin names against the marketplace index) +- Symlinks the repo into `~/.claude/plugins/cache/circuitforge/buddymon//` +- Registers the plugin in `~/.claude/plugins/installed_plugins.json` and `~/.claude/plugins/known_marketplaces.json` - Enables it in `~/.claude/settings.json` - Creates `~/.claude/buddymon/` state directory with initial JSON files diff --git a/hooks-handlers/post-tool-use.py b/hooks-handlers/post-tool-use.py index 663d196..460b569 100755 --- a/hooks-handlers/post-tool-use.py +++ b/hooks-handlers/post-tool-use.py @@ -18,6 +18,7 @@ import re import sys import random from pathlib import Path +from datetime import datetime PLUGIN_ROOT = os.environ.get("CLAUDE_PLUGIN_ROOT", str(Path(__file__).parent.parent)) BUDDYMON_DIR = Path.home() / ".claude" / "buddymon" @@ -280,9 +281,22 @@ def main(): if tool_name == "Bash": output = "" if isinstance(tool_response, dict): - output = tool_response.get("output", "") or tool_response.get("content", "") + # CC may use any of these keys; combine all text fields + parts = [ + tool_response.get("output", ""), + tool_response.get("content", ""), + tool_response.get("stderr", ""), + tool_response.get("text", ""), + ] + output = "\n".join(p for p in parts if isinstance(p, str) and p) elif isinstance(tool_response, str): output = tool_response + elif isinstance(tool_response, list): + # Array of content blocks: [{"type": "text", "text": "..."}] + output = "\n".join( + b.get("text", "") for b in tool_response + if isinstance(b, dict) and b.get("type") == "text" + ) existing = get_active_encounter() diff --git a/install.sh b/install.sh index dc3eeef..35e5600 100755 --- a/install.sh +++ b/install.sh @@ -96,6 +96,13 @@ if '${MARKETPLACE}' in d: PYEOF fi + # Remove marketplace plugin symlink (leave marketplace dir in case other CF plugins exist) + MARKETPLACE_PLUGIN_LINK="${PLUGINS_DIR}/marketplaces/${MARKETPLACE}/plugins/${PLUGIN_NAME}" + if [[ -L "${MARKETPLACE_PLUGIN_LINK}" ]]; then + rm "${MARKETPLACE_PLUGIN_LINK}" + ok "Removed marketplace plugin symlink" + fi + echo "" echo "✓ ${PLUGIN_KEY} uninstalled. Restart Claude Code to apply." } @@ -263,6 +270,12 @@ for name, default in files.items(): print(f" Created {name}") else: print(f" {name} already exists — kept") + +# Pre-create hook_debug.log so the hook sandbox can write to it +log_path = os.path.join(d, 'hook_debug.log') +if not os.path.exists(log_path): + open(log_path, 'w').close() + print(" Created hook_debug.log") PYEOF echo ""