Refactor Constant cockpit runtime and wrappers
This commit is contained in:
@@ -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 machine’s 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 machine’s 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
@@ -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)
|
||||
|
||||
@@ -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],
|
||||
)
|
||||
@@ -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
@@ -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
@@ -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
@@ -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))
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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
@@ -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'
|
||||
|
||||
Executable
+13
@@ -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" "$@"
|
||||
Executable
+13
@@ -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" "$@"
|
||||
Executable
+13
@@ -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" "$@"
|
||||
Executable
+12
@@ -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"
|
||||
Executable
+13
@@ -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" "$@"
|
||||
@@ -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" "$@"
|
||||
|
||||
Executable
+13
@@ -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" "$@"
|
||||
Executable
+13
@@ -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" "$@"
|
||||
@@ -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" "$@"
|
||||
|
||||
Executable
+13
@@ -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" "$@"
|
||||
@@ -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" "$@"
|
||||
|
||||
Executable
+13
@@ -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" "$@"
|
||||
Executable
+254
@@ -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
|
||||
Executable
+318
@@ -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
|
||||
Executable
+136
@@ -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"
|
||||
Executable
+13
@@ -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" "$@"
|
||||
@@ -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"
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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"],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -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
@@ -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" "$@"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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" "$@"
|
||||
|
||||
@@ -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}"
|
||||
|
||||
Reference in New Issue
Block a user