Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
name: Validate

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: LICENSE exists
run: test -s LICENSE || (echo "::error::LICENSE missing or empty" && exit 1)

- name: CHANGELOG.md exists
run: test -s CHANGELOG.md || (echo "::error::CHANGELOG.md missing or empty" && exit 1)

- name: Bash syntax check on all .sh files
run: |
set -e
fail=0
for f in $(git ls-files '*.sh'); do
if ! bash -n "$f"; then
echo "::error file=$f::bash syntax error"
fail=1
fi
done
exit $fail

- name: ShellCheck
# Severity 'error' only — style warnings (SC1090 dynamic source,
# SC2034 unused vars, SC2155 declare-and-assign) are tracked
# separately and do not gate this workflow.
uses: ludeeus/action-shellcheck@master
with:
severity: error
scandir: '.'
continue-on-error: false

- name: All docs/* assets referenced from README exist
run: |
set -e
fail=0
for ref in $(grep -hoE 'docs/[a-zA-Z0-9_/-]+\.(svg|png|jpg|jpeg|gif)' README.md README.ru.md | sort -u); do
if [ ! -f "$ref" ]; then
echo "::error file=README.md::missing referenced asset $ref"
fail=1
fi
done
exit $fail

- name: Internal Markdown links resolve
run: |
set -e
fail=0
for src in README.md README.ru.md CHANGELOG.md CONTRIBUTING.md CLAUDE.md; do
[ -f "$src" ] || continue
base="$(dirname "$src")"
for tgt in $(grep -hoE '\]\([^)]+\)' "$src" | sed 's/](\(.*\))/\1/' | sed 's/#.*$//'); do
case "$tgt" in
http*|mailto:*|"") continue ;;
esac
[ "$base" = "." ] && resolved="$tgt" || resolved="$base/$tgt"
if [ ! -e "$resolved" ] && [ ! -e "$tgt" ]; then
echo "::error file=$src::broken internal link → $tgt"
fail=1
fi
done
done
exit $fail

- name: SVG files are well-formed XML
run: |
set -e
fail=0
for f in $(git ls-files 'docs/*.svg' '*.svg'); do
if ! python -c "import xml.etree.ElementTree as ET; ET.parse('$f')" 2>/dev/null; then
echo "::error file=$f::malformed SVG XML"
fail=1
fi
done
exit $fail

- name: statusline.conf is sourceable bash (no syntax errors)
run: bash -n statusline.conf
50 changes: 50 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Changelog

All notable changes to this project will be documented in this file.
Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) · [SemVer](https://semver.org/spec/v2.0.0.html).

## [0.2.0] — 2026-04-30

### Added

- `docs/architecture.svg` — hand-rendered diagram of the cache-decoupling design (`vps-poller.sh` → `/tmp/vps-*.json` → `statusline.sh`, with the Claude Code transcript driving auto-focus). Renders inline in GitHub README.
- `docs/segments-annotated.svg` — explanatory diagram of every statusline segment with colour-coded labels above and below the rendered bar.
- `docs/screenshots/statusline-live.png` — embedded production screenshot from the [Habr article](https://habr.com/ru/articles/1013414/) (1560×256, 220 KB) showing a 100-hour Opus session with VPS auto-focus active.
- `CLAUDE.md` for this repository — a working Level 1 file documenting the architecture, key files, critical rules, commands, and patterns. Pairs with the [ai-context-hierarchy](https://github.com/CreatmanCEO/ai-context-hierarchy) sister repo.
- `CHANGELOG.md` (this file)
- `CONTRIBUTING.md` with a priority list for community submissions
- `.github/workflows/validate.yml` — CI checking `bash -n` syntax on all `.sh` files, `shellcheck -S warning` lint, internal Markdown link resolution, presence of `LICENSE` and `CHANGELOG.md`, and that every diagram referenced from README actually exists in `docs/`
- `Limitations` section in both READMEs covering the transcript-window heuristic, OS credential storage assumption, bash + jq requirement, global SSH timeout behaviour, lack of metric history, Windows-tmux unsupported, daemon being best-effort
- `Related` section cross-linking to [Claude Code Anti-Regression Setup](https://github.com/CreatmanCEO/claude-code-antiregression-setup) and [ai-context-hierarchy](https://github.com/CreatmanCEO/ai-context-hierarchy) (sister repos, same author)
- "Stars", "Validate", "Featured on Habr 9.3K reads", "Claude Code Opus 4.7" badges

### Changed

- README hero rewritten to lead with two social-proof signals (Habr article + 9.3K reads, Anthropic compatibility) and the one-line value proposition (`pure bash + jq, no Node.js`) — previously the value proposition was below the fold
- `What it shows` table renamed to `What every segment tells you` and is now anchored by the new annotated SVG diagram
- New `Architecture` section with the cache-decoupling diagram and a component-role table (was implicit in the configuration knobs only)
- `Configuration` table extended to include the previously undocumented `VPS_POLL_INTERVAL`, `VPS_SSH_TIMEOUT`, `VPS_STALE_SEC`, `COST_MODEL`, `TMUX_BRIDGE` knobs
- `Troubleshooting` section gained `VPS shows DOWN but server is fine` and `Auto-focus picks the wrong VPS` entries
- Author signature expanded with Habr / dev.to profile links

### Notes

- `screenshot.svg` (the original placeholder) is retained for backward-compat URLs; the README now references the live `statusline-live.png` and the annotated `segments-annotated.svg` instead.
- Topics on GitHub applied separately via `gh api` after merge.

## [0.1.0] — 2026-03-22

### Added

- Initial release accompanying the [Habr article](https://habr.com/ru/articles/1013414/) (9.3K reads as of writing)
- `statusline.sh` — model · git · lines · cost · usage limits · session duration · VPS health · context %
- `vps-poller.sh` — background SSH poller with atomic JSON cache writes
- `install.sh` — installer with `--vps` `--ru` `--tmux` `--minimal` `--uninstall` flags
- `statusline.conf` — full configuration schema with sensible defaults
- Auto-focus VPS detection via Claude Code transcript parsing (`ssh` / `scp` / `sftp` IP matching, with `VPS_MCP_MAP` fallback for MCP-only setups)
- 5-hour and weekly quota tracking via OAuth credential read from `~/.claude/.credentials.json` (inspired by [@AndyShaman/claude-statusline](https://github.com/AndyShaman/claude-statusline))
- Automatic API-vs-subscription cost detection (cost = 0 → theoretical pricing; cost > 0 → real)
- tmux integration (`Prefix+y` popup) when running inside a tmux session
- Russian language pack (`LANG_RU=true`) — labels render as `стр / контекст / мин / etc.`
- Cross-platform support — Linux, macOS, Windows (via Git Bash / WSL)
- `LICENSE` (MIT)
57 changes: 57 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# claude-statusline — CLAUDE.md (Level 1)

## Status: ACTIVE
Public Claude Code statusline tool. Pure bash + jq. No Node.js. Cross-platform via Git Bash / WSL on Windows.

## Architecture

Three artefacts and one cache:

- `vps-poller.sh` — background daemon. Runs `ssh root@host 'free / loadavg / df / uptime'` for each VPS every `VPS_POLL_INTERVAL` seconds (default 30 s). Writes one JSON file per VPS atomically via `mktemp + mv`. SSH timeout `VPS_SSH_TIMEOUT` seconds (default 5 s) → DOWN status.
- `/tmp/vps-<name>.json` — cache. One file per VPS. Statusline reads these instead of blocking on SSH.
- `statusline.sh` — runs on every Claude Code prompt. Reads cache (~0 ms latency), parses transcript for active-VPS auto-focus, applies colour thresholds, prints one bash line.
- `~/.claude/projects/*.jsonl` — Claude Code transcript. Last 20 KB scanned for `ssh` / `scp` / `sftp` invocations to drive auto-focus.

## Key Files

- `statusline.sh` — main script (343 lines). Functions: `color_by_threshold`, `calc_api_cost` (model-specific token pricing), `get_usage_limits` (OAuth → 5h/weekly quota), `format_tokens`, `format_duration`, `get_vps_status`, `detect_active_vps`.
- `vps-poller.sh` — daemon (94 lines). Functions: `log`, `poll_server`, `poll_all`. Controls: `start` / `stop` / `restart` / `status`.
- `install.sh` — installer (108 lines). Flags: `--vps` `--ru` `--tmux` `--minimal` `--uninstall`. Idempotent — re-run safely.
- `statusline.conf` — config schema. All knobs commented with defaults. Gets copied to `~/.claude/statusline.conf` on install.

## CRITICAL RULES — when editing this repo

- **NEVER** break backward compatibility on `statusline.conf` keys without a `CHANGELOG.md` entry under "Breaking changes". Users have hand-edited configs in production.
- **ALWAYS** keep `statusline.sh` portable across `bash 4+ / 5+`, macOS BSD utilities, and Linux GNU utilities. No `awk` features specific to GNU awk only. No bash 5-only constructs (`${var//pattern/}` is fine; `wait -n -p` is not).
- **ALWAYS** maintain `set -euo pipefail` discipline in scripts. `source ~/.claude/statusline.conf` must not crash the line on a typo — wrap in `set +u; source; set -u` or guard with `|| true`.
- **NEVER** introduce a network call from `statusline.sh` itself. The whole design assumes statusline rendering is non-blocking (cache-only). All slow IO lives in `vps-poller.sh`.
- **ALWAYS** mirror `README.md` and `README.ru.md` when changing user-facing surface area.
- **ALWAYS** update `CHANGELOG.md` for any change visible to users (new config key, glyph change, behaviour change, breaking change).

## Commands

- `bash install.sh` — install to `~/.claude/`
- `bash install.sh --uninstall` — clean removal
- `~/claude-statusline/vps-poller.sh start | stop | status` — daemon control
- `tail -f /tmp/vps-poller.log` — watch poller
- `bash -n statusline.sh && bash -n vps-poller.sh && bash -n install.sh` — syntax check (CI runs this)
- `shellcheck statusline.sh vps-poller.sh install.sh` — lint (CI runs this with `-S warning`)

## Key Patterns

1. **Cache decoupling.** Slow side (SSH) writes; fast side (statusline) reads. The decoupling is what makes a statusline running every prompt feasible.
2. **Atomic writes.** `vps-poller.sh` writes via `mktemp` + `mv`. Statusline never reads a half-written cache.
3. **Auto-focus from transcript.** Parses last 20 KB of `~/.claude/projects/*.jsonl`. Two-strategy: `ssh`/`scp`/`sftp` IPs first, then `VPS_MCP_MAP` for MCP-only setups.
4. **Threshold colouring.** `color_by_threshold` is the single function that maps a numeric % to ANSI green/yellow/red based on warn/crit thresholds. Used for context, RAM, CPU, disk, quota.

## Companion content

- [Habr article](https://habr.com/ru/articles/1013414/) — 9.3K reads, describes design rationale and the auto-focus heuristic.

## External validation

- [@AndyShaman/claude-statusline](https://github.com/AndyShaman/claude-statusline) — original inspiration for the H/W limits feature. Credit kept in README and in this file.

## Recent Changes

See [CHANGELOG.md](CHANGELOG.md).
43 changes: 43 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Contributing

Thanks for considering a contribution. This is a focused tool, not a framework — the bar for new content is "real use case, portable bash, doesn't break the cache decoupling."

## Priorities (highest impact first)

1. **Per-distribution package targets** — `apt`, `brew`, `dnf`, `pacman` install paths. Currently install is `git clone` + `bash install.sh`; package targets would let people `brew install claude-statusline`.
2. **Additional language locales** — current set is English (default) and Russian (`LANG_RU=true`). New locales should follow the same pattern: a `LANG_XX=true` flag and a label dictionary at the top of `statusline.sh`.
3. **Alternative VPS metric backends** — currently `vps-poller.sh` runs `free / loadavg / df / uptime` over SSH. Sister backends welcome:
- **Netdata API** — local Netdata install on each VPS exposes `/api/v1/data?chart=…`. Faster, no SSH per poll.
- **Prometheus push** — VPS pushes metrics to a local Prometheus, statusline reads from `/api/v1/query`.
- **MCP `system_*` tools** — when MCP exposes a system-info tool, use it instead of SSH.
4. **Fish / Zsh shells** — `statusline.sh` works under bash. Sister scripts for fish (`statusline.fish`) and zsh (`statusline.zsh`) would let those users run natively without `bash` invocation.
5. **Per-glyph theming** — currently glyphs are hardcoded (`●` / `◉` / `✗` / `↻` / `▽`). A `GLYPH_*` config block would let users pick ASCII-only glyphs (`*` / `o` / `X` / `~` / `-`) for terminals without good Unicode rendering.
6. **systemd-user unit** — `vps-poller.sh start` is a detached process. A `systemd --user` unit file would survive reboots and integrate with `systemctl --user status vps-poller`.

## What we will not merge

- Changes that introduce a network call inside `statusline.sh`. The whole architecture rests on statusline being non-blocking; slow IO lives in the poller.
- Changes that break backward compatibility on `statusline.conf` keys without a `CHANGELOG.md` entry under "Breaking changes". Users have hand-edited configs in production.
- Changes that drop bash-portability (no `bash 5.1+`-only constructs, no GNU-only `awk`).
- New top-level scripts that duplicate `install.sh` / `statusline.sh` / `vps-poller.sh` responsibilities. Add an option to an existing script if possible.
- Pretty refactors with no user-visible benefit. This codebase fits in your head — keep it that way.

## Pull request checklist

- [ ] `bash -n` clean on every modified `.sh` file
- [ ] `shellcheck -S warning` clean (CI runs this)
- [ ] If `statusline.conf` schema changed: documented in README's `Configuration` table, listed in `CHANGELOG.md`, default kept backward-compatible
- [ ] If user-visible behaviour changed: `README.md` updated AND `README.ru.md` mirrored
- [ ] `CHANGELOG.md` entry under Unreleased or a new minor version
- [ ] `validate.yml` workflow passes locally

## Style

- Bash should look like bash, not Python. `[[` over `[`, `$()` over backticks, `local` for function variables, quoted variable expansions everywhere.
- Comment the *why*, never the *what*. `# poll every 30s — 5s SSH timeout × 3 servers must fit` is useful; `# loop 30 times` is not.
- Keep functions under 30 lines. If you need more, split.
- One feature per PR. Stack PRs if you have multiple.

## Author / maintainer

[@CreatmanCEO](https://github.com/CreatmanCEO) — Nick Podolyak. Open an issue first for anything larger than a config knob or a glyph.
Loading
Loading