Skip to content

Commit 55587fc

Browse files
committed
Release v0.1.0: Python-first Discord integration
### Major Changes - Convert all hook scripts from shell to Python for zero external dependencies - Implement consistent naming convention: {event-type}-discord.py - Add Python utility scripts for JSON operations (merge-settings.py, update-state.py, read-state.py) - Update all slash commands to use Python instead of jq - Maintain shell scripts as backup for backward compatibility ### Benefits - Eliminates jq and curl dependencies - Better error handling with Python exceptions - More maintainable and readable codebase - Faster execution and native JSON processing - Enhanced logging capabilities ### Files Added - hooks/stop-discord.py (replaces discord-notify.sh) - hooks/notification-discord.py - hooks/posttooluse-discord.py - commands/discord/merge-settings.py - commands/discord/update-state.py - commands/discord/read-state.py ### Files Modified - commands/discord/*.md (all slash commands updated) - install.sh (installs Python versions) - CLAUDE.md (version bump and description update) - CHANGELOG.md (v0.1.0 release notes)
1 parent cd206a6 commit 55587fc

13 files changed

Lines changed: 989 additions & 60 deletions

CHANGELOG.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,34 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.1.0] - 2025-01-07
9+
10+
### Added
11+
- 🐍 **Python-first implementation** - Eliminates external dependencies (`jq`, `curl`)
12+
- 🏗️ **Consistent naming convention** - All hooks follow `{event-type}-discord.py` pattern
13+
- 🛠️ **Utility scripts** - Modular Python scripts for JSON operations
14+
-**Better error handling** - Native Python exception handling vs shell error codes
15+
- 🔧 **Maintainable codebase** - More readable and debuggable Python code
16+
17+
### Changed
18+
- **Hook scripts converted to Python**:
19+
- `discord-notify.sh``stop-discord.py`
20+
- `notification-discord.sh``notification-discord.py`
21+
- `posttooluse-discord.sh``posttooluse-discord.py`
22+
- **Slash commands updated** to use Python for JSON operations
23+
- **Installation script** now installs Python versions alongside shell backups
24+
25+
### Improved
26+
- **Zero external dependencies** - Only requires Python (universally available)
27+
- **Native JSON processing** - Proper parsing vs shell string manipulation
28+
- **Enhanced logging** - Python logging vs echo-to-file
29+
- **Faster execution** - Python startup vs multiple shell subprocess calls
30+
31+
### Backward Compatibility
32+
- Shell hook scripts maintained as backup
33+
- Existing configurations continue to work
34+
- Non-breaking changes to slash commands
35+
836
## [0.0.1-beta] - 2025-01-06
937

1038
### Added

CLAUDE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
44

55
## Project Overview
66

7-
This is **Claude Code Discord Notification** (version 0.0.1-beta) - a system that provides project-scoped Discord notifications for Claude Code sessions. Stay informed about your Claude Code sessions with real-time Discord notifications - know what Claude is working on even when you're away from your computer.
7+
This is **Claude Code Discord Notification** (version 0.1.0) - a system that provides project-scoped Discord notifications for Claude Code sessions. Stay informed about your Claude Code sessions with real-time Discord notifications - know what Claude is working on even when you're away from your computer.
88

9-
The system uses bash scripts, shell hooks, and custom slash commands to send real-time notifications when Claude completes tasks, needs input, or makes progress on projects.
9+
The system uses Python scripts, shell hooks, and custom slash commands to send real-time notifications when Claude completes tasks, needs input, or makes progress on projects.
1010

1111
## Installation and Setup Commands
1212

commands/discord/merge-settings.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
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+
# Discord hooks configuration
16+
discord_hooks = {
17+
"Stop": [
18+
{
19+
"matcher": "",
20+
"hooks": [
21+
{
22+
"type": "command",
23+
"command": "$HOME/.claude/hooks/stop-discord.py"
24+
}
25+
]
26+
}
27+
],
28+
"Notification": [
29+
{
30+
"matcher": "",
31+
"hooks": [
32+
{
33+
"type": "command",
34+
"command": "$HOME/.claude/hooks/notification-discord.py"
35+
}
36+
]
37+
}
38+
],
39+
"PostToolUse": [
40+
{
41+
"matcher": "",
42+
"hooks": [
43+
{
44+
"type": "command",
45+
"command": "$HOME/.claude/hooks/posttooluse-discord.py"
46+
}
47+
]
48+
}
49+
]
50+
}
51+
52+
try:
53+
# Read existing settings
54+
with open(settings_file, 'r', encoding='utf-8') as f:
55+
settings = json.load(f)
56+
except (FileNotFoundError, json.JSONDecodeError):
57+
settings = {}
58+
59+
# Ensure hooks section exists
60+
if 'hooks' not in settings:
61+
settings['hooks'] = {}
62+
63+
# Merge Discord hooks (this will override existing Discord hooks if they exist)
64+
settings['hooks'].update(discord_hooks)
65+
66+
# Write back to file
67+
with open(settings_file, 'w', encoding='utf-8') as f:
68+
json.dump(settings, f, indent=2)
69+
70+
return True
71+
72+
if __name__ == "__main__":
73+
settings_file = sys.argv[1] if len(sys.argv) > 1 else ".claude/settings.json"
74+
75+
try:
76+
merge_discord_hooks(settings_file)
77+
print("✅ Discord hooks merged successfully")
78+
except Exception as e:
79+
print(f"❌ Failed to merge settings: {e}")
80+
sys.exit(1)

