Skip to content

feat(widgets): add Caveman Mode badge widget#493

Open
Khairul989 wants to merge 1 commit into
sirmalloc:mainfrom
Khairul989:feat/caveman-mode-widget
Open

feat(widgets): add Caveman Mode badge widget#493
Khairul989 wants to merge 1 commit into
sirmalloc:mainfrom
Khairul989:feat/caveman-mode-widget

Conversation

@Khairul989

Copy link
Copy Markdown

What

Adds a Caveman Mode widget (Core category) that surfaces the caveman plugin's active compression mode as a status line badge — [CAVEMAN], [CAVEMAN:ULTRA], etc. — with an optional lifetime token-savings suffix (⛏ 12.4k). The widget hides entirely when the plugin is inactive.

Why

The caveman plugin ships its own statusline badge as a bash script wired into Claude Code's settings.json statusLine slot. That slot is single-occupancy, so anyone running ccstatusline can never see the badge — the plugin's installer detects the existing statusline and backs off. A native widget lets both coexist, enabled from the TUI picker like any other widget.

How

  • src/utils/caveman.tsgetCavemanStatus() reads the plugin's two state files under $CLAUDE_CONFIG_DIR (.caveman-active mode word, .caveman-statusline-suffix pre-rendered savings string). Ports the plugin script's own hardening, and tightens it: atomic O_NOFOLLOW open (the bash original has an lstat-then-read TOCTOU gap), 64-byte read cap, mode whitelist, control-byte strip on the suffix.
  • src/widgets/CavemanMode.ts — mirrors the VoiceStatus pattern: metadata-backed s keybind toggles the savings suffix, raw value mode returns the bare mode word, preview support, no React editor needed.
  • Registered via WIDGET_MANIFEST + barrel export; documented in docs/USAGE.md (widget list + keybinds).

Testing

  • 12 util tests against real temp files (symlink refusal on both state files, 64-byte cap proven positively via suffix length, whitelist rejection, ANSI-escape stripping)
  • 22 widget tests with the reader mocked (badge formats, savings toggle round-trip, rawValue, preview never touches disk)
  • bun run lint clean, full suite 1662 pass / 0 fail
  • Manual E2E: piped payload renders [CAVEMAN:ULTRA] ⛏ 12.4k, hides on absent flag, hides on symlinked flag

Surfaces the caveman plugin's active compression mode in the status
line. The plugin ships its own statusLine bash script, but that slot is
single-occupancy — with ccstatusline installed the badge can never
render. A native widget lets both coexist, enabled from the TUI picker.

## New Widget: Caveman Mode

- src/utils/caveman.ts: getCavemanStatus() reads the plugin's state
  files ($CLAUDE_CONFIG_DIR/.caveman-active mode word and
  .caveman-statusline-suffix pre-rendered savings string) with the same
  hardening as the plugin's own script: O_NOFOLLOW open (atomic symlink
  refusal), 64-byte read cap, mode whitelist, control-byte strip
- src/widgets/CavemanMode.ts: renders [CAVEMAN] / [CAVEMAN:ULTRA] plus
  optional token-savings suffix; hides when the plugin is inactive;
  raw value mode returns the bare mode word; 's' keybind toggles the
  savings suffix via item metadata (mirrors VoiceStatus)

## Integration

- Registered in WIDGET_MANIFEST and the widget barrel; appears under
  the Core category in the TUI picker
- docs/USAGE.md: widget description and keybind entries

## Tests

- src/utils/__tests__/caveman.test.ts: 12 cases against real temp
  files — symlink refusal on both state files, 64-byte cap proven via
  suffix length, whitelist rejection, ANSI-escape stripping
- src/widgets/__tests__/CavemanMode.test.ts: 22 cases with the reader
  mocked — badge formats, savings toggle round-trip, rawValue, preview
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant