mirror of
https://github.com/jarrodwatts/claude-hud.git
synced 2026-04-16 06:32:39 +00:00
docs: add hud config and replay tooling
This commit is contained in:
15
CLAUDE.md
15
CLAUDE.md
@@ -17,6 +17,7 @@ bun run build # Build TypeScript
|
||||
bun run dev # Watch mode for development
|
||||
bun test # Run all tests
|
||||
bun test <pattern> # Run specific test (e.g., bun test sparkline)
|
||||
bun run replay:events -- --input ../tui/test-fixtures/hud-events.jsonl # Replay events
|
||||
|
||||
# Manual testing with a FIFO
|
||||
mkfifo /tmp/test.fifo
|
||||
@@ -77,6 +78,20 @@ Runtime files stored in `~/.claude/hud/`:
|
||||
- `pids/<session_id>.pid` - Process ID for cleanup
|
||||
- `logs/<session_id>.log` - Fallback output when split pane unavailable
|
||||
|
||||
## HUD Configuration
|
||||
|
||||
Optional HUD config lives at `~/.claude/hud/config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"panelOrder": ["status", "context", "tools", "agents", "todos"],
|
||||
"hiddenPanels": ["cost"],
|
||||
"width": 56
|
||||
}
|
||||
```
|
||||
|
||||
Panel IDs: `status`, `context`, `cost`, `contextInfo`, `tools`, `agents`, `todos`.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **Runtime**: Node.js 18+ or Bun, jq (JSON parsing in hooks)
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
Thanks for your interest in contributing! This guide will help you get started.
|
||||
|
||||
## Community Standards
|
||||
|
||||
Please read `CODE_OF_CONDUCT.md`. For security issues, see `SECURITY.md`.
|
||||
|
||||
## Development Setup
|
||||
|
||||
```bash
|
||||
@@ -28,6 +32,17 @@ bun run typecheck
|
||||
|
||||
# Format code
|
||||
bun run format
|
||||
|
||||
# Replay a fixture event stream
|
||||
bun run replay:events -- --input ../tui/test-fixtures/hud-events.jsonl
|
||||
```
|
||||
|
||||
### One-shot checks
|
||||
|
||||
From the repo root:
|
||||
|
||||
```bash
|
||||
./scripts/check.sh
|
||||
```
|
||||
|
||||
### Local Testing
|
||||
|
||||
23
docs/adr/005-hud-store.md
Normal file
23
docs/adr/005-hud-store.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# 005 - HUD Store and Reducer Architecture
|
||||
|
||||
## Status
|
||||
Accepted
|
||||
|
||||
## Context
|
||||
The HUD app was previously managing IO and state derivation inside a React hook.
|
||||
That mixed FIFO reading, settings/config scanning, and UI state updates in one
|
||||
place, which made testing and reuse difficult.
|
||||
|
||||
## Decision
|
||||
Introduce a `HudStore` that owns IO and side effects, and a pure reducer for
|
||||
state transitions.
|
||||
|
||||
- `tui/src/state/hud-store.ts` manages EventReader, trackers, and environment
|
||||
refresh intervals.
|
||||
- `tui/src/state/hud-reducer.ts` handles event-driven state transitions.
|
||||
- `tui/src/state/hud-state.ts` defines public state and internal tracking.
|
||||
|
||||
## Consequences
|
||||
- UI code subscribes to a stable store and stays focused on rendering.
|
||||
- Event handling is testable in isolation via reducer tests.
|
||||
- Future renderers can reuse the store without React.
|
||||
@@ -13,7 +13,8 @@
|
||||
"lint:fix": "eslint src/ --fix",
|
||||
"format": "prettier --write src/",
|
||||
"format:check": "prettier --check src/",
|
||||
"typecheck": "tsc --noEmit"
|
||||
"typecheck": "tsc --noEmit",
|
||||
"replay:events": "bun run scripts/replay-events.ts --"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,tsx}": [
|
||||
|
||||
100
tui/scripts/replay-events.ts
Normal file
100
tui/scripts/replay-events.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import { EventEmitter } from 'node:events';
|
||||
import { HudStore } from '../src/state/hud-store.js';
|
||||
import type { EventSource } from '../src/state/hud-store.js';
|
||||
import { parseHudEvent } from '../src/lib/hud-event.js';
|
||||
|
||||
type Args = {
|
||||
input?: string;
|
||||
json?: boolean;
|
||||
};
|
||||
|
||||
function parseArgs(argv: string[]): Args {
|
||||
const args: Args = {};
|
||||
for (let i = 0; i < argv.length; i += 1) {
|
||||
const arg = argv[i];
|
||||
if (arg === '--input') {
|
||||
args.input = argv[i + 1];
|
||||
i += 1;
|
||||
} else if (arg === '--json') {
|
||||
args.json = true;
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
function createEventSource() {
|
||||
const emitter = new EventEmitter();
|
||||
const source: EventSource = {
|
||||
on(event, listener) {
|
||||
emitter.on(event, listener);
|
||||
},
|
||||
getStatus() {
|
||||
return 'connected';
|
||||
},
|
||||
close() {
|
||||
emitter.removeAllListeners();
|
||||
},
|
||||
switchFifo() {
|
||||
return;
|
||||
},
|
||||
};
|
||||
return { emitter, source };
|
||||
}
|
||||
|
||||
function usage(): void {
|
||||
console.log('Usage: bun run replay:events -- --input path/to/events.jsonl [--json]');
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv.slice(2));
|
||||
if (!args.input) {
|
||||
usage();
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const inputPath = resolve(args.input);
|
||||
const contents = readFileSync(inputPath, 'utf-8');
|
||||
const lines = contents.split('\n').filter((line) => line.trim().length > 0);
|
||||
|
||||
const { emitter, source } = createEventSource();
|
||||
const store = new HudStore({
|
||||
fifoPath: 'replay',
|
||||
clockIntervalMs: 0,
|
||||
eventSourceFactory: () => source,
|
||||
});
|
||||
|
||||
lines.forEach((line, index) => {
|
||||
const event = parseHudEvent(line);
|
||||
if (!event) {
|
||||
console.warn(`Skipping invalid event line ${index + 1}`);
|
||||
return;
|
||||
}
|
||||
emitter.emit('event', event);
|
||||
const state = store.getState();
|
||||
if (args.json) {
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
index: index + 1,
|
||||
event: event.event,
|
||||
tools: state.tools.length,
|
||||
todos: state.todos.length,
|
||||
agents: state.agents.length,
|
||||
cost: state.cost.totalCost,
|
||||
contextPercent: state.context.percent,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
`#${index + 1} ${event.event} tools=${state.tools.length} todos=${state.todos.length} agents=${state.agents.length} cost=$${state.cost.totalCost.toFixed(
|
||||
2,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
store.dispose();
|
||||
5
tui/test-fixtures/hud-events.jsonl
Normal file
5
tui/test-fixtures/hud-events.jsonl
Normal file
@@ -0,0 +1,5 @@
|
||||
{"event":"UserPromptSubmit","session":"fixture","ts":1,"prompt":"Hello"}
|
||||
{"event":"PreToolUse","tool":"Read","toolUseId":"tool-1","input":{"file_path":"README.md"},"response":null,"session":"fixture","ts":2}
|
||||
{"event":"PostToolUse","tool":"Read","toolUseId":"tool-1","input":null,"response":{"duration_ms":200},"session":"fixture","ts":2}
|
||||
{"event":"PreToolUse","tool":"TodoWrite","toolUseId":"tool-2","input":{"todos":[{"content":"Ship HUD tests","status":"in_progress"}]},"response":null,"session":"fixture","ts":3}
|
||||
{"event":"Stop","tool":null,"input":null,"response":null,"session":"fixture","ts":4}
|
||||
Reference in New Issue
Block a user