feat(orchestrator): install-statusline skill — cross-platform role indicator#6
Open
evannadeau wants to merge 2 commits into
Open
Conversation
New skill: installs a role-aware Claude Code statusline that reads
$ORCHESTRATOR_SESSION_KIND + $ORCHESTRATOR_AGENT_NAME (set by the
orchestrator launchers) and emits a one-line, ANSI-colored role
indicator inside the Claude UI. Cross-platform complement to the
Windows-only wt.exe tab color.
Output (per role):
🟡 PA PA-<timestamp> prime (yellow/gold)
⚪ SA SA-<name-or-timestamp> subordinate (neutral grey)
🔴 DISCORD DISCORD-LIVE-... discord-bot (red)
orchestrator <project> no launcher env (graceful fallback)
Files:
- scripts/orchestrator_statusline.py — canonical script, stdlib only
- scripts/orchestrator-statusline.sh — POSIX wrapper
- scripts/orchestrator-statusline.ps1 — Windows PowerShell wrapper
- SKILL.md — install instructions
Non-clobber pattern for existing user statuslines:
Claude Code's statusLine setting is a single command — there's no
compose API. The install skill detects an existing statusLine and
prompts the user with three options:
1. SKIP — keep your existing one
2. REPLACE — clobber (back up first)
3. COMPOSE MANUALLY — install the script + the user merges the
fragment into their existing statusline by hand
Default is SKIP when ambiguous. The skill never auto-composes
arbitrary shell commands (quoting / IFS / side-effects = fragile).
Depends on the env contract from the Python-canonical launcher PR
(pa-start / sa-start / discord-start set ORCHESTRATOR_SESSION_KIND).
If launched without those, the statusline degrades to a neutral
'orchestrator <project>' line rather than erroring.
Stdlib only (Python). Performance: <1ms per render — single env
lookup + string format. No subprocess / file IO / network.
Three fixes surfaced by 2026-05-13 sideload test:
1. **Detect existing statusLine at BOTH global and project level.**
The original install only checked `.claude/settings.json` (project).
But users with a global `~/.claude/settings.json` statusLine would
silently lose that statusline when this skill wrote a project-level
one — the project-level config shadows the global, so the user's
global statusline stopped rendering in this project. Step 3 now
checks both levels in precedence order and surfaces the existing
value with its detected location ("project" or "global").
2. **Add AUTO-COMPOSE as the recommended default** for users with an
existing statusline. Ships a new
`orchestrator-statusline-composed.sh` template that:
- Captures Claude Code's stdin JSON once (since stdin can only be
consumed once per process).
- Runs the orchestrator role indicator (which ignores stdin).
- Re-pipes the captured JSON into the user's `~/.claude/statusline.sh`.
Result: two-line statusline where line 1 is the orchestrator role
indicator and line 2+ is the user's existing renderer, untouched.
Non-destructive — the user's existing script + global settings stay
exactly as they were.
3. **Drop the agent-name from `orchestrator_statusline.py` output.**
The session-name (e.g. `PA-2026-05-13-12-00-00`) added timestamp
noise without informational value. Now renders just the role
glyph + label + project basename: `🟡 PA quayline`.
The previous 3-option menu (SKIP / REPLACE / COMPOSE MANUALLY) becomes
a 4-option menu (AUTO-COMPOSE / SKIP / REPLACE / COMPOSE MANUALLY) with
AUTO-COMPOSE as the recommended default for the existing-statusline
case. Documents the stdin fan-out caveat that motivates AUTO-COMPOSE
over a naive `cmd1; cmd2` composition.
Test-validated locally: composed wrapper renders
'🟡 PA quayline' + the user's personal model/usage line cleanly,
both reading their respective inputs without conflict.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The orchestrator launchers (`pa-start` / `sa-start` / `discord-start`)
already provide visual role distinction on Windows via `wt.exe --tabColor`
(gold for PA, red for Discord, default for SA). There's no portable POSIX
equivalent — Linux/macOS/WSL terminal emulators don't have a unified
tab-color API.
This PR adds a Claude Code statusline-based role indicator that:
top of the statusline.
(`$ORCHESTRATOR_SESSION_KIND`, `$ORCHESTRATOR_PROJECT_ROOT`); no
new env contract.
get both.
The original design also intended to auto-set the in-Claude `/color`
prompt-bar color. That path was abandoned after empirical
verification: built-in Claude Code slash commands (`/color`, `/clear`,
`/compact`, etc.) cannot be invoked programmatically from any
agent-side mechanism (Skill tool, Bash tool, hook output, settings.json,
launcher CLI flags). Confirmed against
official commands docs
and hooks docs. The
statusline approach is the only path that actually works for
programmatic per-session role indication.
What changes
New skill: `plugins/orchestrator/skills/install-statusline/`
```
install-statusline/
├── SKILL.md
└── scripts/
├── orchestrator_statusline.py # canonical renderer (stdlib only)
├── orchestrator-statusline.sh # POSIX wrapper
├── orchestrator-statusline.ps1 # Windows PowerShell wrapper
└── orchestrator-statusline-composed.sh # AUTO-COMPOSE wrapper template
```
Renderer (`orchestrator_statusline.py`):
from env.
No subprocess / file IO / network.
neutral `orchestrator ` line. Never errors the Claude UI.
Non-clobber pattern for users with existing statuslines:
Claude Code's `statusLine` setting is a single command — no built-in
composition API. The install skill detects existing config at BOTH
global (`~/.claude/settings.json`) AND project level
(`.claude/settings.json`) and surfaces a 4-option menu:
that runs both the orchestrator renderer AND the user's existing
statusline. Captures stdin JSON once, fans out to both. The user's
existing script + global settings stay untouched. Non-destructive.
If existing was global, this only shadows in this project; global
stays intact for other projects. If existing was project-level,
it's lost.
User merges by hand. For non-standard setups where AUTO-COMPOSE's
default of `~/.claude/statusline.sh` doesn't match.
Stdin fan-out (the non-obvious bit):
Claude Code pipes session JSON to the statusline command via stdin. A
naive `cmd1 ; cmd2` composition has both fight over stdin — cmd1
consumes it, cmd2 sees EOF and renders blank. The composed wrapper
captures stdin once at the top, runs the orchestrator renderer (which
ignores stdin), then re-pipes the captured JSON to the user's
existing renderer. Tested locally against a real Claude Code session;
both renderers produce their expected output.
Behavior
User-facing statusline before this PR (when launched via `pa-start`):
```
(no role indicator inside Claude UI — only the Windows tab color)
```
After:
```
🟡 PA quayline
Opus · ░░░░░░░░░░░░░░░░░░░░ 0% · 5h 0% · 7d 0% ← user's existing line, preserved
```
Or if the user has no existing statusline:
```
🟡 PA quayline
```
Test plan
Locally tested 2026-05-13 by sideloading into the marketplace skills
directory and running through the install flow against a real Claude
Code session with an existing global statusline. Verified:
on fresh session.
project-level `statusLine` pointing at it, left the global setting
untouched.
role indicator (yellow PA + project basename) followed by user's
personal model/usage statusline.
expected from Claude Code.
Smoke test reproducible standalone:
```bash
echo '{"model":{"id":"claude-opus-4-7"}}' | \
ORCHESTRATOR_SESSION_KIND=prime \
ORCHESTRATOR_PROJECT_ROOT=/tmp/some-project \
./orchestrator-statusline-composed.sh
```
Operator E2E pending against fresh Windows + WSL hosts post-merge
(per the bash-port verification protocol).
Stack independence
Depends conceptually on `$ORCHESTRATOR_SESSION_KIND` being set by
the Python-canonical launcher PR (#5), but does not depend on PR #5
landing first — the env var has been set by the prior PowerShell
launchers since 0.30.31 (WI `c03c9d6a`). This PR cleanly stands alone.
Unrelated to PR #2 (sidecar boot timeout) and PR #4 (backup-plugin-db).
Mergeable in any order.
Files changed
```
plugins/orchestrator/skills/install-statusline/SKILL.md (new, 326 lines)
plugins/orchestrator/skills/install-statusline/scripts/orchestrator_statusline.py (new, 82 lines)
plugins/orchestrator/skills/install-statusline/scripts/orchestrator-statusline.sh (new, 24 lines)
plugins/orchestrator/skills/install-statusline/scripts/orchestrator-statusline.ps1 (new, 33 lines)
plugins/orchestrator/skills/install-statusline/scripts/orchestrator-statusline-composed.sh (new, 44 lines)
```
No changes to existing files. No new third-party deps. Python 3.10+ is
already a baseline plugin dependency via `sidecar/embed_server.py`.