@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.
@@ -0,0 +1,389 @@
1
+ #!/usr/bin/env bash
2
+ # claude-buddy status line — animated, right-aligned multi-line companion
3
+ #
4
+ # Animation matches the original:
5
+ # - 500ms per tick, sequence: [0,0,0,0,1,0,0,0,-1,0,0,2,0,0,0]
6
+ # - Frame -1 = blink (eyes replaced with "-")
7
+ # - Frames 0,1,2 = the 3 idle art variants per species
8
+ # - refreshInterval: 1s in settings.json cycles the animation
9
+ #
10
+ # Uses Braille Blank (U+2800) for padding — survives JS .trim()
11
+
12
+ STATE="$HOME/.claude-buddy/status.json"
13
+ # Session ID: sanitized tmux pane number, or "default" outside tmux
14
+ SID="${TMUX_PANE#%}"
15
+ SID="${SID:-default}"
16
+
17
+ [ -f "$STATE" ] || exit 0
18
+
19
+ MUTED=$(jq -r '.muted // false' "$STATE" 2>/dev/null)
20
+ [ "$MUTED" = "true" ] && exit 0
21
+
22
+ NAME=$(jq -r '.name // ""' "$STATE" 2>/dev/null)
23
+ [ -z "$NAME" ] && exit 0
24
+
25
+ SPECIES=$(jq -r '.species // ""' "$STATE" 2>/dev/null)
26
+ HAT=$(jq -r '.hat // "none"' "$STATE" 2>/dev/null)
27
+ RARITY=$(jq -r '.rarity // "common"' "$STATE" 2>/dev/null)
28
+ REACTION=$(jq -r '.reaction // ""' "$STATE" 2>/dev/null)
29
+ ACHIEVEMENT=$(jq -r '.achievement // ""' "$STATE" 2>/dev/null)
30
+ # eye is written to status.json by writeStatusState (v2+); fall back to "°"
31
+ E=$(jq -r '.eye // "°"' "$STATE" 2>/dev/null)
32
+
33
+ cat > /dev/null # drain stdin
34
+
35
+ # ─── Animation: frame from timestamp ─────────────────────────────────────────
36
+ # Original sequence: [0,0,0,0,1,0,0,0,-1,0,0,2,0,0,0] with 500ms ticks
37
+ # Since refreshInterval=1s, each call = 2 ticks. We use seconds as index.
38
+ SEQ=(0 0 0 0 1 0 0 0 -1 0 0 2 0 0 0)
39
+ SEQ_LEN=${#SEQ[@]}
40
+ NOW=$(date +%s)
41
+ FRAME_IDX=$(( NOW % SEQ_LEN ))
42
+ FRAME=${SEQ[$FRAME_IDX]}
43
+
44
+ BLINK=0
45
+ if [ "$FRAME" -eq -1 ]; then
46
+ BLINK=1
47
+ FRAME=0
48
+ fi
49
+
50
+ # ─── Rarity color (pC4 = dark theme, the default) ────────────────────────────
51
+ NC=$'\033[0m'
52
+ case "$RARITY" in
53
+ common) C=$'\033[38;2;153;153;153m' ;;
54
+ uncommon) C=$'\033[38;2;78;186;101m' ;;
55
+ rare) C=$'\033[38;2;177;185;249m' ;;
56
+ epic) C=$'\033[38;2;175;135;255m' ;;
57
+ legendary) C=$'\033[38;2;255;193;7m' ;;
58
+ *) C=$'\033[0m' ;;
59
+ esac
60
+
61
+ B=$'\xe2\xa0\x80' # Braille Blank U+2800
62
+
63
+ # ─── Terminal width ──────────────────────────────────────────────────────────
64
+ COLS=0
65
+ PID=$$
66
+ for _ in 1 2 3 4 5; do
67
+ PID=$(ps -o ppid= -p "$PID" 2>/dev/null | tr -d ' ')
68
+ [ -z "$PID" ] || [ "$PID" = "1" ] && break
69
+ PTY=$(readlink "/proc/${PID}/fd/0" 2>/dev/null)
70
+ if [ -c "$PTY" ] 2>/dev/null; then
71
+ COLS=$(stty size < "$PTY" 2>/dev/null | awk '{print $2}')
72
+ [ "${COLS:-0}" -gt 40 ] 2>/dev/null && break
73
+ fi
74
+ done
75
+ [ "${COLS:-0}" -lt 40 ] 2>/dev/null && COLS=${COLUMNS:-0}
76
+ [ "${COLS:-0}" -lt 40 ] 2>/dev/null && COLS=125
77
+
78
+ # ─── Species art: 3 frames each (F0, F1, F2) ────────────────────────────────
79
+ # Each frame = 4 lines (L1..L4). Selected by $FRAME.
80
+ case "$SPECIES" in
81
+ duck)
82
+ case $FRAME in
83
+ 0) L1=" __"; L2=" <(${E} )___"; L3=" ( ._>"; L4=" \`--'" ;;
84
+ 1) L1=" __"; L2=" <(${E} )___"; L3=" ( ._>"; L4=" \`--'~" ;;
85
+ 2) L1=" __"; L2=" <(${E} )___"; L3=" ( .__>"; L4=" \`--'" ;;
86
+ esac ;;
87
+ goose)
88
+ case $FRAME in
89
+ 0) L1=" (${E}>"; L2=" ||"; L3=" _(__)_"; L4=" ^^^^" ;;
90
+ 1) L1=" (${E}>"; L2=" ||"; L3=" _(__)_"; L4=" ^^^^" ;;
91
+ 2) L1=" (${E}>>"; L2=" ||"; L3=" _(__)_"; L4=" ^^^^" ;;
92
+ esac ;;
93
+ blob)
94
+ case $FRAME in
95
+ 0) L1=" .----."; L2="( ${E} ${E} )"; L3="( )"; L4=" \`----'" ;;
96
+ 1) L1=".------."; L2="( ${E} ${E} )"; L3="( )"; L4="\`------'" ;;
97
+ 2) L1=" .--."; L2=" (${E} ${E})"; L3=" ( )"; L4=" \`--'" ;;
98
+ esac ;;
99
+ cat)
100
+ case $FRAME in
101
+ 0) L1=" /\\_/\\"; L2="( ${E} ${E})"; L3="( ω )"; L4="(\")_(\")" ;;
102
+ 1) L1=" /\\_/\\"; L2="( ${E} ${E})"; L3="( ω )"; L4="(\")_(\")~" ;;
103
+ 2) L1=" /\\-/\\"; L2="( ${E} ${E})"; L3="( ω )"; L4="(\")_(\")" ;;
104
+ esac ;;
105
+ dragon)
106
+ case $FRAME in
107
+ 0) L1="/^\\ /^\\"; L2="< ${E} ${E} >"; L3="( ~~ )"; L4=" \`-vvvv-'" ;;
108
+ 1) L1="/^\\ /^\\"; L2="< ${E} ${E} >"; L3="( )"; L4=" \`-vvvv-'" ;;
109
+ 2) L1="/^\\ /^\\"; L2="< ${E} ${E} >"; L3="( ~~ )"; L4=" \`-vvvv-'" ;;
110
+ esac ;;
111
+ octopus)
112
+ case $FRAME in
113
+ 0) L1=" .----."; L2="( ${E} ${E} )"; L3="(______)"; L4="/\\/\\/\\/\\" ;;
114
+ 1) L1=" .----."; L2="( ${E} ${E} )"; L3="(______)"; L4="\\/\\/\\/\\/" ;;
115
+ 2) L1=" .----."; L2="( ${E} ${E} )"; L3="(______)"; L4="/\\/\\/\\/\\" ;;
116
+ esac ;;
117
+ owl)
118
+ case $FRAME in
119
+ 0) L1=" /\\ /\\"; L2="((${E})(${E}))"; L3="( >< )"; L4=" \`----'" ;;
120
+ 1) L1=" /\\ /\\"; L2="((${E})(${E}))"; L3="( >< )"; L4=" .----." ;;
121
+ 2) L1=" /\\ /\\"; L2="((${E})(-))"; L3="( >< )"; L4=" \`----'" ;;
122
+ esac ;;
123
+ penguin)
124
+ case $FRAME in
125
+ 0) L1=" .---."; L2=" (${E}>${E})"; L3="/( )\\"; L4=" \`---'" ;;
126
+ 1) L1=" .---."; L2=" (${E}>${E})"; L3="|( )|"; L4=" \`---'" ;;
127
+ 2) L1=" .---."; L2=" (${E}>${E})"; L3="/( )\\"; L4=" \`---'" ;;
128
+ esac ;;
129
+ turtle)
130
+ case $FRAME in
131
+ 0) L1=" _,--._"; L2="( ${E} ${E} )"; L3="[______]"; L4="\`\` \`\`" ;;
132
+ 1) L1=" _,--._"; L2="( ${E} ${E} )"; L3="[______]"; L4=" \`\` \`\`" ;;
133
+ 2) L1=" _,--._"; L2="( ${E} ${E} )"; L3="[======]"; L4="\`\` \`\`" ;;
134
+ esac ;;
135
+ snail)
136
+ case $FRAME in
137
+ 0) L1="${E} .--."; L2="\\ ( @ )"; L3=" \\_\`--'"; L4="~~~~~~~" ;;
138
+ 1) L1=" ${E} .--."; L2="| ( @ )"; L3=" \\_\`--'"; L4="~~~~~~~" ;;
139
+ 2) L1="${E} .--."; L2="\\ ( @ )"; L3=" \\_\`--'"; L4=" ~~~~~~" ;;
140
+ esac ;;
141
+ ghost)
142
+ case $FRAME in
143
+ 0) L1=" .----."; L2="/ ${E} ${E} \\"; L3="| |"; L4="~\`~\`\`~\`~" ;;
144
+ 1) L1=" .----."; L2="/ ${E} ${E} \\"; L3="| |"; L4="\`~\`~~\`~\`" ;;
145
+ 2) L1=" .----."; L2="/ ${E} ${E} \\"; L3="| |"; L4="~~\`~~\`~~" ;;
146
+ esac ;;
147
+ axolotl)
148
+ case $FRAME in
149
+ 0) L1="}~(____)~{"; L2="}~(${E}..${E})~{"; L3=" (.--.)"; L4=" (_/\\_)" ;;
150
+ 1) L1="~}(____){~"; L2="~}(${E}..${E}){~"; L3=" (.--.)"; L4=" (_/\\_)" ;;
151
+ 2) L1="}~(____)~{"; L2="}~(${E}..${E})~{"; L3=" ( -- )"; L4=" ~_/\\_~" ;;
152
+ esac ;;
153
+ capybara)
154
+ case $FRAME in
155
+ 0) L1="n______n"; L2="( ${E} ${E} )"; L3="( oo )"; L4="\`------'" ;;
156
+ 1) L1="n______n"; L2="( ${E} ${E} )"; L3="( Oo )"; L4="\`------'" ;;
157
+ 2) L1="u______n"; L2="( ${E} ${E} )"; L3="( oo )"; L4="\`------'" ;;
158
+ esac ;;
159
+ cactus)
160
+ case $FRAME in
161
+ 0) L1="n ____ n"; L2="||${E} ${E}||"; L3="|_| |_|"; L4=" | |" ;;
162
+ 1) L1=" ____"; L2="n|${E} ${E}|n"; L3="|_| |_|"; L4=" | |" ;;
163
+ 2) L1="n ____ n"; L2="||${E} ${E}||"; L3="|_| |_|"; L4=" | |" ;;
164
+ esac ;;
165
+ robot)
166
+ case $FRAME in
167
+ 0) L1=" .[||]."; L2="[ ${E} ${E} ]"; L3="[ ==== ]"; L4="\`------'" ;;
168
+ 1) L1=" .[||]."; L2="[ ${E} ${E} ]"; L3="[ -==- ]"; L4="\`------'" ;;
169
+ 2) L1=" .[||]."; L2="[ ${E} ${E} ]"; L3="[ ==== ]"; L4="\`------'" ;;
170
+ esac ;;
171
+ rabbit)
172
+ case $FRAME in
173
+ 0) L1=" (\\__/)"; L2="( ${E} ${E} )"; L3="=( .. )="; L4="(\")__(\")" ;;
174
+ 1) L1=" (|__/)"; L2="( ${E} ${E} )"; L3="=( .. )="; L4="(\")__(\")" ;;
175
+ 2) L1=" (\\__/)"; L2="( ${E} ${E} )"; L3="=( . . )="; L4="(\")__(\")" ;;
176
+ esac ;;
177
+ mushroom)
178
+ case $FRAME in
179
+ 0) L1="-o-OO-o-"; L2="(________)"; L3=" |${E}${E}|"; L4=" |__|" ;;
180
+ 1) L1="-O-oo-O-"; L2="(________)"; L3=" |${E}${E}|"; L4=" |__|" ;;
181
+ 2) L1="-o-OO-o-"; L2="(________)"; L3=" |${E}${E}|"; L4=" |__|" ;;
182
+ esac ;;
183
+ chonk)
184
+ case $FRAME in
185
+ 0) L1="/\\ /\\"; L2="( ${E} ${E} )"; L3="( .. )"; L4="\`------'" ;;
186
+ 1) L1="/\\ /|"; L2="( ${E} ${E} )"; L3="( .. )"; L4="\`------'" ;;
187
+ 2) L1="/\\ /\\"; L2="( ${E} ${E} )"; L3="( .. )"; L4="\`------'~" ;;
188
+ esac ;;
189
+ *)
190
+ L1="(${E}${E})"; L2="( )"; L3=""; L4="" ;;
191
+ esac
192
+
193
+ # ─── Blink: replace eyes with "-" ────────────────────────────────────────────
194
+ if [ "$BLINK" -eq 1 ]; then
195
+ L1="${L1//${E}/-}"
196
+ L2="${L2//${E}/-}"
197
+ L3="${L3//${E}/-}"
198
+ L4="${L4//${E}/-}"
199
+ fi
200
+
201
+ # ─── Hat ──────────────────────────────────────────────────────────────────────
202
+ HAT_LINE=""
203
+ case "$HAT" in
204
+ crown) HAT_LINE=" \\^^^/" ;;
205
+ tophat) HAT_LINE=" [___]" ;;
206
+ propeller) HAT_LINE=" -+-" ;;
207
+ halo) HAT_LINE=" ( )" ;;
208
+ wizard) HAT_LINE=" /^\\" ;;
209
+ beanie) HAT_LINE=" (___)" ;;
210
+ tinyduck) HAT_LINE=" ,>" ;;
211
+ esac
212
+
213
+ # ─── Reaction bubble (with TTL check) ────────────────────────────────────────
214
+ BUBBLE=""
215
+ if [ -n "$ACHIEVEMENT" ] && [ "$ACHIEVEMENT" != "null" ] && [ "$ACHIEVEMENT" != "" ]; then
216
+ BUBBLE=$'\xf0\x9f\x8f\x86'" $ACHIEVEMENT"
217
+ fi
218
+ REACTION_FILE="$HOME/.claude-buddy/reaction.$SID.json"
219
+ REACTION_TTL=0
220
+ CONFIG_FILE="$HOME/.claude-buddy/config.json"
221
+ if [ -f "$CONFIG_FILE" ]; then
222
+ _ttl=$(jq -r '.reactionTTL // 0' "$CONFIG_FILE" 2>/dev/null || echo 0)
223
+ case "$_ttl" in ''|*[!0-9]*) ;; *) REACTION_TTL="$_ttl" ;; esac
224
+ fi
225
+ if [ -n "$REACTION" ] && [ "$REACTION" != "null" ] && [ "$REACTION" != "" ]; then
226
+ FRESH=0
227
+ if [ "$REACTION_TTL" -eq 0 ]; then
228
+ FRESH=1
229
+ elif [ -f "$REACTION_FILE" ]; then
230
+ TS=$(jq -r '.timestamp // 0' "$REACTION_FILE" 2>/dev/null || echo 0)
231
+ if [ "$TS" != "0" ]; then
232
+ NOW=$(date +%s)
233
+ AGE=$(( NOW - TS / 1000 ))
234
+ [ "$AGE" -lt "$REACTION_TTL" ] && FRESH=1
235
+ fi
236
+ fi
237
+ if [ "$FRESH" -eq 1 ]; then
238
+ if [ -n "$BUBBLE" ]; then
239
+ BUBBLE="$BUBBLE | \"${REACTION}\""
240
+ else
241
+ BUBBLE="\"${REACTION}\""
242
+ fi
243
+ fi
244
+ fi
245
+
246
+ # ─── Build art lines ─────────────────────────────────────────────────────────
247
+ ART_LINES=("$L1" "$L2" "$L3")
248
+ [ -n "$L4" ] && ART_LINES+=("$L4")
249
+
250
+ # Center the name
251
+ NAME_LEN=${#NAME}
252
+ ART_CENTER=4
253
+ NAME_PAD=$(( ART_CENTER - NAME_LEN / 2 ))
254
+ [ "$NAME_PAD" -lt 0 ] && NAME_PAD=0
255
+ NAME_LINE="$(printf '%*s%s' "$NAME_PAD" '' "$NAME")"
256
+
257
+ # ─── Build all art lines ──────────────────────────────────────────────────────
258
+ DIM=$'\033[2;3m'
259
+
260
+ ALL_LINES=()
261
+ ALL_COLORS=()
262
+ [ -n "$HAT_LINE" ] && { ALL_LINES+=("$HAT_LINE"); ALL_COLORS+=("$C"); }
263
+ for line in "${ART_LINES[@]}"; do
264
+ ALL_LINES+=("$line"); ALL_COLORS+=("$C")
265
+ done
266
+ ALL_LINES+=("$NAME_LINE"); ALL_COLORS+=("$DIM")
267
+
268
+ ART_W=14
269
+ ART_COUNT=${#ALL_LINES[@]}
270
+
271
+ # ─── Speech bubble (left of art, word-wrapped) ──────────────────────────────
272
+ # Strip the quotes we added earlier
273
+ BUBBLE_TEXT=""
274
+ if [ -n "$BUBBLE" ]; then
275
+ BUBBLE_TEXT="${BUBBLE%\"}"
276
+ BUBBLE_TEXT="${BUBBLE_TEXT#\"}"
277
+ fi
278
+
279
+ # ─── Word-wrap bubble text ────────────────────────────────────────────────────
280
+ INNER_W=28
281
+ TEXT_LINES=()
282
+ if [ -n "$BUBBLE_TEXT" ]; then
283
+ WORDS=($BUBBLE_TEXT)
284
+ CUR_LINE=""
285
+ for word in "${WORDS[@]}"; do
286
+ if [ -z "$CUR_LINE" ]; then
287
+ CUR_LINE="$word"
288
+ elif [ $(( ${#CUR_LINE} + 1 + ${#word} )) -le $INNER_W ]; then
289
+ CUR_LINE="$CUR_LINE $word"
290
+ else
291
+ TEXT_LINES+=("$CUR_LINE")
292
+ CUR_LINE="$word"
293
+ fi
294
+ done
295
+ [ -n "$CUR_LINE" ] && TEXT_LINES+=("$CUR_LINE")
296
+ fi
297
+
298
+ TEXT_COUNT=${#TEXT_LINES[@]}
299
+
300
+ # Build box as plain strings (no ANSI). Color applied at output time.
301
+ # Box display width = INNER_W + 4: "| " + text(INNER_W) + " |"
302
+ BOX_W=$(( INNER_W + 4 ))
303
+ BUBBLE_LINES=()
304
+ BUBBLE_TYPES=() # "border" or "text" — determines coloring
305
+ if [ $TEXT_COUNT -gt 0 ]; then
306
+ # Top border
307
+ BORDER=$(printf '%*s' "$(( BOX_W - 2 ))" '' | tr ' ' '-')
308
+ BUBBLE_LINES+=(".${BORDER}.")
309
+ BUBBLE_TYPES+=("border")
310
+ # Text rows: "| text padded |"
311
+ for tl in "${TEXT_LINES[@]}"; do
312
+ tpad=$(( INNER_W - ${#tl} ))
313
+ [ "$tpad" -lt 0 ] && tpad=0
314
+ padding=$(printf '%*s' "$tpad" '')
315
+ BUBBLE_LINES+=("| ${tl}${padding} |")
316
+ BUBBLE_TYPES+=("text")
317
+ done
318
+ # Bottom border
319
+ BUBBLE_LINES+=("\`${BORDER}'")
320
+ BUBBLE_TYPES+=("border")
321
+ fi
322
+
323
+ BUBBLE_COUNT=${#BUBBLE_LINES[@]}
324
+
325
+ # ─── Right-align with bubble box to the left ─────────────────────────────────
326
+ GAP=2
327
+ if [ $BUBBLE_COUNT -gt 0 ]; then
328
+ TOTAL_W=$(( BOX_W + GAP + ART_W ))
329
+ else
330
+ TOTAL_W=$ART_W
331
+ fi
332
+ MARGIN=8
333
+ PAD=$(( COLS - TOTAL_W - MARGIN ))
334
+ [ "$PAD" -lt 0 ] && PAD=0
335
+
336
+ SPACER=$(printf "${B}%${PAD}s" "")
337
+ GAP_STR=$(printf '%*s' "$GAP" '')
338
+
339
+ # Vertically center bubble box on the art
340
+ BUBBLE_START=0
341
+ if [ $BUBBLE_COUNT -gt 0 ] && [ $BUBBLE_COUNT -lt $ART_COUNT ]; then
342
+ BUBBLE_START=$(( (ART_COUNT - BUBBLE_COUNT) / 2 ))
343
+ fi
344
+
345
+ # ─── Find the connector line (middle text line → points to buddy's mouth) ─────
346
+ # The connector goes on the middle text row of the bubble
347
+ CONNECTOR_BI=-1
348
+ if [ $BUBBLE_COUNT -gt 2 ]; then
349
+ # text rows are indices 1..(BUBBLE_COUNT-2), pick the middle one
350
+ FIRST_TEXT=1
351
+ LAST_TEXT=$(( BUBBLE_COUNT - 2 ))
352
+ CONNECTOR_BI=$(( (FIRST_TEXT + LAST_TEXT) / 2 ))
353
+ fi
354
+
355
+ # ─── Output: merged bubble box + connector + art per line ─────────────────────
356
+ for (( i=0; i<ART_COUNT; i++ )); do
357
+ art_part="${ALL_COLORS[$i]}${ALL_LINES[$i]}${NC}"
358
+
359
+ if [ $BUBBLE_COUNT -gt 0 ]; then
360
+ bi=$(( i - BUBBLE_START ))
361
+ if [ $bi -ge 0 ] && [ $bi -lt $BUBBLE_COUNT ]; then
362
+ bline="${BUBBLE_LINES[$bi]}"
363
+ btype="${BUBBLE_TYPES[$bi]}"
364
+
365
+ # Connector: "-- " on the middle text line, spaces otherwise
366
+ if [ $bi -eq $CONNECTOR_BI ]; then
367
+ gap="${C}--${NC} "
368
+ else
369
+ gap=" "
370
+ fi
371
+
372
+ if [ "$btype" = "border" ]; then
373
+ echo "${SPACER}${C}${bline}${NC}${gap}${art_part}"
374
+ else
375
+ pipe_l="${bline:0:1}"
376
+ pipe_r="${bline: -1}"
377
+ inner="${bline:1:$(( ${#bline} - 2 ))}"
378
+ echo "${SPACER}${C}${pipe_l}${NC}${DIM}${inner}${NC}${C}${pipe_r}${NC}${gap}${art_part}"
379
+ fi
380
+ else
381
+ empty=$(printf '%*s' "$BOX_W" '')
382
+ echo "${SPACER}${empty} ${art_part}"
383
+ fi
384
+ else
385
+ echo "${SPACER}${art_part}"
386
+ fi
387
+ done
388
+
389
+ exit 0