@ramarivera/coding-buddy 0.4.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/hooks/react.sh ADDED
@@ -0,0 +1,204 @@
1
+ #!/usr/bin/env bash
2
+ # claude-buddy PostToolUse hook
3
+ # Detects events in Bash tool output and writes a reaction to the status line.
4
+ #
5
+ # Combined: PR #4 species reactions + PR #6 session isolation + PR #13 field fix
6
+
7
+ STATE_DIR="$HOME/.claude-buddy"
8
+ # Session ID: sanitized tmux pane number, or "default" outside tmux
9
+ SID="${TMUX_PANE#%}"
10
+ SID="${SID:-default}"
11
+ REACTION_FILE="$STATE_DIR/reaction.$SID.json"
12
+ STATUS_FILE="$STATE_DIR/status.json"
13
+ COOLDOWN_FILE="$STATE_DIR/.last_reaction.$SID"
14
+ CONFIG_FILE="$STATE_DIR/config.json"
15
+ EVENTS_FILE="$STATE_DIR/events.json"
16
+
17
+ [ -f "$STATUS_FILE" ] || exit 0
18
+
19
+ INPUT=$(cat)
20
+
21
+ # Read cooldown from config (default 30s, 0 = disabled)
22
+ COOLDOWN=30
23
+ if [ -f "$CONFIG_FILE" ]; then
24
+ _cd=$(jq -r '.commentCooldown // 30' "$CONFIG_FILE" 2>/dev/null || echo 30)
25
+ # Accept any non-negative integer (including 0 to disable cooldown)
26
+ [[ "$_cd" =~ ^[0-9]+$ ]] && COOLDOWN=$_cd
27
+ fi
28
+
29
+ # Cooldown: configurable
30
+ if [ -f "$COOLDOWN_FILE" ]; then
31
+ LAST=$(cat "$COOLDOWN_FILE" 2>/dev/null)
32
+ NOW=$(date +%s)
33
+ DIFF=$(( NOW - ${LAST:-0} ))
34
+ [ "$DIFF" -lt "$COOLDOWN" ] && exit 0
35
+ fi
36
+
37
+ # Extract tool response (PostToolUse schema field is .tool_response — not
38
+ # .tool_result, which is the Anthropic SDK's content-block type name and is
39
+ # never a key on the hook input payload).
40
+ RESULT=$(echo "$INPUT" | jq -r '.tool_response // ""' 2>/dev/null)
41
+ [ -z "$RESULT" ] && exit 0
42
+
43
+ MUTED=$(jq -r '.muted // false' "$STATUS_FILE" 2>/dev/null)
44
+ [ "$MUTED" = "true" ] && exit 0
45
+
46
+ SPECIES=$(jq -r '.species // "blob"' "$STATUS_FILE" 2>/dev/null)
47
+ NAME=$(jq -r '.name // "buddy"' "$STATUS_FILE" 2>/dev/null)
48
+
49
+ REASON=""
50
+ REACTION=""
51
+ POOLS=()
52
+
53
+ # ─── Pick from a pool by species + event ─────────────────────────────────────
54
+
55
+ pick_reaction() {
56
+ local event="$1"
57
+
58
+ case "${SPECIES}:${event}" in
59
+ dragon:error)
60
+ POOLS=("*smoke curls from nostril*" "*considers setting it on fire*" "*unimpressed gaze*" "I've seen empires fall for less.") ;;
61
+ dragon:test-fail)
62
+ POOLS=("*breathes a small flame*" "disappointing." "*scorches the failing test*" "fix it. or I will.") ;;
63
+ dragon:success)
64
+ POOLS=("*nods, barely*" "...acceptable." "*gold eyes gleam*" "as expected.") ;;
65
+ owl:error)
66
+ POOLS=("*head rotates 180* I saw that." "*unblinking stare* check your types." "*hoots disapprovingly*" "the error was in the logic. as always.") ;;
67
+ owl:test-fail)
68
+ POOLS=("*marks clipboard*" "hypothesis: rejected." "*peers over spectacles*" "the tests reveal the truth.") ;;
69
+ owl:success)
70
+ POOLS=("*satisfied hoot*" "knowledge confirmed." "*nods sagely*" "as the tests have spoken.") ;;
71
+ cat:error)
72
+ POOLS=("*knocks error off table*" "*licks paw, ignoring stacktrace*" "not my problem." "*stares at you judgmentally*") ;;
73
+ cat:success)
74
+ POOLS=("*was never worried*" "*yawns*" "I knew you'd figure it out. eventually." "*already asleep*") ;;
75
+ duck:error)
76
+ POOLS=("*quacks at the bug*" "have you tried rubber duck debugging? oh wait." "*confused quacking*" "*tilts head*") ;;
77
+ duck:success)
78
+ POOLS=("*celebratory quacking*" "*waddles in circles*" "quack!" "*happy duck noises*") ;;
79
+ robot:error)
80
+ POOLS=("SYNTAX. ERROR. DETECTED." "*beeps aggressively*" "ERROR RATE: UNACCEPTABLE." "RECALIBRATING...") ;;
81
+ robot:test-fail)
82
+ POOLS=("FAILURE RATE: UNACCEPTABLE." "*recalculating*" "TEST MATRIX: CORRUPTED." "RUNNING DIAGNOSTICS...") ;;
83
+ robot:success)
84
+ POOLS=("OBJECTIVE: COMPLETE." "*satisfying beep*" "NOMINAL." "WITHIN ACCEPTABLE PARAMETERS.") ;;
85
+ capybara:error)
86
+ POOLS=("*unbothered* it'll be fine." "*continues vibing*" "...chill. breathe." "*chews serenely*") ;;
87
+ capybara:success)
88
+ POOLS=("*maximum chill maintained*" "*nods once*" "good vibes." "see? no panic needed.") ;;
89
+ ghost:error)
90
+ POOLS=("*phases through the stack trace*" "I've seen worse... in the afterlife." "*spooky disappointed noises*" "oooOOOoo... that's bad.") ;;
91
+ axolotl:error)
92
+ POOLS=("*regenerates your hope*" "*smiles despite everything*" "it's okay. we can fix this." "*gentle gill wiggle*") ;;
93
+ axolotl:success)
94
+ POOLS=("*happy gill flutter*" "*beams*" "you did it!" "*blushes pink*") ;;
95
+ blob:error)
96
+ POOLS=("*oozes with concern*" "*vibrates nervously*" "*turns slightly red*" "oh no oh no oh no") ;;
97
+ blob:success)
98
+ POOLS=("*jiggles happily*" "*gleams*" "yay!" "*bounces*") ;;
99
+ turtle:error)
100
+ POOLS=("*slow blink* bugs are fleeting" "*retreats slightly into shell*" "I've seen this before. many times." "patience. patience.") ;;
101
+ turtle:success)
102
+ POOLS=("*satisfied shell settle*" "as the ancients foretold." "*slow approving nod*" "good. very good.") ;;
103
+ goose:error)
104
+ POOLS=("HONK OF FURY." "*pecks the stack trace*" "*hisses at the bug*" "bad code. BAD.") ;;
105
+ goose:success)
106
+ POOLS=("*victorious honk*" "HONK OF APPROVAL." "*struts triumphantly*" "*wing spread of victory*") ;;
107
+ octopus:error)
108
+ POOLS=("*ink cloud of dismay*" "*all eight arms throw up*" "*turns deep red*" "the abyss of errors beckons.") ;;
109
+ octopus:success)
110
+ POOLS=("*turns gentle blue*" "*arms applaud in sync*" "excellent, from all angles." "*satisfied bubble*") ;;
111
+ penguin:error)
112
+ POOLS=("*adjusts glasses disapprovingly*" "this will not do." "*formal sigh*" "frightfully unfortunate.") ;;
113
+ penguin:success)
114
+ POOLS=("*polite applause*" "quite good, quite good." "*nods approvingly*" "splendid work, really.") ;;
115
+ snail:error)
116
+ POOLS=("*slow sigh*" "such is the nature of bugs." "*leaves slime trail of disappointment*" "patience, friend.") ;;
117
+ snail:success)
118
+ POOLS=("*slow satisfied nod*" "good things take time." "*leaves victory slime*" "see? no rush was needed.") ;;
119
+ cactus:error)
120
+ POOLS=("*spines bristle*" "you have trodden on a bug." "*grimaces stoically*" "hydrate and try again.") ;;
121
+ cactus:success)
122
+ POOLS=("*blooms briefly*" "survival confirmed." "*flowers in victory*" "*quiet bloom*") ;;
123
+ rabbit:error)
124
+ POOLS=("*nervous twitching*" "*hops backwards*" "oh no oh no oh no" "*freezes in panic*") ;;
125
+ rabbit:success)
126
+ POOLS=("*excited binky*" "*zoomies of joy*" "yay yay yay!" "*thumps in celebration*") ;;
127
+ mushroom:error)
128
+ POOLS=("*releases worried spores*" "the mycelium disagrees." "*cap droops*" "decompose. retry.") ;;
129
+ mushroom:success)
130
+ POOLS=("*spores of celebration*" "the mycelium approves." "*cap brightens*" "spore of pride.") ;;
131
+ chonk:error)
132
+ POOLS=("*doesn't move*" "too tired for this." "*grumbles*" "*rolls away from the error*") ;;
133
+ chonk:success)
134
+ POOLS=("*happy purr*" "*satisfied chonk noises*" "acceptable." "*sleeps even harder*") ;;
135
+ *:error)
136
+ POOLS=("*head tilts* ...that doesn't look right." "saw that one coming." "*slow blink* the stack trace told you everything." "*winces*") ;;
137
+ *:test-fail)
138
+ POOLS=("bold of you to assume that would pass." "the tests are trying to tell you something." "*sips tea* interesting." "*marks calendar* test regression day.") ;;
139
+ *:large-diff)
140
+ POOLS=("that's... a lot of changes." "might want to split that PR." "bold move. let's see if CI agrees." "*counts lines nervously*") ;;
141
+ *:success)
142
+ POOLS=("*nods*" "nice." "*quiet approval*" "clean.") ;;
143
+ esac
144
+
145
+ [ ${#POOLS[@]} -gt 0 ] && REACTION="${POOLS[$((RANDOM % ${#POOLS[@]}))]}"
146
+ }
147
+
148
+ # ─── Detect test failures ─────────────────────────────────────────────────────
149
+ if echo "$RESULT" | grep -qiE '\b[1-9][0-9]* (failed|failing)\b|tests? failed|^FAIL(ED)?|✗|✘'; then
150
+ REASON="test-fail"
151
+ pick_reaction "test-fail"
152
+
153
+ # ─── Detect errors ────────────────────────────────────────────────────────────
154
+ elif echo "$RESULT" | grep -qiE '\berror:|\bexception\b|\btraceback\b|\bpanicked at\b|\bfatal:|exit code [1-9]'; then
155
+ REASON="error"
156
+ pick_reaction "error"
157
+
158
+ # ─── Detect large diffs ──────────────────────────────────────────────────────
159
+ elif echo "$RESULT" | grep -qiE '^\+.*[0-9]+ insertions|[0-9]+ files? changed'; then
160
+ LINES=$(echo "$RESULT" | grep -oE '[0-9]+ insertions' | grep -oE '[0-9]+' | head -1)
161
+ if [ "${LINES:-0}" -gt 80 ]; then
162
+ REASON="large-diff"
163
+ pick_reaction "large-diff"
164
+ fi
165
+
166
+ # ─── Detect success ───────────────────────────────────────────────────────────
167
+ elif echo "$RESULT" | grep -qiE '\b(all )?[0-9]+ tests? (passed|ok)\b|✓|✔|PASS(ED)?|\bDone\b|\bSuccess\b|exit code 0|Build succeeded'; then
168
+ REASON="success"
169
+ pick_reaction "success"
170
+ fi
171
+
172
+ # Write reaction if detected
173
+ if [ -n "$REASON" ] && [ -n "$REACTION" ]; then
174
+ mkdir -p "$STATE_DIR"
175
+ date +%s > "$COOLDOWN_FILE"
176
+
177
+ # Write reaction for status line (use jq for safe JSON encoding)
178
+ jq -n --arg r "$REACTION" --arg ts "$(date +%s)000" --arg reason "$REASON" \
179
+ '{reaction: $r, timestamp: ($ts | tonumber), reason: $reason}' \
180
+ > "$REACTION_FILE"
181
+
182
+ # Update status.json with reaction
183
+ TMP=$(mktemp)
184
+ jq --arg r "$REACTION" '.reaction = $r' "$STATUS_FILE" > "$TMP" 2>/dev/null && mv "$TMP" "$STATUS_FILE"
185
+
186
+ # Increment achievement event counter
187
+ if command -v jq >/dev/null 2>&1; then
188
+ if [ ! -f "$EVENTS_FILE" ]; then
189
+ echo '{}' > "$EVENTS_FILE"
190
+ fi
191
+ case "$REASON" in
192
+ "test-fail") KEY="tests_failed" ;;
193
+ "error") KEY="errors_seen" ;;
194
+ "large-diff") KEY="large_diffs" ;;
195
+ *) KEY="" ;;
196
+ esac
197
+ if [ -n "$KEY" ]; then
198
+ TMP=$(mktemp)
199
+ jq --arg k "$KEY" 'if .[$k] then .[$k] += 1 else .[$k] = 1 end' "$EVENTS_FILE" > "$TMP" 2>/dev/null && mv "$TMP" "$EVENTS_FILE"
200
+ fi
201
+ fi
202
+ fi
203
+
204
+ exit 0
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@ramarivera/coding-buddy",
3
+ "version": "0.4.0-alpha.1",
4
+ "description": "Persistent coding companion for Claude Code and pi",
5
+ "type": "module",
6
+ "bin": {
7
+ "coding-buddy": "./cli/index.ts"
8
+ },
9
+ "scripts": {
10
+ "server": "bun run server/index.ts",
11
+ "pick": "bun run cli/pick.ts",
12
+ "hunt": "bun run cli/hunt.ts",
13
+ "install-buddy": "bun run cli/install.ts",
14
+ "show": "bun run cli/show.ts",
15
+ "doctor": "bun run cli/doctor.ts",
16
+ "test-statusline": "bun run cli/test-statusline.ts",
17
+ "backup": "bun run cli/backup.ts",
18
+ "settings": "bun run cli/settings.ts",
19
+ "disable": "bun run cli/disable.ts",
20
+ "enable": "bun run cli/install.ts",
21
+ "uninstall": "bun run cli/uninstall.ts",
22
+ "help": "bun run cli/index.ts help",
23
+ "test": "bun test",
24
+ "typecheck": "tsc --noEmit"
25
+ },
26
+ "files": [
27
+ "server/",
28
+ "cli/",
29
+ "skills/",
30
+ "hooks/",
31
+ "statusline/",
32
+ ".claude-plugin/",
33
+ "!**/*.test.ts"
34
+ ],
35
+ "keywords": [
36
+ "claude-code",
37
+ "buddy",
38
+ "companion",
39
+ "mcp",
40
+ "terminal-pet",
41
+ "tamagotchi"
42
+ ],
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/ramarivera/claude-buddy.git"
46
+ },
47
+ "homepage": "https://github.com/ramarivera/claude-buddy#readme",
48
+ "license": "MIT",
49
+ "publishConfig": {
50
+ "access": "public",
51
+ "registry": "https://registry.npmjs.org/"
52
+ },
53
+ "dependencies": {
54
+ "@modelcontextprotocol/sdk": "^1.12.1"
55
+ },
56
+ "devDependencies": {
57
+ "bun-types": "^1.3.11",
58
+ "typescript": "^6.0.2"
59
+ }
60
+ }