Refactor Constant cockpit runtime and wrappers

This commit is contained in:
L'électron rare
2026-04-05 19:47:07 +02:00
parent f32adf105a
commit 796e1008df
38 changed files with 1476 additions and 552 deletions
+26 -16
View File
@@ -19,7 +19,7 @@ Not another “agent framework” that only looks good in slides.
It is a pragmatic, local-first orchestration layer that sits on top of:
- a 4-pane Zellij machine cockpit
- a 4-pane `tmux` machine cockpit
- a multi-machine fleet view
- host-local CLIs like `claude`, `codex`, `copilot`, and `vibe`
- a mission runner that can route work across machines
@@ -36,10 +36,11 @@ The vibe is somewhere between:
Current build status in this repo:
- a 4-pane host-local Zellij session per machine
- a fleet cockpit with one tab per machine
- a 4-pane host-local `tmux` session per machine
- a fleet cockpit with one window per machine plus a central `Constant` window
- `Constant`, the orchestration CLI on top of the cockpit
- a terminal TUI with a central `hexapus` buddy rail
- live cockpit controls for focus, capture, send, and pane restart
- local mission planning and verification
- host-local execution for `claude`, `codex`, and `vibe`
- `copilot` as a manual lane
@@ -47,8 +48,8 @@ Current build status in this repo:
- MLX-ready model plumbing for a small local orchestrator stack on macOS
- workspace-first durable memory with lexical + local vector search
The historical repo name may still say `zellij-ai-triple`.
The product name is now `Constant`.
The canonical script surface is `constant-*`.
Legacy `zellij-ai-*` aliases remain only as compatibility shims.
## Why This Exists
@@ -91,12 +92,13 @@ No Docker dependency is required for the standard session.
### 2. Fleet cockpit
From your command center machine, you open one Zellij tab per machine.
Each tab attaches to the machines own local session.
From your command center machine, you open one `tmux` window per machine plus a central `Constant` window.
Each machine window attaches to that machines own local `tmux` session.
That gives you:
- one place to supervise the whole fleet
- one active orchestration surface in the `Constant` window
- local clipboard behavior on the operator machine
- remote sessions that stay remote
- a clean separation between orchestration and execution
@@ -132,7 +134,7 @@ The design goal is simple:
### Requirements
- `zellij`
- `tmux`
- `git`
- `node`
- `npm`
@@ -163,12 +165,11 @@ uv tool install mistral-vibe
./scripts/constant-fleet.sh --workspace "$PWD"
```
By default, the machine launcher creates a fresh temporary Zellij config directory for each new session.
That is deliberate: it isolates `Constant` from an existing user Zellij config, custom layouts, plugins,
and stale resurrected state. If you want to pin a specific Zellij config dir, use:
For non-interactive setup or tests, you can create the sessions without attaching:
```bash
./scripts/constant-machine.sh --zellij-config-dir /path/to/zellij-config
./scripts/constant-machine.sh --workspace "$PWD" --ensure-only
./scripts/constant-fleet.sh --workspace "$PWD" --ensure-only
```
On first launch of the Codex pane, if `auth.json` is missing in its profile, the pane runs:
@@ -183,6 +184,10 @@ codex login --device-auth
./scripts/Constant
./scripts/Constant tui
./scripts/Constant doctor
./scripts/Constant cockpit doctor --json
./scripts/Constant cockpit status --json
./scripts/Constant cockpit focus --machine command-center --pane codex
./scripts/Constant cockpit capture --machine command-center --pane claude
./scripts/Constant mission create "audit the repo" --workspace "$PWD"
./scripts/Constant mission status
./scripts/Constant memory rebuild --workspace "$PWD"
@@ -252,12 +257,18 @@ Constant fleet configure \
- a mission deck
- a mission board
- a runtime view of machine windows and pane states
- a `hexapus` buddy rail
- a bottom timeline mixed with memory echoes
Useful keys:
- `j` / `k`: move between missions
- `[` / `]`: move between machines in the runtime view
- `1`..`4`: focus `claude`, `codex`, `copilot`, `vibe` on the selected machine
- `o`: jump to the selected machine window
- `r`: restart the selected pane
- `x`: capture a short preview of the selected pane
- `e`: rebuild memory for the current workspace
- `s`: summarize the selected mission into durable memory
- `z`: open the full fleet cockpit
@@ -301,7 +312,7 @@ The shell launchers also read `fleet.json` now, so fleet tabs, install/check, an
Each machine exposes a local bus:
```text
~/.cache/constant/zellij/<session>/bus
~/.cache/constant/cockpit/<session>/bus
```
Use it from any pane:
@@ -332,9 +343,7 @@ Clipboard behavior is split on purpose:
- remote tabs rely on `OSC52`
- the command center keeps ownership of the actual system clipboard
This matters.
If a remote pane copies text, it should land in the operator machine clipboard, not in some random remote shell session.
The current runtime relies on `tmux` clipboard forwarding and terminal `OSC52` behavior when you are driving remote sessions from the command center.
## Constant Philosophy
@@ -390,6 +399,7 @@ scripts/ai-bridge.sh inter-machine bridge
- the public-facing TUI is still evolving
- some setup paths and local assumptions are operator-oriented
- `copilot` is manual-only in the current autonomous flow
- remote `tmux` introspection is best when targets expose `tmux` on a non-interactive shell `PATH`
That is acceptable for now.
The system is being built in the open, from the terminal outward.
+110 -3
View File
@@ -9,6 +9,7 @@ from pathlib import Path
from typing import Any
from . import __version__
from .cockpit import capture_pane, cockpit_doctor, focus_machine, restart_pane, runtime_status, send_to_pane
from .daemon import daemon_status, request as daemon_request, serve_foreground, start_background, stop_background
from .executors import bridge_sync, execute_step, fleet_check
from .memory import (
@@ -110,6 +111,7 @@ def cmd_doctor(args: argparse.Namespace) -> int:
health = daemon_request("health", auto_start=False)
fleet = load_fleet_config()
models = load_models_config()
cockpit = cockpit_doctor()
report = {
"version": __version__,
"repo_root": str(repo_root()),
@@ -122,7 +124,7 @@ def cmd_doctor(args: argparse.Namespace) -> int:
"codex": _command_exists("codex"),
"copilot": _command_exists("copilot"),
"vibe": _command_exists("vibe"),
"constant-fleet": (scripts_dir() / "constant-fleet.sh").exists() or (scripts_dir() / "zellij-ai-fleet.sh").exists(),
"constant-fleet": (scripts_dir() / "constant-fleet.sh").exists(),
"ai-bridge": (scripts_dir() / "ai-bridge.sh").exists(),
},
"daemon": status,
@@ -130,6 +132,7 @@ def cmd_doctor(args: argparse.Namespace) -> int:
"health": health,
"memory": memory_status(),
"fleet": [{"label": entry["label"], "target": entry["target"]} for entry in fleet["machines"]],
"cockpit": cockpit,
}
_print(report, args.json)
return 0
@@ -235,7 +238,7 @@ def cmd_fleet_deploy(args: argparse.Namespace) -> int:
def cmd_cockpit_open(args: argparse.Namespace) -> int:
workspace = args.workspace or os.getcwd()
command = [
str((scripts_dir() / "constant-fleet.sh") if (scripts_dir() / "constant-fleet.sh").exists() else (scripts_dir() / "zellij-ai-fleet.sh")),
str(scripts_dir() / "constant-fleet.sh"),
"--workspace",
workspace,
"--local-session",
@@ -243,19 +246,81 @@ def cmd_cockpit_open(args: argparse.Namespace) -> int:
"--session",
args.session or "constant",
]
if getattr(args, "recreate", False):
command.append("--recreate")
if getattr(args, "remote_recreate", False):
command.append("--remote-recreate")
os.execv(command[0], command)
return 0
def cmd_cockpit_attach(args: argparse.Namespace) -> int:
command = [
str(scripts_dir() / "constant-fleet.sh"),
"--attach-only",
"--local-session",
args.local_session or "constant-fleet",
]
os.execv(command[0], command)
return 0
def cmd_cockpit_doctor(args: argparse.Namespace) -> int:
payload = cockpit_doctor(local_session=args.local_session or "constant-fleet", machine_session=args.session or "constant")
_print(payload, args.json)
return 0 if payload["tmux"]["available"] else 1
def cmd_cockpit_focus(args: argparse.Namespace) -> int:
payload = focus_machine(
args.machine,
args.pane,
local_session=args.local_session or "constant-fleet",
machine_session=args.session or "constant",
)
_print(payload, args.json)
return 0 if payload["returncode"] == 0 else 1
def cmd_cockpit_send(args: argparse.Namespace) -> int:
payload = send_to_pane(args.machine, args.pane, args.command, machine_session=args.session or "constant")
_print(payload, args.json)
return 0 if payload["returncode"] == 0 else 1
def cmd_cockpit_capture(args: argparse.Namespace) -> int:
payload = capture_pane(args.machine, args.pane, lines=args.lines, machine_session=args.session or "constant")
_print(payload if args.json else payload.get("stdout", ""), args.json)
return 0 if payload["returncode"] == 0 else 1
def cmd_cockpit_restart(args: argparse.Namespace) -> int:
payload = restart_pane(args.machine, args.pane, machine_session=args.session or "constant")
_print(payload, args.json)
return 0 if payload["returncode"] == 0 else 1
def cmd_cockpit_status(args: argparse.Namespace) -> int:
payload = runtime_status(local_session=args.local_session or "constant-fleet", machine_session=args.session or "constant")
_print(payload, args.json)
return 0
def cmd_tui(args: argparse.Namespace) -> int:
workspace = str(Path(args.workspace or os.getcwd()).expanduser().resolve())
action = run_tui(workspace)
action = run_tui(
workspace,
local_session=args.local_session or "constant-fleet",
machine_session=args.session or "constant",
)
if action and action.get("action") == "cockpit":
return cmd_cockpit_open(
argparse.Namespace(
workspace=action.get("workspace", workspace),
local_session=args.local_session,
session=args.session,
recreate=False,
remote_recreate=False,
)
)
return 0
@@ -560,7 +625,49 @@ def build_parser() -> argparse.ArgumentParser:
cockpit_open.add_argument("--workspace")
cockpit_open.add_argument("--local-session")
cockpit_open.add_argument("--session")
cockpit_open.add_argument("--recreate", action="store_true")
cockpit_open.add_argument("--remote-recreate", action="store_true")
cockpit_open.set_defaults(func=cmd_cockpit_open)
cockpit_attach = cockpit_sub.add_parser("attach")
cockpit_attach.add_argument("--local-session")
cockpit_attach.set_defaults(func=cmd_cockpit_attach)
cockpit_doctor_cmd = cockpit_sub.add_parser("doctor")
cockpit_doctor_cmd.add_argument("--local-session")
cockpit_doctor_cmd.add_argument("--session")
cockpit_doctor_cmd.add_argument("--json", action="store_true")
cockpit_doctor_cmd.set_defaults(func=cmd_cockpit_doctor)
cockpit_status_cmd = cockpit_sub.add_parser("status")
cockpit_status_cmd.add_argument("--local-session")
cockpit_status_cmd.add_argument("--session")
cockpit_status_cmd.add_argument("--json", action="store_true")
cockpit_status_cmd.set_defaults(func=cmd_cockpit_status)
cockpit_focus_cmd = cockpit_sub.add_parser("focus")
cockpit_focus_cmd.add_argument("--machine", required=True)
cockpit_focus_cmd.add_argument("--pane")
cockpit_focus_cmd.add_argument("--local-session")
cockpit_focus_cmd.add_argument("--session")
cockpit_focus_cmd.add_argument("--json", action="store_true")
cockpit_focus_cmd.set_defaults(func=cmd_cockpit_focus)
cockpit_send_cmd = cockpit_sub.add_parser("send")
cockpit_send_cmd.add_argument("--machine", required=True)
cockpit_send_cmd.add_argument("--pane", required=True)
cockpit_send_cmd.add_argument("--command", required=True)
cockpit_send_cmd.add_argument("--session")
cockpit_send_cmd.add_argument("--json", action="store_true")
cockpit_send_cmd.set_defaults(func=cmd_cockpit_send)
cockpit_capture_cmd = cockpit_sub.add_parser("capture")
cockpit_capture_cmd.add_argument("--machine", required=True)
cockpit_capture_cmd.add_argument("--pane", required=True)
cockpit_capture_cmd.add_argument("--lines", type=int, default=120)
cockpit_capture_cmd.add_argument("--session")
cockpit_capture_cmd.add_argument("--json", action="store_true")
cockpit_capture_cmd.set_defaults(func=cmd_cockpit_capture)
cockpit_restart_cmd = cockpit_sub.add_parser("restart")
cockpit_restart_cmd.add_argument("--machine", required=True)
cockpit_restart_cmd.add_argument("--pane", required=True)
cockpit_restart_cmd.add_argument("--session")
cockpit_restart_cmd.add_argument("--json", action="store_true")
cockpit_restart_cmd.set_defaults(func=cmd_cockpit_restart)
mission = subparsers.add_parser("mission")
mission_sub = mission.add_subparsers(dest="mission_command", required=True)
+192
View File
@@ -0,0 +1,192 @@
from __future__ import annotations
import shlex
import subprocess
import socket
from pathlib import Path
from typing import Any
from .paths import scripts_dir
from .state import fleet_machine, load_fleet_config
ROLES = ("claude", "codex", "copilot", "vibe")
def _run(args: list[str]) -> dict[str, Any]:
process = subprocess.run(args, capture_output=True, text=True)
return {
"argv": args,
"returncode": process.returncode,
"stdout": process.stdout,
"stderr": process.stderr,
}
def _tmux_list_command(session_name: str) -> list[str]:
return [
"tmux",
"list-panes",
"-t",
session_name,
"-F",
"#{session_name}\t#{window_name}\t#{pane_id}\t#{pane_index}\t#{@constant_role}\t#{pane_title}\t#{pane_current_command}\t#{pane_active}\t#{pane_dead}\t#{pane_dead_status}",
]
def _ssh_command(target: str, inner: str) -> list[str]:
prefixed = (
'export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"; '
f"{inner}"
)
return ["ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=1", target, "bash", "--noprofile", "--norc", "-lc", prefixed]
def _machine_session_name(session_name: str, machine_label: str) -> str:
return f"{session_name}:{machine_label}"
def _parse_panes(stdout: str) -> list[dict[str, Any]]:
panes: list[dict[str, Any]] = []
for raw in stdout.splitlines():
parts = raw.split("\t")
if len(parts) != 10:
continue
session_name, window_name, pane_id, pane_index, pane_role, pane_title, pane_command, pane_active, pane_dead, pane_dead_status = parts
role = pane_role or (pane_title if pane_title in ROLES else pane_command)
panes.append(
{
"session_name": session_name,
"window_name": window_name,
"pane_id": pane_id,
"pane_index": int(pane_index),
"role": role,
"pane_role": pane_role,
"pane_title": pane_title,
"pane_command": pane_command,
"active": pane_active == "1",
"dead": pane_dead == "1",
"dead_status": int(pane_dead_status or "0"),
}
)
return sorted(panes, key=lambda item: item["pane_index"])
def _machine_tmux_status(machine: dict[str, Any], session: str) -> dict[str, Any]:
label = machine["label"]
target = machine["target"]
local_names = {socket.gethostname(), socket.getfqdn(), socket.gethostname().split(".")[0], "local", "localhost", "127.0.0.1", "::1"}
is_local = target in local_names
session_target = _machine_session_name(session, label)
if is_local:
result = _run(_tmux_list_command(session_target))
if result["returncode"] != 0:
for fallback_label in (socket.gethostname().split(".")[0], socket.getfqdn(), socket.gethostname()):
fallback_target = _machine_session_name(session, fallback_label)
result = _run(_tmux_list_command(fallback_target))
if result["returncode"] == 0:
session_target = fallback_target
break
else:
inner = shlex.join(_tmux_list_command(session_target))
result = _run(_ssh_command(target, inner))
panes = _parse_panes(result["stdout"]) if result["returncode"] == 0 else []
roles = {
role: next((pane for pane in panes if pane["role"] == role), None)
for role in ROLES
}
return {
"label": label,
"target": target,
"session": session_target,
"reachable": result["returncode"] == 0 or is_local,
"attached_window": label,
"session_exists": result["returncode"] == 0,
"panes": panes,
"roles": roles,
"stderr": result["stderr"].strip(),
}
def runtime_status(local_session: str = "constant-fleet", machine_session: str = "constant") -> dict[str, Any]:
fleet = load_fleet_config()
local_tmux = _run(["tmux", "list-windows", "-t", local_session, "-F", "#{window_name}"])
fleet_windows = local_tmux["stdout"].splitlines() if local_tmux["returncode"] == 0 else []
machines = [_machine_tmux_status(machine, machine_session) for machine in fleet["machines"]]
return {
"local_session": local_session,
"machine_session": machine_session,
"fleet_session_exists": local_tmux["returncode"] == 0,
"fleet_windows": fleet_windows,
"machines": machines,
"fleet_stderr": local_tmux["stderr"].strip(),
}
def cockpit_doctor(local_session: str = "constant-fleet", machine_session: str = "constant") -> dict[str, Any]:
tmux_check = _run(["tmux", "-V"])
payload = runtime_status(local_session=local_session, machine_session=machine_session)
payload["tmux"] = {
"available": tmux_check["returncode"] == 0,
"stdout": tmux_check["stdout"].strip(),
"stderr": tmux_check["stderr"].strip(),
}
return payload
def _machine_control_script(machine_label: str, machine_session: str) -> tuple[dict[str, Any], Path]:
machine = fleet_machine(machine_label)
script = scripts_dir() / "constant-machine.sh"
return machine, script
def _run_machine_command(machine: dict[str, Any], args: list[str]) -> dict[str, Any]:
target = machine["target"]
local_names = {socket.gethostname(), socket.getfqdn(), socket.gethostname().split(".")[0], "local", "localhost", "127.0.0.1", "::1"}
if target in local_names:
return _run(args)
quoted = shlex.join(args)
return _run(_ssh_command(target, quoted))
def focus_machine(machine_label: str, pane_role: str | None, local_session: str = "constant-fleet", machine_session: str = "constant") -> dict[str, Any]:
machine, script = _machine_control_script(machine_label, machine_session)
if pane_role:
payload = _run_machine_command(machine, [str(script), "--session", machine_session, "--focus-pane", pane_role])
if payload["returncode"] != 0:
return payload
select_window = _run(["tmux", "select-window", "-t", f"{local_session}:{machine_label}"])
return {
"returncode": select_window["returncode"],
"stdout": select_window["stdout"],
"stderr": select_window["stderr"],
"machine": machine_label,
"pane": pane_role,
}
def send_to_pane(machine_label: str, pane_role: str, command: str, machine_session: str = "constant") -> dict[str, Any]:
machine, script = _machine_control_script(machine_label, machine_session)
return _run_machine_command(
machine,
[str(script), "--session", machine_session, "--send-pane", pane_role, "--command", command],
)
def capture_pane(machine_label: str, pane_role: str, lines: int = 120, machine_session: str = "constant") -> dict[str, Any]:
machine, script = _machine_control_script(machine_label, machine_session)
return _run_machine_command(
machine,
[str(script), "--session", machine_session, "--capture-pane", pane_role, "--lines", str(lines)],
)
def restart_pane(machine_label: str, pane_role: str, machine_session: str = "constant") -> dict[str, Any]:
machine, script = _machine_control_script(machine_label, machine_session)
return _run_machine_command(
machine,
[str(script), "--session", machine_session, "--restart-pane", pane_role],
)
+2 -4
View File
@@ -74,8 +74,8 @@ def execute_step(step: dict[str, Any], mission: dict[str, Any], fleet: dict[str,
"duration_s": 0.0,
}
if backend == "zellij":
cockpit = str((scripts_dir() / "constant-fleet.sh") if (scripts_dir() / "constant-fleet.sh").exists() else (scripts_dir() / "zellij-ai-fleet.sh"))
if backend in {"zellij", "cockpit"}:
cockpit = str(scripts_dir() / "constant-fleet.sh")
return {
"argv": [cockpit, "--workspace", workspace],
"returncode": 0,
@@ -99,8 +99,6 @@ def execute_step(step: dict[str, Any], mission: dict[str, Any], fleet: dict[str,
def fleet_check() -> dict[str, Any]:
check_script = repo_root() / "scripts" / "constant-fleet-install.sh"
if not check_script.exists():
check_script = repo_root() / "scripts" / "zellij-ai-fleet-install.sh"
process = subprocess.run([str(check_script), "check"], capture_output=True, text=True)
machines: list[dict[str, Any]] = []
+1 -1
View File
@@ -303,7 +303,7 @@ class PlannerEngine:
"goal": mission["goal"],
"workspace": mission["workspace"],
"machines": machine_labels,
"allowed_backends": ["omc", "cli-local", "cli-ssh", "zellij"],
"allowed_backends": ["omc", "cli-local", "cli-ssh", "cockpit"],
"allowed_clis": ["claude", "codex", "vibe"],
},
indent=2,
+12 -6
View File
@@ -31,35 +31,35 @@ DEFAULT_FLEET: dict[str, Any] = {
"target": "local",
"auto_clis": ["codex", "vibe", "claude"],
"manual_clis": ["copilot"],
"backends": ["omc", "cli-local", "zellij"],
"backends": ["omc", "cli-local", "cockpit"],
},
{
"label": "builder-a",
"target": "dev@builder-a",
"auto_clis": ["codex", "vibe", "claude"],
"manual_clis": ["copilot"],
"backends": ["cli-ssh", "zellij"],
"backends": ["cli-ssh", "cockpit"],
},
{
"label": "builder-b",
"target": "dev@builder-b",
"auto_clis": ["codex", "vibe", "claude"],
"manual_clis": ["copilot"],
"backends": ["cli-ssh", "zellij"],
"backends": ["cli-ssh", "cockpit"],
},
{
"label": "edge-a",
"target": "dev@edge-a",
"auto_clis": ["codex", "vibe", "claude"],
"manual_clis": ["copilot"],
"backends": ["cli-ssh", "zellij"],
"backends": ["cli-ssh", "cockpit"],
},
{
"label": "lab-a",
"target": "dev@lab-a",
"auto_clis": ["codex", "vibe", "claude"],
"manual_clis": ["copilot"],
"backends": ["cli-ssh", "zellij"],
"backends": ["cli-ssh", "cockpit"],
},
],
}
@@ -140,7 +140,13 @@ def _write_json_yaml(path: Path, payload: dict[str, Any]) -> None:
def load_fleet_config() -> dict[str, Any]:
return _read_json_yaml(fleet_config_path(), DEFAULT_FLEET)
payload = _read_json_yaml(fleet_config_path(), DEFAULT_FLEET)
for machine in payload.get("machines", []):
backends = []
for backend in machine.get("backends", []):
backends.append("cockpit" if backend == "zellij" else backend)
machine["backends"] = backends
return payload
def load_models_config() -> dict[str, Any]:
+158 -10
View File
@@ -7,6 +7,7 @@ import time
from pathlib import Path
from typing import Any
from .cockpit import ROLES, capture_pane, focus_machine, restart_pane, runtime_status
from .daemon import request as daemon_request
from .memory import list_decisions, memory_status, persona_markdown, rebuild_workspace_memory, summarize_mission
from .state import list_missions, load_fleet_config, mission_events_file
@@ -46,6 +47,18 @@ def _safe_decision_lines(workspace: str) -> list[str]:
return [_error_line("decisions", exc)]
def _safe_runtime_status(local_session: str, machine_session: str) -> dict[str, Any]:
try:
return runtime_status(local_session=local_session, machine_session=machine_session)
except Exception as exc: # noqa: BLE001
return {
"fleet_session_exists": False,
"machines": [],
"fleet_windows": [],
"fleet_stderr": _error_line("cockpit", exc),
}
def _clip(text: str, width: int) -> str:
if width <= 0:
return ""
@@ -219,17 +232,29 @@ def _status_tag(status: str) -> str:
}.get(status, status[:4].upper())
def _draw_header(stdscr: Any, workspace: str, mission_count: int, fleet_count: int, memory: dict[str, Any], health: dict[str, Any], colors: dict[str, int]) -> None:
def _role_state(machine: dict[str, Any], role: str) -> str:
pane = machine.get("roles", {}).get(role)
if not pane:
return ".."
if pane.get("dead"):
return "xx"
if pane.get("active"):
return ">>"
return "ok"
def _draw_header(stdscr: Any, workspace: str, mission_count: int, fleet_count: int, runtime: dict[str, Any], memory: dict[str, Any], health: dict[str, Any], colors: dict[str, int]) -> None:
memory_counts = memory.get("counts", {})
planner_backend = health.get("models", {}).get("planner", {}).get("backend", "heuristic")
buddy_backend = health.get("models", {}).get("buddy", {}).get("backend", "heuristic")
cockpit_state = "up" if runtime.get("fleet_session_exists") else "down"
_safe_addstr(stdscr, 0, 2, "########## CONSTANT::DEMOSCENE::TUI ##########", colors["accent"])
_safe_addstr(
stdscr,
1,
2,
_clip(
f"workspace={workspace} fleet={fleet_count} missions={mission_count} docs={memory_counts.get('documents', 0)} chunks={memory_counts.get('chunks', 0)} decisions={memory_counts.get('decisions', 0)}",
f"workspace={workspace} fleet={fleet_count} cockpit={cockpit_state} missions={mission_count} docs={memory_counts.get('documents', 0)} chunks={memory_counts.get('chunks', 0)} decisions={memory_counts.get('decisions', 0)}",
max(20, stdscr.getmaxyx()[1] - 4),
),
colors["base"],
@@ -239,7 +264,7 @@ def _draw_header(stdscr: Any, workspace: str, mission_count: int, fleet_count: i
2,
2,
_clip(
f"planner={planner_backend} buddy={buddy_backend} keys: j/k move z cockpit e index s summarize q quit",
f"planner={planner_backend} buddy={buddy_backend} keys: j/k mission [/ ] machine 1..4 pane o jump r restart z cockpit q quit",
max(20, stdscr.getmaxyx()[1] - 4),
),
colors["muted"],
@@ -304,6 +329,46 @@ def _draw_board(stdscr: Any, y: int, x: int, height: int, width: int, mission: d
break
def _draw_runtime(
stdscr: Any,
y: int,
x: int,
height: int,
width: int,
runtime: dict[str, Any],
selected_machine: int,
selected_role: str,
colors: dict[str, int],
) -> None:
_draw_box(stdscr, y, x, height, width, "Cockpit Runtime", colors["accent"])
row = y + 1
machines = runtime.get("machines", [])
if not machines:
_safe_addstr(stdscr, row, x + 2, "No runtime data yet. Open the cockpit with z.", colors["muted"])
return
header = "machine claude codex copilot vibe session target"
_safe_addstr(stdscr, row, x + 2, _clip(header, width - 4), colors["hot"])
row += 1
for index, machine in enumerate(machines[: max(1, height - 3)]):
marker = ">" if index == selected_machine else " "
attr = colors["accent"] if index == selected_machine else colors["base"]
status = "up" if machine.get("session_exists") else "down"
role_bits = []
for role in ROLES:
state = _role_state(machine, role)
token = f"[{state}]"
if role == selected_role and index == selected_machine:
token = f"<{state}>"
role_bits.append(token)
line = f"{marker} {machine['label']:<16} {' '.join(role_bits)} {status:<7} {machine['target']}"
_safe_addstr(stdscr, row, x + 1, _clip(line, width - 3), attr)
row += 1
if row >= y + height - 1:
break
def _draw_buddy(stdscr: Any, y: int, x: int, height: int, width: int, review: dict[str, str], memory: dict[str, Any], colors: dict[str, int]) -> None:
_draw_box(stdscr, y, x, height, width, "Hexapus Buddy Rail", colors["accent"])
row = y + 1
@@ -359,7 +424,7 @@ def _draw_timeline(stdscr: Any, y: int, x: int, height: int, width: int, events:
break
def _collect_snapshot(default_workspace: str, selected_index: int) -> dict[str, Any]:
def _collect_snapshot(default_workspace: str, selected_index: int, local_session: str, machine_session: str, runtime_snapshot: dict[str, Any] | None = None) -> dict[str, Any]:
missions = sorted(list_missions(), key=lambda item: item.get("updated_at", ""), reverse=True)
if missions:
selected_index = max(0, min(selected_index, len(missions) - 1))
@@ -376,6 +441,7 @@ def _collect_snapshot(default_workspace: str, selected_index: int) -> dict[str,
decision_lines = _safe_decision_lines(workspace)
events = _recent_events(selected_mission["mission_id"] if selected_mission else None)
fleet = load_fleet_config()
runtime = runtime_snapshot if runtime_snapshot is not None else _safe_runtime_status(local_session, machine_session)
return {
"missions": missions,
"selected_index": selected_index,
@@ -388,10 +454,11 @@ def _collect_snapshot(default_workspace: str, selected_index: int) -> dict[str,
"decision_lines": decision_lines,
"events": events,
"fleet_count": len(fleet["machines"]),
"runtime": runtime,
}
def _run(stdscr: Any, workspace: str) -> dict[str, Any] | None:
def _run(stdscr: Any, workspace: str, local_session: str, machine_session: str) -> dict[str, Any] | None:
try:
curses.curs_set(0)
except curses.error:
@@ -400,12 +467,25 @@ def _run(stdscr: Any, workspace: str) -> dict[str, Any] | None:
stdscr.timeout(250)
colors = _init_colors()
selected_index = 0
selected_machine = 0
selected_role_index = 1
flash = ""
flash_until = 0.0
runtime_cache: dict[str, Any] | None = None
runtime_refresh_at = 0.0
while True:
snapshot = _collect_snapshot(workspace, selected_index)
now = time.time()
if runtime_cache is None or now >= runtime_refresh_at:
runtime_cache = _safe_runtime_status(local_session, machine_session)
runtime_refresh_at = now + 2.0
snapshot = _collect_snapshot(workspace, selected_index, local_session, machine_session, runtime_snapshot=runtime_cache)
selected_index = snapshot["selected_index"]
runtime_machines = snapshot["runtime"].get("machines", [])
if runtime_machines:
selected_machine = max(0, min(selected_machine, len(runtime_machines) - 1))
else:
selected_machine = 0
stdscr.erase()
height, width = stdscr.getmaxyx()
@@ -414,6 +494,7 @@ def _run(stdscr: Any, workspace: str) -> dict[str, Any] | None:
snapshot["workspace"],
len(snapshot["missions"]),
snapshot["fleet_count"],
snapshot["runtime"],
snapshot["memory"],
snapshot["health"],
colors,
@@ -425,9 +506,22 @@ def _run(stdscr: Any, workspace: str) -> dict[str, Any] | None:
left_width = max(28, min(34, width // 4))
right_width = max(34, min(42, width // 3))
center_width = max(28, width - left_width - right_width - 4)
board_height = max(8, int(main_height * 0.52))
runtime_height = max(6, main_height - board_height)
_draw_missions(stdscr, top, 0, main_height, left_width, snapshot["missions"], selected_index, colors)
_draw_board(stdscr, top, left_width + 1, main_height, center_width, snapshot["selected_mission"], colors)
_draw_board(stdscr, top, left_width + 1, board_height, center_width, snapshot["selected_mission"], colors)
_draw_runtime(
stdscr,
top + board_height,
left_width + 1,
runtime_height,
center_width,
snapshot["runtime"],
selected_machine,
ROLES[selected_role_index],
colors,
)
_draw_buddy(stdscr, top, left_width + center_width + 2, main_height, right_width, snapshot["review"], snapshot["memory"], colors)
_draw_timeline(stdscr, top + main_height, 0, timeline_height, width, snapshot["events"], snapshot["persona_lines"], snapshot["decision_lines"], colors)
@@ -435,7 +529,7 @@ def _run(stdscr: Any, workspace: str) -> dict[str, Any] | None:
_safe_addstr(stdscr, height - 1, 2, _clip(flash, width - 4), colors["warn"])
else:
flash = ""
_safe_addstr(stdscr, height - 1, 2, "q quit | j/k select mission | z open cockpit | e rebuild memory | s summarize mission", colors["muted"])
_safe_addstr(stdscr, height - 1, 2, "q quit | j/k mission | [/] machine | 1..4 pane | o jump | r restart | x capture | z open cockpit", colors["muted"])
stdscr.refresh()
key = stdscr.getch()
@@ -451,6 +545,60 @@ def _run(stdscr: Any, workspace: str) -> dict[str, Any] | None:
if snapshot["missions"]:
selected_index = max(0, selected_index - 1)
continue
if key == ord("["):
if snapshot["runtime"]["machines"]:
selected_machine = max(0, selected_machine - 1)
continue
if key == ord("]"):
if snapshot["runtime"]["machines"]:
selected_machine = min(len(snapshot["runtime"]["machines"]) - 1, selected_machine + 1)
continue
if key in (ord("1"), ord("2"), ord("3"), ord("4")) and snapshot["runtime"]["machines"]:
selected_role_index = int(chr(key)) - 1
machine = snapshot["runtime"]["machines"][selected_machine]
try:
focus_machine(
machine["label"],
ROLES[selected_role_index],
local_session=local_session,
machine_session=machine_session,
)
runtime_cache = None
flash = f"focused {machine['label']}:{ROLES[selected_role_index]}"
except Exception as exc: # noqa: BLE001
flash = _error_line("focus failed", exc)
flash_until = time.time() + 2.5
continue
if key == ord("o") and snapshot["runtime"]["machines"]:
machine = snapshot["runtime"]["machines"][selected_machine]
try:
focus_machine(machine["label"], None, local_session=local_session, machine_session=machine_session)
runtime_cache = None
flash = f"jumped to {machine['label']}"
except Exception as exc: # noqa: BLE001
flash = _error_line("jump failed", exc)
flash_until = time.time() + 2.5
continue
if key == ord("r") and snapshot["runtime"]["machines"]:
machine = snapshot["runtime"]["machines"][selected_machine]
try:
restart_pane(machine["label"], ROLES[selected_role_index], machine_session=machine_session)
runtime_cache = None
flash = f"restarted {machine['label']}:{ROLES[selected_role_index]}"
except Exception as exc: # noqa: BLE001
flash = _error_line("restart failed", exc)
flash_until = time.time() + 2.5
continue
if key == ord("x") and snapshot["runtime"]["machines"]:
machine = snapshot["runtime"]["machines"][selected_machine]
try:
capture = capture_pane(machine["label"], ROLES[selected_role_index], lines=12, machine_session=machine_session)
preview = (capture.get("stdout") or "").splitlines()[-1:] or ["(empty capture)"]
flash = f"{machine['label']}:{ROLES[selected_role_index]} :: {preview[0]}"
except Exception as exc: # noqa: BLE001
flash = _error_line("capture failed", exc)
flash_until = time.time() + 3.5
continue
if key == ord("z"):
return {"action": "cockpit", "workspace": snapshot["workspace"]}
if key == ord("e"):
@@ -471,9 +619,9 @@ def _run(stdscr: Any, workspace: str) -> dict[str, Any] | None:
continue
def run_tui(workspace: str) -> dict[str, Any] | None:
def run_tui(workspace: str, local_session: str = "constant-fleet", machine_session: str = "constant") -> dict[str, Any] | None:
try:
rebuild_workspace_memory(workspace, enroll=True)
except Exception:
pass
return curses.wrapper(lambda stdscr: _run(stdscr, str(Path(workspace).expanduser().resolve())))
return curses.wrapper(lambda stdscr: _run(stdscr, str(Path(workspace).expanduser().resolve()), local_session, machine_session))
+5 -5
View File
@@ -8,35 +8,35 @@
"target": "local",
"auto_clis": ["codex", "vibe", "claude"],
"manual_clis": ["copilot"],
"backends": ["omc", "cli-local", "zellij"]
"backends": ["omc", "cli-local", "cockpit"]
},
{
"label": "builder-a",
"target": "dev@builder-a",
"auto_clis": ["codex", "vibe", "claude"],
"manual_clis": ["copilot"],
"backends": ["cli-ssh", "zellij"]
"backends": ["cli-ssh", "cockpit"]
},
{
"label": "builder-b",
"target": "dev@builder-b",
"auto_clis": ["codex", "vibe", "claude"],
"manual_clis": ["copilot"],
"backends": ["cli-ssh", "zellij"]
"backends": ["cli-ssh", "cockpit"]
},
{
"label": "edge-a",
"target": "dev@edge-a",
"auto_clis": ["codex", "vibe", "claude"],
"manual_clis": ["copilot"],
"backends": ["cli-ssh", "zellij"]
"backends": ["cli-ssh", "cockpit"]
},
{
"label": "lab-a",
"target": "dev@lab-a",
"auto_clis": ["codex", "vibe", "claude"],
"manual_clis": ["copilot"],
"backends": ["cli-ssh", "zellij"]
"backends": ["cli-ssh", "cockpit"]
}
]
}
+1 -1
View File
@@ -8,7 +8,7 @@ while [[ -L "$script_source" ]]; do
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
source "$script_dir/zellij-ai-common.sh"
source "$script_dir/constant-common.sh"
usage() {
cat <<EOF
+1 -1
View File
@@ -8,7 +8,7 @@ while [[ -L "$script_source" ]]; do
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
source "$script_dir/zellij-ai-common.sh"
source "$script_dir/constant-common.sh"
usage() {
cat <<'EOF'
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
script_source="${BASH_SOURCE[0]:-$0}"
while [[ -L "$script_source" ]]; do
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
script_source="$(readlink "$script_source")"
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
export CONSTANT_SCRIPT_NAME="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
exec "$script_dir/zellij-ai-bootstrap.sh" "$@"
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
script_source="${BASH_SOURCE[0]:-$0}"
while [[ -L "$script_source" ]]; do
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
script_source="$(readlink "$script_source")"
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
export CONSTANT_SCRIPT_NAME="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
exec "$script_dir/zellij-ai-claude-pane.sh" "$@"
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
script_source="${BASH_SOURCE[0]:-$0}"
while [[ -L "$script_source" ]]; do
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
script_source="$(readlink "$script_source")"
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
export CONSTANT_SCRIPT_NAME="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
exec "$script_dir/zellij-ai-codex-pane.sh" "$@"
+12
View File
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -euo pipefail
script_source="${BASH_SOURCE[0]:-$0}"
while [[ -L "$script_source" ]]; do
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
script_source="$(readlink "$script_source")"
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
source "$script_dir/zellij-ai-common.sh"
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
script_source="${BASH_SOURCE[0]:-$0}"
while [[ -L "$script_source" ]]; do
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
script_source="$(readlink "$script_source")"
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
export CONSTANT_SCRIPT_NAME="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
exec "$script_dir/zellij-ai-copilot-pane.sh" "$@"
+1 -1
View File
@@ -10,4 +10,4 @@ done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
export CONSTANT_SCRIPT_NAME="$(basename "$0")"
exec "$script_dir/zellij-ai-fleet-deploy.sh" "$@"
exec "$script_dir/constant-fleet-deploy.sh" "$@"
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
script_source="${BASH_SOURCE[0]:-$0}"
while [[ -L "$script_source" ]]; do
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
script_source="$(readlink "$script_source")"
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
export CONSTANT_SCRIPT_NAME="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
exec "$script_dir/zellij-ai-fleet-bootstrap.sh" "$@"
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
script_source="${BASH_SOURCE[0]:-$0}"
while [[ -L "$script_source" ]]; do
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
script_source="$(readlink "$script_source")"
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
export CONSTANT_SCRIPT_NAME="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
exec "$script_dir/zellij-ai-fleet-deploy.sh" "$@"
+1 -1
View File
@@ -10,4 +10,4 @@ done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
export CONSTANT_SCRIPT_NAME="$(basename "$0")"
exec "$script_dir/zellij-ai-fleet.sh" "$@"
exec "$script_dir/constant-tmux-fleet.sh" "$@"
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
script_source="${BASH_SOURCE[0]:-$0}"
while [[ -L "$script_source" ]]; do
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
script_source="$(readlink "$script_source")"
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
export CONSTANT_SCRIPT_NAME="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
exec "$script_dir/zellij-ai-machine-install.sh" "$@"
+1 -1
View File
@@ -10,4 +10,4 @@ done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
export CONSTANT_SCRIPT_NAME="$(basename "$0")"
exec "$script_dir/zellij-ai-triple.sh" "$@"
exec "$script_dir/constant-tmux-machine.sh" "$@"
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
script_source="${BASH_SOURCE[0]:-$0}"
while [[ -L "$script_source" ]]; do
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
script_source="$(readlink "$script_source")"
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
export CONSTANT_SCRIPT_NAME="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
exec "$script_dir/zellij-ai-remote-tab.sh" "$@"
+254
View File
@@ -0,0 +1,254 @@
#!/usr/bin/env bash
set -euo pipefail
script_source="${BASH_SOURCE[0]:-$0}"
while [[ -L "$script_source" ]]; do
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
script_source="$(readlink "$script_source")"
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
source "$script_dir/constant-common.sh"
usage() {
local script_name="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
cat <<EOF
Usage: ${script_name} --workspace DIR [options]
Open the Constant tmux fleet cockpit with one central window and one window per machine.
Options:
--workspace DIR Shared workspace path
--session NAME Machine tmux session name
default: $(zellij_ai_default_session)
--local-session NAME Fleet tmux session name on this machine
default: $(zellij_ai_default_local_session)
--repo-dir DIR Constant repo path on each machine
default: $(zellij_ai_default_repo_dir)
--machine SPEC Machine definition or label to include
--claude-config-dir D Claude config directory for the Claude pane
--recreate Recreate the local fleet session before launch
--remote-recreate Recreate each machine tmux session before attach
--attach-only Attach to an existing local fleet session
--ensure-only Create/update the fleet session, then exit
-h, --help Show this help
EOF
}
require_arg() {
local flag="$1"
local value="${2:-}"
if [[ -z "$value" ]]; then
echo "Missing value for ${flag}" >&2
exit 2
fi
}
build_constant_window_command() {
local inner
printf -v inner 'cd %q && export PATH=%q; exec %q tui --workspace %q --local-session %q --session %q' \
"$workspace" \
"$(zellij_ai_agent_path)" \
"$repo_dir/scripts/Constant" \
"$workspace" \
"$local_session" \
"$remote_session"
printf 'bash -lc %q' "$inner"
}
build_remote_window_command() {
local label="$1"
local target="$2"
local inner remote_script cmd cmd_joined
remote_script="$repo_dir/scripts/constant-tmux-remote-window.sh"
cmd=(
"$remote_script"
--label "$label"
--target "$target"
--workspace "$workspace_spec"
--repo-dir "$repo_dir_spec"
--session "$remote_session"
)
if [[ -n "$claude_config_dir" ]]; then
cmd+=(--claude-config-dir "$claude_config_dir")
fi
if $remote_recreate; then
cmd+=(--recreate)
fi
printf -v cmd_joined '%q ' "${cmd[@]}"
printf -v inner 'cd %q && export PATH=%q; exec %s' \
"$repo_dir" \
"$(zellij_ai_agent_path)" \
"$cmd_joined"
printf 'bash -lc %q' "$inner"
}
link_local_window() {
local label="$1"
local launcher="$repo_dir/scripts/constant-machine.sh"
local args=(
--workspace "$workspace"
--session "$remote_session"
--repo-dir "$repo_dir"
--ensure-only
)
if [[ ! -x "$launcher" ]]; then
launcher="$repo_dir/scripts/constant-tmux-machine.sh"
fi
if [[ -n "$claude_config_dir" ]]; then
args+=(--claude-config-dir "$claude_config_dir")
fi
if $remote_recreate; then
args+=(--recreate)
fi
ZELLIJ_AI_MACHINE_NAME="$label" "$launcher" "${args[@]}"
if ! constant_tmux_window_exists "$local_session" "$label"; then
tmux link-window -d -s "${remote_session}:${label}" -t "${local_session}:"
fi
}
constant_tmux_require
zellij_ai_require_command ssh
workspace=""
workspace_spec=""
remote_session="$(zellij_ai_default_session)"
local_session="$(zellij_ai_default_local_session)"
repo_dir="$(zellij_ai_expand_home_path "$(zellij_ai_default_repo_dir)")"
repo_dir_spec="$(zellij_ai_default_repo_dir)"
claude_config_dir=""
recreate=false
remote_recreate=false
attach_only=false
ensure_only=false
machines=()
while [[ $# -gt 0 ]]; do
case "$1" in
--workspace)
require_arg "$1" "${2:-}"
workspace_spec="$2"
workspace="$(zellij_ai_expand_home_path "$2")"
shift 2
;;
--session)
require_arg "$1" "${2:-}"
remote_session="$2"
shift 2
;;
--local-session)
require_arg "$1" "${2:-}"
local_session="$2"
shift 2
;;
--repo-dir)
require_arg "$1" "${2:-}"
repo_dir_spec="$2"
repo_dir="$(zellij_ai_expand_home_path "$2")"
shift 2
;;
--machine)
require_arg "$1" "${2:-}"
machines+=("$2")
shift 2
;;
--claude-config-dir)
require_arg "$1" "${2:-}"
claude_config_dir="$(zellij_ai_expand_home_path "$2")"
shift 2
;;
--recreate)
recreate=true
shift
;;
--remote-recreate)
remote_recreate=true
shift
;;
--attach-only)
attach_only=true
shift
;;
--ensure-only)
ensure_only=true
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown option: $1" >&2
usage >&2
exit 2
;;
esac
done
if $attach_only; then
if [[ -n "${TMUX:-}" ]]; then
exec tmux switch-client -t "$local_session"
fi
exec tmux attach-session -t "$local_session"
fi
if [[ -z "$workspace" ]]; then
usage >&2
exit 2
fi
if [[ ! -d "$workspace" ]]; then
echo "Workspace not found: $workspace" >&2
exit 1
fi
if [[ ${#machines[@]} -eq 0 ]]; then
mapfile -t machines < <(zellij_ai_default_machine_specs)
fi
if $recreate && constant_tmux_session_exists "$local_session"; then
tmux kill-session -t "$local_session" >/dev/null 2>&1 || true
fi
if constant_tmux_session_exists "$local_session"; then
if [[ -n "${TMUX:-}" ]]; then
exec tmux switch-client -t "$local_session"
fi
exec tmux attach-session -t "$local_session"
fi
mkdir -p "$(zellij_ai_fleet_state_dir "$local_session")"
tmux new-session -d -s "$local_session" -n Constant "$(build_constant_window_command)"
tmux set-option -t "$local_session" -g set-clipboard on >/dev/null 2>&1 || true
tmux set-option -t "$local_session" -g remain-on-exit on >/dev/null 2>&1 || true
tmux set-option -t "$local_session" -g mouse on >/dev/null 2>&1 || true
for machine_spec in "${machines[@]}"; do
parsed_spec="$(zellij_ai_parse_machine_spec "$machine_spec")"
IFS=$'\t' read -r label target <<<"$parsed_spec"
if [[ "$label" == "Constant" ]]; then
continue
fi
if zellij_ai_is_local_target "$target"; then
link_local_window "$label"
else
tmux new-window -d -t "${local_session}:" -n "$label" "$(build_remote_window_command "$label" "$target")"
fi
done
tmux select-window -t "${local_session}:Constant" >/dev/null 2>&1 || true
if $ensure_only; then
exit 0
fi
if [[ -n "${TMUX:-}" ]]; then
exec tmux switch-client -t "$local_session"
else
exec tmux attach-session -t "$local_session"
fi
+318
View File
@@ -0,0 +1,318 @@
#!/usr/bin/env bash
set -euo pipefail
script_source="${BASH_SOURCE[0]:-$0}"
while [[ -L "$script_source" ]]; do
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
script_source="$(readlink "$script_source")"
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
source "$script_dir/constant-common.sh"
usage() {
local script_name="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
cat <<EOF
Usage: ${script_name} [options]
Create or control the host-local tmux cockpit for one machine.
Options:
--workspace DIR Workspace for all 4 panes
default: current directory
--session NAME Machine tmux session name
default: $(zellij_ai_default_session)
--repo-dir DIR Repo containing Constant scripts
default: $(zellij_ai_default_repo_dir)
--codex-home DIR Codex profile directory
default: $(zellij_ai_default_codex_home)
--claude-config-dir D Claude config directory passed to the Claude pane
--recreate Kill and recreate the machine session
--ensure-only Ensure the session exists, then exit
--focus-pane ROLE Focus one pane: claude|codex|copilot|vibe
--capture-pane ROLE Print pane capture for the selected role
--send-pane ROLE Send --command text to the selected pane
--restart-pane ROLE Respawn the selected pane with its launcher command
--command TEXT Text to send with --send-pane
--lines N Lines to print with --capture-pane
default: 120
-h, --help Show this help
EOF
}
require_arg() {
local flag="$1"
local value="${2:-}"
if [[ -z "$value" ]]; then
echo "Missing value for ${flag}" >&2
exit 2
fi
}
tmux_target_window() {
printf '%s:%s\n' "$session" "$machine_name"
}
role_pane_id() {
local role="$1"
tmux list-panes -t "$(tmux_target_window)" -F '#{pane_id} #{@constant_role} #{pane_title}' 2>/dev/null | awk -F '\t' -v role="$role" '$2 == role || $3 == role {print $1; exit}'
}
ensure_workspace() {
if [[ ! -d "$workspace" ]]; then
echo "Workspace not found: $workspace" >&2
exit 1
fi
}
pane_command() {
constant_tmux_role_command "$1" "$script_dir" "$workspace" "$repo_dir" "$session" "$bus_dir" "$machine_name" "$codex_home" "$claude_config_dir"
}
set_pane_meta() {
local pane_id="$1"
local role="$2"
tmux select-pane -t "$pane_id" -T "$role" >/dev/null 2>&1 || true
tmux set-option -p -t "$pane_id" @constant_role "$role" >/dev/null 2>&1 || true
}
create_session() {
local target_window claude_cmd codex_cmd copilot_cmd vibe_cmd
local claude_pane codex_pane copilot_pane vibe_pane
target_window="$(tmux_target_window)"
claude_cmd="$(pane_command claude)"
codex_cmd="$(pane_command codex)"
copilot_cmd="$(pane_command copilot)"
vibe_cmd="$(pane_command vibe)"
tmux new-session -d -s "$session" -n "$machine_name" "$claude_cmd"
tmux set-option -t "$session" -g set-clipboard on >/dev/null 2>&1 || true
tmux set-option -t "$session" -g remain-on-exit on >/dev/null 2>&1 || true
tmux set-option -t "$session" -g mouse on >/dev/null 2>&1 || true
tmux set-option -t "$session" -g status-position top >/dev/null 2>&1 || true
tmux set-window-option -t "$target_window" -g pane-base-index 0 >/dev/null 2>&1 || true
claude_pane="$(tmux list-panes -t "$target_window" -F '#{pane_id}' | head -n 1)"
codex_pane="$(tmux split-window -h -P -F '#{pane_id}' -t "$claude_pane" "$codex_cmd")"
copilot_pane="$(tmux split-window -v -P -F '#{pane_id}' -t "$codex_pane" "$copilot_cmd")"
vibe_pane="$(tmux split-window -v -P -F '#{pane_id}' -t "$copilot_pane" "$vibe_cmd")"
tmux select-layout -t "$target_window" main-vertical >/dev/null
tmux resize-pane -t "$claude_pane" -x 120 >/dev/null 2>&1 || true
set_pane_meta "$claude_pane" claude
set_pane_meta "$codex_pane" codex
set_pane_meta "$copilot_pane" copilot
set_pane_meta "$vibe_pane" vibe
tmux select-pane -t "$claude_pane" >/dev/null
}
ensure_session() {
mkdir -p "$state_dir" "$bus_dir/messages"
if $recreate && constant_tmux_session_exists "$session"; then
tmux kill-session -t "$session" >/dev/null 2>&1 || true
fi
if ! constant_tmux_session_exists "$session"; then
create_session
return 0
fi
if ! constant_tmux_window_exists "$session" "$machine_name"; then
recreate=true
tmux kill-session -t "$session" >/dev/null 2>&1 || true
create_session
fi
}
focus_role() {
local role="$1"
local pane_id
ensure_session
pane_id="$(role_pane_id "$role")"
if [[ -z "$pane_id" ]]; then
echo "No pane found for role: $role" >&2
exit 1
fi
tmux select-window -t "$(tmux_target_window)" >/dev/null
tmux select-pane -t "$pane_id" >/dev/null
}
restart_role() {
local role="$1"
local pane_id command_string
ensure_session
pane_id="$(role_pane_id "$role")"
if [[ -z "$pane_id" ]]; then
echo "No pane found for role: $role" >&2
exit 1
fi
command_string="$(pane_command "$role")"
tmux respawn-pane -k -t "$pane_id" "$command_string"
set_pane_meta "$pane_id" "$role"
tmux select-layout -t "$(tmux_target_window)" main-vertical >/dev/null
}
capture_role() {
local role="$1"
local pane_id
ensure_session
pane_id="$(role_pane_id "$role")"
if [[ -z "$pane_id" ]]; then
echo "No pane found for role: $role" >&2
exit 1
fi
tmux capture-pane -p -S "-${lines}" -t "$pane_id"
}
send_role() {
local role="$1"
local pane_id
require_arg --command "$send_command"
ensure_session
pane_id="$(role_pane_id "$role")"
if [[ -z "$pane_id" ]]; then
echo "No pane found for role: $role" >&2
exit 1
fi
tmux send-keys -t "$pane_id" -l "$send_command"
tmux send-keys -t "$pane_id" Enter
}
session="$(zellij_ai_default_session)"
workspace="$PWD"
repo_dir="$(zellij_ai_expand_home_path "$(zellij_ai_default_repo_dir)")"
codex_home="$(zellij_ai_expand_home_path "$(zellij_ai_default_codex_home)")"
claude_config_dir=""
machine_name="${ZELLIJ_AI_MACHINE_NAME:-$(zellij_ai_local_machine_label)}"
recreate=false
ensure_only=false
focus_pane=""
capture_pane=""
restart_pane=""
send_pane=""
send_command=""
lines=120
while [[ $# -gt 0 ]]; do
case "$1" in
--workspace)
require_arg "$1" "${2:-}"
workspace="$(zellij_ai_expand_home_path "$2")"
shift 2
;;
--session)
require_arg "$1" "${2:-}"
session="$2"
shift 2
;;
--repo-dir)
require_arg "$1" "${2:-}"
repo_dir="$(zellij_ai_expand_home_path "$2")"
shift 2
;;
--codex-home)
require_arg "$1" "${2:-}"
codex_home="$(zellij_ai_expand_home_path "$2")"
shift 2
;;
--claude-config-dir)
require_arg "$1" "${2:-}"
claude_config_dir="$(zellij_ai_expand_home_path "$2")"
shift 2
;;
--recreate)
recreate=true
shift
;;
--ensure-only)
ensure_only=true
shift
;;
--focus-pane)
require_arg "$1" "${2:-}"
focus_pane="$2"
shift 2
;;
--capture-pane)
require_arg "$1" "${2:-}"
capture_pane="$2"
shift 2
;;
--send-pane)
require_arg "$1" "${2:-}"
send_pane="$2"
shift 2
;;
--restart-pane)
require_arg "$1" "${2:-}"
restart_pane="$2"
shift 2
;;
--command)
require_arg "$1" "${2:-}"
send_command="$2"
shift 2
;;
--lines)
require_arg "$1" "${2:-}"
lines="$2"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown option: $1" >&2
usage >&2
exit 2
;;
esac
done
constant_tmux_require
ensure_workspace
state_dir="$(zellij_ai_session_state_dir "$session")"
bus_dir="$state_dir/bus"
if [[ -n "$focus_pane" ]]; then
focus_role "$focus_pane"
exit 0
fi
if [[ -n "$capture_pane" ]]; then
capture_role "$capture_pane"
exit 0
fi
if [[ -n "$send_pane" ]]; then
send_role "$send_pane"
exit 0
fi
if [[ -n "$restart_pane" ]]; then
restart_role "$restart_pane"
exit 0
fi
ensure_session
if $ensure_only; then
exit 0
fi
if [[ -n "${TMUX:-}" ]]; then
exec tmux switch-client -t "$session"
else
exec tmux attach-session -t "$session"
fi
+136
View File
@@ -0,0 +1,136 @@
#!/usr/bin/env bash
set -euo pipefail
script_source="${BASH_SOURCE[0]:-$0}"
while [[ -L "$script_source" ]]; do
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
script_source="$(readlink "$script_source")"
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
source "$script_dir/constant-common.sh"
usage() {
local script_name="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
cat <<EOF
Usage: ${script_name} --label LABEL --target TARGET --workspace DIR [options]
Attach a fleet window to the host-local tmux machine session on a local or remote target.
Options:
--label LABEL Machine label used for the window title
--target TARGET SSH target or local
--workspace DIR Workspace path passed to the machine launcher
--repo-dir DIR Repo path on the target
default: $(zellij_ai_default_repo_dir)
--session NAME Machine tmux session name
default: $(zellij_ai_default_session)
--claude-config-dir DIR Claude config directory on the target
--recreate Recreate the target machine session before attach
-h, --help Show this help
EOF
}
require_arg() {
local flag="$1"
local value="${2:-}"
if [[ -z "$value" ]]; then
echo "Missing value for ${flag}" >&2
exit 2
fi
}
label=""
target=""
workspace=""
repo_dir="$(zellij_ai_default_repo_dir)"
session="$(zellij_ai_default_session)"
claude_config_dir=""
recreate=false
while [[ $# -gt 0 ]]; do
case "$1" in
--label)
require_arg "$1" "${2:-}"
label="$2"
shift 2
;;
--target)
require_arg "$1" "${2:-}"
target="$2"
shift 2
;;
--workspace)
require_arg "$1" "${2:-}"
workspace="$2"
shift 2
;;
--repo-dir)
require_arg "$1" "${2:-}"
repo_dir="$2"
shift 2
;;
--session)
require_arg "$1" "${2:-}"
session="$2"
shift 2
;;
--claude-config-dir)
require_arg "$1" "${2:-}"
claude_config_dir="$2"
shift 2
;;
--recreate)
recreate=true
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown option: $1" >&2
usage >&2
exit 2
;;
esac
done
[[ -n "$label" ]] || { usage >&2; exit 2; }
[[ -n "$target" ]] || { usage >&2; exit 2; }
[[ -n "$workspace" ]] || { usage >&2; exit 2; }
if zellij_ai_is_local_target "$target"; then
local_repo_dir="$(zellij_ai_expand_home_path "$repo_dir")"
local_workspace="$(zellij_ai_expand_home_path "$workspace")"
launcher="$local_repo_dir/scripts/constant-machine.sh"
args=(
--workspace "$local_workspace"
--session "$session"
--repo-dir "$local_repo_dir"
)
[[ -x "$launcher" ]] || launcher="$local_repo_dir/scripts/constant-tmux-machine.sh"
if [[ -n "$claude_config_dir" ]]; then
args+=(--claude-config-dir "$claude_config_dir")
fi
if $recreate; then
args+=(--recreate)
fi
exec env ZELLIJ_AI_MACHINE_NAME="$label" "$launcher" "${args[@]}"
fi
printf -v remote_inner 'repo_dir=%q; workspace=%q; session_name=%q; repo_dir="${repo_dir#\\}"; workspace="${workspace#\\}"; case "$repo_dir" in \$HOME|\$HOME/*) repo_dir="${HOME}${repo_dir#\$HOME}" ;; "~") repo_dir="$HOME" ;; ~/*) repo_dir="$HOME/${repo_dir#~/}" ;; esac; case "$workspace" in \$HOME|\$HOME/*) workspace="${HOME}${workspace#\$HOME}" ;; "~") workspace="$HOME" ;; ~/*) workspace="$HOME/${workspace#~/}" ;; esac; launcher="$repo_dir/scripts/constant-machine.sh"; if [[ ! -x "$launcher" ]]; then launcher="$repo_dir/scripts/constant-tmux-machine.sh"; fi; args=(--workspace "$workspace" --session "$session_name" --repo-dir "$repo_dir");' \
"$repo_dir" \
"$workspace" \
"$session"
if [[ -n "$claude_config_dir" ]]; then
printf -v remote_inner '%s args+=(--claude-config-dir %q);' "$remote_inner" "$claude_config_dir"
fi
if $recreate; then
printf -v remote_inner '%s args+=(--recreate);' "$remote_inner"
fi
printf -v remote_inner '%s exec env ZELLIJ_AI_MACHINE_NAME=%q ZELLIJ_AI_FORCE_OSC52=true "$launcher" "${args[@]}"' "$remote_inner" "$label"
exec ssh -tt "$target" bash -lc "$remote_inner"
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
script_source="${BASH_SOURCE[0]:-$0}"
while [[ -L "$script_source" ]]; do
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
script_source="$(readlink "$script_source")"
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
export CONSTANT_SCRIPT_NAME="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
exec "$script_dir/zellij-ai-vibe-pane.sh" "$@"
+4 -4
View File
@@ -18,9 +18,9 @@ fi
if [[ -n "$bootstrap_file" && ! -f "$bootstrap_file" ]]; then
: >"$bootstrap_file"
printf -v codex_cmd '%q' "$script_dir/zellij-ai-codex-pane.sh"
printf -v copilot_cmd '%q' "$script_dir/zellij-ai-copilot-pane.sh"
printf -v vibe_cmd '%q' "$script_dir/zellij-ai-vibe-pane.sh"
printf -v codex_cmd '%q' "$script_dir/constant-codex-pane.sh"
printf -v copilot_cmd '%q' "$script_dir/constant-copilot-pane.sh"
printf -v vibe_cmd '%q' "$script_dir/constant-vibe-pane.sh"
zellij action new-pane -d right -n "${ZELLIJ_AI_CODEX_LABEL:-codex}" --cwd "$workspace"
zellij action move-focus right
@@ -40,4 +40,4 @@ if [[ -n "$bootstrap_file" && ! -f "$bootstrap_file" ]]; then
zellij action move-focus left
fi
exec "$script_dir/zellij-ai-claude-pane.sh"
exec "$script_dir/constant-claude-pane.sh"
+1 -1
View File
@@ -8,7 +8,7 @@ while [[ -L "$script_source" ]]; do
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
source "$script_dir/zellij-ai-common.sh"
source "$script_dir/constant-common.sh"
workspace="${ZELLIJ_AI_WORKSPACE:-$PWD}"
machine_name="${ZELLIJ_AI_MACHINE_NAME:-unknown}"
+4 -3
View File
@@ -8,11 +8,12 @@ while [[ -L "$script_source" ]]; do
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
source "$script_dir/zellij-ai-common.sh"
source "$script_dir/constant-common.sh"
usage() {
cat <<'EOF'
Usage: zellij-ai-codex-pane.sh [slot]
local script_name="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
cat <<EOF
Usage: ${script_name} [slot]
slot:
1 Deprecated alias for the single host-local Codex pane
+85 -2
View File
@@ -96,7 +96,7 @@ zellij_ai_default_codex_image() {
}
zellij_ai_runtime_root() {
printf '%s\n' "${ZELLIJ_AI_RUNTIME_ROOT:-$HOME/.cache/constant/zellij}"
printf '%s\n' "${ZELLIJ_AI_RUNTIME_ROOT:-$HOME/.cache/constant/cockpit}"
}
zellij_ai_session_state_dir() {
@@ -133,7 +133,7 @@ zellij_ai_strip_ansi() {
}
zellij_ai_session_exists() {
zellij list-sessions 2>/dev/null | zellij_ai_strip_ansi | awk '{print $1}' | grep -Fxq "$1"
tmux has-session -t "$1" >/dev/null 2>&1
}
zellij_ai_kdl_escape() {
@@ -271,3 +271,86 @@ zellij_ai_agent_path() {
printf '%s\n' "$path_value"
}
constant_tmux_require() {
zellij_ai_require_command tmux
}
constant_tmux_session_exists() {
tmux has-session -t "$1" >/dev/null 2>&1
}
constant_tmux_window_exists() {
local session_name="$1"
local window_name="$2"
tmux list-windows -t "$session_name" -F '#{window_name}' 2>/dev/null | grep -Fxq "$window_name"
}
constant_tmux_base_command() {
local role="$1"
local launcher="$2"
local workspace="$3"
local repo_dir="$4"
local session_name="$5"
local bus_dir="$6"
local machine_name="$7"
local codex_home="$8"
local claude_config_dir="$9"
local inner
printf -v inner \
'cd %q && export ZELLIJ_AI_WORKSPACE=%q ZELLIJ_AI_REPO_DIR=%q ZELLIJ_AI_SESSION=%q ZELLIJ_AI_BUS_DIR=%q ZELLIJ_AI_MACHINE_NAME=%q ZELLIJ_AI_CODEX_HOME=%q ZELLIJ_AI_CODEX_LABEL=%q ZELLIJ_AI_COPILOT_LABEL=%q ZELLIJ_AI_VIBE_LABEL=%q ZELLIJ_AI_ROLE=%q PATH=%q;' \
"$workspace" \
"$workspace" \
"$repo_dir" \
"$session_name" \
"$bus_dir" \
"$machine_name" \
"$codex_home" \
"codex" \
"copilot" \
"vibe" \
"$role" \
"$(zellij_ai_agent_path)"
if [[ -n "$claude_config_dir" ]]; then
printf -v inner '%s export ZELLIJ_AI_CLAUDE_CONFIG_DIR=%q;' "$inner" "$claude_config_dir"
fi
printf -v inner '%s exec %q' "$inner" "$launcher"
printf 'bash -lc %q' "$inner"
}
constant_tmux_role_command() {
local role="$1"
local script_dir="$2"
local workspace="$3"
local repo_dir="$4"
local session_name="$5"
local bus_dir="$6"
local machine_name="$7"
local codex_home="$8"
local claude_config_dir="$9"
local launcher=""
case "$role" in
claude)
launcher="$script_dir/constant-claude-pane.sh"
;;
codex)
launcher="$script_dir/constant-codex-pane.sh"
;;
copilot)
launcher="$script_dir/constant-copilot-pane.sh"
;;
vibe)
launcher="$script_dir/constant-vibe-pane.sh"
;;
*)
echo "Unknown Constant pane role: $role" >&2
return 1
;;
esac
constant_tmux_base_command "$role" "$launcher" "$workspace" "$repo_dir" "$session_name" "$bus_dir" "$machine_name" "$codex_home" "$claude_config_dir"
}
+1 -1
View File
@@ -8,7 +8,7 @@ while [[ -L "$script_source" ]]; do
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
source "$script_dir/zellij-ai-common.sh"
source "$script_dir/constant-common.sh"
workspace="${ZELLIJ_AI_WORKSPACE:-$PWD}"
machine_name="${ZELLIJ_AI_MACHINE_NAME:-unknown}"
+3 -3
View File
@@ -8,7 +8,7 @@ while [[ -L "$script_source" ]]; do
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
source "$script_dir/zellij-ai-common.sh"
source "$script_dir/constant-common.sh"
usage() {
local script_name="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
@@ -450,7 +450,7 @@ for raw in finalized.read_text(encoding="utf-8").splitlines():
"target": "local",
"auto_clis": ["codex", "vibe", "claude"],
"manual_clis": ["copilot"],
"backends": ["omc", "cli-local", "zellij"],
"backends": ["omc", "cli-local", "cockpit"],
}
)
else:
@@ -460,7 +460,7 @@ for raw in finalized.read_text(encoding="utf-8").splitlines():
"target": f"{user}@{seed}",
"auto_clis": ["codex", "vibe", "claude"],
"manual_clis": ["copilot"],
"backends": ["cli-ssh", "zellij"],
"backends": ["cli-ssh", "cockpit"],
}
)
+5 -5
View File
@@ -9,7 +9,7 @@ while [[ -L "$script_source" ]]; do
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
repo_source="$(cd "$script_dir/.." && pwd -P)"
source "$script_dir/zellij-ai-common.sh"
source "$script_dir/constant-common.sh"
usage() {
local script_name="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
@@ -176,20 +176,20 @@ for machine_spec in "${machines[@]}"; do
fi
if zellij_ai_is_local_target "$target"; then
ZELLIJ_AI_MACHINE_NAME="$label" "$script_dir/zellij-ai-machine-install.sh" "${install_args[@]}" || overall_status=1
ZELLIJ_AI_MACHINE_NAME="$label" "$script_dir/constant-machine-install.sh" "${install_args[@]}" || overall_status=1
continue
fi
if [[ "$mode" == "install" ]]; then
remote_script="${target_repo_dir}/scripts/zellij-ai-machine-install.sh"
remote_script="${target_repo_dir}/scripts/constant-machine-install.sh"
ssh -tt "$target" env ZELLIJ_AI_MACHINE_NAME="$label" "$remote_script" "${install_args[@]}" || overall_status=1
continue
fi
if $yes; then
ssh "$target" env ZELLIJ_AI_MACHINE_NAME="$label" bash -s -- "${install_args[@]}" <"$script_dir/zellij-ai-machine-install.sh" || overall_status=1
ssh "$target" env ZELLIJ_AI_MACHINE_NAME="$label" bash -s -- "${install_args[@]}" <"$script_dir/constant-machine-install.sh" || overall_status=1
else
ssh "$target" env ZELLIJ_AI_MACHINE_NAME="$label" bash -s -- "${install_args[@]}" <"$script_dir/zellij-ai-machine-install.sh" || overall_status=1
ssh "$target" env ZELLIJ_AI_MACHINE_NAME="$label" bash -s -- "${install_args[@]}" <"$script_dir/constant-machine-install.sh" || overall_status=1
fi
done
+2 -239
View File
@@ -8,243 +8,6 @@ while [[ -L "$script_source" ]]; do
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
repo_dir="$(cd "$script_dir/.." && pwd -P)"
source "$script_dir/zellij-ai-common.sh"
warn_deprecated() {
printf 'Warning: %s\n' "$*" >&2
}
usage() {
local script_name="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
cat <<EOF
Usage: ${script_name} [options]
Open a local Zellij cockpit with one tab per machine.
Each tab attaches to the machine's own local Constant machine session.
Options:
--machine SPEC Machine definition. Repeat once per machine.
format: [TAB_NAME=]TARGET
default fleet: command-center, builder-a, builder-b, edge-a, lab-a
--session NAME Remote/local machine session name
default: $(zellij_ai_default_session)
--local-session NAME Cockpit session name on this machine
default: $(zellij_ai_default_local_session)
--repo-dir DIR Repository path on each machine
default: $(zellij_ai_default_repo_dir)
--workspace DIR Workspace path passed to each machine session
required for the fixed 5-machine fleet
--codex-image IMAGE Deprecated, ignored
--claude-config DIR Optional Claude config override passed to each machine
--remote-recreate Recreate each machine session before attaching
--recreate Recreate the local cockpit session before launching
-h, --help Show this help
EOF
}
machines=()
remote_session="$(zellij_ai_default_session)"
local_session="$(zellij_ai_default_local_session)"
remote_repo_dir="$(zellij_ai_default_repo_dir)"
workspace=""
codex_image=""
claude_config_dir=""
recreate=false
remote_recreate=false
while [[ $# -gt 0 ]]; do
case "$1" in
--machine)
machines+=("$2")
shift 2
;;
--session)
remote_session="$2"
shift 2
;;
--local-session)
local_session="$2"
shift 2
;;
--repo-dir)
remote_repo_dir="$2"
shift 2
;;
--workspace)
workspace="$2"
shift 2
;;
--codex-image)
warn_deprecated "--codex-image is deprecated and ignored; Codex now runs on the host."
codex_image="$2"
shift 2
;;
--claude-config)
claude_config_dir="$2"
shift 2
;;
--remote-recreate)
remote_recreate=true
shift
;;
--recreate)
recreate=true
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown option: $1" >&2
usage >&2
exit 2
;;
esac
done
if [[ ${#machines[@]} -eq 0 ]]; then
mapfile -t machines < <(zellij_ai_default_machine_specs)
fi
if [[ -z "$workspace" ]]; then
echo "--workspace is required for fleet mode." >&2
exit 2
fi
workspace="$(cd "$workspace" && pwd -P)"
for required in \
zellij \
ssh \
"$script_dir/zellij-ai-common.sh" \
"$script_dir/zellij-ai-fleet-bootstrap.sh" \
"$script_dir/zellij-ai-remote-tab.sh"
do
if [[ "$required" == */* ]]; then
if [[ ! -x "$required" ]]; then
echo "Helper script not executable: $required" >&2
exit 1
fi
else
zellij_ai_require_command "$required"
fi
done
state_dir="$(zellij_ai_fleet_state_dir "$local_session")"
tabs_file="$state_dir/tabs.tsv"
tabs_created_file="$state_dir/tabs-created"
first_layout_file="$state_dir/first-tab.kdl"
mkdir -p "$state_dir"
: >"$tabs_file"
rm -f "$tabs_created_file"
bootstrap_path="$(zellij_ai_kdl_escape "$script_dir/zellij-ai-fleet-bootstrap.sh")"
first_label=""
first_wrapper=""
seen_labels='|'
machine_index=0
for machine_spec in "${machines[@]}"; do
if ! parsed_spec="$(zellij_ai_parse_machine_spec "$machine_spec")"; then
echo "Invalid machine spec: $machine_spec" >&2
exit 2
fi
IFS=$'\t' read -r machine_label machine_target <<<"$parsed_spec"
if [[ "$seen_labels" == *"|${machine_label}|"* ]]; then
echo "Duplicate machine label: $machine_label" >&2
exit 2
fi
seen_labels+="${machine_label}|"
wrapper_path="$state_dir/machine-${machine_index}.sh"
layout_path="$state_dir/machine-${machine_index}.kdl"
wrapper_cmd=(
"$script_dir/zellij-ai-remote-tab.sh"
--label "$machine_label"
--target "$machine_target"
--session "$remote_session"
--repo-dir "$remote_repo_dir"
)
if [[ -n "$workspace" ]]; then
wrapper_cmd+=(--workspace "$workspace")
fi
if [[ -n "$codex_image" ]]; then
wrapper_cmd+=(--codex-image "$codex_image")
fi
if [[ -n "$claude_config_dir" ]]; then
wrapper_cmd+=(--claude-config "$claude_config_dir")
fi
if $remote_recreate; then
wrapper_cmd+=(--remote-recreate)
fi
printf -v wrapper_exec '%q ' "${wrapper_cmd[@]}"
wrapper_exec="${wrapper_exec% }"
cat >"$wrapper_path" <<EOF
#!/usr/bin/env bash
set -euo pipefail
exec $wrapper_exec
EOF
chmod +x "$wrapper_path"
wrapper_path_escaped="$(zellij_ai_kdl_escape "$wrapper_path")"
cat >"$layout_path" <<EOF
layout {
pane size=1 borderless=true {
plugin location="tab-bar"
}
pane command="$wrapper_path_escaped"
pane size=2 borderless=true {
plugin location="status-bar"
}
}
EOF
if [[ $machine_index -eq 0 ]]; then
first_label="$machine_label"
first_wrapper="$wrapper_path"
else
printf '%s\t%s\t%s\n' "$machine_label" "$layout_path" "$repo_dir" >>"$tabs_file"
fi
machine_index=$((machine_index + 1))
done
if [[ -z "$first_label" || -z "$first_wrapper" ]]; then
echo "No machine tabs were generated" >&2
exit 1
fi
cat >"$first_layout_file" <<EOF
layout {
pane size=1 borderless=true {
plugin location="tab-bar"
}
pane command="$bootstrap_path"
pane size=2 borderless=true {
plugin location="status-bar"
}
}
EOF
export ZELLIJ_AI_FLEET_TABS_FILE="$tabs_file"
export ZELLIJ_AI_FLEET_CREATED_FILE="$tabs_created_file"
export ZELLIJ_AI_FLEET_CURRENT_LABEL="$first_label"
export ZELLIJ_AI_FLEET_CURRENT_WRAPPER="$first_wrapper"
if $recreate && zellij_ai_session_exists "$local_session"; then
zellij kill-session "$local_session" >/dev/null 2>&1 || true
fi
if zellij_ai_session_exists "$local_session"; then
exec zellij attach "$local_session"
fi
exec zellij --new-session-with-layout "$first_layout_file" --session "$local_session"
export CONSTANT_SCRIPT_NAME="$(basename "$0")"
exec "$script_dir/constant-tmux-fleet.sh" "$@"
+14 -12
View File
@@ -8,8 +8,8 @@ while [[ -L "$script_source" ]]; do
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
if [[ -f "$script_dir/zellij-ai-common.sh" ]]; then
source "$script_dir/zellij-ai-common.sh"
if [[ -f "$script_dir/constant-common.sh" ]]; then
source "$script_dir/constant-common.sh"
else
zellij_ai_default_repo_dir() {
printf '%s\n' '$HOME/constant'
@@ -273,7 +273,7 @@ run_check() {
print_status repo_dir "$repo_dir"
print_status codex_home "$codex_home"
for binary in bash git zellij node npm uv claude codex copilot vibe; do
for binary in bash git tmux node npm uv claude codex copilot vibe; do
if command -v "$binary" >/dev/null 2>&1; then
print_status "$binary" "$(command -v "$binary")"
else
@@ -310,7 +310,7 @@ run_check() {
install_darwin() {
ensure_homebrew
brew install git zellij node uv
brew install git tmux node uv
}
install_linux_ubuntu() {
@@ -325,12 +325,8 @@ install_linux_ubuntu() {
run_root apt-get install -y npm
fi
if ! command -v zellij >/dev/null 2>&1; then
if command -v snap >/dev/null 2>&1; then
run_root snap install zellij --classic
else
run_root apt-get install -y zellij
fi
if ! command -v tmux >/dev/null 2>&1; then
run_root apt-get install -y tmux
fi
}
@@ -354,8 +350,14 @@ install_linux_photon() {
run_root tdnf install -y "${packages[@]}"
fi
if ! command -v zellij >/dev/null 2>&1; then
fail "zellij is missing on Photon OS and no native install path is scripted here."
if ! command -v tmux >/dev/null 2>&1; then
if command -v tdnf >/dev/null 2>&1; then
run_root tdnf install -y tmux
fi
fi
if ! command -v tmux >/dev/null 2>&1; then
fail "tmux is missing on Photon OS and no native install path is scripted here."
fi
}
+6 -5
View File
@@ -8,22 +8,23 @@ while [[ -L "$script_source" ]]; do
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
source "$script_dir/zellij-ai-common.sh"
source "$script_dir/constant-common.sh"
warn_deprecated() {
printf 'Warning: %s\n' "$*" >&2
}
usage() {
cat <<'EOF'
Usage: zellij-ai-remote-tab.sh [options]
local script_name="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
cat <<EOF
Usage: ${script_name} [options]
Open or attach the local AI Zellij session for one machine.
Open or attach the local Constant machine session for one machine.
Options:
--label NAME Display label for logs and tab naming
--target TARGET SSH target, or "local" for the current machine
--session NAME Remote/local Zellij session name
--session NAME Remote/local Constant tmux session name
default: constant
--repo-dir DIR Repository path on the target machine
default: $HOME/constant
+2 -226
View File
@@ -8,230 +8,6 @@ while [[ -L "$script_source" ]]; do
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
repo_dir="$(cd "$script_dir/.." && pwd -P)"
source "$script_dir/zellij-ai-common.sh"
usage() {
local script_name="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
cat <<EOF
Usage: ${script_name} [options]
Start a 4-pane Constant machine session:
- left pane: Claude on the host
- top-right pane: Codex on the host
- middle-right pane: Copilot CLI on the host
- bottom-right pane: Mistral Vibe on the host
Options:
--workspace DIR Workspace used by all panes
default: current directory
--session NAME Session name
default: $(zellij_ai_default_session)
--codex-home DIR Persistent CODEX_HOME for the Codex pane
default: $(zellij_ai_default_codex_home)
--codex-label LABEL Display label for the Codex pane
default: codex
--copilot-label LABEL Display label for the Copilot pane
default: copilot
--vibe-label LABEL Display label for the Vibe pane
default: vibe
--claude-config DIR Optional CLAUDE_CONFIG_DIR override for the Claude pane
--zellij-config-dir DIR
Optional isolated Zellij config dir for session creation
default: a fresh temporary config dir per new session
--codex-image IMAGE Deprecated, ignored
--codex1-home DIR Deprecated alias for --codex-home
--codex2-home DIR Deprecated, ignored
--codex1-label LABEL Deprecated alias for --codex-label
--codex2-label LABEL Deprecated, ignored
--recreate Recreate the session and purge stale Zellij state first
-h, --help Show this help
EOF
}
warn_deprecated() {
printf 'Warning: %s\n' "$*" >&2
}
workspace="$PWD"
session="$(zellij_ai_default_session)"
codex_home="$(zellij_ai_default_codex_home)"
codex_label="codex"
copilot_label="copilot"
vibe_label="vibe"
claude_config_dir=""
zellij_config_dir=""
recreate=false
while [[ $# -gt 0 ]]; do
case "$1" in
--workspace)
workspace="$2"
shift 2
;;
--session)
session="$2"
shift 2
;;
--codex-home)
codex_home="$2"
shift 2
;;
--codex-label)
codex_label="$2"
shift 2
;;
--copilot-label)
copilot_label="$2"
shift 2
;;
--vibe-label)
vibe_label="$2"
shift 2
;;
--codex-image)
warn_deprecated "--codex-image is deprecated and ignored; Codex now runs on the host."
shift 2
;;
--codex1-home)
warn_deprecated "--codex1-home is deprecated; use --codex-home."
codex_home="$2"
shift 2
;;
--codex2-home)
warn_deprecated "--codex2-home is deprecated and ignored."
shift 2
;;
--codex1-label)
warn_deprecated "--codex1-label is deprecated; use --codex-label."
codex_label="$2"
shift 2
;;
--codex2-label)
warn_deprecated "--codex2-label is deprecated and ignored."
shift 2
;;
--claude-config)
claude_config_dir="$2"
shift 2
;;
--zellij-config-dir)
zellij_config_dir="$2"
shift 2
;;
--recreate)
recreate=true
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown option: $1" >&2
usage >&2
exit 2
;;
esac
done
workspace="$(cd "$workspace" && pwd -P)"
codex_home="$(mkdir -p "$(zellij_ai_expand_home_path "$codex_home")" && cd "$(zellij_ai_expand_home_path "$codex_home")" && pwd -P)"
machine_name="$(zellij_ai_current_machine_name)"
state_dir="$(zellij_ai_session_state_dir "$session")"
bus_dir="$state_dir/bus"
bootstrap_file="$state_dir/bootstrapped"
layout_file="$state_dir/layout.kdl"
clipboard_config_file="$state_dir/zellij.config.kdl"
if [[ ! -d "$workspace" ]]; then
echo "Workspace not found: $workspace" >&2
exit 1
fi
for required in \
"$script_dir/zellij-ai-claude-pane.sh" \
"$script_dir/zellij-ai-codex-pane.sh" \
"$script_dir/zellij-ai-copilot-pane.sh" \
"$script_dir/zellij-ai-vibe-pane.sh" \
"$script_dir/zellij-ai-bootstrap.sh" \
"$script_dir/zellij-ai-common.sh"
do
if [[ ! -x "$required" ]]; then
echo "Helper script not executable: $required" >&2
exit 1
fi
done
mkdir -p "$bus_dir/messages"
if [[ "${ZELLIJ_AI_FORCE_OSC52:-false}" == "true" || "${ZELLIJ_AI_FORCE_OSC52:-0}" == "1" ]]; then
cat >"$clipboard_config_file" <<'EOF'
copy_clipboard "system"
EOF
elif [[ "$(uname -s)" == "Darwin" && -x /usr/bin/pbcopy ]]; then
cat >"$clipboard_config_file" <<'EOF'
copy_command "pbcopy"
copy_clipboard "system"
EOF
else
cat >"$clipboard_config_file" <<'EOF'
copy_clipboard "system"
EOF
fi
export ZELLIJ_AI_WORKSPACE="$workspace"
export ZELLIJ_AI_CODEX_HOME="$codex_home"
export ZELLIJ_AI_CODEX_LABEL="$codex_label"
export ZELLIJ_AI_COPILOT_LABEL="$copilot_label"
export ZELLIJ_AI_VIBE_LABEL="$vibe_label"
export ZELLIJ_AI_MAIN_CODEX_CONFIG="$HOME/.codex/config.toml"
export ZELLIJ_AI_BOOTSTRAP_FILE="$bootstrap_file"
export ZELLIJ_AI_REPO_DIR="$repo_dir"
export ZELLIJ_AI_SESSION="$session"
export ZELLIJ_AI_STATE_DIR="$state_dir"
export ZELLIJ_AI_BUS_DIR="$bus_dir"
export ZELLIJ_AI_MACHINE_NAME="$machine_name"
if [[ -n "$claude_config_dir" ]]; then
export ZELLIJ_AI_CLAUDE_CONFIG_DIR="$claude_config_dir"
fi
zellij_attach_cmd=(zellij --config "$clipboard_config_file")
if $recreate && zellij_ai_session_exists "$session"; then
zellij delete-session -f "$session" >/dev/null 2>&1 || true
fi
if zellij_ai_session_exists "$session"; then
exec "${zellij_attach_cmd[@]}" attach "$session"
fi
zellij delete-session "$session" >/dev/null 2>&1 || true
mkdir -p "$state_dir"
rm -f "$bootstrap_file"
bootstrap_path="$(zellij_ai_kdl_escape "$script_dir/zellij-ai-bootstrap.sh")"
cat >"$layout_file" <<EOF
layout {
tab name="Constant" {
pane size=1 borderless=true {
plugin location="tab-bar"
}
pane command="$bootstrap_path"
pane size=2 borderless=true {
plugin location="status-bar"
}
}
}
EOF
if [[ -z "$zellij_config_dir" ]]; then
zellij_config_dir="$(mktemp -d "${TMPDIR:-/tmp}/constant-zellij-${session}.XXXXXX")"
else
zellij_config_dir="$(zellij_ai_expand_home_path "$zellij_config_dir")"
mkdir -p "$zellij_config_dir"
fi
exec zellij --config "$clipboard_config_file" --config-dir "$zellij_config_dir" --new-session-with-layout "$layout_file" --session "$session"
export CONSTANT_SCRIPT_NAME="$(basename "$0")"
exec "$script_dir/constant-tmux-machine.sh" "$@"
+1 -1
View File
@@ -8,7 +8,7 @@ while [[ -L "$script_source" ]]; do
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
source "$script_dir/zellij-ai-common.sh"
source "$script_dir/constant-common.sh"
workspace="${ZELLIJ_AI_WORKSPACE:-$PWD}"
machine_name="${ZELLIJ_AI_MACHINE_NAME:-unknown}"