@ramarivera/coding-buddy 0.9.0 → 0.9.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/adapters/omp/ui.ts +13 -31
- package/adapters/pi/ui.ts +13 -31
- package/adapters/shared/file-storage.ts +1 -0
- package/adapters/shared/widget-layout.ts +125 -11
- package/core/model.ts +1 -0
- package/package.json +1 -2
- package/scripts/ci/check-statusline.sh +231 -118
- package/scripts/ci/render-statusline-pty.py +12 -4
- package/scripts/ci/render-vhs-goldens.sh +10 -19
- package/scripts/ci/statusline.tape +28 -2
- package/scripts/ci/vhs-density-fixtures.sh +27 -0
- package/scripts/ci/vhs-run-statusline.sh +13 -1
- package/scripts/ci/write-density-status.ts +86 -0
- package/server/art.ts +29 -8
- package/server/state.ts +85 -25
- package/statusline/buddy-status.sh +161 -41
- package/scripts/postinstall.cjs +0 -42
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
# controlling PTY after startup. Snapshot the exported value before any
|
|
17
17
|
# sourced helper or external command can trigger that refresh.
|
|
18
18
|
_INHERITED_COLUMNS="${COLUMNS:-}"
|
|
19
|
+
_INHERITED_ROWS="${LINES:-}"
|
|
19
20
|
# Width math and substring slicing must agree on UTF-8 codepoints. Claude Code
|
|
20
21
|
# emits Unicode art even when a caller inherited the POSIX C locale.
|
|
21
22
|
_LOCALE_HINT="${LC_ALL:-${LC_CTYPE:-${LANG:-}}}"
|
|
@@ -62,24 +63,9 @@ MOOD=$(jq -r '.mood // "focused"' "$STATE" 2>/dev/null)
|
|
|
62
63
|
|
|
63
64
|
BUDDY_STATUSLINE_INPUT=$(cat)
|
|
64
65
|
|
|
65
|
-
# ─── Animation
|
|
66
|
+
# ─── Animation timing ───────────────────────────────────────────────────────
|
|
66
67
|
NOW=${BUDDY_FAKE_NOW:-$(date +%s)}
|
|
67
|
-
|
|
68
|
-
.frameSequence[$now % (.frameSequence | length)] as $idx
|
|
69
|
-
| .frames[$idx] // ""
|
|
70
|
-
' "$STATE" 2>/dev/null)
|
|
71
|
-
|
|
72
|
-
# Fallback when status.json lacks .frames — e.g. server/bash version skew
|
|
73
|
-
# during install or while the MCP server hasn't rewritten the file yet. Keep
|
|
74
|
-
# the buddy visible in a degraded form instead of emitting an empty block.
|
|
75
|
-
if [ -z "$FRAME_BODY" ]; then
|
|
76
|
-
FRAME_BODY=$' \n (°°) \n ( ) \n \n '
|
|
77
|
-
fi
|
|
78
|
-
|
|
79
|
-
ART_LINES=()
|
|
80
|
-
while IFS= read -r line; do
|
|
81
|
-
ART_LINES+=("$line")
|
|
82
|
-
done <<< "$FRAME_BODY"
|
|
68
|
+
# The actual frame body is selected later once density/rows are known.
|
|
83
69
|
|
|
84
70
|
# ─── Rarity color (theme-aware) ─────────────────────────────────────────────
|
|
85
71
|
_THEME="dark"
|
|
@@ -139,7 +125,7 @@ COLOR_ENABLED=1
|
|
|
139
125
|
RAINBOW_LEN=${#RAINBOW[@]}
|
|
140
126
|
RAINBOW_OFFSET=$(( NOW % RAINBOW_LEN ))
|
|
141
127
|
|
|
142
|
-
|
|
128
|
+
_is_positive_int() {
|
|
143
129
|
case "$1" in
|
|
144
130
|
''|*[!0-9]*) return 1 ;;
|
|
145
131
|
esac
|
|
@@ -147,8 +133,9 @@ _is_positive_cols() {
|
|
|
147
133
|
return 1
|
|
148
134
|
}
|
|
149
135
|
|
|
150
|
-
# ─── Terminal
|
|
136
|
+
# ─── Terminal size (rows + columns from the same stty size probe) ─────────────
|
|
151
137
|
COLS=0
|
|
138
|
+
ROWS=0
|
|
152
139
|
|
|
153
140
|
# Claude Code renders this command inside a content box, not across the full
|
|
154
141
|
# terminal. Reserve two columns for settings.json padding (1 per side) and
|
|
@@ -159,18 +146,32 @@ COLS=0
|
|
|
159
146
|
CHROME_RESERVE=14
|
|
160
147
|
STATUSLINE_WIDTH_ADJUST=0
|
|
161
148
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
#
|
|
166
|
-
#
|
|
167
|
-
#
|
|
168
|
-
#
|
|
169
|
-
|
|
149
|
+
ROWS=0
|
|
150
|
+
COLS=0
|
|
151
|
+
|
|
152
|
+
# Rows/cols resolution precedence (real use):
|
|
153
|
+
# 1. BUDDY_STATUSLINE_ROWS / BUDDY_STATUSLINE_COLS (validated positive ints)
|
|
154
|
+
# 2. exported LINES / COLUMNS (validated positive ints)
|
|
155
|
+
# 3. Linux: /proc/$PID/fd/0 PTY via stty size (captures both values)
|
|
156
|
+
# 4. macOS: ps + stty on /dev/$TTY_NAME (captures both values)
|
|
157
|
+
# 5. Windows: PowerShell (Get-Host).UI.RawUI.WindowSize.Height/Width
|
|
158
|
+
# 6. final defaults: ROWS 999 (full), COLS 125
|
|
159
|
+
if _is_positive_int "${BUDDY_STATUSLINE_ROWS:-}"; then
|
|
160
|
+
ROWS=$((10#${BUDDY_STATUSLINE_ROWS}))
|
|
161
|
+
elif _is_positive_int "$_INHERITED_ROWS"; then
|
|
162
|
+
ROWS=$((10#$_INHERITED_ROWS))
|
|
163
|
+
fi
|
|
164
|
+
|
|
165
|
+
if _is_positive_int "${BUDDY_STATUSLINE_COLS:-}"; then
|
|
170
166
|
COLS=$((10#${BUDDY_STATUSLINE_COLS}))
|
|
171
|
-
elif
|
|
167
|
+
elif _is_positive_int "$_INHERITED_COLUMNS"; then
|
|
172
168
|
COLS=$((10#$_INHERITED_COLUMNS))
|
|
173
|
-
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
# If either dimension is still unknown, walk the process tree and read the
|
|
172
|
+
# PTY dimensions once. stty size returns "rows cols"; parse both from the same
|
|
173
|
+
# probe so rows and cols are consistent and no extra detection pass is needed.
|
|
174
|
+
if [ "$ROWS" -lt 1 ] 2>/dev/null || [ "$COLS" -lt 1 ] 2>/dev/null; then
|
|
174
175
|
PID=$$
|
|
175
176
|
for _ in 1 2 3 4 5; do
|
|
176
177
|
PID=$(ps -o ppid= -p "$PID" 2>/dev/null | tr -d ' ')
|
|
@@ -179,8 +180,14 @@ else
|
|
|
179
180
|
# Linux: read PTY device from /proc
|
|
180
181
|
PTY=$(readlink "/proc/${PID}/fd/0" 2>/dev/null)
|
|
181
182
|
if [ -c "$PTY" ] 2>/dev/null; then
|
|
182
|
-
|
|
183
|
-
|
|
183
|
+
_stty=$(stty size < "$PTY" 2>/dev/null)
|
|
184
|
+
if [ -n "$_stty" ]; then
|
|
185
|
+
_rows=${_stty%% *}
|
|
186
|
+
_cols=${_stty##* }
|
|
187
|
+
[ "$ROWS" -lt 1 ] 2>/dev/null && _is_positive_int "$_rows" && ROWS=$((10#$_rows))
|
|
188
|
+
[ "$COLS" -lt 1 ] 2>/dev/null && _is_positive_int "$_cols" && COLS=$((10#$_cols))
|
|
189
|
+
[ "$ROWS" -gt 0 ] 2>/dev/null && [ "$COLS" -gt 0 ] 2>/dev/null && break
|
|
190
|
+
fi
|
|
184
191
|
fi
|
|
185
192
|
|
|
186
193
|
# macOS: /proc doesn't exist — get TTY name from process table
|
|
@@ -188,24 +195,38 @@ else
|
|
|
188
195
|
if [ -n "$TTY_NAME" ] && [ "$TTY_NAME" != "??" ] && [ "$TTY_NAME" != "?" ]; then
|
|
189
196
|
TTY_DEV="/dev/$TTY_NAME"
|
|
190
197
|
if [ -c "$TTY_DEV" ] 2>/dev/null; then
|
|
191
|
-
|
|
192
|
-
|
|
198
|
+
_stty=$(stty size < "$TTY_DEV" 2>/dev/null)
|
|
199
|
+
if [ -n "$_stty" ]; then
|
|
200
|
+
_rows=${_stty%% *}
|
|
201
|
+
_cols=${_stty##* }
|
|
202
|
+
[ "$ROWS" -lt 1 ] 2>/dev/null && _is_positive_int "$_rows" && ROWS=$((10#$_rows))
|
|
203
|
+
[ "$COLS" -lt 1 ] 2>/dev/null && _is_positive_int "$_cols" && COLS=$((10#$_cols))
|
|
204
|
+
[ "$ROWS" -gt 0 ] 2>/dev/null && [ "$COLS" -gt 0 ] 2>/dev/null && break
|
|
205
|
+
fi
|
|
193
206
|
fi
|
|
194
207
|
fi
|
|
195
208
|
done
|
|
209
|
+
fi
|
|
196
210
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
fi
|
|
211
|
+
[ "${COLS:-0}" -lt 1 ] 2>/dev/null && COLS=0
|
|
212
|
+
# Windows: /proc and TTY device detection don't exist; use PowerShell as fallback
|
|
213
|
+
if [ "${COLS:-0}" -lt 1 ] 2>/dev/null; then
|
|
214
|
+
_ps_cols=$(powershell.exe -NoProfile -Command "(Get-Host).UI.RawUI.WindowSize.Width" 2>/dev/null | tr -d '\r\n')
|
|
215
|
+
if _is_positive_int "$_ps_cols"; then
|
|
216
|
+
[ "$_ps_cols" -gt 40 ] 2>/dev/null && COLS=$((10#$_ps_cols))
|
|
204
217
|
fi
|
|
205
|
-
[ "${COLS:-0}" -lt 1 ] 2>/dev/null && COLS=125
|
|
206
218
|
fi
|
|
219
|
+
if [ "${ROWS:-0}" -lt 1 ] 2>/dev/null; then
|
|
220
|
+
_ps_rows=$(powershell.exe -NoProfile -Command "(Get-Host).UI.RawUI.WindowSize.Height" 2>/dev/null | tr -d '\r\n')
|
|
221
|
+
_is_positive_int "$_ps_rows" && ROWS=$((10#$_ps_rows))
|
|
222
|
+
fi
|
|
223
|
+
|
|
224
|
+
[ "${COLS:-0}" -lt 1 ] 2>/dev/null && COLS=125
|
|
225
|
+
[ "${ROWS:-0}" -lt 1 ] 2>/dev/null && ROWS=999
|
|
226
|
+
|
|
207
227
|
COLS=$((10#$COLS))
|
|
208
228
|
DETECTED_COLS="$COLS"
|
|
229
|
+
DETECTED_ROWS="$ROWS"
|
|
209
230
|
|
|
210
231
|
# ─── Reaction bubble (with TTL check) ────────────────────────────────────────
|
|
211
232
|
BUBBLE=""
|
|
@@ -215,6 +236,7 @@ fi
|
|
|
215
236
|
REACTION_TTL=900
|
|
216
237
|
INNER_W=44
|
|
217
238
|
MARGIN=8
|
|
239
|
+
DENSITY="auto"
|
|
218
240
|
if [ -f "$CONFIG_FILE" ]; then
|
|
219
241
|
_ttl=$(jq -r '.reactionTTL // 900' "$CONFIG_FILE" 2>/dev/null || echo 900)
|
|
220
242
|
case "$_ttl" in ''|*[!0-9]*) ;; *) REACTION_TTL="$_ttl" ;; esac
|
|
@@ -230,7 +252,34 @@ if [ -f "$CONFIG_FILE" ]; then
|
|
|
230
252
|
*) STATUSLINE_WIDTH_ADJUST=$((10#$_wa)) ;;
|
|
231
253
|
esac
|
|
232
254
|
fi
|
|
255
|
+
_density=$(jq -r '.statuslineDensity // "auto"' "$CONFIG_FILE" 2>/dev/null || echo "auto")
|
|
256
|
+
case "$_density" in
|
|
257
|
+
auto|full|compact|minimal) DENSITY="$_density" ;;
|
|
258
|
+
*) DENSITY="auto" ;;
|
|
259
|
+
esac
|
|
260
|
+
fi
|
|
261
|
+
# ─── Statusline density tier ─────────────────────────────────────────────────
|
|
262
|
+
# Explicit config/env density overrides pin the tier; otherwise rows drive it:
|
|
263
|
+
# full >= 40, compact 20-39, minimal < 20. Very narrow terminals also force minimal.
|
|
264
|
+
if [ -n "${BUDDY_STATUSLINE_DENSITY:-}" ]; then
|
|
265
|
+
DENSITY="${BUDDY_STATUSLINE_DENSITY}"
|
|
233
266
|
fi
|
|
267
|
+
case "$DENSITY" in
|
|
268
|
+
full|compact|minimal) TIER="$DENSITY" ;;
|
|
269
|
+
*)
|
|
270
|
+
TIER="full"
|
|
271
|
+
if [ "$DETECTED_ROWS" -lt 40 ] 2>/dev/null; then
|
|
272
|
+
TIER="compact"
|
|
273
|
+
fi
|
|
274
|
+
if [ "$DETECTED_ROWS" -lt 20 ] 2>/dev/null; then
|
|
275
|
+
TIER="minimal"
|
|
276
|
+
fi
|
|
277
|
+
if [ "$DETECTED_COLS" -lt 40 ] 2>/dev/null; then
|
|
278
|
+
TIER="minimal"
|
|
279
|
+
fi
|
|
280
|
+
;;
|
|
281
|
+
esac
|
|
282
|
+
|
|
234
283
|
_sweep_expired_reactions() {
|
|
235
284
|
[ "$REACTION_TTL" -gt 0 ] 2>/dev/null || return 0
|
|
236
285
|
local now cutoff_ms cutoff_seconds file ts
|
|
@@ -282,6 +331,28 @@ if [ -n "$REACTION" ] && [ "$REACTION" != "null" ] && [ "$REACTION" != "" ]; the
|
|
|
282
331
|
fi
|
|
283
332
|
fi
|
|
284
333
|
|
|
334
|
+
# ─── Animation: pick current density frame from server-rendered frames ───────
|
|
335
|
+
NOW=${BUDDY_FAKE_NOW:-$(date +%s)}
|
|
336
|
+
FRAME_BODY=$(jq -r --argjson now "$NOW" --arg tier "$TIER" '
|
|
337
|
+
.frameSequence[$now % (.frameSequence | length)] as $idx
|
|
338
|
+
| if $tier == "compact" then ((.compactFrames? // .frames) | .[$idx] // .frames[$idx])
|
|
339
|
+
elif $tier == "minimal" then ((.minimalFrames? // .frames) | .[$idx] // .frames[$idx])
|
|
340
|
+
else .frames[$idx]
|
|
341
|
+
end // ""
|
|
342
|
+
' "$STATE" 2>/dev/null)
|
|
343
|
+
|
|
344
|
+
# Fallback when status.json lacks .frames — e.g. server/bash version skew
|
|
345
|
+
# during install or while the MCP server hasn't rewritten the file yet. Keep
|
|
346
|
+
# the buddy visible in a degraded form instead of emitting an empty block.
|
|
347
|
+
if [ -z "$FRAME_BODY" ]; then
|
|
348
|
+
FRAME_BODY=$' \n (°°) \n ( ) \n \n '
|
|
349
|
+
fi
|
|
350
|
+
|
|
351
|
+
ART_LINES=()
|
|
352
|
+
while IFS= read -r line; do
|
|
353
|
+
ART_LINES+=("$line")
|
|
354
|
+
done <<< "$FRAME_BODY"
|
|
355
|
+
|
|
285
356
|
# ─── Build all art lines ──────────────────────────────────────────────────────
|
|
286
357
|
# ART_LINES comes from the pre-rendered frame (already includes hat + blink).
|
|
287
358
|
# Center the name under the art. Frames are 12 cols wide (see server/art.ts),
|
|
@@ -478,6 +549,47 @@ if [ "$STATUSLINE_BUDGET" -lt "$ART_W" ]; then
|
|
|
478
549
|
fi
|
|
479
550
|
COLS="$STATUSLINE_BUDGET"
|
|
480
551
|
|
|
552
|
+
# ─── Density branch: compact drops the bubble; minimal is a single line. ─────
|
|
553
|
+
if [ "$TIER" = "minimal" ]; then
|
|
554
|
+
_face_plain="${ART_LINES[0]:-}"
|
|
555
|
+
if [ -z "$_face_plain" ]; then
|
|
556
|
+
for _fline in "${ART_LINES[@]}"; do
|
|
557
|
+
[ -n "$_fline" ] && _face_plain="$_fline" && break
|
|
558
|
+
done
|
|
559
|
+
fi
|
|
560
|
+
[ -z "$_face_plain" ] && _face_plain=$' (°°) '
|
|
561
|
+
_min_plain="${_face_plain} ${NAME_WITH_LEVEL}"
|
|
562
|
+
_min_w=$(dwidth "$_min_plain")
|
|
563
|
+
if [ -n "$REACTION" ]; then
|
|
564
|
+
_react_plain=" │ \"${REACTION}\""
|
|
565
|
+
_react_w=$(dwidth "$_react_plain")
|
|
566
|
+
if [ $((_min_w + _react_w)) -le "$STATUSLINE_BUDGET" ]; then
|
|
567
|
+
_min_plain="${_min_plain}${_react_plain}"
|
|
568
|
+
_min_w=$(dwidth "$_min_plain")
|
|
569
|
+
fi
|
|
570
|
+
fi
|
|
571
|
+
if [ "$STATUSLINE_BUDGET" -lt "$_min_w" ]; then
|
|
572
|
+
STATUSLINE_BUDGET="$DETECTED_COLS"
|
|
573
|
+
fi
|
|
574
|
+
# Never widen past the real terminal: a hard-coded fallback here would
|
|
575
|
+
# defeat ansi_truncate below and emit rows wider than the pane, wrapping
|
|
576
|
+
# and clobbering the prompt on genuinely narrow terminals.
|
|
577
|
+
if [ "$STATUSLINE_BUDGET" -lt "$_min_w" ]; then
|
|
578
|
+
STATUSLINE_BUDGET="$DETECTED_COLS"
|
|
579
|
+
fi
|
|
580
|
+
COLS="$STATUSLINE_BUDGET"
|
|
581
|
+
ART_LINES=("$_min_plain")
|
|
582
|
+
ALL_COLORS=("$C")
|
|
583
|
+
ALL_LINES=("$_min_plain")
|
|
584
|
+
ART_COUNT=1
|
|
585
|
+
ART_W="$_min_w"
|
|
586
|
+
BUBBLE=""
|
|
587
|
+
BUBBLE_TEXT=""
|
|
588
|
+
elif [ "$TIER" = "compact" ]; then
|
|
589
|
+
BUBBLE=""
|
|
590
|
+
BUBBLE_TEXT=""
|
|
591
|
+
fi
|
|
592
|
+
|
|
481
593
|
# The bubble, tail, and sprite are one unit. At narrow widths, drop the
|
|
482
594
|
# bubble rather than allowing a partial border or tail to escape the panel.
|
|
483
595
|
MIN_BUBBLE_INNER=12
|
|
@@ -557,6 +669,14 @@ case "$(uname -s)" in
|
|
|
557
669
|
*) SPACER=$(printf "${B}%${PAD}s" "") ;;
|
|
558
670
|
esac
|
|
559
671
|
|
|
672
|
+
# Minimal tier uses plain spaces so the one-line sprite + name fits without
|
|
673
|
+
# sacrificing a Braille Blank column on narrow terminals.
|
|
674
|
+
if [ "$TIER" = "minimal" ]; then
|
|
675
|
+
PAD=$(( COLS - ART_W ))
|
|
676
|
+
[ "$PAD" -lt 0 ] && PAD=0
|
|
677
|
+
SPACER=$(printf '%*s' "$PAD" '')
|
|
678
|
+
fi
|
|
679
|
+
|
|
560
680
|
# Vertically center bubble box on the art
|
|
561
681
|
BUBBLE_START=0
|
|
562
682
|
if [ $BUBBLE_COUNT -gt 0 ] && [ $BUBBLE_COUNT -lt $ART_COUNT ]; then
|
package/scripts/postinstall.cjs
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Post-install check: warn if bun is unavailable.
|
|
4
|
-
* Installation must succeed for Node-only Pi/OMP consumers; Claude-only commands
|
|
5
|
-
* will diagnose bun at runtime.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const { spawn } = require("child_process");
|
|
9
|
-
|
|
10
|
-
function checkBun() {
|
|
11
|
-
return new Promise((resolve) => {
|
|
12
|
-
const proc = spawn("bun", ["--version"], { stdio: "pipe" });
|
|
13
|
-
let output = "";
|
|
14
|
-
proc.stdout.on("data", (data) => {
|
|
15
|
-
output += data.toString();
|
|
16
|
-
});
|
|
17
|
-
proc.on("close", (code) => {
|
|
18
|
-
if (code === 0) {
|
|
19
|
-
console.log(`✓ bun ${output.trim()} detected`);
|
|
20
|
-
resolve(true);
|
|
21
|
-
} else {
|
|
22
|
-
console.warn(
|
|
23
|
-
"Bun is required only for Claude-only commands; install it from https://bun.sh or run `npm install -g bun`."
|
|
24
|
-
);
|
|
25
|
-
resolve(false);
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
proc.on("error", () => {
|
|
29
|
-
console.warn(
|
|
30
|
-
"Bun is required only for Claude-only commands; install it from https://bun.sh or run `npm install -g bun`."
|
|
31
|
-
);
|
|
32
|
-
resolve(false);
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
checkBun().then((ok) => {
|
|
38
|
-
if (!ok) {
|
|
39
|
-
console.warn("Install completed, but bun is required to run Claude-only commands.");
|
|
40
|
-
}
|
|
41
|
-
process.exit(0);
|
|
42
|
-
});
|