commands/discord/read-state.py

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

commands/discord/setup.md

Lines changed: 5 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
description: Setup Discord integration for this project (configurable and non-destructive)
3-
allowed-tools: Bash(jq:*), Bash(echo:*), Bash(mkdir:*), Bash(cat:*), Bash(read:*)
3+
allowed-tools: Bash(python3:*), Bash(echo:*), Bash(mkdir:*), Bash(cat:*), Bash(read:*)
44
---
55

66
! mkdir -p .claude
@@ -78,43 +78,7 @@ EOF
7878
cp .claude/settings.json .claude/settings.json.backup
7979

8080
# Merge Discord hooks with existing configuration
81-
jq '. + {
82-
"hooks": (.hooks // {}) + {
83-
"Stop": [
84-
{
85-
"matcher": "",
86-
"hooks": [
87-
{
88-
"type": "command",
89-
"command": "$HOME/.claude/hooks/discord-notify.sh"
90-
}
91-
]
92-
}
93-
],
94-
"Notification": [
95-
{
96-
"matcher": "",
97-
"hooks": [
98-
{
99-
"type": "command",
100-
"command": "$HOME/.claude/hooks/notification-discord.sh"
101-
}
102-
]
103-
}
104-
],
105-
"PostToolUse": [
106-
{
107-
"matcher": "",
108-
"hooks": [
109-
{
110-
"type": "command",
111-
"command": "$HOME/.claude/hooks/posttooluse-discord.sh"
112-
}
113-
]
114-
}
115-
]
116-
}
117-
}' .claude/settings.json > .claude/settings-tmp.json && mv .claude/settings-tmp.json .claude/settings.json
81+
python3 "$HOME/.claude/commands/discord/merge-settings.py" .claude/settings.json
11882

11983
echo "✅ Discord hooks merged with existing configuration"
12084
echo "📁 Backup saved as .claude/settings.json.backup"
@@ -130,7 +94,7 @@ EOF
13094
"hooks": [
13195
{
13296
"type": "command",
133-
"command": "$HOME/.claude/hooks/discord-notify.sh"
97+
"command": "$HOME/.claude/hooks/stop-discord.py"
13498
}
13599
]
136100
}
@@ -141,7 +105,7 @@ EOF
141105
"hooks": [
142106
{
143107
"type": "command",
144-
"command": "$HOME/.claude/hooks/notification-discord.sh"
108+
"command": "$HOME/.claude/hooks/notification-discord.py"
145109
}
146110
]
147111
}
@@ -152,7 +116,7 @@ EOF
152116
"hooks": [
153117
{
154118
"type": "command",
155-
"command": "$HOME/.claude/hooks/posttooluse-discord.sh"
119+
"command": "$HOME/.claude/hooks/posttooluse-discord.py"
156120
}
157121
]
158122
}

commands/discord/start.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
description: Start Discord notifications for this project (channel or thread)
3-
allowed-tools: Bash(jq:*), Bash(echo:*), Bash(mkdir:*)
3+
allowed-tools: Bash(python3:*), Bash(echo:*), Bash(mkdir:*)
44
---
55

66
! mkdir -p .claude
@@ -13,12 +13,12 @@ allowed-tools: Bash(jq:*), Bash(echo:*), Bash(mkdir:*)
1313

