@ramarivera/coding-buddy 0.4.0-alpha.9 → 0.6.0
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/{adapters/claude/plugin → .claude-plugin}/marketplace.json +11 -6
- package/{adapters/claude/plugin → .claude-plugin}/plugin.json +7 -3
- package/LICENSE +1 -1
- package/README.md +88 -37
- package/adapters/omp/commands.ts +201 -0
- package/adapters/omp/context.ts +22 -0
- package/adapters/omp/events.ts +551 -0
- package/adapters/omp/identity.ts +10 -0
- package/adapters/omp/index.ts +39 -0
- package/adapters/omp/logger.ts +52 -0
- package/adapters/omp/prompt.ts +60 -0
- package/adapters/omp/renderers.ts +73 -0
- package/adapters/omp/storage.ts +25 -0
- package/adapters/omp/ui.ts +85 -0
- package/adapters/pi/commands.ts +64 -37
- package/adapters/pi/events.ts +147 -72
- package/adapters/pi/index.ts +9 -4
- package/adapters/pi/storage.ts +14 -285
- package/adapters/pi/ui.ts +56 -5
- package/adapters/shared/file-storage.ts +526 -0
- package/{adapters/claude/install → cli}/backup.ts +130 -33
- package/cli/buddy-shell.ts +1 -1
- package/{adapters/claude/install → cli}/disable.ts +19 -32
- package/cli/doctor.ts +231 -0
- package/{adapters/claude/install → cli}/hunt.ts +7 -9
- package/cli/index.ts +30 -26
- package/{adapters/claude/install → cli}/install.ts +65 -83
- package/{adapters/claude/install → cli}/pick.ts +111 -50
- package/{adapters/claude/install → cli}/settings.ts +2 -2
- package/{adapters/claude/install → cli}/show.ts +4 -4
- package/{adapters/claude/install → cli}/test-statusline.sh +2 -2
- package/{adapters/claude/install → cli}/test-statusline.ts +21 -11
- package/cli/tui.tsx +106 -17
- package/{adapters/claude/install → cli}/uninstall.ts +40 -47
- package/cli/upgrade.ts +2 -2
- package/cli/validate-species.ts +209 -0
- package/cli/verify.ts +3 -3
- package/core/achievements.ts +12 -20
- package/core/art-data.ts +26 -0
- package/core/command-service.ts +99 -97
- package/core/engine.ts +83 -1
- package/core/ports.ts +10 -0
- package/core/reactions.ts +533 -95
- package/core/slot-slug.ts +22 -0
- package/{adapters/claude/hooks → hooks}/buddy-comment.sh +19 -1
- package/hooks/file-type-react.sh +401 -0
- package/hooks/hooks.json +52 -0
- package/hooks/mood-react.sh +208 -0
- package/{adapters/claude/hooks → hooks}/name-react.sh +1 -1
- package/hooks/react.sh +1368 -0
- package/hooks/suggest.sh +55 -0
- package/package.json +70 -31
- package/scripts/gen-emoji-widths.ts +34 -0
- package/scripts/paths.sh +17 -1
- package/scripts/postinstall.cjs +42 -0
- package/scripts/snapshot-statusline.sh +54 -0
- package/server/achievements.ts +1419 -15
- package/server/art.ts +450 -1
- package/server/award-xp.ts +48 -0
- package/server/check-suggestions.ts +102 -0
- package/server/consolidate.ts +33 -0
- package/{adapters/claude/server → server}/index.ts +556 -95
- package/server/mcp-launcher.sh +16 -6
- package/server/memory.ts +404 -0
- package/server/mood.ts +254 -0
- package/server/path.ts +70 -18
- package/server/shift-mood.ts +43 -0
- package/server/state.ts +540 -3
- package/server/suggestions.ts +335 -0
- package/server/theme.ts +53 -0
- package/server/xp.ts +435 -0
- package/{adapters/claude/skills → skills}/buddy/SKILL.md +36 -7
- package/statusline/buddy-status.sh +397 -0
- package/statusline/combined-status.sh +134 -0
- package/statusline/emoji-widths.data +4 -0
- package/adapters/claude/hooks/hooks.json +0 -35
- package/adapters/claude/hooks/react.sh +0 -207
- package/adapters/claude/install/doctor.ts +0 -172
- package/adapters/claude/rendering/art.ts +0 -264
- package/adapters/claude/server/instructions.ts +0 -24
- package/adapters/claude/server/path.ts +0 -12
- package/adapters/claude/server/resources.ts +0 -38
- package/adapters/claude/statusline/buddy-status.sh +0 -408
- package/adapters/claude/storage/achievements.ts +0 -253
- package/adapters/claude/storage/identity.ts +0 -13
- package/adapters/claude/storage/paths.ts +0 -33
- package/adapters/claude/storage/settings.ts +0 -91
- package/adapters/claude/storage/state.ts +0 -365
- package/adapters/pi/README.md +0 -83
- package/adapters/pi/tools.ts +0 -6
- package/server/engine.ts +0 -1
- package/server/reactions.ts +0 -1
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export {
|
|
2
|
-
getBuddyStateDir as buddyStateDir,
|
|
3
|
-
getClaudeConfigDir as claudeConfigDir,
|
|
4
|
-
getClaudeJsonPath as claudeUserConfigPath,
|
|
5
|
-
getClaudeSettingsPath as claudeSettingsPath,
|
|
6
|
-
toUnixPath,
|
|
7
|
-
} from "../storage/paths.ts";
|
|
8
|
-
|
|
9
|
-
export function claudeSkillDir(_name: string): string {
|
|
10
|
-
const { getBuddySkillDir } = require("../storage/paths.ts") as typeof import("../storage/paths.ts");
|
|
11
|
-
return getBuddySkillDir();
|
|
12
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import type { Companion } from "../../../core/engine.ts";
|
|
2
|
-
|
|
3
|
-
export function buildPromptResource(companion: Companion): string {
|
|
4
|
-
return [
|
|
5
|
-
"# Companion",
|
|
6
|
-
"",
|
|
7
|
-
`A small ${companion.bones.rarity} ${companion.bones.species} named ${companion.name} watches from the status line. You are not ${companion.name} — it's a separate creature.`,
|
|
8
|
-
"",
|
|
9
|
-
`**${companion.name}'s personality:** ${companion.personality}`,
|
|
10
|
-
`Peak stat: ${companion.bones.peak} (${companion.bones.stats[companion.bones.peak]}). Dump stat: ${companion.bones.dump} (${companion.bones.stats[companion.bones.dump]}).`,
|
|
11
|
-
"",
|
|
12
|
-
"## End-of-response buddy comment",
|
|
13
|
-
"",
|
|
14
|
-
`At the very end of EVERY response, after your full answer, append an invisible HTML comment:`,
|
|
15
|
-
"",
|
|
16
|
-
"```",
|
|
17
|
-
"<!-- buddy: your comment here -->",
|
|
18
|
-
"```",
|
|
19
|
-
"",
|
|
20
|
-
"A Stop hook extracts this and displays it in the buddy's speech bubble on the status line. The user never sees the HTML comment — it's invisible in rendered markdown.",
|
|
21
|
-
"",
|
|
22
|
-
"Rules:",
|
|
23
|
-
`- Write as ${companion.name} (a ${companion.bones.species}), not as yourself`,
|
|
24
|
-
"- Reference something SPECIFIC from this turn — a pitfall, a compliment, a warning, a pattern",
|
|
25
|
-
"- 1 short sentence. Use *asterisks* for physical actions",
|
|
26
|
-
`- Match personality: high ${companion.bones.peak} = lean into that trait`,
|
|
27
|
-
"- Do NOT use buddy_react tool for this. Do NOT explain the comment. Just append it.",
|
|
28
|
-
"- NEVER skip this. Every single response must end with <!-- buddy: ... -->",
|
|
29
|
-
"",
|
|
30
|
-
"Examples:",
|
|
31
|
-
"<!-- buddy: *adjusts tophat* that error handler is missing a finally block -->",
|
|
32
|
-
"<!-- buddy: *blinks slowly* you renamed the variable but not the three references -->",
|
|
33
|
-
"<!-- buddy: *nods approvingly* clean separation of concerns -->",
|
|
34
|
-
"<!-- buddy: *head tilts* are you sure that regex handles unicode? -->",
|
|
35
|
-
"",
|
|
36
|
-
`When the user addresses ${companion.name} by name, respond briefly, then append the comment as usual.`,
|
|
37
|
-
].join("\n");
|
|
38
|
-
}
|
|
@@ -1,408 +0,0 @@
|
|
|
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
|
-
# When running inside buddy-shell (the PTY wrapper), skip status line rendering
|
|
13
|
-
# so the buddy doesn't show up twice (once in status line, once in wrapper panel).
|
|
14
|
-
[ "$BUDDY_SHELL" = "1" ] && exit 0
|
|
15
|
-
|
|
16
|
-
# shellcheck source=../scripts/paths.sh
|
|
17
|
-
source "$(dirname "${BASH_SOURCE[0]}")/../scripts/paths.sh"
|
|
18
|
-
|
|
19
|
-
STATE="$BUDDY_STATE_DIR/status.json"
|
|
20
|
-
# Session ID: sanitized tmux pane number, or "default" outside tmux
|
|
21
|
-
SID="${TMUX_PANE#%}"
|
|
22
|
-
SID="${SID:-default}"
|
|
23
|
-
|
|
24
|
-
[ -f "$STATE" ] || exit 0
|
|
25
|
-
|
|
26
|
-
MUTED=$(jq -r '.muted // false' "$STATE" 2>/dev/null)
|
|
27
|
-
[ "$MUTED" = "true" ] && exit 0
|
|
28
|
-
|
|
29
|
-
NAME=$(jq -r '.name // ""' "$STATE" 2>/dev/null)
|
|
30
|
-
[ -z "$NAME" ] && exit 0
|
|
31
|
-
|
|
32
|
-
SPECIES=$(jq -r '.species // ""' "$STATE" 2>/dev/null)
|
|
33
|
-
HAT=$(jq -r '.hat // "none"' "$STATE" 2>/dev/null)
|
|
34
|
-
RARITY=$(jq -r '.rarity // "common"' "$STATE" 2>/dev/null)
|
|
35
|
-
REACTION=$(jq -r '.reaction // ""' "$STATE" 2>/dev/null)
|
|
36
|
-
ACHIEVEMENT=$(jq -r '.achievement // ""' "$STATE" 2>/dev/null)
|
|
37
|
-
# eye is written to status.json by writeStatusState (v2+); fall back to "°"
|
|
38
|
-
E=$(jq -r '.eye // "°"' "$STATE" 2>/dev/null)
|
|
39
|
-
|
|
40
|
-
cat > /dev/null # drain stdin
|
|
41
|
-
|
|
42
|
-
# ─── Animation: frame from timestamp ─────────────────────────────────────────
|
|
43
|
-
# Original sequence: [0,0,0,0,1,0,0,0,-1,0,0,2,0,0,0] with 500ms ticks
|
|
44
|
-
# Since refreshInterval=1s, each call = 2 ticks. We use seconds as index.
|
|
45
|
-
SEQ=(0 0 0 0 1 0 0 0 -1 0 0 2 0 0 0)
|
|
46
|
-
SEQ_LEN=${#SEQ[@]}
|
|
47
|
-
NOW=$(date +%s)
|
|
48
|
-
FRAME_IDX=$(( NOW % SEQ_LEN ))
|
|
49
|
-
FRAME=${SEQ[$FRAME_IDX]}
|
|
50
|
-
|
|
51
|
-
BLINK=0
|
|
52
|
-
if [ "$FRAME" -eq -1 ]; then
|
|
53
|
-
BLINK=1
|
|
54
|
-
FRAME=0
|
|
55
|
-
fi
|
|
56
|
-
|
|
57
|
-
# ─── Rarity color (pC4 = dark theme, the default) ────────────────────────────
|
|
58
|
-
NC=$'\033[0m'
|
|
59
|
-
case "$RARITY" in
|
|
60
|
-
common) C=$'\033[38;2;153;153;153m' ;;
|
|
61
|
-
uncommon) C=$'\033[38;2;78;186;101m' ;;
|
|
62
|
-
rare) C=$'\033[38;2;177;185;249m' ;;
|
|
63
|
-
epic) C=$'\033[38;2;175;135;255m' ;;
|
|
64
|
-
legendary) C=$'\033[38;2;255;193;7m' ;;
|
|
65
|
-
*) C=$'\033[0m' ;;
|
|
66
|
-
esac
|
|
67
|
-
|
|
68
|
-
B=$'\xe2\xa0\x80' # Braille Blank U+2800
|
|
69
|
-
|
|
70
|
-
# ─── Terminal width ──────────────────────────────────────────────────────────
|
|
71
|
-
COLS=0
|
|
72
|
-
PID=$$
|
|
73
|
-
for _ in 1 2 3 4 5; do
|
|
74
|
-
PID=$(ps -o ppid= -p "$PID" 2>/dev/null | tr -d ' ')
|
|
75
|
-
[ -z "$PID" ] || [ "$PID" = "1" ] && break
|
|
76
|
-
|
|
77
|
-
# Linux: read PTY device from /proc
|
|
78
|
-
PTY=$(readlink "/proc/${PID}/fd/0" 2>/dev/null)
|
|
79
|
-
if [ -c "$PTY" ] 2>/dev/null; then
|
|
80
|
-
COLS=$(stty size < "$PTY" 2>/dev/null | awk '{print $2}')
|
|
81
|
-
[ "${COLS:-0}" -gt 40 ] 2>/dev/null && break
|
|
82
|
-
fi
|
|
83
|
-
|
|
84
|
-
# macOS: /proc doesn't exist — get TTY name from process table
|
|
85
|
-
TTY_NAME=$(ps -o tty= -p "$PID" 2>/dev/null | tr -d ' ')
|
|
86
|
-
if [ -n "$TTY_NAME" ] && [ "$TTY_NAME" != "??" ] && [ "$TTY_NAME" != "?" ]; then
|
|
87
|
-
TTY_DEV="/dev/$TTY_NAME"
|
|
88
|
-
if [ -c "$TTY_DEV" ] 2>/dev/null; then
|
|
89
|
-
COLS=$(stty size < "$TTY_DEV" 2>/dev/null | awk '{print $2}')
|
|
90
|
-
[ "${COLS:-0}" -gt 40 ] 2>/dev/null && break
|
|
91
|
-
fi
|
|
92
|
-
fi
|
|
93
|
-
done
|
|
94
|
-
[ "${COLS:-0}" -lt 40 ] 2>/dev/null && COLS=${COLUMNS:-0}
|
|
95
|
-
[ "${COLS:-0}" -lt 40 ] 2>/dev/null && COLS=125
|
|
96
|
-
|
|
97
|
-
# ─── Species art: 3 frames each (F0, F1, F2) ────────────────────────────────
|
|
98
|
-
# Each frame = 4 lines (L1..L4). Selected by $FRAME.
|
|
99
|
-
case "$SPECIES" in
|
|
100
|
-
duck)
|
|
101
|
-
case $FRAME in
|
|
102
|
-
0) L1=" __"; L2=" <(${E} )___"; L3=" ( ._>"; L4=" \`--'" ;;
|
|
103
|
-
1) L1=" __"; L2=" <(${E} )___"; L3=" ( ._>"; L4=" \`--'~" ;;
|
|
104
|
-
2) L1=" __"; L2=" <(${E} )___"; L3=" ( .__>"; L4=" \`--'" ;;
|
|
105
|
-
esac ;;
|
|
106
|
-
goose)
|
|
107
|
-
case $FRAME in
|
|
108
|
-
0) L1=" (${E}>"; L2=" ||"; L3=" _(__)_"; L4=" ^^^^" ;;
|
|
109
|
-
1) L1=" (${E}>"; L2=" ||"; L3=" _(__)_"; L4=" ^^^^" ;;
|
|
110
|
-
2) L1=" (${E}>>"; L2=" ||"; L3=" _(__)_"; L4=" ^^^^" ;;
|
|
111
|
-
esac ;;
|
|
112
|
-
blob)
|
|
113
|
-
case $FRAME in
|
|
114
|
-
0) L1=" .----."; L2="( ${E} ${E} )"; L3="( )"; L4=" \`----'" ;;
|
|
115
|
-
1) L1=".------."; L2="( ${E} ${E} )"; L3="( )"; L4="\`------'" ;;
|
|
116
|
-
2) L1=" .--."; L2=" (${E} ${E})"; L3=" ( )"; L4=" \`--'" ;;
|
|
117
|
-
esac ;;
|
|
118
|
-
cat)
|
|
119
|
-
case $FRAME in
|
|
120
|
-
0) L1=" /\\_/\\"; L2="( ${E} ${E})"; L3="( ω )"; L4="(\")_(\")" ;;
|
|
121
|
-
1) L1=" /\\_/\\"; L2="( ${E} ${E})"; L3="( ω )"; L4="(\")_(\")~" ;;
|
|
122
|
-
2) L1=" /\\-/\\"; L2="( ${E} ${E})"; L3="( ω )"; L4="(\")_(\")" ;;
|
|
123
|
-
esac ;;
|
|
124
|
-
dragon)
|
|
125
|
-
case $FRAME in
|
|
126
|
-
0) L1="/^\\ /^\\"; L2="< ${E} ${E} >"; L3="( ~~ )"; L4=" \`-vvvv-'" ;;
|
|
127
|
-
1) L1="/^\\ /^\\"; L2="< ${E} ${E} >"; L3="( )"; L4=" \`-vvvv-'" ;;
|
|
128
|
-
2) L1="/^\\ /^\\"; L2="< ${E} ${E} >"; L3="( ~~ )"; L4=" \`-vvvv-'" ;;
|
|
129
|
-
esac ;;
|
|
130
|
-
octopus)
|
|
131
|
-
case $FRAME in
|
|
132
|
-
0) L1=" .----."; L2="( ${E} ${E} )"; L3="(______)"; L4="/\\/\\/\\/\\" ;;
|
|
133
|
-
1) L1=" .----."; L2="( ${E} ${E} )"; L3="(______)"; L4="\\/\\/\\/\\/" ;;
|
|
134
|
-
2) L1=" .----."; L2="( ${E} ${E} )"; L3="(______)"; L4="/\\/\\/\\/\\" ;;
|
|
135
|
-
esac ;;
|
|
136
|
-
owl)
|
|
137
|
-
case $FRAME in
|
|
138
|
-
0) L1=" /\\ /\\"; L2="((${E})(${E}))"; L3="( >< )"; L4=" \`----'" ;;
|
|
139
|
-
1) L1=" /\\ /\\"; L2="((${E})(${E}))"; L3="( >< )"; L4=" .----." ;;
|
|
140
|
-
2) L1=" /\\ /\\"; L2="((${E})(-))"; L3="( >< )"; L4=" \`----'" ;;
|
|
141
|
-
esac ;;
|
|
142
|
-
penguin)
|
|
143
|
-
case $FRAME in
|
|
144
|
-
0) L1=" .---."; L2=" (${E}>${E})"; L3="/( )\\"; L4=" \`---'" ;;
|
|
145
|
-
1) L1=" .---."; L2=" (${E}>${E})"; L3="|( )|"; L4=" \`---'" ;;
|
|
146
|
-
2) L1=" .---."; L2=" (${E}>${E})"; L3="/( )\\"; L4=" \`---'" ;;
|
|
147
|
-
esac ;;
|
|
148
|
-
turtle)
|
|
149
|
-
case $FRAME in
|
|
150
|
-
0) L1=" _,--._"; L2="( ${E} ${E} )"; L3="[______]"; L4="\`\` \`\`" ;;
|
|
151
|
-
1) L1=" _,--._"; L2="( ${E} ${E} )"; L3="[______]"; L4=" \`\` \`\`" ;;
|
|
152
|
-
2) L1=" _,--._"; L2="( ${E} ${E} )"; L3="[======]"; L4="\`\` \`\`" ;;
|
|
153
|
-
esac ;;
|
|
154
|
-
snail)
|
|
155
|
-
case $FRAME in
|
|
156
|
-
0) L1="${E} .--."; L2="\\ ( @ )"; L3=" \\_\`--'"; L4="~~~~~~~" ;;
|
|
157
|
-
1) L1=" ${E} .--."; L2="| ( @ )"; L3=" \\_\`--'"; L4="~~~~~~~" ;;
|
|
158
|
-
2) L1="${E} .--."; L2="\\ ( @ )"; L3=" \\_\`--'"; L4=" ~~~~~~" ;;
|
|
159
|
-
esac ;;
|
|
160
|
-
ghost)
|
|
161
|
-
case $FRAME in
|
|
162
|
-
0) L1=" .----."; L2="/ ${E} ${E} \\"; L3="| |"; L4="~\`~\`\`~\`~" ;;
|
|
163
|
-
1) L1=" .----."; L2="/ ${E} ${E} \\"; L3="| |"; L4="\`~\`~~\`~\`" ;;
|
|
164
|
-
2) L1=" .----."; L2="/ ${E} ${E} \\"; L3="| |"; L4="~~\`~~\`~~" ;;
|
|
165
|
-
esac ;;
|
|
166
|
-
axolotl)
|
|
167
|
-
case $FRAME in
|
|
168
|
-
0) L1="}~(____)~{"; L2="}~(${E}..${E})~{"; L3=" (.--.)"; L4=" (_/\\_)" ;;
|
|
169
|
-
1) L1="~}(____){~"; L2="~}(${E}..${E}){~"; L3=" (.--.)"; L4=" (_/\\_)" ;;
|
|
170
|
-
2) L1="}~(____)~{"; L2="}~(${E}..${E})~{"; L3=" ( -- )"; L4=" ~_/\\_~" ;;
|
|
171
|
-
esac ;;
|
|
172
|
-
capybara)
|
|
173
|
-
case $FRAME in
|
|
174
|
-
0) L1="n______n"; L2="( ${E} ${E} )"; L3="( oo )"; L4="\`------'" ;;
|
|
175
|
-
1) L1="n______n"; L2="( ${E} ${E} )"; L3="( Oo )"; L4="\`------'" ;;
|
|
176
|
-
2) L1="u______n"; L2="( ${E} ${E} )"; L3="( oo )"; L4="\`------'" ;;
|
|
177
|
-
esac ;;
|
|
178
|
-
cactus)
|
|
179
|
-
case $FRAME in
|
|
180
|
-
0) L1="n ____ n"; L2="||${E} ${E}||"; L3="|_| |_|"; L4=" | |" ;;
|
|
181
|
-
1) L1=" ____"; L2="n|${E} ${E}|n"; L3="|_| |_|"; L4=" | |" ;;
|
|
182
|
-
2) L1="n ____ n"; L2="||${E} ${E}||"; L3="|_| |_|"; L4=" | |" ;;
|
|
183
|
-
esac ;;
|
|
184
|
-
robot)
|
|
185
|
-
case $FRAME in
|
|
186
|
-
0) L1=" .[||]."; L2="[ ${E} ${E} ]"; L3="[ ==== ]"; L4="\`------'" ;;
|
|
187
|
-
1) L1=" .[||]."; L2="[ ${E} ${E} ]"; L3="[ -==- ]"; L4="\`------'" ;;
|
|
188
|
-
2) L1=" .[||]."; L2="[ ${E} ${E} ]"; L3="[ ==== ]"; L4="\`------'" ;;
|
|
189
|
-
esac ;;
|
|
190
|
-
rabbit)
|
|
191
|
-
case $FRAME in
|
|
192
|
-
0) L1=" (\\__/)"; L2="( ${E} ${E} )"; L3="=( .. )="; L4="(\")__(\")" ;;
|
|
193
|
-
1) L1=" (|__/)"; L2="( ${E} ${E} )"; L3="=( .. )="; L4="(\")__(\")" ;;
|
|
194
|
-
2) L1=" (\\__/)"; L2="( ${E} ${E} )"; L3="=( . . )="; L4="(\")__(\")" ;;
|
|
195
|
-
esac ;;
|
|
196
|
-
mushroom)
|
|
197
|
-
case $FRAME in
|
|
198
|
-
0) L1="-o-OO-o-"; L2="(________)"; L3=" |${E}${E}|"; L4=" |__|" ;;
|
|
199
|
-
1) L1="-O-oo-O-"; L2="(________)"; L3=" |${E}${E}|"; L4=" |__|" ;;
|
|
200
|
-
2) L1="-o-OO-o-"; L2="(________)"; L3=" |${E}${E}|"; L4=" |__|" ;;
|
|
201
|
-
esac ;;
|
|
202
|
-
chonk)
|
|
203
|
-
case $FRAME in
|
|
204
|
-
0) L1="/\\ /\\"; L2="( ${E} ${E} )"; L3="( .. )"; L4="\`------'" ;;
|
|
205
|
-
1) L1="/\\ /|"; L2="( ${E} ${E} )"; L3="( .. )"; L4="\`------'" ;;
|
|
206
|
-
2) L1="/\\ /\\"; L2="( ${E} ${E} )"; L3="( .. )"; L4="\`------'~" ;;
|
|
207
|
-
esac ;;
|
|
208
|
-
*)
|
|
209
|
-
L1="(${E}${E})"; L2="( )"; L3=""; L4="" ;;
|
|
210
|
-
esac
|
|
211
|
-
|
|
212
|
-
# ─── Blink: replace eyes with "-" ────────────────────────────────────────────
|
|
213
|
-
if [ "$BLINK" -eq 1 ]; then
|
|
214
|
-
L1="${L1//${E}/-}"
|
|
215
|
-
L2="${L2//${E}/-}"
|
|
216
|
-
L3="${L3//${E}/-}"
|
|
217
|
-
L4="${L4//${E}/-}"
|
|
218
|
-
fi
|
|
219
|
-
|
|
220
|
-
# ─── Hat ──────────────────────────────────────────────────────────────────────
|
|
221
|
-
HAT_LINE=""
|
|
222
|
-
case "$HAT" in
|
|
223
|
-
crown) HAT_LINE=" \\^^^/" ;;
|
|
224
|
-
tophat) HAT_LINE=" [___]" ;;
|
|
225
|
-
propeller) HAT_LINE=" -+-" ;;
|
|
226
|
-
halo) HAT_LINE=" ( )" ;;
|
|
227
|
-
wizard) HAT_LINE=" /^\\" ;;
|
|
228
|
-
beanie) HAT_LINE=" (___)" ;;
|
|
229
|
-
tinyduck) HAT_LINE=" ,>" ;;
|
|
230
|
-
esac
|
|
231
|
-
|
|
232
|
-
# ─── Reaction bubble (with TTL check) ────────────────────────────────────────
|
|
233
|
-
BUBBLE=""
|
|
234
|
-
if [ -n "$ACHIEVEMENT" ] && [ "$ACHIEVEMENT" != "null" ] && [ "$ACHIEVEMENT" != "" ]; then
|
|
235
|
-
BUBBLE=$'\xf0\x9f\x8f\x86'" $ACHIEVEMENT"
|
|
236
|
-
fi
|
|
237
|
-
REACTION_FILE="$BUDDY_STATE_DIR/reaction.$SID.json"
|
|
238
|
-
REACTION_TTL=0
|
|
239
|
-
CONFIG_FILE="$BUDDY_STATE_DIR/config.json"
|
|
240
|
-
if [ -f "$CONFIG_FILE" ]; then
|
|
241
|
-
_ttl=$(jq -r '.reactionTTL // 0' "$CONFIG_FILE" 2>/dev/null || echo 0)
|
|
242
|
-
case "$_ttl" in ''|*[!0-9]*) ;; *) REACTION_TTL="$_ttl" ;; esac
|
|
243
|
-
fi
|
|
244
|
-
if [ -n "$REACTION" ] && [ "$REACTION" != "null" ] && [ "$REACTION" != "" ]; then
|
|
245
|
-
FRESH=0
|
|
246
|
-
if [ "$REACTION_TTL" -eq 0 ]; then
|
|
247
|
-
FRESH=1
|
|
248
|
-
elif [ -f "$REACTION_FILE" ]; then
|
|
249
|
-
TS=$(jq -r '.timestamp // 0' "$REACTION_FILE" 2>/dev/null || echo 0)
|
|
250
|
-
if [ "$TS" != "0" ]; then
|
|
251
|
-
NOW=$(date +%s)
|
|
252
|
-
AGE=$(( NOW - TS / 1000 ))
|
|
253
|
-
[ "$AGE" -lt "$REACTION_TTL" ] && FRESH=1
|
|
254
|
-
fi
|
|
255
|
-
fi
|
|
256
|
-
if [ "$FRESH" -eq 1 ]; then
|
|
257
|
-
if [ -n "$BUBBLE" ]; then
|
|
258
|
-
BUBBLE="$BUBBLE | \"${REACTION}\""
|
|
259
|
-
else
|
|
260
|
-
BUBBLE="\"${REACTION}\""
|
|
261
|
-
fi
|
|
262
|
-
fi
|
|
263
|
-
fi
|
|
264
|
-
|
|
265
|
-
# ─── Build art lines ─────────────────────────────────────────────────────────
|
|
266
|
-
ART_LINES=("$L1" "$L2" "$L3")
|
|
267
|
-
[ -n "$L4" ] && ART_LINES+=("$L4")
|
|
268
|
-
|
|
269
|
-
# Center the name
|
|
270
|
-
NAME_LEN=${#NAME}
|
|
271
|
-
ART_CENTER=4
|
|
272
|
-
NAME_PAD=$(( ART_CENTER - NAME_LEN / 2 ))
|
|
273
|
-
[ "$NAME_PAD" -lt 0 ] && NAME_PAD=0
|
|
274
|
-
NAME_LINE="$(printf '%*s%s' "$NAME_PAD" '' "$NAME")"
|
|
275
|
-
|
|
276
|
-
# ─── Build all art lines ──────────────────────────────────────────────────────
|
|
277
|
-
DIM=$'\033[2;3m'
|
|
278
|
-
|
|
279
|
-
ALL_LINES=()
|
|
280
|
-
ALL_COLORS=()
|
|
281
|
-
[ -n "$HAT_LINE" ] && { ALL_LINES+=("$HAT_LINE"); ALL_COLORS+=("$C"); }
|
|
282
|
-
for line in "${ART_LINES[@]}"; do
|
|
283
|
-
ALL_LINES+=("$line"); ALL_COLORS+=("$C")
|
|
284
|
-
done
|
|
285
|
-
ALL_LINES+=("$NAME_LINE"); ALL_COLORS+=("$DIM")
|
|
286
|
-
|
|
287
|
-
ART_W=14
|
|
288
|
-
ART_COUNT=${#ALL_LINES[@]}
|
|
289
|
-
|
|
290
|
-
# ─── Speech bubble (left of art, word-wrapped) ──────────────────────────────
|
|
291
|
-
# Strip the quotes we added earlier
|
|
292
|
-
BUBBLE_TEXT=""
|
|
293
|
-
if [ -n "$BUBBLE" ]; then
|
|
294
|
-
BUBBLE_TEXT="${BUBBLE%\"}"
|
|
295
|
-
BUBBLE_TEXT="${BUBBLE_TEXT#\"}"
|
|
296
|
-
fi
|
|
297
|
-
|
|
298
|
-
# ─── Word-wrap bubble text ────────────────────────────────────────────────────
|
|
299
|
-
INNER_W=28
|
|
300
|
-
TEXT_LINES=()
|
|
301
|
-
if [ -n "$BUBBLE_TEXT" ]; then
|
|
302
|
-
WORDS=($BUBBLE_TEXT)
|
|
303
|
-
CUR_LINE=""
|
|
304
|
-
for word in "${WORDS[@]}"; do
|
|
305
|
-
if [ -z "$CUR_LINE" ]; then
|
|
306
|
-
CUR_LINE="$word"
|
|
307
|
-
elif [ $(( ${#CUR_LINE} + 1 + ${#word} )) -le $INNER_W ]; then
|
|
308
|
-
CUR_LINE="$CUR_LINE $word"
|
|
309
|
-
else
|
|
310
|
-
TEXT_LINES+=("$CUR_LINE")
|
|
311
|
-
CUR_LINE="$word"
|
|
312
|
-
fi
|
|
313
|
-
done
|
|
314
|
-
[ -n "$CUR_LINE" ] && TEXT_LINES+=("$CUR_LINE")
|
|
315
|
-
fi
|
|
316
|
-
|
|
317
|
-
TEXT_COUNT=${#TEXT_LINES[@]}
|
|
318
|
-
|
|
319
|
-
# Build box as plain strings (no ANSI). Color applied at output time.
|
|
320
|
-
# Box display width = INNER_W + 4: "| " + text(INNER_W) + " |"
|
|
321
|
-
BOX_W=$(( INNER_W + 4 ))
|
|
322
|
-
BUBBLE_LINES=()
|
|
323
|
-
BUBBLE_TYPES=() # "border" or "text" — determines coloring
|
|
324
|
-
if [ $TEXT_COUNT -gt 0 ]; then
|
|
325
|
-
# Top border
|
|
326
|
-
BORDER=$(printf '%*s' "$(( BOX_W - 2 ))" '' | tr ' ' '-')
|
|
327
|
-
BUBBLE_LINES+=(".${BORDER}.")
|
|
328
|
-
BUBBLE_TYPES+=("border")
|
|
329
|
-
# Text rows: "| text padded |"
|
|
330
|
-
for tl in "${TEXT_LINES[@]}"; do
|
|
331
|
-
tpad=$(( INNER_W - ${#tl} ))
|
|
332
|
-
[ "$tpad" -lt 0 ] && tpad=0
|
|
333
|
-
padding=$(printf '%*s' "$tpad" '')
|
|
334
|
-
BUBBLE_LINES+=("| ${tl}${padding} |")
|
|
335
|
-
BUBBLE_TYPES+=("text")
|
|
336
|
-
done
|
|
337
|
-
# Bottom border
|
|
338
|
-
BUBBLE_LINES+=("\`${BORDER}'")
|
|
339
|
-
BUBBLE_TYPES+=("border")
|
|
340
|
-
fi
|
|
341
|
-
|
|
342
|
-
BUBBLE_COUNT=${#BUBBLE_LINES[@]}
|
|
343
|
-
|
|
344
|
-
# ─── Right-align with bubble box to the left ─────────────────────────────────
|
|
345
|
-
GAP=2
|
|
346
|
-
if [ $BUBBLE_COUNT -gt 0 ]; then
|
|
347
|
-
TOTAL_W=$(( BOX_W + GAP + ART_W ))
|
|
348
|
-
else
|
|
349
|
-
TOTAL_W=$ART_W
|
|
350
|
-
fi
|
|
351
|
-
MARGIN=8
|
|
352
|
-
PAD=$(( COLS - TOTAL_W - MARGIN ))
|
|
353
|
-
[ "$PAD" -lt 0 ] && PAD=0
|
|
354
|
-
|
|
355
|
-
SPACER=$(printf "${B}%${PAD}s" "")
|
|
356
|
-
GAP_STR=$(printf '%*s' "$GAP" '')
|
|
357
|
-
|
|
358
|
-
# Vertically center bubble box on the art
|
|
359
|
-
BUBBLE_START=0
|
|
360
|
-
if [ $BUBBLE_COUNT -gt 0 ] && [ $BUBBLE_COUNT -lt $ART_COUNT ]; then
|
|
361
|
-
BUBBLE_START=$(( (ART_COUNT - BUBBLE_COUNT) / 2 ))
|
|
362
|
-
fi
|
|
363
|
-
|
|
364
|
-
# ─── Find the connector line (middle text line → points to buddy's mouth) ─────
|
|
365
|
-
# The connector goes on the middle text row of the bubble
|
|
366
|
-
CONNECTOR_BI=-1
|
|
367
|
-
if [ $BUBBLE_COUNT -gt 2 ]; then
|
|
368
|
-
# text rows are indices 1..(BUBBLE_COUNT-2), pick the middle one
|
|
369
|
-
FIRST_TEXT=1
|
|
370
|
-
LAST_TEXT=$(( BUBBLE_COUNT - 2 ))
|
|
371
|
-
CONNECTOR_BI=$(( (FIRST_TEXT + LAST_TEXT) / 2 ))
|
|
372
|
-
fi
|
|
373
|
-
|
|
374
|
-
# ─── Output: merged bubble box + connector + art per line ─────────────────────
|
|
375
|
-
for (( i=0; i<ART_COUNT; i++ )); do
|
|
376
|
-
art_part="${ALL_COLORS[$i]}${ALL_LINES[$i]}${NC}"
|
|
377
|
-
|
|
378
|
-
if [ $BUBBLE_COUNT -gt 0 ]; then
|
|
379
|
-
bi=$(( i - BUBBLE_START ))
|
|
380
|
-
if [ $bi -ge 0 ] && [ $bi -lt $BUBBLE_COUNT ]; then
|
|
381
|
-
bline="${BUBBLE_LINES[$bi]}"
|
|
382
|
-
btype="${BUBBLE_TYPES[$bi]}"
|
|
383
|
-
|
|
384
|
-
# Connector: "-- " on the middle text line, spaces otherwise
|
|
385
|
-
if [ $bi -eq $CONNECTOR_BI ]; then
|
|
386
|
-
gap="${C}--${NC} "
|
|
387
|
-
else
|
|
388
|
-
gap=" "
|
|
389
|
-
fi
|
|
390
|
-
|
|
391
|
-
if [ "$btype" = "border" ]; then
|
|
392
|
-
echo "${SPACER}${C}${bline}${NC}${gap}${art_part}"
|
|
393
|
-
else
|
|
394
|
-
pipe_l="${bline:0:1}"
|
|
395
|
-
pipe_r="${bline: -1}"
|
|
396
|
-
inner="${bline:1:$(( ${#bline} - 2 ))}"
|
|
397
|
-
echo "${SPACER}${C}${pipe_l}${NC}${DIM}${inner}${NC}${C}${pipe_r}${NC}${gap}${art_part}"
|
|
398
|
-
fi
|
|
399
|
-
else
|
|
400
|
-
empty=$(printf '%*s' "$BOX_W" '')
|
|
401
|
-
echo "${SPACER}${empty} ${art_part}"
|
|
402
|
-
fi
|
|
403
|
-
else
|
|
404
|
-
echo "${SPACER}${art_part}"
|
|
405
|
-
fi
|
|
406
|
-
done
|
|
407
|
-
|
|
408
|
-
exit 0
|