legbar 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,134 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: ci-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ jobs:
17
+ test:
18
+ # legbar claims 3.9+ on macOS, Linux, and Windows. curses is stdlib on the
19
+ # first two; Windows gets it from the windows-curses wheel (declared in
20
+ # pyproject.toml, platform-gated), which is why this job installs it
21
+ # explicitly rather than relying on the package install step alone.
22
+ #
23
+ # 3.14 is in the matrix because it is what the primary dev box runs -- the
24
+ # sibling repos went months testing only 3.9 and 3.13 while every local run
25
+ # used 3.14. macos-13 is here because macos-latest is arm64, so without it
26
+ # Intel Macs have no coverage.
27
+ #
28
+ # Windows is excluded from 3.14 on purpose: windows-curses wheels top out at
29
+ # 3.13, so that leg would fail on dependency resolution rather than on
30
+ # anything in this repo. Drop the exclusion once upstream ships a wheel.
31
+ strategy:
32
+ fail-fast: false
33
+ matrix:
34
+ os: [ubuntu-latest, macos-latest, macos-13, windows-latest]
35
+ python: ["3.9", "3.13", "3.14"]
36
+ exclude:
37
+ - os: windows-latest
38
+ python: "3.14"
39
+ runs-on: ${{ matrix.os }}
40
+ steps:
41
+ - uses: actions/checkout@v7
42
+ - uses: actions/setup-python@v7
43
+ with:
44
+ python-version: ${{ matrix.python }}
45
+
46
+ - name: Install windows-curses
47
+ if: runner.os == 'Windows'
48
+ run: pip install windows-curses
49
+
50
+ - name: Run tests
51
+ run: python -m unittest discover -s tests -t . -v
52
+
53
+ # Everything here must work on a box with no Claude Code, no sessions, no
54
+ # Cursor install and no gh auth -- degradation is the contract under test.
55
+ # A fleet dashboard that crashes on an empty fleet is worse than useless,
56
+ # because the empty case is exactly when someone is debugging why nothing
57
+ # is running.
58
+ - name: Smoke test with nothing running
59
+ shell: bash
60
+ env:
61
+ LEGBAR_SESSIONS_DIR: ${{ runner.temp }}/no-sessions
62
+ LEGBAR_PROJECTS_DIR: ${{ runner.temp }}/no-projects
63
+ LEGBAR_CURSOR_HOME: ${{ runner.temp }}/no-cursor
64
+ run: |
65
+ python legbar.py --version
66
+ python legbar.py --help > /dev/null
67
+ python legbar.py --once --no-git --no-ci
68
+ # Workspace-relative, not /tmp: on the Windows runner bash writes
69
+ # /tmp into Git Bash's temp while Python reads it as C:\tmp, so the
70
+ # two disagree and the read fails on that leg alone.
71
+ python legbar.py --json --no-git --no-ci > state.json
72
+ python -c "import json; d=json.load(open('state.json')); assert d['sessions']==[], d['sessions']; print('empty-fleet JSON ok')"
73
+
74
+ # henhouse is a working CLI in its own right, and legbar is only a
75
+ # renderer over it -- so it gets its own smoke test rather than being
76
+ # covered incidentally.
77
+ - name: Smoke test henhouse
78
+ shell: bash
79
+ env:
80
+ LEGBAR_SESSIONS_DIR: ${{ runner.temp }}/no-sessions
81
+ LEGBAR_PROJECTS_DIR: ${{ runner.temp }}/no-projects
82
+ run: python henhouse.py --no-git
83
+
84
+ lint:
85
+ runs-on: ubuntu-latest
86
+ steps:
87
+ - uses: actions/checkout@v7
88
+ - uses: actions/setup-python@v7
89
+ with:
90
+ python-version: "3.13"
91
+
92
+ # Stdlib-only is a promise this repo makes in its README and pyproject,
93
+ # and the sibling repos enforce it the same way: the only third-party
94
+ # import allowed anywhere is windows-curses, and only behind a platform
95
+ # guard. A dependency that sneaks in is a dependency someone's locked-down
96
+ # box will not have.
97
+ - name: Assert stdlib-only
98
+ run: |
99
+ python - <<'PY'
100
+ import ast, sys, pathlib
101
+ ALLOWED = {"henhouse", "legbar", "curses", "_curses"}
102
+ bad = []
103
+ for f in ("legbar.py", "henhouse.py"):
104
+ tree = ast.parse(pathlib.Path(f).read_text(encoding="utf-8"))
105
+ for node in ast.walk(tree):
106
+ if isinstance(node, ast.Import):
107
+ names = [a.name.split(".")[0] for a in node.names]
108
+ elif isinstance(node, ast.ImportFrom):
109
+ names = [(node.module or "").split(".")[0]]
110
+ else:
111
+ continue
112
+ for n in names:
113
+ if n and n not in ALLOWED and n not in sys.stdlib_module_names:
114
+ bad.append(f"{f}: {n}")
115
+ if bad:
116
+ print("non-stdlib imports found:", *bad, sep="\n ")
117
+ raise SystemExit(1)
118
+ print("stdlib-only: ok")
119
+ PY
120
+
121
+ - name: Assert the output stays ASCII
122
+ # Block-drawing characters mojibake in the Windows console. This is a
123
+ # promise the README makes and the tests assert per-line; this catches
124
+ # a stray glyph landing in a source literal that no test happens to
125
+ # render.
126
+ run: |
127
+ python - <<'PY'
128
+ import pathlib
129
+ for f in ("legbar.py", "henhouse.py"):
130
+ for i, line in enumerate(pathlib.Path(f).read_text(encoding="utf-8").splitlines(), 1):
131
+ if not line.isascii():
132
+ raise SystemExit(f"{f}:{i} non-ASCII: {line.strip()[:60]}")
133
+ print("ascii-only: ok")
134
+ PY
@@ -0,0 +1,142 @@
1
+ name: release
2
+
3
+ # Builds the wheel and sdist for a tag, verifies the built package actually
4
+ # runs, attaches both to the GitHub Release, and publishes to PyPI via OIDC
5
+ # trusted publishing -- no API token exists anywhere, not in secrets and not
6
+ # on a laptop.
7
+ #
8
+ # PyPI is deliberately the only channel here. The sibling repos publish to
9
+ # brew, npm and a signed apt repo as well, and each of those carries its own
10
+ # one-time credential setup (a tap PAT, an npm trusted publisher, a GPG
11
+ # signing key). Adding them before the first PyPI release has ever run would
12
+ # mean debugging four publish paths at once against a package that has never
13
+ # been published. They come one at a time, after this one is green.
14
+ on:
15
+ push:
16
+ tags: ["v*"]
17
+ workflow_dispatch:
18
+ inputs:
19
+ dry_run:
20
+ description: Build and verify only -- publishes nothing, attaches nothing.
21
+ type: boolean
22
+ default: true
23
+
24
+ permissions:
25
+ contents: write
26
+
27
+ jobs:
28
+ build:
29
+ runs-on: ubuntu-latest
30
+ steps:
31
+ - uses: actions/checkout@v7
32
+ - uses: actions/setup-python@v7
33
+ with:
34
+ python-version: "3.13"
35
+
36
+ - name: Build wheel and sdist
37
+ run: |
38
+ python -m pip install --upgrade build packaging
39
+ python -m build
40
+
41
+ # The version lives in legbar.py alone. A tag that disagrees with it
42
+ # would publish artifacts whose filenames contradict the release they
43
+ # hang off.
44
+ - name: Check the tag matches __version__
45
+ if: startsWith(github.ref, 'refs/tags/v')
46
+ run: |
47
+ code_version=$(sed -n 's/^__version__ = "\(.*\)"/\1/p' legbar.py)
48
+ tag_version="${GITHUB_REF_NAME#v}"
49
+ if [ "$code_version" != "$tag_version" ]; then
50
+ echo "::error::tag $tag_version != __version__ $code_version" >&2
51
+ exit 1
52
+ fi
53
+
54
+ # PEP 440 normalises the version before it reaches the wheel filename --
55
+ # "0.1.0-alpha" becomes "0.1.0a0", so a raw string comparison against
56
+ # __version__ would fail on a perfectly valid prerelease. Compare the
57
+ # normalisation instead, and print both so the drift between the tag and
58
+ # what PyPI will display is visible in the log rather than discovered on
59
+ # the project page.
60
+ - name: Check the wheel filename matches the normalised version
61
+ run: |
62
+ python - <<'PY'
63
+ import pathlib, re, sys
64
+ from packaging.version import Version
65
+ code = re.search(r'^__version__ = "(.*)"', pathlib.Path("legbar.py").read_text(), re.M).group(1)
66
+ wheel = next(pathlib.Path("dist").glob("*.whl")).name.split("-")[1]
67
+ print(f"__version__ / tag : {code}")
68
+ print(f"wheel and sdist : {wheel} (what PyPI will display)")
69
+ if str(Version(code)) != wheel:
70
+ print(f"::error::{code} normalises to {Version(code)}, but the wheel says {wheel}")
71
+ sys.exit(1)
72
+ if code != wheel:
73
+ print(f"::warning::PyPI will show {wheel}, not {code} -- valid, but the tag and the project page will read differently")
74
+ PY
75
+
76
+ # Everything below runs against the built artifact, not the source tree.
77
+ # A wheel that imports in the repo root and nowhere else is the classic
78
+ # packaging failure, and it is invisible until someone installs it.
79
+ - name: Verify the built package actually runs
80
+ run: |
81
+ python -m venv /tmp/v
82
+ /tmp/v/bin/pip install --quiet dist/*.whl
83
+ /tmp/v/bin/legbar --version
84
+ /tmp/v/bin/python -c "import legbar, henhouse"
85
+
86
+ # The empty-fleet case is the contract legbar's CI already guards, and
87
+ # it has to survive packaging too: henhouse must be importable *and*
88
+ # runnable from the installed wheel, not just from a checkout.
89
+ - name: Verify the installed package degrades on an empty fleet
90
+ env:
91
+ LEGBAR_SESSIONS_DIR: ${{ runner.temp }}/no-sessions
92
+ LEGBAR_PROJECTS_DIR: ${{ runner.temp }}/no-projects
93
+ LEGBAR_CURSOR_HOME: ${{ runner.temp }}/no-cursor
94
+ run: |
95
+ cd /tmp
96
+ /tmp/v/bin/legbar --json --no-git --no-ci > state.json
97
+ /tmp/v/bin/python -c "import json; d=json.load(open('state.json')); assert d['sessions']==[], d['sessions']; print('empty-fleet JSON ok')"
98
+
99
+ - uses: actions/upload-artifact@v7
100
+ with:
101
+ name: dist
102
+ path: dist/*
103
+
104
+ # Pushing a tag does not create a GitHub Release, so uploading straight
105
+ # to one fails on a plain `git push --tags`. Create it if it is not there
106
+ # and upload either way, so the tag is the only step a release needs.
107
+ - name: Attach to the release
108
+ if: startsWith(github.ref, 'refs/tags/v')
109
+ env:
110
+ GH_TOKEN: ${{ github.token }}
111
+ run: |
112
+ # Canonical PEP 440 prerelease spellings only -- aN / bN / rcN. The
113
+ # repo does not use -alpha or .beta forms precisely because they are
114
+ # not what ends up on the wheel.
115
+ prerelease=""
116
+ case "$GITHUB_REF_NAME" in *a[0-9]*|*b[0-9]*|*rc[0-9]*) prerelease="--prerelease" ;; esac
117
+ gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1 \
118
+ || gh release create "$GITHUB_REF_NAME" --title "$GITHUB_REF_NAME" --generate-notes $prerelease
119
+ gh release upload "$GITHUB_REF_NAME" dist/* --clobber
120
+
121
+ # Publishes the tagged build to PyPI via trusted publishing (OIDC): PyPI
122
+ # trusts this repo's release.yml through the "pypi" environment, so there is
123
+ # no token to store, leak or rotate.
124
+ #
125
+ # One-time setup this job depends on and cannot do for itself: register a
126
+ # pending Trusted Publisher on PyPI for the project "legbar", pointing at
127
+ # owner gmhoward9289-ops, repository legbar, workflow release.yml,
128
+ # environment "pypi". Until that exists this job fails alone -- the release
129
+ # itself, and the artifacts attached to it, still land.
130
+ pypi:
131
+ if: startsWith(github.ref, 'refs/tags/v')
132
+ needs: build
133
+ runs-on: ubuntu-latest
134
+ environment: pypi
135
+ permissions:
136
+ id-token: write
137
+ steps:
138
+ - uses: actions/download-artifact@v8
139
+ with:
140
+ name: dist
141
+ path: dist
142
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,7 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .pytest_cache/
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ state.json
@@ -0,0 +1 @@
1
+ public
@@ -0,0 +1,59 @@
1
+ # Changelog
2
+
3
+ Notable changes to legbar; entries newest first.
4
+
5
+ ## Versioning
6
+
7
+ Plain SemVer. `0.1.0` is the first release: the program works, both lanes render
8
+ against real data, and calling that an alpha would understate it. A leading `0.`
9
+ already says the API may move.
10
+
11
+ - **Source of truth:** `legbar.__version__`; the man page and any package
12
+ metadata must agree with it.
13
+ - **Tags:** `v` + the version string.
14
+ - **If a prerelease is ever needed,** it is written in canonical PEP 440 form:
15
+ `0.2.0a1`, `0.2.0b1`, `0.2.0rc1` — never `-alpha`, `-alpha.2` or `alpha2`.
16
+ PEP 440 rewrites all three before they reach a wheel filename (`0.2.0-alpha`
17
+ becomes `0.2.0a0`), so the tag and the PyPI page would read differently for
18
+ the same build. `release.yml` checks the normalisation on every tag.
19
+
20
+ ## v0.1.0 - 2026-08-09
21
+
22
+ First cut. Both lanes render against real data on COOPER.
23
+
24
+ ### Added
25
+ - **One merged view.** `SESSIONS` (every live agent, with model, context burn
26
+ and status) beside `CI / PRS` (GitHub runs and open pull requests, failures
27
+ pinned). Panes stack instead of splitting below 110 columns.
28
+ - **A Cursor lane, which neither parent tool had on this side.** Cursor writes
29
+ no session marker and no claim, so a fleet view reading only Claude's session
30
+ directory is blind to every Cursor agent beside it. legbar infers them from
31
+ `~/.cursor/projects/<slug>/agent-transcripts/`, marks them `cu`, and leaves
32
+ `pid` as `None` — liveness there is a transcript mtime, not a process probe,
33
+ and the weaker signal is labelled rather than dressed up as the stronger one.
34
+ - **Filesystem-resolved Cursor slugs.** The slug joins path parts with `-`,
35
+ which is legal inside a directory name, so a naive split gets every
36
+ hyphenated repo wrong (`heron-ops`, `swamp-ops`, `open-vanity`). Each segment
37
+ now resolves longest-first against what exists on disk, falling back to the
38
+ naive split when nothing matches rather than raising.
39
+ - `--once`, `--json`, `--no-git`, `--no-ci`; `q` / `g` / `r` in the full-screen
40
+ view.
41
+
42
+ ### Changed
43
+ - **One configuration block, one prefix.** roost and leghorn grew four env-var
44
+ conventions between them (`ROOST_*`, `LEGHORN_ROOT`, `CCWORK_*`, and bare
45
+ `CLAUDE_PROJECTS_DIR` / `CURSOR_AGENT_HOME`), and — worse — the same two paths
46
+ were hardcoded in one tool and overridable in the other, so the pair could be
47
+ pointed at different data on one machine and silently disagree about what was
48
+ running. Every override is now `LEGBAR_*`, declared once, with the older names
49
+ still honoured so existing installs keep their configuration.
50
+ - `henhouse.py` (renamed from leghorn's `coop.py`) is legbar's canonical copy of
51
+ the discovery layer.
52
+
53
+ ### Known gaps
54
+ - Worktrees of one GitHub repo are listed as separate clones, so the same run
55
+ and PR appear once per worktree in the CI pane. Inherited from leghorn's
56
+ `github_repos()`; it wants a dedup by origin.
57
+ - leghorn and roost do not import this copy of `henhouse.py` yet — that
58
+ consolidation is the next step, and until it lands the drift the shared layer
59
+ was meant to end is only fixed on legbar's side.
legbar-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 George M. Howard
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
legbar-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,170 @@
1
+ Metadata-Version: 2.4
2
+ Name: legbar
3
+ Version: 0.1.0
4
+ Summary: One screen for the whole fleet: live agent sessions beside GitHub CI
5
+ Project-URL: Homepage, https://github.com/gmhoward9289-ops/legbar
6
+ Project-URL: Issues, https://github.com/gmhoward9289-ops/legbar/issues
7
+ Author-email: "George M. Howard" <dev@swamplink.com>
8
+ License: MIT License
9
+
10
+ Copyright (c) 2026 George M. Howard
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
29
+ License-File: LICENSE
30
+ Keywords: agents,ci,claude,cursor,dashboard,tui
31
+ Classifier: Development Status :: 3 - Alpha
32
+ Classifier: Environment :: Console :: Curses
33
+ Classifier: Intended Audience :: Developers
34
+ Classifier: License :: OSI Approved :: MIT License
35
+ Classifier: Programming Language :: Python :: 3
36
+ Classifier: Topic :: Software Development :: Build Tools
37
+ Requires-Python: >=3.9
38
+ Requires-Dist: windows-curses; sys_platform == 'win32'
39
+ Description-Content-Type: text/markdown
40
+
41
+ <img src="assets/legbar.svg" alt="" width="120" align="right">
42
+
43
+ # legbar
44
+
45
+ One screen for the whole fleet: **live agent sessions beside GitHub CI**, drawn
46
+ from a single discovery layer so the two panes can never disagree.
47
+
48
+ `roost` answers *what are the models doing*. `leghorn` answers *what are the
49
+ repos doing*. Both are true at once and neither view contains the other — so
50
+ watching a fleet has meant watching two windows and joining them by eye.
51
+ legbar draws both lanes on one canvas.
52
+
53
+ ```
54
+ legbar 5 sessions | 1 cursor | 1 need input | 15 ci red | 125k held | 18:37:00
55
+
56
+ SESSIONS CI / PRS
57
+ -------- --------
58
+ cc heron-ops-3c FB5 ###------- 32% needsinput dev X roost ci
59
+ cc swamp-ops-ad OP5 ###------- 25% working dev X roost #56 ci: skip winget~
60
+ cc heron-ops-16 FB5 #--------- 10% working dev X copilot-money~ Tests
61
+ cc claude-10 OP5 #--------- 8% idle Claude > leghorn release
62
+ cu c5468eb1 - - idle dev . leghorn #61 checks pending
63
+ ```
64
+
65
+ > **A recorded demo is coming.** The frame above is real output, captured from
66
+ > a live fleet. The animated walkthrough its sibling tools ship
67
+ > ([roost](https://github.com/gmhoward9289-ops/roost),
68
+ > [leghorn](https://github.com/gmhoward9289-ops/leghorn)) lands once the
69
+ > early bugs are shaken out — recording a tool while it is still moving
70
+ > produces a demo that is wrong by the time anyone watches it.
71
+
72
+ ## What each lane shows
73
+
74
+ **SESSIONS** — every live agent on the machine. Claude Code sessions are joined
75
+ by pid against `~/.claude/sessions/<pid>.json`, with model, context burn, and
76
+ status read from the session's own JSONL transcript. **Cursor agents appear
77
+ too**, marked `cu` — Cursor writes no session marker and no claim, so a fleet
78
+ view that only reads Claude's session directory is blind to every Cursor agent
79
+ running beside it.
80
+
81
+ **CI / PRS** — GitHub Actions runs and open pull requests across every clone,
82
+ with failures pinned so a red build cannot scroll away.
83
+
84
+ ## Install
85
+
86
+ ```
87
+ pipx install legbar
88
+ ```
89
+
90
+ Python 3.9+, standard library only. Works on macOS, Linux and Windows.
91
+
92
+ ## Usage
93
+
94
+ ```
95
+ legbar # the full-screen view
96
+ legbar --once # render one frame and exit (pipes, CI, screenshots)
97
+ legbar --json # the joined state, for piping somewhere else
98
+ legbar --no-git # skip git probing if it is ever slow
99
+ legbar --no-ci # skip the gh sweep (offline, or when it is slow)
100
+ ```
101
+
102
+ In the full-screen view: `q` quit, `g` toggle git probing, `r` refresh now.
103
+
104
+ ## Configuration
105
+
106
+ Every override is `LEGBAR_*`. The older `roost` / `leghorn` / `ccwork` variable
107
+ names are still honoured, so an existing install keeps whatever it had
108
+ configured; the new name wins when both are set.
109
+
110
+ | Variable | Default | What it points at |
111
+ |---|---|---|
112
+ | `LEGBAR_REPOS_ROOT` | `~/GitHub` | where the clones live |
113
+ | `LEGBAR_SESSIONS_DIR` | `~/.claude/sessions` | live-session markers |
114
+ | `LEGBAR_PROJECTS_DIR` | `~/.claude/projects` | session transcripts |
115
+ | `LEGBAR_CURSOR_HOME` | `~/.cursor` | Cursor's agent transcripts |
116
+ | `LEGBAR_CURSOR_MAX_IDLE_SECS` | `86400` | how far back a Cursor agent counts as live |
117
+ | `LEGBAR_BACKENDS` | `claude,cursor` | which discovery lanes run at all |
118
+
119
+ ## Two honest caveats
120
+
121
+ **Cursor liveness is inferred, not probed.** A Claude row means *this process
122
+ exists* (`os.kill(pid, 0)`). A Cursor row means *this transcript moved
123
+ recently* — Cursor writes no pid to join against. That is a weaker signal and
124
+ it is labelled as one; `pid` is `None` for Cursor rows rather than invented.
125
+
126
+ **Cursor's project slugs are lossy.** The slug joins path parts with `-`, and
127
+ `-` is legal inside a directory name, so `c-Users-me-dev-heron-ops` reads
128
+ equally as `dev/heron/ops` and `dev/heron-ops`. legbar resolves each segment
129
+ longest-first against what actually exists on disk. A slug that resolves
130
+ nowhere — another machine's checkout, a deleted clone — falls back to the
131
+ naive split rather than raising.
132
+
133
+ ## It is ASCII on purpose
134
+
135
+ Block-drawing characters mojibake in the Windows console, so the context bars,
136
+ the pane rules and the status glyphs are all plain ASCII. Same constraint
137
+ `roost`'s sparklines and `leghorn`'s tables are built around.
138
+
139
+ ```
140
+ ,__
141
+ _(o \__
142
+ / \
143
+ | ====== |
144
+ | ====== |
145
+ \ ==== /
146
+ \______/
147
+ || ||
148
+ ^^ ^^
149
+ ```
150
+
151
+ ## Read-only
152
+
153
+ legbar reads transcripts and registries, and runs `git` and `gh` in read-only
154
+ modes. It never writes to a repo.
155
+
156
+ ## The family
157
+
158
+ - **[roost](https://github.com/gmhoward9289-ops/roost)** — `top` for Claude
159
+ Code: per-session context burn, models, and the subagents a session spawned.
160
+ - **[leghorn](https://github.com/gmhoward9289-ops/leghorn)** — the repo lane on
161
+ its own: sessions joined to worktrees and real git state, CI, and a commit
162
+ feed.
163
+ - **legbar** — both lanes on one screen, over one discovery layer.
164
+
165
+ `henhouse.py` is that discovery layer: sessions, transcripts, git, GitHub, and
166
+ Cursor. It is a working CLI in its own right (`python henhouse.py`).
167
+
168
+ ## License
169
+
170
+ MIT
legbar-0.1.0/README.md ADDED
@@ -0,0 +1,130 @@
1
+ <img src="assets/legbar.svg" alt="" width="120" align="right">
2
+
3
+ # legbar
4
+
5
+ One screen for the whole fleet: **live agent sessions beside GitHub CI**, drawn
6
+ from a single discovery layer so the two panes can never disagree.
7
+
8
+ `roost` answers *what are the models doing*. `leghorn` answers *what are the
9
+ repos doing*. Both are true at once and neither view contains the other — so
10
+ watching a fleet has meant watching two windows and joining them by eye.
11
+ legbar draws both lanes on one canvas.
12
+
13
+ ```
14
+ legbar 5 sessions | 1 cursor | 1 need input | 15 ci red | 125k held | 18:37:00
15
+
16
+ SESSIONS CI / PRS
17
+ -------- --------
18
+ cc heron-ops-3c FB5 ###------- 32% needsinput dev X roost ci
19
+ cc swamp-ops-ad OP5 ###------- 25% working dev X roost #56 ci: skip winget~
20
+ cc heron-ops-16 FB5 #--------- 10% working dev X copilot-money~ Tests
21
+ cc claude-10 OP5 #--------- 8% idle Claude > leghorn release
22
+ cu c5468eb1 - - idle dev . leghorn #61 checks pending
23
+ ```
24
+
25
+ > **A recorded demo is coming.** The frame above is real output, captured from
26
+ > a live fleet. The animated walkthrough its sibling tools ship
27
+ > ([roost](https://github.com/gmhoward9289-ops/roost),
28
+ > [leghorn](https://github.com/gmhoward9289-ops/leghorn)) lands once the
29
+ > early bugs are shaken out — recording a tool while it is still moving
30
+ > produces a demo that is wrong by the time anyone watches it.
31
+
32
+ ## What each lane shows
33
+
34
+ **SESSIONS** — every live agent on the machine. Claude Code sessions are joined
35
+ by pid against `~/.claude/sessions/<pid>.json`, with model, context burn, and
36
+ status read from the session's own JSONL transcript. **Cursor agents appear
37
+ too**, marked `cu` — Cursor writes no session marker and no claim, so a fleet
38
+ view that only reads Claude's session directory is blind to every Cursor agent
39
+ running beside it.
40
+
41
+ **CI / PRS** — GitHub Actions runs and open pull requests across every clone,
42
+ with failures pinned so a red build cannot scroll away.
43
+
44
+ ## Install
45
+
46
+ ```
47
+ pipx install legbar
48
+ ```
49
+
50
+ Python 3.9+, standard library only. Works on macOS, Linux and Windows.
51
+
52
+ ## Usage
53
+
54
+ ```
55
+ legbar # the full-screen view
56
+ legbar --once # render one frame and exit (pipes, CI, screenshots)
57
+ legbar --json # the joined state, for piping somewhere else
58
+ legbar --no-git # skip git probing if it is ever slow
59
+ legbar --no-ci # skip the gh sweep (offline, or when it is slow)
60
+ ```
61
+
62
+ In the full-screen view: `q` quit, `g` toggle git probing, `r` refresh now.
63
+
64
+ ## Configuration
65
+
66
+ Every override is `LEGBAR_*`. The older `roost` / `leghorn` / `ccwork` variable
67
+ names are still honoured, so an existing install keeps whatever it had
68
+ configured; the new name wins when both are set.
69
+
70
+ | Variable | Default | What it points at |
71
+ |---|---|---|
72
+ | `LEGBAR_REPOS_ROOT` | `~/GitHub` | where the clones live |
73
+ | `LEGBAR_SESSIONS_DIR` | `~/.claude/sessions` | live-session markers |
74
+ | `LEGBAR_PROJECTS_DIR` | `~/.claude/projects` | session transcripts |
75
+ | `LEGBAR_CURSOR_HOME` | `~/.cursor` | Cursor's agent transcripts |
76
+ | `LEGBAR_CURSOR_MAX_IDLE_SECS` | `86400` | how far back a Cursor agent counts as live |
77
+ | `LEGBAR_BACKENDS` | `claude,cursor` | which discovery lanes run at all |
78
+
79
+ ## Two honest caveats
80
+
81
+ **Cursor liveness is inferred, not probed.** A Claude row means *this process
82
+ exists* (`os.kill(pid, 0)`). A Cursor row means *this transcript moved
83
+ recently* — Cursor writes no pid to join against. That is a weaker signal and
84
+ it is labelled as one; `pid` is `None` for Cursor rows rather than invented.
85
+
86
+ **Cursor's project slugs are lossy.** The slug joins path parts with `-`, and
87
+ `-` is legal inside a directory name, so `c-Users-me-dev-heron-ops` reads
88
+ equally as `dev/heron/ops` and `dev/heron-ops`. legbar resolves each segment
89
+ longest-first against what actually exists on disk. A slug that resolves
90
+ nowhere — another machine's checkout, a deleted clone — falls back to the
91
+ naive split rather than raising.
92
+
93
+ ## It is ASCII on purpose
94
+
95
+ Block-drawing characters mojibake in the Windows console, so the context bars,
96
+ the pane rules and the status glyphs are all plain ASCII. Same constraint
97
+ `roost`'s sparklines and `leghorn`'s tables are built around.
98
+
99
+ ```
100
+ ,__
101
+ _(o \__
102
+ / \
103
+ | ====== |
104
+ | ====== |
105
+ \ ==== /
106
+ \______/
107
+ || ||
108
+ ^^ ^^
109
+ ```
110
+
111
+ ## Read-only
112
+
113
+ legbar reads transcripts and registries, and runs `git` and `gh` in read-only
114
+ modes. It never writes to a repo.
115
+
116
+ ## The family
117
+
118
+ - **[roost](https://github.com/gmhoward9289-ops/roost)** — `top` for Claude
119
+ Code: per-session context burn, models, and the subagents a session spawned.
120
+ - **[leghorn](https://github.com/gmhoward9289-ops/leghorn)** — the repo lane on
121
+ its own: sessions joined to worktrees and real git state, CI, and a commit
122
+ feed.
123
+ - **legbar** — both lanes on one screen, over one discovery layer.
124
+
125
+ `henhouse.py` is that discovery layer: sessions, transcripts, git, GitHub, and
126
+ Cursor. It is a working CLI in its own right (`python henhouse.py`).
127
+
128
+ ## License
129
+
130
+ MIT