examples/claude-code-memory-plugin/STATUSLINE.md
A guide for an AI assistant (or you) to read the OpenViking statusline and customize it beyond the defaults the installer sets up. The "What each segment means" section below is the canonical glossary; the rest covers personalization recipes that env vars don't cover. For the environment-variable reference, see $REPO/docs/en/agent-integrations/02-claude-code.md.
When a user asks for something the env vars don't cover, prefer the smallest local edit over inventing a new configurable knob.
The OpenViking repo and plugin code are checked out to a known location by the installer. Throughout this doc:
$REPO = the OpenViking repo root. Default: ~/.openviking/openviking-repo (override via OPENVIKING_REPO_DIR at install time). Verify with ls "$REPO/examples/claude-code-memory-plugin" — if that path is wrong, find the real one with jq -r '.statusLine.command' ~/.claude/settings.json (the registered command points into the plugin) or find ~ -path '*/claude-code-memory-plugin/scripts/statusline.mjs' 2>/dev/null.$PLUGIN = $REPO/examples/claude-code-memory-plugin. The plugin's own root.$STATE = ~/.openviking/state (override via OPENVIKING_HOME). Where hook-written JSON snapshots live.Resolve $REPO once before editing anything — every relative path below is anchored to it.
| Concern | File |
|---|---|
| Composing the line / segment order | $PLUGIN/scripts/statusline.mjs |
| Server-reachability probe + cache | $PLUGIN/scripts/lib/server-probe.mjs |
Atomic state read/write under $STATE/ | $PLUGIN/scripts/lib/state.mjs |
Recall summary (writes last-recall.json) | $PLUGIN/scripts/auto-recall.mjs |
Capture summary (writes last-capture.json, daily-stats.json) | $PLUGIN/scripts/auto-capture.mjs |
Resume/compact event marker (last-session-event.json) | $PLUGIN/scripts/session-start.mjs |
| Where CC actually invokes the script | ~/.claude/settings.json .statusLine.command |
| Per-host / per-shell env overrides | ~/.zshrc (or equivalent) — env vars beat config files |
State files live at $STATE/. They are small JSON snapshots written atomically (temp + rename); deleting them clears the corresponding segment until the next hook fires.
The default composition runs left to right, joined by │. Segments are conditional — most only appear when their underlying signal is non-trivial, so a quiet line in a fresh session is normal, not a bug.
| Segment | Example | Color | Shows when |
|---|---|---|---|
| Health | OV ✓ | green | server reachable in ≤1 s |
OV ⚠ slow | yellow | probe timed out (>1 s) — server may be alive but lagging | |
OV ✗ offline | red | probe errored (refused, DNS fail, network down) | |
OV ⚡ bypass | yellow | session matched OPENVIKING_BYPASS_SESSION or *_PATTERNS | |
| Recall | ↩ 6 mem (0.92) · 50ms | dim | last user prompt actually injected memories. (0.92) is the top similarity score among picked items; latency is the recall round-trip |
| Capture | ✎ 573/20k · 2 arch | dim | tokens pending toward the next archive (sawtooth — resets on commit), 2 arch = archives produced this session |
✎ committed · 2 arch | dim | the turn that just finished produced an archive | |
✎ 2 arch | dim | nothing pending, but archives already exist this session | |
| Failures | ✗ 1 dropped | red | auto-capture failed N turns this batch — overwrites every Stop, so transient single-turn failures self-clear |
| Session event | 🔗 resumed | cyan | SessionStart hook fired with source: resume within the last minute (1 min TTL) |
🔗 compact | cyan | same, with source: compact | |
| Daily | +3 today | dim | archives committed across all sessions today (UTC date rollover) |
When a segment is missing but you'd expect it, the most common reasons are:
✎ capture — cc_session_id mismatch. After /branch or a freshly started CC session, no Stop hook has run yet for the new ID, so last-capture.json is from a different session and statusline filters it out. Self-resolves on the next assistant turn.🔗 session event — only resume and compact write a marker; startup and clear don't. The 60 s TTL is intentional so the badge fades.+N today — no archives committed yet today, or daily-stats.json is missing entirely.↩ recall — last prompt produced no usable memories (too-short query, score under threshold, or all results filtered out). The hook ran; it just had nothing worth showing.The whole line is hard-capped at 80 visible chars and trailing-truncated with … if it overflows.
These are sketches, not scripts to copy verbatim. The composer in $PLUGIN/scripts/statusline.mjs is small enough that an assistant can read it end-to-end before editing.
Each segment in $PLUGIN/scripts/statusline.mjs is gated by an if. To make a segment user-suppressible, wrap its branch in an env-var check (e.g. process.env.OPENVIKING_STATUSLINE_HIDE_DAILY === "1"). To remove it permanently, delete the branch — the line keeps composing the rest.
The output is parts.join(dim(" │ ")). Reorder the parts.push(...) calls to taste, or swap │ for · / — / a colored separator. Width is bounded by MAX_WIDTH (default 80) — change there if your terminal is wider.
Helpers green / red / yellow / cyan / dim wrap an ANSI SGR code. Adjust the codes (e.g. swap "32" for "92" to get bright green), or build a new helper like magenta = (s) => c("35", s) and apply it to a chosen segment. Color is auto-disabled under NO_COLOR, OPENVIKING_STATUSLINE_NO_COLOR, or TERM=dumb.
The installer detects an existing .statusLine.command and offers replace / skip. To run both, write a small wrapper that fans the same JSON payload to each and concatenates the outputs (substitute the actual $PLUGIN path you resolved above):
#!/usr/bin/env bash
INPUT=$(cat)
ov=$(node "$PLUGIN/scripts/statusline.mjs" <<<"$INPUT")
mine=$(your_other_statusline <<<"$INPUT")
printf '%s %s\n' "$mine" "$ov"
Then point .statusLine.command at that wrapper. Keep the OV portion last so it gets truncated (not your line) when the terminal narrows.
REQUEST_TIMEOUT_MS in $PLUGIN/scripts/lib/server-probe.mjs. The default 1000 ms is generous for LAN and tight for transcontinental SaaS. The probe is cached for 5 s across processes (file-locked under $STATE/), so this only affects the first hit per cache window.
Read the existing segments for the pattern: read state (or call a fast endpoint), guard with a condition, parts.push(dim("⋯ ...")). Keep custom segments cheap — the whole script must finish in well under CC's statusline timeout (≈300 ms wall clock is the working budget). Anything that needs the network should go through the file-cached probe pattern in $PLUGIN/scripts/lib/server-probe.mjs.
rm -f "$STATE"/last-*.json
The next hook fire repopulates whichever segments are currently active. This is handy after testing or when a session ID got out of sync.
export OPENVIKING_STATUSLINE=offjq 'del(.statusLine)' ~/.claude/settings.json (back up first)For an assistant adding a segment that consumes existing state. All files live under $STATE/ and use the same atomic-write helper in $PLUGIN/scripts/lib/state.mjs; readers should pass { maxAgeMs } to filter stale snapshots.
last-recall.json:
{
"reason": "ok" | "bypass" | "offline" | "no_results" | "filtered_out" | "short_query" | ...,
"count": 6, // memories actually injected
"top_score": 0.92, // max score among picked items
"latency_ms": 180,
"cc_session_id": "ff875009-...",
"ts": 1778139288759
}
last-capture.json:
{
"turns_captured": 3,
"turns_failed": 0,
"pending_tokens": 12450, // sawtooth — resets to 0 on commit
"commit_threshold": 20000,
"committed": false, // true on the turn a commit happened
"commit_count": 2, // total archives in this session
"total_message_count": 412,
"ov_session_id": "cc-62e5af67...",
"cc_session_id": "ff875009-...", // statusline filters by exact match
"ts": 1778139288759
}
last-session-event.json (1-minute TTL in the consumer):
{
"source": "resume" | "compact",
"had_context": true, // false when OV had no archive to inject
"cc_session_id": "...",
"ov_session_id": "cc-...",
"ts": 1778139288759
}
daily-stats.json (UTC date rollover):
{ "date": "2026-05-07", "archives": 3 }
If a request can't be served by an env var or a small local edit (a custom backend, a domain-specific signal, deeper restyling), the cleanest path is:
node "$PLUGIN/scripts/statusline.mjs" <<<'{"session_id":"...","cwd":"/tmp"}'.Keep changes to $PLUGIN/scripts/statusline.mjs shallow; the value of this script is that it stays readable in one screen.