Documentation
¶
Overview ¶
Package watcher tails Claude Code's JSONL session logs under $HOME/.claude/projects and streams parsed events to a handler.
Claude Code writes one JSON object per line per tool call into files like:
~/.claude/projects/<slug>/<session-uuid>.jsonl ~/.claude/projects/<slug>/subagents/*.jsonl
The watcher is a thin I/O layer: it uses fsnotify to learn when files are created or appended-to, reads new bytes since the last known offset, splits them on '\n', and calls the provided handler for every parsed billable event.
The watcher is NOT responsible for:
- parsing (that lives in internal/parser)
- pricing, rollups, budget evaluation, enforcement, push notifications
Those are composed by the CLI's `watch` command, which wires parser → pricing → db → budget → enforcer → ntfy into one Handler closure and passes it here.
Design notes:
Recursive watching: fsnotify watches are single-directory, so we walk the root on startup and add each subdirectory, then add new subdirectories as Create events arrive.
Append-tailing: we keep a per-file byte offset. On each Write event, we seek to that offset, read to EOF, and process only complete lines (those ending in '\n'). Incomplete trailing bytes are re-read on the next Write event.
Truncation: if a file shrinks below our offset, we reset to 0 and reprocess. The db layer dedupes by UUID so no events are double-counted.
Handler errors are logged and ignored. One bad event must not stop the watcher. Fatal errors (context cancellation, fsnotify channel close) do return from Run.
Dedup across restarts: we do not persist the offsets. On restart we re-scan everything from byte 0. The db layer dedupes by UUID so this is correct but slightly wasteful (O(total-bytes) on every start). Persisting offsets is a v0.2 enhancement — see the TODO in processFile.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Handler ¶
Handler is called once per parsed billable event. The source argument is the absolute path of the JSONL file the event came from — useful for logging and for any future per-file state.
Handler errors are logged by the watcher but do not abort the watch loop. If the handler needs to abort (e.g. fatal db error), it should cancel the Run context externally.
type Options ¶
type Options struct {
// Logger receives structured debug + error messages. Defaults
// to a discarding logger (no output) so quiet by default.
Logger *slog.Logger
}
Options tunes a Watcher. All fields are optional.
type Watcher ¶
type Watcher struct {
// contains filtered or unexported fields
}
Watcher tails a tree of JSONL files and calls a Handler for every parsed event. Instantiate with New, then call Run in a goroutine (or the main loop) and Close on shutdown.
func New ¶
New constructs a Watcher rooted at `root`. The directory must already exist — that is Claude Code's responsibility, not ours. A missing root returns a wrapped fs.ErrNotExist so the CLI can print a helpful "run claude code at least once" message.
func (*Watcher) Close ¶
Close releases the underlying fsnotify resources. Safe to call multiple times; subsequent calls are no-ops.