Skip to content

Commit 8db2ef8

Browse files
jubalmclaude
andcommitted
Release v0.3.0: Local-first Discord integration
🏠 Local-First Architecture - Default installation to current project (.claude/) - Simple one-command setup with GitHub downloads - Self-contained project installations 🌐 Global Installation Option - --global flag for advanced multi-project workflows - Backward compatible with existing global setups - Intelligent path detection (local takes priority) 🔧 Enhanced Features - Smart settings.json merging with backup creation - Safe uninstall with surgical hook removal - Installation type detection and status reporting 📚 Complete Documentation Rewrite - Updated README.md with local-first examples - Comprehensive CLAUDE.md architecture documentation - New troubleshooting guides for both installation types Breaking Changes: - Default installation behavior changed from global to local - Uninstall requires --global flag for global removal - curl install URLs now download files instead of requiring local repo 🧪 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent b8da48f commit 8db2ef8

23 files changed

Lines changed: 2125 additions & 246 deletions
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#!/usr/bin/env python3
2+
3+
"""
4+
Merge Discord hooks into existing Claude settings.json
5+
This replaces the complex jq operation with pure Python
6+
"""
7+
8+
import json
9+
import sys
10+
from pathlib import Path
11+
12+
def merge_discord_hooks(settings_file):
13+
"""Merge Discord hooks into existing settings."""
14+
15+
# Determine hook paths (local-first, fallback to global)
16+
if Path(".claude/hooks/stop-discord.py").exists():
17+
# Local installation
18+
hooks_base = ".claude/hooks"
19+
else:
20+
# Global installation
21+
hooks_base = "$HOME/.claude/hooks"
22+
23+
# Discord hooks configuration
24+
discord_hooks = {
25+
"Stop": [
26+
{
27+
"matcher": "",
28+
"hooks": [
29+
{
30+
"type": "command",
31+
"command": f"{hooks_base}/stop-discord.py"
32+
}
33+
]
34+
}
35+
],
36+
"Notification": [
37+
{
38+
"matcher": "",
39+
"hooks": [
40+
{
41+
"type": "command",
42+
"command": f"{hooks_base}/notification-discord.py"
43+
}
44+
]
45+
}
46+
],
47+
"PostToolUse": [
48+
{
49+
"matcher": "",
50+
"hooks": [
51+
{
52+
"type": "command",
53+
"command": f"{hooks_base}/posttooluse-discord.py"
54+
}
55+
]
56+
}
57+
]
58+
}
59+
60+
try:
61+
# Read existing settings
62+
with open(settings_file, 'r', encoding='utf-8') as f:
63+
settings = json.load(f)
64+
except (FileNotFoundError, json.JSONDecodeError):
65+
settings = {}
66+
67+
# Ensure hooks section exists
68+
if 'hooks' not in settings:
69+
settings['hooks'] = {}
70+
71+
# Merge Discord hooks (this will override existing Discord hooks if they exist)
72+
settings['hooks'].update(discord_hooks)
73+
74+
# Write back to file
75+
with open(settings_file, 'w', encoding='utf-8') as f:
76+
json.dump(settings, f, indent=2)
77+
78+
return True
79+
80+
if __name__ == "__main__":
81+
settings_file = sys.argv[1] if len(sys.argv) > 1 else ".claude/settings.json"
82+
83+
try:
84+
merge_discord_hooks(settings_file)
85+
print("✅ Discord hooks merged successfully")
86+
except Exception as e:
87+
print(f"❌ Failed to merge settings: {e}")
88+
sys.exit(1)
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#!/usr/bin/env python3
2+
3+
"""
4+
Read values from Discord state JSON file
5+
Replaces jq read operations with pure Python
6+
"""
7+
8+
import json
9+
import sys
10+
from pathlib import Path
11+
12+
def read_discord_state(state_file, key, default=""):
13+
"""Read a specific key from Discord state."""
14+
15+
try:
16+
# Read existing state
17+
with open(state_file, 'r', encoding='utf-8') as f:
18+
state = json.load(f)
19+
except (FileNotFoundError, json.JSONDecodeError):
20+
state = {}
21+
22+
# Get the value with default fallback
23+
value = state.get(key, default)
24+
25+
# Handle boolean values
26+
if isinstance(value, bool):
27+
print("true" if value else "false")
28+
else:
29+
print(value)
30+
31+
if __name__ == "__main__":
32+
if len(sys.argv) < 3:
33+
print("Usage: read-state.py <state_file> <key> [default]")
34+
sys.exit(1)
35+
36+
state_file = sys.argv[1]
37+
key = sys.argv[2]
38+
default = sys.argv[3] if len(sys.argv) > 3 else ""
39+
40+
try:
41+
read_discord_state(state_file, key, default)
42+
except Exception as e:
43+
print(default) # Return default on any error