1414
! if [ -n "$ARGUMENTS" ]; then
1515
# Update existing config with specific thread ID
16-
jq --arg thread_id "$ARGUMENTS" '.active = true | .thread_id = $thread_id' .claude/discord-state.json > .claude/discord-state-tmp.json && mv .claude/discord-state-tmp.json .claude/discord-state.json
16+
python3 "$HOME/.claude/commands/discord/update-state.py" .claude/discord-state.json start "$ARGUMENTS"
1717
echo "✅ Discord notifications enabled for thread: $ARGUMENTS"
1818
else
1919
# Enable notifications with existing config
20-
jq '.active = true' .claude/discord-state.json > .claude/discord-state-tmp.json && mv .claude/discord-state-tmp.json .claude/discord-state.json
21-
THREAD_ID=$(jq -r '.thread_id // ""' .claude/discord-state.json)
20+
python3 "$HOME/.claude/commands/discord/update-state.py" .claude/discord-state.json start
21+
THREAD_ID=$(python3 "$HOME/.claude/commands/discord/update-state.py" .claude/discord-state.json get_thread_id)
2222
if [ -n "$THREAD_ID" ] && [ "$THREAD_ID" != "" ]; then
2323
echo "✅ Discord notifications enabled for thread: $THREAD_ID"
2424
else

commands/discord/status.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
---
22
description: Show Discord integration status for this project
3-
allowed-tools: Bash(jq:*), Bash(cat:*)
3+
allowed-tools: Bash(python3:*), Bash(cat:*)
44
---
55

66
! if [ -f ".claude/discord-state.json" ]; then
77
echo "📊 Discord Status for $(basename $(pwd))"
88
echo "=================================="
99

10-
ACTIVE=$(jq -r '.active // false' .claude/discord-state.json)
11-
PROJECT_NAME=$(jq -r '.project_name // "unknown"' .claude/discord-state.json)
12-
THREAD_ID=$(jq -r '.thread_id // ""' .claude/discord-state.json)
13-
HAS_AUTH=$(jq -r '.auth_token // ""' .claude/discord-state.json)
10+
ACTIVE=$(python3 "$HOME/.claude/commands/discord/read-state.py" .claude/discord-state.json active false)
11+
PROJECT_NAME=$(python3 "$HOME/.claude/commands/discord/read-state.py" .claude/discord-state.json project_name unknown)
12+
THREAD_ID=$(python3 "$HOME/.claude/commands/discord/read-state.py" .claude/discord-state.json thread_id)
13+
HAS_AUTH=$(python3 "$HOME/.claude/commands/discord/read-state.py" .claude/discord-state.json auth_token)
1414

1515
echo "Project: $PROJECT_NAME"
1616
echo "Status: $([ "$ACTIVE" = "true" ] && echo "🟢 Active" || echo "🔴 Disabled")"

commands/discord/stop.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
---
22
description: Stop Discord notifications for this project
3-
allowed-tools: Bash(jq:*)
3+
allowed-tools: Bash(python3:*)
44
---
55

66
! if [ -f ".claude/discord-state.json" ]; then
7-
jq '.active = false' .claude/discord-state.json > .claude/discord-state-tmp.json && mv .claude/discord-state-tmp.json .claude/discord-state.json
7+
python3 "$HOME/.claude/commands/discord/update-state.py" .claude/discord-state.json stop
88
echo "🔕 Discord notifications disabled for project: $(basename $(pwd))"
99
else
1010
echo "ℹ️ No Discord configuration found for this project"

commands/discord/update-state.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#!/usr/bin/env python3
2+
3+
"""
4+
Update Discord state JSON file
5+
Replaces jq operations with pure Python
6+
"""
7+
8+
import json
9+
import sys
10+
from pathlib import Path
11+
12+
def update_discord_state(state_file, action, thread_id=None):
13+
"""Update Discord state based on action."""
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+
if action == 'start':
23+
state['active'] = True
24+
if thread_id:
25+
state['thread_id'] = thread_id
26+
elif action == 'stop':
27+
state['active'] = False
28+
elif action == 'get_thread_id':
29+
# Just return the thread_id, don't modify file
30+
print(state.get('thread_id', ''))
31+
return
32+
33+
# Write back to file
34+
with open(state_file, 'w', encoding='utf-8') as f:
35+
json.dump(state, f, indent=2)
36+
37+
if __name__ == "__main__":
38+
if len(sys.argv) < 3:
39+
print("Usage: update-state.py <state_file> <action> [thread_id]")
40+
print("Actions: start, stop, get_thread_id")
41+
sys.exit(1)
42+
43+
state_file = sys.argv[1]
44+
action = sys.argv[2]
45+
thread_id = sys.argv[3] if len(sys.argv) > 3 else None
46+
47+
try:
48+
update_discord_state(state_file, action, thread_id)
49+
except Exception as e:
50+
print(f"❌ Failed to update state: {e}")
51+
sys.exit(1)

0 commit comments

Comments
 (0)