@ramarivera/coding-buddy 0.9.0 → 0.9.2

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.
@@ -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: pick current frame from server-rendered frames ──────────────
66
+ # ─── Animation timing ───────────────────────────────────────────────────────
66
67
  NOW=${BUDDY_FAKE_NOW:-$(date +%s)}
67
- FRAME_BODY=$(jq -r --argjson now "$NOW" '
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
- _is_positive_cols() {
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 width ──────────────────────────────────────────────────────────
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
- # Width resolution precedence (real use):
163
- # 1. BUDDY_STATUSLINE_COLS (validated positive integer)
164
- # 2. exported COLUMNS (validated positive integer)
165
- # 3. Linux: /proc/$PID/fd/0 PTY via stty size
166
- # 4. macOS: ps + stty on /dev/$TTY_NAME
167
- # 5. Windows: PowerShell (Get-Host).UI.RawUI.WindowSize.Width
168
- # 6. final default 125
169
- if _is_positive_cols "${BUDDY_STATUSLINE_COLS:-}"; then
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 _is_positive_cols "$_INHERITED_COLUMNS"; then
167
+ elif _is_positive_int "$_INHERITED_COLUMNS"; then
172
168
  COLS=$((10#$_INHERITED_COLUMNS))
173
- else
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
- COLS=$(stty size < "$PTY" 2>/dev/null | awk '{print $2}')
183
- _is_positive_cols "${COLS:-}" && break
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
- COLS=$(stty size < "$TTY_DEV" 2>/dev/null | awk '{print $2}')
192
- _is_positive_cols "${COLS:-}" && break
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
- [ "${COLS:-0}" -lt 1 ] 2>/dev/null && COLS=0
198
- # Windows: /proc and TTY device detection don't exist; use PowerShell as fallback
199
- if [ "${COLS:-0}" -lt 1 ] 2>/dev/null; then
200
- _ps_cols=$(powershell.exe -NoProfile -Command "(Get-Host).UI.RawUI.WindowSize.Width" 2>/dev/null | tr -d '\r\n')
201
- if _is_positive_cols "$_ps_cols"; then
202
- [ "$_ps_cols" -gt 40 ] 2>/dev/null && COLS=$((10#$_ps_cols))
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
@@ -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
- });