.claude/commands/discord/remove.md

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
---
2+
description: Remove Discord integration from this project
3+
allowed-tools: Bash(rm:*), Bash(python3:*), Bash(cat:*), Bash(echo:*)
4+
---
5+
6+
! if [ ! -f ".claude/discord-state.json" ]; then
7+
echo "ℹ️ No Discord integration found in this project"
8+
echo ""
9+
echo "Current project: $(basename $(pwd))"
10+
echo "Status: Not configured"
11+
echo ""
12+
echo "To set up Discord integration:"
13+
echo "• /user:discord:setup WEBHOOK_URL - Configure Discord integration"
14+
exit 0
15+
fi
16+
17+
! # Show current configuration
18+
! echo "🗑️ Discord Integration Removal for $(basename $(pwd))"
19+
! echo "=================================================="
20+
! echo ""
21+
22+
! # Determine command script paths (local-first, fallback to global)
23+
! if [ -f ".claude/commands/discord/read-state.py" ]; then
24+
COMMANDS_BASE=".claude/commands/discord"
25+
else
26+
COMMANDS_BASE="$HOME/.claude/commands/discord"
27+
fi
28+
29+
! # Get current state
30+
! ACTIVE=$(python3 "$COMMANDS_BASE/read-state.py" .claude/discord-state.json active false)
31+
! PROJECT_NAME=$(python3 "$COMMANDS_BASE/read-state.py" .claude/discord-state.json project_name unknown)
32+
! WEBHOOK_URL=$(python3 "$COMMANDS_BASE/read-state.py" .claude/discord-state.json webhook_url)
33+
34+
! echo "📊 Current Configuration:"
35+
! echo " Project: $PROJECT_NAME"
36+
! echo " Status: $([ "$ACTIVE" = "true" ] && echo "🟢 Active" || echo "🔴 Disabled")"
37+
! echo " Webhook: $(echo "$WEBHOOK_URL" | sed 's/\(.*webhooks\/[0-9]*\).*/\1.../' 2>/dev/null || echo 'Not configured')"
38+
! echo " Hooks: $([ -f ".claude/settings.json" ] && echo "✅ Configured" || echo "❌ Not configured")"
39+
! echo ""
40+
41+
! # Warning and confirmation
42+
! echo "⚠️ WARNING: This will remove Discord integration from this project:"
43+
! echo " • Delete .claude/discord-state.json"
44+
! echo " • Remove Discord hooks from .claude/settings.json"
45+
! echo " • Preserve other hooks and settings"
46+
! echo ""
47+
! echo "Global Discord components (in ~/.claude/) will NOT be affected."
48+
! echo ""
49+
50+
! # Simple confirmation (no interactive read in Claude Code)
51+
! echo "🔄 Proceeding with removal..."
52+
! echo ""
53+
54+
! # Create backup of settings.json
55+
! if [ -f ".claude/settings.json" ]; then
56+
echo "📁 Creating backup of settings.json..."
57+
cp .claude/settings.json .claude/settings.json.backup-$(date +%Y%m%d-%H%M%S)
58+
echo "✅ Backup created"
59+
fi
60+
61+
! # Remove discord-state.json
62+
! if [ -f ".claude/discord-state.json" ]; then
63+
rm -f .claude/discord-state.json
64+
echo "✅ Removed discord-state.json"
65+
fi
66+
67+
! # Remove Discord hooks from settings.json while preserving other hooks
68+
! if [ -f ".claude/settings.json" ]; then
69+
echo "🔧 Removing Discord hooks from settings.json..."
70+
71+
# Use Python to remove only Discord hooks
72+
python3 -c "
73+
import json
74+
import sys
75+
76+
try:
77+
with open('.claude/settings.json', 'r') as f:
78+
settings = json.load(f)
79+
80+
if 'hooks' in settings:
81+
# Remove Discord hooks while preserving others
82+
hooks = settings['hooks']
83+
84+
# Remove Stop hooks that point to Discord
85+
if 'Stop' in hooks:
86+
hooks['Stop'] = [h for h in hooks['Stop'] if not any('discord' in hook.get('command', '') for hook in h.get('hooks', []))]
87+
if not hooks['Stop']:
88+
del hooks['Stop']
89+
90+
# Remove Notification hooks that point to Discord
91+
if 'Notification' in hooks:
92+
hooks['Notification'] = [h for h in hooks['Notification'] if not any('discord' in hook.get('command', '') for hook in h.get('hooks', []))]
93+
if not hooks['Notification']:
94+
del hooks['Notification']
95+
96+
# Remove PostToolUse hooks that point to Discord
97+
if 'PostToolUse' in hooks:
98+
hooks['PostToolUse'] = [h for h in hooks['PostToolUse'] if not any('discord' in hook.get('command', '') for hook in h.get('hooks', []))]
99+
if not hooks['PostToolUse']:
100+
del hooks['PostToolUse']
101+
102+
# Write back the cleaned settings
103+
with open('.claude/settings.json', 'w') as f:
104+
json.dump(settings, f, indent=2)
105+
106+
print('✅ Discord hooks removed from settings.json')
107+
108+
except Exception as e:
109+
print(f'❌ Error updating settings.json: {e}')
110+
sys.exit(1)
111+
"
112+
113+
else
114+
echo "ℹ️ No settings.json file found"
115+
fi
116+
117+
! echo ""
118+
! echo "=================================================="
119+
! echo "✅ Discord integration removal completed!"
120+
! echo "=================================================="
121+
! echo ""
122+
! echo "📊 What was removed:"
123+
! echo " • .claude/discord-state.json (Discord configuration)"
124+
! echo " • Discord hooks from .claude/settings.json"
125+
! echo ""
126+
! echo "📁 What was preserved:"
127+
! echo " • Other hooks in .claude/settings.json"
128+
! echo " • Global Discord components in ~/.claude/"
129+
! echo " • Backup of settings.json created"
130+
! echo ""
131+
! echo "🔄 To re-enable Discord integration:"
132+
! echo " • /user:discord:setup WEBHOOK_URL - Reconfigure"
133+
! echo ""
134+
! echo "🗑️ To completely remove global Discord components:"
135+
! echo " • Run ./uninstall.sh from the claude-discord-integration directory"

0 commit comments

Comments
 (0)