console (shell wrapper)
└── src/operator_console/cli.py ← main dispatcher + repo discovery + profile picker
├── profile_loader.py ← YAML profile loading + validation
├── launcher.py ← Zellij session creation / tab management
├── demo.py ← end-to-end platform validation flow
├── providers.py ← provider dashboard helper + free-provider guide
├── layout.py ← layout persistence (save/load/show/reset)
├── tab_capture.py ← live Zellij layout capture (dump-layout + KDL extraction)
├── session_group.py ← session-group persistence (save/load last group for `console restore`)
├── session.py ← Zellij session state queries
├── guardrails.py ← branch detection + warnings
├── bootstrap.py ← Claude/Codex/Aider context generation + session wrapper scripts
├── git_watcher.py ← curses multi-repo git-dirty watcher (multi-repo left column)
├── watcher_status_pane.py ← Rich status pane for the right column
├── delegate.py ← `console run` wizard + queue writer
├── queue.py / queue_status.py ← OperationsCenter run-queue submission + display
├── runs.py / runs_cmd.py / last.py ← `console runs` / `console last` inspectors
├── observer.py ← repo observation snapshots for `console overview`
├── watcher_status_pane.py ← `console status` (live curses pane: roles, queue, budget, backend caps, resources)
├── auto_once.py ← single-shot autonomy helpers
├── clean.py ← `console reset` / `console clear` cleanup
└── commands.py ← interactive TUI commands and run management
Running console (no subcommand) is equivalent to console open. The shell wrapper (console) runs first:
- Check for
.venv/bin/python3— if absent, runbootstrap.shautomatically (first run only) - Set
PYTHONPATHtosrc/ - Exec
python3 -m operator_console.cliwith any arguments
console open is preserved as an explicit alias; both paths are identical.
console / console open:
- Scan
~/Documents/GitHub/for git repos; overlay any YAML profiles fromconfig/profiles/ - Group profiles (those with
group:and norepo_root:) are registered separately and never shadow repo entries - If cwd is inside a known repo → always auto-select that repo, skip picker entirely
- If that repo's tab is already open →
launch()attaches without adding a duplicate tab - If cwd is outside all known repos (e.g.
~/Documents/GitHub/) → single-select picker (fzf or numbered fallback) console multi→ explicit multi-select picker (Tab to toggle); each selected repo opens as a named tab- Group selection is expanded to constituent profiles via
_expand_selection() - For each selected repo: initialize
.console/if missing; if multiple repos selected, inject siblings as implicit peers in each context file; write.console/.context, ensureCLAUDE.md - Multi-repo layout: Claude pane starts at
~/Documents/GitHub/instead of the individual repo root - Auto-save group to
~/.local/share/console/last-session.json(forconsole restore) - Print structured summary block:
session attaching/creating (operator_console),layout fresh/saved(new sessions only), active task snippet - Check branch via
guardrails.py— warn if on main/master - If session
consoleexists → add each repo as a new named tab (skip if tab already open) - Otherwise → generate fresh KDL layout (or use saved layout if
--layoutflag passed), launchzellij --session operator_console --new-session-with-layout <kdl>
console restore: loads last-session.json, resolves repo names against _discover_repos(), injects siblings as implicit peers (same logic as multi-select open), regenerates context files, then calls launch().
OperatorConsole uses an isolated venv at .venv/ inside the OperatorConsole repo root. bootstrap.sh creates it and installs PyYAML from requirements.txt. The console wrapper triggers this automatically on first run — no manual setup step required.
OperatorConsole uses a single named session: operator_console. Each project opens as a named tab within that session.
- Tab bar and status bar are present in every tab via explicit chrome panes in each layout
- Running
consolefrom inside the session adds tabs without re-attaching - Dead (EXITED) sessions are auto-deleted before creating a new one
console killterminates the session and all panes (with confirmation prompt);tput resetis run afterwards to clear any stale terminal state
Layout files:
- Session start:
/tmp/console-session.kdl— includesdefault_tab_template+ named first tab - New tabs:
/tmp/console-tab-<name>.kdl— panes + explicit chrome
Pane arrangement — single repo:
- Left 28%: Git (
lazygit) - Center: a Zellij stack of
claude --resume <id>/codex --resume <id>/aider, switchable withAlt+↑/↓ - Right 28%: a stack of
shell+ watcherstatuspane
Pane arrangement — multi repo (single tab):
- Left 28%: interactive
git_watcher(curses; ↑↓ navigate the repo list, Enter → lazygit for that repo) - Center: a stack of
claude/codex/aider, all rooted at~/Documents/GitHub/ - Right 28%: a stack of
shell(rooted at~/Documents/GitHub/) + watcherstatuspane
Tab naming: group profiles use the group name (e.g. platform); ad-hoc multi-select joins all repo names (RepoA+RepoB+RepoC). _multi_tab_name() always lists all repos — no truncation.
Profiles live in config/profiles/<name>.yaml. They are optional — any git repo under ~/Documents/GitHub/ is auto-discovered without one.
Two profile types:
Repo profile — has repo_root:. Adds claude.* (bootstrap files, peer context), panes.* (per-pane command overrides), helpers.* (test/audit commands), and status_repos (OperationsCenter status filter).
Group profile — has group: list but no repo_root:. Appears in picker with ▸ prefix. Selecting it expands to constituent profiles.
Git visibility: platform group profiles are tracked; all others (*.yaml, *.kdl, *.session) are gitignored.
See profiles.md for format reference.
_discover_repos() in cli.py:
- Scans
~/Documents/GitHub/for directories containing.git/ - Builds a
name → {name, repo_root}dict (lowercase key, original-case display name) - Overlays configured YAML profiles: any profile whose
repo_rootmatches a discovered repo replaces the auto-generated entry - Group profiles (no
repo_root:) are registered under their own key and never replace repo entries
_profile_for_cwd() uses the same discovery to find which profile's repo_root contains the current directory — used by console status, console context, console test, and console audit.
get_claude_command(), get_codex_command(), and get_aider_command() in
bootstrap.py each generate a per-profile shell wrapper script (e.g.
/tmp/console-claude-<name>.sh). Each wrapper:
- Reads the saved conversation ID from
config/profiles/<name>.<tool>-session - Runs the tool with
--resume <id>if saved; falls back to a fresh start if the saved session no longer exists - After the tool exits, locates the newest session record under that tool's storage layout and saves its identifier for next launch
Both claude --resume and codex --resume are first-class. aider does not
support --resume — it always starts a fresh session, but bootstrap still
manages its working directory and environment.
Console-launched Codex sessions default to full local access:
codex -a never -s danger-full-access. Profiles can opt out with
codex.approval_mode: "" or codex.sandbox_mode: "", or choose a different
Codex sandbox with codex.sandbox_mode.
The Claude project path is derived from the working directory: <cwd> with
/ → - (mirrors Claude Code's own storage layout under ~/.claude/projects/).
For single-profile tabs: session key = profile name, project dir derived from
repo_root. For multi-profile (group) tabs: session key = _multi_tab_name(profiles),
project dir derived from ~/Documents/GitHub/.
Session files (config/profiles/*.*-session) are always gitignored.
tab_capture.py provides:
dump_live_layout()— runszellij action dump-layout, returns KDL stringextract_panes_kdl(kdl, tab_name)— extracts the inner content panes from a named tab, stripping chrome plugins (tab-bar, status-bar) and the tab wrapper; returns raw KDL ready to embed in a session or tab layoutfocused_tab_name(kdl)— returns the name of the focused tab from a dump
console save [name] (in commands.py) calls these to capture the live layout and write it to config/profiles/<name>.kdl. On the next console open, _saved_panes_kdl() in launcher.py checks for this file and uses it instead of generating from YAML.
Two separate systems:
Live capture (config/profiles/<name>.kdl):
- Captures actual live pane arrangement via
zellij action dump-layout - Gitignored; profile-scoped
- Used automatically on next
console open console save/console save --reset
KDL-based restore (.console/layout.kdl + .console/layout.json):
- Saves the YAML-generated layout for session-level restore
layout.pyprovidessave,load,load_any,resetconsole openalways generates fresh layout;console open --layoutis the explicit opt-inconsole layout loadstarts Zellij directly with the saved KDL
OperatorConsole uses a two-layer model for Claude context:
Layer 1 — Human-editable source files (edit these directly):
| File | Role |
|---|---|
task.md |
Current objective — singular, concise, replace when focus changes |
guidelines.md |
Stable repo policy — branch rules, operating constraints, low-churn |
backlog.md |
Work inventory — in-progress, up-next, done |
log.md |
Chronological log — decisions, stop points, what changed and why |
Layer 2 — Compiled launch artifact (generated, do not edit):
| File | Role |
|---|---|
.context |
Compiled startup context — all four files + runtime context, regenerated each launch |
console init creates the source files from templates/console/ if missing. console auto-inits on first launch.
CLAUDE.md in the repo root tells Claude to read .console/.context as the primary startup context.
bootstrap.py reads the four source files and compiles .console/.context at launch time. The context includes:
- Task, Guidelines, Backlog, Log (from source files)
- Runtime context: repo name, repo root, current branch, timestamp, profile name
- Peer sections if
claude.peersis configured
The context is regenerated fresh on every console open run — it is always current.
console context prints the compiled context to stdout so the operator can inspect what Claude will see.
OperatorConsole state is distributed across five distinct layers:
| Layer | What persists | Location |
|---|---|---|
| Zellij | Session name, tabs, live pane processes | Zellij session manager |
.console/ |
Context files, layout files, compiled context | <repo>/.console/ (gitignored) |
| CLI config | Profile YAML (platform group tracked) | config/profiles/*.yaml |
| Private config | Saved live layouts, Claude session IDs | config/profiles/*.kdl, *.session (gitignored) |
| Global state | Last session group (for console restore) |
~/.local/share/console/last-session.json |
These layers are independent. Resetting one does not affect the others. console reset scopes resets explicitly:
--session→ kills Zellij session only--layout→ deletes.console/layout.json+.console/layout.kdlonly--state→ deletes the four context source files only- bare
console reset→ all three (with confirmation)
console status — shows session (running/stopped, attached/detached), layout (saved/none, metadata), branch, profile, and .console/ file presence. Active task snippet is shown if the file exists.
console overview — structured full-state snapshot. Includes repo info, session state, layout metadata, and context file presence. --json flag emits machine-readable JSON for tooling/piping.
OperatorConsole also owns operator-facing validation commands for the shared platform:
console demo— the golden-path architecture check: preflight, stack health, SwitchBoard route selection, and OperationsCenter handoffconsole demo --no-start— same validation but assumes the stack is already upconsole providers— reports selector and lane readinessconsole providers --wait— polls until the selector is healthy and then points the operator back to the demo flow
These are operator UX commands. They do not move infrastructure ownership out of PlatformDeployment.
console install runs tools/loadout.sh — an interactive installer for the recommended dev toolchain (fzf, bat, eza, ripgrep, fd, zoxide, delta, lazygit, starship, fastfetch). Tools not available in Ubuntu's standard apt repos (eza, git-delta, fastfetch) use custom GitHub release installers.
console doctor checks both core OperatorConsole dependencies and all loadout tools, with install status for each.