@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/.claude-plugin/marketplace.json +40 -0
- package/.claude-plugin/plugin.json +28 -0
- package/LICENSE +21 -0
- package/README.md +451 -0
- package/cli/backup.ts +336 -0
- package/cli/disable.ts +94 -0
- package/cli/doctor.ts +220 -0
- package/cli/hunt.ts +167 -0
- package/cli/index.ts +115 -0
- package/cli/install.ts +335 -0
- package/cli/pick.ts +492 -0
- package/cli/settings.ts +68 -0
- package/cli/show.ts +31 -0
- package/cli/test-statusline.sh +41 -0
- package/cli/test-statusline.ts +122 -0
- package/cli/uninstall.ts +110 -0
- package/cli/verify.ts +19 -0
- package/hooks/buddy-comment.sh +65 -0
- package/hooks/hooks.json +35 -0
- package/hooks/name-react.sh +176 -0
- package/hooks/react.sh +204 -0
- package/package.json +60 -0
- package/server/achievements.ts +445 -0
- package/server/art.ts +376 -0
- package/server/engine.ts +448 -0
- package/server/index.ts +774 -0
- package/server/reactions.ts +187 -0
- package/server/state.ts +409 -0
- package/skills/buddy/SKILL.md +59 -0
- package/statusline/buddy-status.sh +389 -0
|
@@ -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
|