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.
- legbar-0.1.0/.github/workflows/ci.yml +134 -0
- legbar-0.1.0/.github/workflows/release.yml +142 -0
- legbar-0.1.0/.gitignore +7 -0
- legbar-0.1.0/.repo-visibility +1 -0
- legbar-0.1.0/CHANGELOG.md +59 -0
- legbar-0.1.0/LICENSE +21 -0
- legbar-0.1.0/PKG-INFO +170 -0
- legbar-0.1.0/README.md +130 -0
- legbar-0.1.0/assets/legbar.svg +56 -0
- legbar-0.1.0/henhouse.py +1280 -0
- legbar-0.1.0/legbar.py +537 -0
- legbar-0.1.0/pyproject.toml +49 -0
- legbar-0.1.0/tests/__init__.py +0 -0
- legbar-0.1.0/tests/test_henhouse_cursor.py +118 -0
- legbar-0.1.0/tests/test_legbar.py +279 -0
|
@@ -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
|
legbar-0.1.0/.gitignore
ADDED
|
@@ -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
|