Refactor Constant cockpit runtime and wrappers

This commit is contained in:
L'électron rare
2026-04-05 19:47:07 +02:00
parent f32adf105a
commit 796e1008df
38 changed files with 1476 additions and 552 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ while [[ -L "$script_source" ]]; do
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
source "$script_dir/zellij-ai-common.sh"
source "$script_dir/constant-common.sh"
usage() {
cat <<EOF
+1 -1
View File
@@ -8,7 +8,7 @@ while [[ -L "$script_source" ]]; do
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
source "$script_dir/zellij-ai-common.sh"
source "$script_dir/constant-common.sh"
usage() {
cat <<'EOF'
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
script_source="${BASH_SOURCE[0]:-$0}"
while [[ -L "$script_source" ]]; do
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
script_source="$(readlink "$script_source")"
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
export CONSTANT_SCRIPT_NAME="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
exec "$script_dir/zellij-ai-bootstrap.sh" "$@"
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
script_source="${BASH_SOURCE[0]:-$0}"
while [[ -L "$script_source" ]]; do
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
script_source="$(readlink "$script_source")"
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
export CONSTANT_SCRIPT_NAME="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
exec "$script_dir/zellij-ai-claude-pane.sh" "$@"
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
script_source="${BASH_SOURCE[0]:-$0}"
while [[ -L "$script_source" ]]; do
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
script_source="$(readlink "$script_source")"
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
export CONSTANT_SCRIPT_NAME="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
exec "$script_dir/zellij-ai-codex-pane.sh" "$@"
+12
View File
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -euo pipefail
script_source="${BASH_SOURCE[0]:-$0}"
while [[ -L "$script_source" ]]; do
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
script_source="$(readlink "$script_source")"
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
source "$script_dir/zellij-ai-common.sh"
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
script_source="${BASH_SOURCE[0]:-$0}"
while [[ -L "$script_source" ]]; do
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
script_source="$(readlink "$script_source")"
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
export CONSTANT_SCRIPT_NAME="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
exec "$script_dir/zellij-ai-copilot-pane.sh" "$@"
+1 -1
View File
@@ -10,4 +10,4 @@ done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
export CONSTANT_SCRIPT_NAME="$(basename "$0")"
exec "$script_dir/zellij-ai-fleet-deploy.sh" "$@"
exec "$script_dir/constant-fleet-deploy.sh" "$@"
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
script_source="${BASH_SOURCE[0]:-$0}"
while [[ -L "$script_source" ]]; do
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
script_source="$(readlink "$script_source")"
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
export CONSTANT_SCRIPT_NAME="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
exec "$script_dir/zellij-ai-fleet-bootstrap.sh" "$@"
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
script_source="${BASH_SOURCE[0]:-$0}"
while [[ -L "$script_source" ]]; do
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
script_source="$(readlink "$script_source")"
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
export CONSTANT_SCRIPT_NAME="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
exec "$script_dir/zellij-ai-fleet-deploy.sh" "$@"
+1 -1
View File
@@ -10,4 +10,4 @@ done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
export CONSTANT_SCRIPT_NAME="$(basename "$0")"
exec "$script_dir/zellij-ai-fleet.sh" "$@"
exec "$script_dir/constant-tmux-fleet.sh" "$@"
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
script_source="${BASH_SOURCE[0]:-$0}"
while [[ -L "$script_source" ]]; do
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
script_source="$(readlink "$script_source")"
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
export CONSTANT_SCRIPT_NAME="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
exec "$script_dir/zellij-ai-machine-install.sh" "$@"
+1 -1
View File
@@ -10,4 +10,4 @@ done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
export CONSTANT_SCRIPT_NAME="$(basename "$0")"
exec "$script_dir/zellij-ai-triple.sh" "$@"
exec "$script_dir/constant-tmux-machine.sh" "$@"
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
script_source="${BASH_SOURCE[0]:-$0}"
while [[ -L "$script_source" ]]; do
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
script_source="$(readlink "$script_source")"
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
export CONSTANT_SCRIPT_NAME="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
exec "$script_dir/zellij-ai-remote-tab.sh" "$@"
+254
View File
@@ -0,0 +1,254 @@
#!/usr/bin/env bash
set -euo pipefail
script_source="${BASH_SOURCE[0]:-$0}"
while [[ -L "$script_source" ]]; do
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
script_source="$(readlink "$script_source")"
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
source "$script_dir/constant-common.sh"
usage() {
local script_name="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
cat <<EOF
Usage: ${script_name} --workspace DIR [options]
Open the Constant tmux fleet cockpit with one central window and one window per machine.
Options:
--workspace DIR Shared workspace path
--session NAME Machine tmux session name
default: $(zellij_ai_default_session)
--local-session NAME Fleet tmux session name on this machine
default: $(zellij_ai_default_local_session)
--repo-dir DIR Constant repo path on each machine
default: $(zellij_ai_default_repo_dir)
--machine SPEC Machine definition or label to include
--claude-config-dir D Claude config directory for the Claude pane
--recreate Recreate the local fleet session before launch
--remote-recreate Recreate each machine tmux session before attach
--attach-only Attach to an existing local fleet session
--ensure-only Create/update the fleet session, then exit
-h, --help Show this help
EOF
}
require_arg() {
local flag="$1"
local value="${2:-}"
if [[ -z "$value" ]]; then
echo "Missing value for ${flag}" >&2
exit 2
fi
}
build_constant_window_command() {
local inner
printf -v inner 'cd %q && export PATH=%q; exec %q tui --workspace %q --local-session %q --session %q' \
"$workspace" \
"$(zellij_ai_agent_path)" \
"$repo_dir/scripts/Constant" \
"$workspace" \
"$local_session" \
"$remote_session"
printf 'bash -lc %q' "$inner"
}
build_remote_window_command() {
local label="$1"
local target="$2"
local inner remote_script cmd cmd_joined
remote_script="$repo_dir/scripts/constant-tmux-remote-window.sh"
cmd=(
"$remote_script"
--label "$label"
--target "$target"
--workspace "$workspace_spec"
--repo-dir "$repo_dir_spec"
--session "$remote_session"
)
if [[ -n "$claude_config_dir" ]]; then
cmd+=(--claude-config-dir "$claude_config_dir")
fi
if $remote_recreate; then
cmd+=(--recreate)
fi
printf -v cmd_joined '%q ' "${cmd[@]}"
printf -v inner 'cd %q && export PATH=%q; exec %s' \
"$repo_dir" \
"$(zellij_ai_agent_path)" \
"$cmd_joined"
printf 'bash -lc %q' "$inner"
}
link_local_window() {
local label="$1"
local launcher="$repo_dir/scripts/constant-machine.sh"
local args=(
--workspace "$workspace"
--session "$remote_session"
--repo-dir "$repo_dir"
--ensure-only
)
if [[ ! -x "$launcher" ]]; then
launcher="$repo_dir/scripts/constant-tmux-machine.sh"
fi
if [[ -n "$claude_config_dir" ]]; then
args+=(--claude-config-dir "$claude_config_dir")
fi
if $remote_recreate; then
args+=(--recreate)
fi
ZELLIJ_AI_MACHINE_NAME="$label" "$launcher" "${args[@]}"
if ! constant_tmux_window_exists "$local_session" "$label"; then
tmux link-window -d -s "${remote_session}:${label}" -t "${local_session}:"
fi
}
constant_tmux_require
zellij_ai_require_command ssh
workspace=""
workspace_spec=""
remote_session="$(zellij_ai_default_session)"
local_session="$(zellij_ai_default_local_session)"
repo_dir="$(zellij_ai_expand_home_path "$(zellij_ai_default_repo_dir)")"
repo_dir_spec="$(zellij_ai_default_repo_dir)"
claude_config_dir=""
recreate=false
remote_recreate=false
attach_only=false
ensure_only=false
machines=()
while [[ $# -gt 0 ]]; do
case "$1" in
--workspace)
require_arg "$1" "${2:-}"
workspace_spec="$2"
workspace="$(zellij_ai_expand_home_path "$2")"
shift 2
;;
--session)
require_arg "$1" "${2:-}"
remote_session="$2"
shift 2
;;
--local-session)
require_arg "$1" "${2:-}"
local_session="$2"
shift 2
;;
--repo-dir)
require_arg "$1" "${2:-}"
repo_dir_spec="$2"
repo_dir="$(zellij_ai_expand_home_path "$2")"
shift 2
;;
--machine)
require_arg "$1" "${2:-}"
machines+=("$2")
shift 2
;;
--claude-config-dir)
require_arg "$1" "${2:-}"
claude_config_dir="$(zellij_ai_expand_home_path "$2")"
shift 2
;;
--recreate)
recreate=true
shift
;;
--remote-recreate)
remote_recreate=true
shift
;;
--attach-only)
attach_only=true
shift
;;
--ensure-only)
ensure_only=true
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown option: $1" >&2
usage >&2
exit 2
;;
esac
done
if $attach_only; then
if [[ -n "${TMUX:-}" ]]; then
exec tmux switch-client -t "$local_session"
fi
exec tmux attach-session -t "$local_session"
fi
if [[ -z "$workspace" ]]; then
usage >&2
exit 2
fi
if [[ ! -d "$workspace" ]]; then
echo "Workspace not found: $workspace" >&2
exit 1
fi
if [[ ${#machines[@]} -eq 0 ]]; then
mapfile -t machines < <(zellij_ai_default_machine_specs)
fi
if $recreate && constant_tmux_session_exists "$local_session"; then
tmux kill-session -t "$local_session" >/dev/null 2>&1 || true
fi
if constant_tmux_session_exists "$local_session"; then
if [[ -n "${TMUX:-}" ]]; then
exec tmux switch-client -t "$local_session"
fi
exec tmux attach-session -t "$local_session"
fi
mkdir -p "$(zellij_ai_fleet_state_dir "$local_session")"
tmux new-session -d -s "$local_session" -n Constant "$(build_constant_window_command)"
tmux set-option -t "$local_session" -g set-clipboard on >/dev/null 2>&1 || true
tmux set-option -t "$local_session" -g remain-on-exit on >/dev/null 2>&1 || true
tmux set-option -t "$local_session" -g mouse on >/dev/null 2>&1 || true
for machine_spec in "${machines[@]}"; do
parsed_spec="$(zellij_ai_parse_machine_spec "$machine_spec")"
IFS=$'\t' read -r label target <<<"$parsed_spec"
if [[ "$label" == "Constant" ]]; then
continue
fi
if zellij_ai_is_local_target "$target"; then
link_local_window "$label"
else
tmux new-window -d -t "${local_session}:" -n "$label" "$(build_remote_window_command "$label" "$target")"
fi
done
tmux select-window -t "${local_session}:Constant" >/dev/null 2>&1 || true
if $ensure_only; then
exit 0
fi
if [[ -n "${TMUX:-}" ]]; then
exec tmux switch-client -t "$local_session"
else
exec tmux attach-session -t "$local_session"
fi
+318
View File
@@ -0,0 +1,318 @@
#!/usr/bin/env bash
set -euo pipefail
script_source="${BASH_SOURCE[0]:-$0}"
while [[ -L "$script_source" ]]; do
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
script_source="$(readlink "$script_source")"
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
source "$script_dir/constant-common.sh"
usage() {
local script_name="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
cat <<EOF
Usage: ${script_name} [options]
Create or control the host-local tmux cockpit for one machine.
Options:
--workspace DIR Workspace for all 4 panes
default: current directory
--session NAME Machine tmux session name
default: $(zellij_ai_default_session)
--repo-dir DIR Repo containing Constant scripts
default: $(zellij_ai_default_repo_dir)
--codex-home DIR Codex profile directory
default: $(zellij_ai_default_codex_home)
--claude-config-dir D Claude config directory passed to the Claude pane
--recreate Kill and recreate the machine session
--ensure-only Ensure the session exists, then exit
--focus-pane ROLE Focus one pane: claude|codex|copilot|vibe
--capture-pane ROLE Print pane capture for the selected role
--send-pane ROLE Send --command text to the selected pane
--restart-pane ROLE Respawn the selected pane with its launcher command
--command TEXT Text to send with --send-pane
--lines N Lines to print with --capture-pane
default: 120
-h, --help Show this help
EOF
}
require_arg() {
local flag="$1"
local value="${2:-}"
if [[ -z "$value" ]]; then
echo "Missing value for ${flag}" >&2
exit 2
fi
}
tmux_target_window() {
printf '%s:%s\n' "$session" "$machine_name"
}
role_pane_id() {
local role="$1"
tmux list-panes -t "$(tmux_target_window)" -F '#{pane_id} #{@constant_role} #{pane_title}' 2>/dev/null | awk -F '\t' -v role="$role" '$2 == role || $3 == role {print $1; exit}'
}
ensure_workspace() {
if [[ ! -d "$workspace" ]]; then
echo "Workspace not found: $workspace" >&2
exit 1
fi
}
pane_command() {
constant_tmux_role_command "$1" "$script_dir" "$workspace" "$repo_dir" "$session" "$bus_dir" "$machine_name" "$codex_home" "$claude_config_dir"
}
set_pane_meta() {
local pane_id="$1"
local role="$2"
tmux select-pane -t "$pane_id" -T "$role" >/dev/null 2>&1 || true
tmux set-option -p -t "$pane_id" @constant_role "$role" >/dev/null 2>&1 || true
}
create_session() {
local target_window claude_cmd codex_cmd copilot_cmd vibe_cmd
local claude_pane codex_pane copilot_pane vibe_pane
target_window="$(tmux_target_window)"
claude_cmd="$(pane_command claude)"
codex_cmd="$(pane_command codex)"
copilot_cmd="$(pane_command copilot)"
vibe_cmd="$(pane_command vibe)"
tmux new-session -d -s "$session" -n "$machine_name" "$claude_cmd"
tmux set-option -t "$session" -g set-clipboard on >/dev/null 2>&1 || true
tmux set-option -t "$session" -g remain-on-exit on >/dev/null 2>&1 || true
tmux set-option -t "$session" -g mouse on >/dev/null 2>&1 || true
tmux set-option -t "$session" -g status-position top >/dev/null 2>&1 || true
tmux set-window-option -t "$target_window" -g pane-base-index 0 >/dev/null 2>&1 || true
claude_pane="$(tmux list-panes -t "$target_window" -F '#{pane_id}' | head -n 1)"
codex_pane="$(tmux split-window -h -P -F '#{pane_id}' -t "$claude_pane" "$codex_cmd")"
copilot_pane="$(tmux split-window -v -P -F '#{pane_id}' -t "$codex_pane" "$copilot_cmd")"
vibe_pane="$(tmux split-window -v -P -F '#{pane_id}' -t "$copilot_pane" "$vibe_cmd")"
tmux select-layout -t "$target_window" main-vertical >/dev/null
tmux resize-pane -t "$claude_pane" -x 120 >/dev/null 2>&1 || true
set_pane_meta "$claude_pane" claude
set_pane_meta "$codex_pane" codex
set_pane_meta "$copilot_pane" copilot
set_pane_meta "$vibe_pane" vibe
tmux select-pane -t "$claude_pane" >/dev/null
}
ensure_session() {
mkdir -p "$state_dir" "$bus_dir/messages"
if $recreate && constant_tmux_session_exists "$session"; then
tmux kill-session -t "$session" >/dev/null 2>&1 || true
fi
if ! constant_tmux_session_exists "$session"; then
create_session
return 0
fi
if ! constant_tmux_window_exists "$session" "$machine_name"; then
recreate=true
tmux kill-session -t "$session" >/dev/null 2>&1 || true
create_session
fi
}
focus_role() {
local role="$1"
local pane_id
ensure_session
pane_id="$(role_pane_id "$role")"
if [[ -z "$pane_id" ]]; then
echo "No pane found for role: $role" >&2
exit 1
fi
tmux select-window -t "$(tmux_target_window)" >/dev/null
tmux select-pane -t "$pane_id" >/dev/null
}
restart_role() {
local role="$1"
local pane_id command_string
ensure_session
pane_id="$(role_pane_id "$role")"
if [[ -z "$pane_id" ]]; then
echo "No pane found for role: $role" >&2
exit 1
fi
command_string="$(pane_command "$role")"
tmux respawn-pane -k -t "$pane_id" "$command_string"
set_pane_meta "$pane_id" "$role"
tmux select-layout -t "$(tmux_target_window)" main-vertical >/dev/null
}
capture_role() {
local role="$1"
local pane_id
ensure_session
pane_id="$(role_pane_id "$role")"
if [[ -z "$pane_id" ]]; then
echo "No pane found for role: $role" >&2
exit 1
fi
tmux capture-pane -p -S "-${lines}" -t "$pane_id"
}
send_role() {
local role="$1"
local pane_id
require_arg --command "$send_command"
ensure_session
pane_id="$(role_pane_id "$role")"
if [[ -z "$pane_id" ]]; then
echo "No pane found for role: $role" >&2
exit 1
fi
tmux send-keys -t "$pane_id" -l "$send_command"
tmux send-keys -t "$pane_id" Enter
}
session="$(zellij_ai_default_session)"
workspace="$PWD"
repo_dir="$(zellij_ai_expand_home_path "$(zellij_ai_default_repo_dir)")"
codex_home="$(zellij_ai_expand_home_path "$(zellij_ai_default_codex_home)")"
claude_config_dir=""
machine_name="${ZELLIJ_AI_MACHINE_NAME:-$(zellij_ai_local_machine_label)}"
recreate=false
ensure_only=false
focus_pane=""
capture_pane=""
restart_pane=""
send_pane=""
send_command=""
lines=120
while [[ $# -gt 0 ]]; do
case "$1" in
--workspace)
require_arg "$1" "${2:-}"
workspace="$(zellij_ai_expand_home_path "$2")"
shift 2
;;
--session)
require_arg "$1" "${2:-}"
session="$2"
shift 2
;;
--repo-dir)
require_arg "$1" "${2:-}"
repo_dir="$(zellij_ai_expand_home_path "$2")"
shift 2
;;
--codex-home)
require_arg "$1" "${2:-}"
codex_home="$(zellij_ai_expand_home_path "$2")"
shift 2
;;
--claude-config-dir)
require_arg "$1" "${2:-}"
claude_config_dir="$(zellij_ai_expand_home_path "$2")"
shift 2
;;
--recreate)
recreate=true
shift
;;
--ensure-only)
ensure_only=true
shift
;;
--focus-pane)
require_arg "$1" "${2:-}"
focus_pane="$2"
shift 2
;;
--capture-pane)
require_arg "$1" "${2:-}"
capture_pane="$2"
shift 2
;;
--send-pane)
require_arg "$1" "${2:-}"
send_pane="$2"
shift 2
;;
--restart-pane)
require_arg "$1" "${2:-}"
restart_pane="$2"
shift 2
;;
--command)
require_arg "$1" "${2:-}"
send_command="$2"
shift 2
;;
--lines)
require_arg "$1" "${2:-}"
lines="$2"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown option: $1" >&2
usage >&2
exit 2
;;
esac
done
constant_tmux_require
ensure_workspace
state_dir="$(zellij_ai_session_state_dir "$session")"
bus_dir="$state_dir/bus"
if [[ -n "$focus_pane" ]]; then
focus_role "$focus_pane"
exit 0
fi
if [[ -n "$capture_pane" ]]; then
capture_role "$capture_pane"
exit 0
fi
if [[ -n "$send_pane" ]]; then
send_role "$send_pane"
exit 0
fi
if [[ -n "$restart_pane" ]]; then
restart_role "$restart_pane"
exit 0
fi
ensure_session
if $ensure_only; then
exit 0
fi
if [[ -n "${TMUX:-}" ]]; then
exec tmux switch-client -t "$session"
else
exec tmux attach-session -t "$session"
fi
+136
View File
@@ -0,0 +1,136 @@
#!/usr/bin/env bash
set -euo pipefail
script_source="${BASH_SOURCE[0]:-$0}"
while [[ -L "$script_source" ]]; do
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
script_source="$(readlink "$script_source")"
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
source "$script_dir/constant-common.sh"
usage() {
local script_name="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
cat <<EOF
Usage: ${script_name} --label LABEL --target TARGET --workspace DIR [options]
Attach a fleet window to the host-local tmux machine session on a local or remote target.
Options:
--label LABEL Machine label used for the window title
--target TARGET SSH target or local
--workspace DIR Workspace path passed to the machine launcher
--repo-dir DIR Repo path on the target
default: $(zellij_ai_default_repo_dir)
--session NAME Machine tmux session name
default: $(zellij_ai_default_session)
--claude-config-dir DIR Claude config directory on the target
--recreate Recreate the target machine session before attach
-h, --help Show this help
EOF
}
require_arg() {
local flag="$1"
local value="${2:-}"
if [[ -z "$value" ]]; then
echo "Missing value for ${flag}" >&2
exit 2
fi
}
label=""
target=""
workspace=""
repo_dir="$(zellij_ai_default_repo_dir)"
session="$(zellij_ai_default_session)"
claude_config_dir=""
recreate=false
while [[ $# -gt 0 ]]; do
case "$1" in
--label)
require_arg "$1" "${2:-}"
label="$2"
shift 2
;;
--target)
require_arg "$1" "${2:-}"
target="$2"
shift 2
;;
--workspace)
require_arg "$1" "${2:-}"
workspace="$2"
shift 2
;;
--repo-dir)
require_arg "$1" "${2:-}"
repo_dir="$2"
shift 2
;;
--session)
require_arg "$1" "${2:-}"
session="$2"
shift 2
;;
--claude-config-dir)
require_arg "$1" "${2:-}"
claude_config_dir="$2"
shift 2
;;
--recreate)
recreate=true
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown option: $1" >&2
usage >&2
exit 2
;;
esac
done
[[ -n "$label" ]] || { usage >&2; exit 2; }
[[ -n "$target" ]] || { usage >&2; exit 2; }
[[ -n "$workspace" ]] || { usage >&2; exit 2; }
if zellij_ai_is_local_target "$target"; then
local_repo_dir="$(zellij_ai_expand_home_path "$repo_dir")"
local_workspace="$(zellij_ai_expand_home_path "$workspace")"
launcher="$local_repo_dir/scripts/constant-machine.sh"
args=(
--workspace "$local_workspace"
--session "$session"
--repo-dir "$local_repo_dir"
)
[[ -x "$launcher" ]] || launcher="$local_repo_dir/scripts/constant-tmux-machine.sh"
if [[ -n "$claude_config_dir" ]]; then
args+=(--claude-config-dir "$claude_config_dir")
fi
if $recreate; then
args+=(--recreate)
fi
exec env ZELLIJ_AI_MACHINE_NAME="$label" "$launcher" "${args[@]}"
fi
printf -v remote_inner 'repo_dir=%q; workspace=%q; session_name=%q; repo_dir="${repo_dir#\\}"; workspace="${workspace#\\}"; case "$repo_dir" in \$HOME|\$HOME/*) repo_dir="${HOME}${repo_dir#\$HOME}" ;; "~") repo_dir="$HOME" ;; ~/*) repo_dir="$HOME/${repo_dir#~/}" ;; esac; case "$workspace" in \$HOME|\$HOME/*) workspace="${HOME}${workspace#\$HOME}" ;; "~") workspace="$HOME" ;; ~/*) workspace="$HOME/${workspace#~/}" ;; esac; launcher="$repo_dir/scripts/constant-machine.sh"; if [[ ! -x "$launcher" ]]; then launcher="$repo_dir/scripts/constant-tmux-machine.sh"; fi; args=(--workspace "$workspace" --session "$session_name" --repo-dir "$repo_dir");' \
"$repo_dir" \
"$workspace" \
"$session"
if [[ -n "$claude_config_dir" ]]; then
printf -v remote_inner '%s args+=(--claude-config-dir %q);' "$remote_inner" "$claude_config_dir"
fi
if $recreate; then
printf -v remote_inner '%s args+=(--recreate);' "$remote_inner"
fi
printf -v remote_inner '%s exec env ZELLIJ_AI_MACHINE_NAME=%q ZELLIJ_AI_FORCE_OSC52=true "$launcher" "${args[@]}"' "$remote_inner" "$label"
exec ssh -tt "$target" bash -lc "$remote_inner"
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
script_source="${BASH_SOURCE[0]:-$0}"
while [[ -L "$script_source" ]]; do
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
script_source="$(readlink "$script_source")"
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
export CONSTANT_SCRIPT_NAME="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
exec "$script_dir/zellij-ai-vibe-pane.sh" "$@"
+4 -4
View File
@@ -18,9 +18,9 @@ fi
if [[ -n "$bootstrap_file" && ! -f "$bootstrap_file" ]]; then
: >"$bootstrap_file"
printf -v codex_cmd '%q' "$script_dir/zellij-ai-codex-pane.sh"
printf -v copilot_cmd '%q' "$script_dir/zellij-ai-copilot-pane.sh"
printf -v vibe_cmd '%q' "$script_dir/zellij-ai-vibe-pane.sh"
printf -v codex_cmd '%q' "$script_dir/constant-codex-pane.sh"
printf -v copilot_cmd '%q' "$script_dir/constant-copilot-pane.sh"
printf -v vibe_cmd '%q' "$script_dir/constant-vibe-pane.sh"
zellij action new-pane -d right -n "${ZELLIJ_AI_CODEX_LABEL:-codex}" --cwd "$workspace"
zellij action move-focus right
@@ -40,4 +40,4 @@ if [[ -n "$bootstrap_file" && ! -f "$bootstrap_file" ]]; then
zellij action move-focus left
fi
exec "$script_dir/zellij-ai-claude-pane.sh"
exec "$script_dir/constant-claude-pane.sh"
+1 -1
View File
@@ -8,7 +8,7 @@ while [[ -L "$script_source" ]]; do
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
source "$script_dir/zellij-ai-common.sh"
source "$script_dir/constant-common.sh"
workspace="${ZELLIJ_AI_WORKSPACE:-$PWD}"
machine_name="${ZELLIJ_AI_MACHINE_NAME:-unknown}"
+4 -3
View File
@@ -8,11 +8,12 @@ while [[ -L "$script_source" ]]; do
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
source "$script_dir/zellij-ai-common.sh"
source "$script_dir/constant-common.sh"
usage() {
cat <<'EOF'
Usage: zellij-ai-codex-pane.sh [slot]
local script_name="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
cat <<EOF
Usage: ${script_name} [slot]
slot:
1 Deprecated alias for the single host-local Codex pane
+85 -2
View File
@@ -96,7 +96,7 @@ zellij_ai_default_codex_image() {
}
zellij_ai_runtime_root() {
printf '%s\n' "${ZELLIJ_AI_RUNTIME_ROOT:-$HOME/.cache/constant/zellij}"
printf '%s\n' "${ZELLIJ_AI_RUNTIME_ROOT:-$HOME/.cache/constant/cockpit}"
}
zellij_ai_session_state_dir() {
@@ -133,7 +133,7 @@ zellij_ai_strip_ansi() {
}
zellij_ai_session_exists() {
zellij list-sessions 2>/dev/null | zellij_ai_strip_ansi | awk '{print $1}' | grep -Fxq "$1"
tmux has-session -t "$1" >/dev/null 2>&1
}
zellij_ai_kdl_escape() {
@@ -271,3 +271,86 @@ zellij_ai_agent_path() {
printf '%s\n' "$path_value"
}
constant_tmux_require() {
zellij_ai_require_command tmux
}
constant_tmux_session_exists() {
tmux has-session -t "$1" >/dev/null 2>&1
}
constant_tmux_window_exists() {
local session_name="$1"
local window_name="$2"
tmux list-windows -t "$session_name" -F '#{window_name}' 2>/dev/null | grep -Fxq "$window_name"
}
constant_tmux_base_command() {
local role="$1"
local launcher="$2"
local workspace="$3"
local repo_dir="$4"
local session_name="$5"
local bus_dir="$6"
local machine_name="$7"
local codex_home="$8"
local claude_config_dir="$9"
local inner
printf -v inner \
'cd %q && export ZELLIJ_AI_WORKSPACE=%q ZELLIJ_AI_REPO_DIR=%q ZELLIJ_AI_SESSION=%q ZELLIJ_AI_BUS_DIR=%q ZELLIJ_AI_MACHINE_NAME=%q ZELLIJ_AI_CODEX_HOME=%q ZELLIJ_AI_CODEX_LABEL=%q ZELLIJ_AI_COPILOT_LABEL=%q ZELLIJ_AI_VIBE_LABEL=%q ZELLIJ_AI_ROLE=%q PATH=%q;' \
"$workspace" \
"$workspace" \
"$repo_dir" \
"$session_name" \
"$bus_dir" \
"$machine_name" \
"$codex_home" \
"codex" \
"copilot" \
"vibe" \
"$role" \
"$(zellij_ai_agent_path)"
if [[ -n "$claude_config_dir" ]]; then
printf -v inner '%s export ZELLIJ_AI_CLAUDE_CONFIG_DIR=%q;' "$inner" "$claude_config_dir"
fi
printf -v inner '%s exec %q' "$inner" "$launcher"
printf 'bash -lc %q' "$inner"
}
constant_tmux_role_command() {
local role="$1"
local script_dir="$2"
local workspace="$3"
local repo_dir="$4"
local session_name="$5"
local bus_dir="$6"
local machine_name="$7"
local codex_home="$8"
local claude_config_dir="$9"
local launcher=""
case "$role" in
claude)
launcher="$script_dir/constant-claude-pane.sh"
;;
codex)
launcher="$script_dir/constant-codex-pane.sh"
;;
copilot)
launcher="$script_dir/constant-copilot-pane.sh"
;;
vibe)
launcher="$script_dir/constant-vibe-pane.sh"
;;
*)
echo "Unknown Constant pane role: $role" >&2
return 1
;;
esac
constant_tmux_base_command "$role" "$launcher" "$workspace" "$repo_dir" "$session_name" "$bus_dir" "$machine_name" "$codex_home" "$claude_config_dir"
}
+1 -1
View File
@@ -8,7 +8,7 @@ while [[ -L "$script_source" ]]; do
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
source "$script_dir/zellij-ai-common.sh"
source "$script_dir/constant-common.sh"
workspace="${ZELLIJ_AI_WORKSPACE:-$PWD}"
machine_name="${ZELLIJ_AI_MACHINE_NAME:-unknown}"
+3 -3
View File
@@ -8,7 +8,7 @@ while [[ -L "$script_source" ]]; do
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
source "$script_dir/zellij-ai-common.sh"
source "$script_dir/constant-common.sh"
usage() {
local script_name="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
@@ -450,7 +450,7 @@ for raw in finalized.read_text(encoding="utf-8").splitlines():
"target": "local",
"auto_clis": ["codex", "vibe", "claude"],
"manual_clis": ["copilot"],
"backends": ["omc", "cli-local", "zellij"],
"backends": ["omc", "cli-local", "cockpit"],
}
)
else:
@@ -460,7 +460,7 @@ for raw in finalized.read_text(encoding="utf-8").splitlines():
"target": f"{user}@{seed}",
"auto_clis": ["codex", "vibe", "claude"],
"manual_clis": ["copilot"],
"backends": ["cli-ssh", "zellij"],
"backends": ["cli-ssh", "cockpit"],
}
)
+5 -5
View File
@@ -9,7 +9,7 @@ while [[ -L "$script_source" ]]; do
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
repo_source="$(cd "$script_dir/.." && pwd -P)"
source "$script_dir/zellij-ai-common.sh"
source "$script_dir/constant-common.sh"
usage() {
local script_name="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
@@ -176,20 +176,20 @@ for machine_spec in "${machines[@]}"; do
fi
if zellij_ai_is_local_target "$target"; then
ZELLIJ_AI_MACHINE_NAME="$label" "$script_dir/zellij-ai-machine-install.sh" "${install_args[@]}" || overall_status=1
ZELLIJ_AI_MACHINE_NAME="$label" "$script_dir/constant-machine-install.sh" "${install_args[@]}" || overall_status=1
continue
fi
if [[ "$mode" == "install" ]]; then
remote_script="${target_repo_dir}/scripts/zellij-ai-machine-install.sh"
remote_script="${target_repo_dir}/scripts/constant-machine-install.sh"
ssh -tt "$target" env ZELLIJ_AI_MACHINE_NAME="$label" "$remote_script" "${install_args[@]}" || overall_status=1
continue
fi
if $yes; then
ssh "$target" env ZELLIJ_AI_MACHINE_NAME="$label" bash -s -- "${install_args[@]}" <"$script_dir/zellij-ai-machine-install.sh" || overall_status=1
ssh "$target" env ZELLIJ_AI_MACHINE_NAME="$label" bash -s -- "${install_args[@]}" <"$script_dir/constant-machine-install.sh" || overall_status=1
else
ssh "$target" env ZELLIJ_AI_MACHINE_NAME="$label" bash -s -- "${install_args[@]}" <"$script_dir/zellij-ai-machine-install.sh" || overall_status=1
ssh "$target" env ZELLIJ_AI_MACHINE_NAME="$label" bash -s -- "${install_args[@]}" <"$script_dir/constant-machine-install.sh" || overall_status=1
fi
done
+2 -239
View File
@@ -8,243 +8,6 @@ while [[ -L "$script_source" ]]; do
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
repo_dir="$(cd "$script_dir/.." && pwd -P)"
source "$script_dir/zellij-ai-common.sh"
warn_deprecated() {
printf 'Warning: %s\n' "$*" >&2
}
usage() {
local script_name="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
cat <<EOF
Usage: ${script_name} [options]
Open a local Zellij cockpit with one tab per machine.
Each tab attaches to the machine's own local Constant machine session.
Options:
--machine SPEC Machine definition. Repeat once per machine.
format: [TAB_NAME=]TARGET
default fleet: command-center, builder-a, builder-b, edge-a, lab-a
--session NAME Remote/local machine session name
default: $(zellij_ai_default_session)
--local-session NAME Cockpit session name on this machine
default: $(zellij_ai_default_local_session)
--repo-dir DIR Repository path on each machine
default: $(zellij_ai_default_repo_dir)
--workspace DIR Workspace path passed to each machine session
required for the fixed 5-machine fleet
--codex-image IMAGE Deprecated, ignored
--claude-config DIR Optional Claude config override passed to each machine
--remote-recreate Recreate each machine session before attaching
--recreate Recreate the local cockpit session before launching
-h, --help Show this help
EOF
}
machines=()
remote_session="$(zellij_ai_default_session)"
local_session="$(zellij_ai_default_local_session)"
remote_repo_dir="$(zellij_ai_default_repo_dir)"
workspace=""
codex_image=""
claude_config_dir=""
recreate=false
remote_recreate=false
while [[ $# -gt 0 ]]; do
case "$1" in
--machine)
machines+=("$2")
shift 2
;;
--session)
remote_session="$2"
shift 2
;;
--local-session)
local_session="$2"
shift 2
;;
--repo-dir)
remote_repo_dir="$2"
shift 2
;;
--workspace)
workspace="$2"
shift 2
;;
--codex-image)
warn_deprecated "--codex-image is deprecated and ignored; Codex now runs on the host."
codex_image="$2"
shift 2
;;
--claude-config)
claude_config_dir="$2"
shift 2
;;
--remote-recreate)
remote_recreate=true
shift
;;
--recreate)
recreate=true
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown option: $1" >&2
usage >&2
exit 2
;;
esac
done
if [[ ${#machines[@]} -eq 0 ]]; then
mapfile -t machines < <(zellij_ai_default_machine_specs)
fi
if [[ -z "$workspace" ]]; then
echo "--workspace is required for fleet mode." >&2
exit 2
fi
workspace="$(cd "$workspace" && pwd -P)"
for required in \
zellij \
ssh \
"$script_dir/zellij-ai-common.sh" \
"$script_dir/zellij-ai-fleet-bootstrap.sh" \
"$script_dir/zellij-ai-remote-tab.sh"
do
if [[ "$required" == */* ]]; then
if [[ ! -x "$required" ]]; then
echo "Helper script not executable: $required" >&2
exit 1
fi
else
zellij_ai_require_command "$required"
fi
done
state_dir="$(zellij_ai_fleet_state_dir "$local_session")"
tabs_file="$state_dir/tabs.tsv"
tabs_created_file="$state_dir/tabs-created"
first_layout_file="$state_dir/first-tab.kdl"
mkdir -p "$state_dir"
: >"$tabs_file"
rm -f "$tabs_created_file"
bootstrap_path="$(zellij_ai_kdl_escape "$script_dir/zellij-ai-fleet-bootstrap.sh")"
first_label=""
first_wrapper=""
seen_labels='|'
machine_index=0
for machine_spec in "${machines[@]}"; do
if ! parsed_spec="$(zellij_ai_parse_machine_spec "$machine_spec")"; then
echo "Invalid machine spec: $machine_spec" >&2
exit 2
fi
IFS=$'\t' read -r machine_label machine_target <<<"$parsed_spec"
if [[ "$seen_labels" == *"|${machine_label}|"* ]]; then
echo "Duplicate machine label: $machine_label" >&2
exit 2
fi
seen_labels+="${machine_label}|"
wrapper_path="$state_dir/machine-${machine_index}.sh"
layout_path="$state_dir/machine-${machine_index}.kdl"
wrapper_cmd=(
"$script_dir/zellij-ai-remote-tab.sh"
--label "$machine_label"
--target "$machine_target"
--session "$remote_session"
--repo-dir "$remote_repo_dir"
)
if [[ -n "$workspace" ]]; then
wrapper_cmd+=(--workspace "$workspace")
fi
if [[ -n "$codex_image" ]]; then
wrapper_cmd+=(--codex-image "$codex_image")
fi
if [[ -n "$claude_config_dir" ]]; then
wrapper_cmd+=(--claude-config "$claude_config_dir")
fi
if $remote_recreate; then
wrapper_cmd+=(--remote-recreate)
fi
printf -v wrapper_exec '%q ' "${wrapper_cmd[@]}"
wrapper_exec="${wrapper_exec% }"
cat >"$wrapper_path" <<EOF
#!/usr/bin/env bash
set -euo pipefail
exec $wrapper_exec
EOF
chmod +x "$wrapper_path"
wrapper_path_escaped="$(zellij_ai_kdl_escape "$wrapper_path")"
cat >"$layout_path" <<EOF
layout {
pane size=1 borderless=true {
plugin location="tab-bar"
}
pane command="$wrapper_path_escaped"
pane size=2 borderless=true {
plugin location="status-bar"
}
}
EOF
if [[ $machine_index -eq 0 ]]; then
first_label="$machine_label"
first_wrapper="$wrapper_path"
else
printf '%s\t%s\t%s\n' "$machine_label" "$layout_path" "$repo_dir" >>"$tabs_file"
fi
machine_index=$((machine_index + 1))
done
if [[ -z "$first_label" || -z "$first_wrapper" ]]; then
echo "No machine tabs were generated" >&2
exit 1
fi
cat >"$first_layout_file" <<EOF
layout {
pane size=1 borderless=true {
plugin location="tab-bar"
}
pane command="$bootstrap_path"
pane size=2 borderless=true {
plugin location="status-bar"
}
}
EOF
export ZELLIJ_AI_FLEET_TABS_FILE="$tabs_file"
export ZELLIJ_AI_FLEET_CREATED_FILE="$tabs_created_file"
export ZELLIJ_AI_FLEET_CURRENT_LABEL="$first_label"
export ZELLIJ_AI_FLEET_CURRENT_WRAPPER="$first_wrapper"
if $recreate && zellij_ai_session_exists "$local_session"; then
zellij kill-session "$local_session" >/dev/null 2>&1 || true
fi
if zellij_ai_session_exists "$local_session"; then
exec zellij attach "$local_session"
fi
exec zellij --new-session-with-layout "$first_layout_file" --session "$local_session"
export CONSTANT_SCRIPT_NAME="$(basename "$0")"
exec "$script_dir/constant-tmux-fleet.sh" "$@"
+14 -12
View File
@@ -8,8 +8,8 @@ while [[ -L "$script_source" ]]; do
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
if [[ -f "$script_dir/zellij-ai-common.sh" ]]; then
source "$script_dir/zellij-ai-common.sh"
if [[ -f "$script_dir/constant-common.sh" ]]; then
source "$script_dir/constant-common.sh"
else
zellij_ai_default_repo_dir() {
printf '%s\n' '$HOME/constant'
@@ -273,7 +273,7 @@ run_check() {
print_status repo_dir "$repo_dir"
print_status codex_home "$codex_home"
for binary in bash git zellij node npm uv claude codex copilot vibe; do
for binary in bash git tmux node npm uv claude codex copilot vibe; do
if command -v "$binary" >/dev/null 2>&1; then
print_status "$binary" "$(command -v "$binary")"
else
@@ -310,7 +310,7 @@ run_check() {
install_darwin() {
ensure_homebrew
brew install git zellij node uv
brew install git tmux node uv
}
install_linux_ubuntu() {
@@ -325,12 +325,8 @@ install_linux_ubuntu() {
run_root apt-get install -y npm
fi
if ! command -v zellij >/dev/null 2>&1; then
if command -v snap >/dev/null 2>&1; then
run_root snap install zellij --classic
else
run_root apt-get install -y zellij
fi
if ! command -v tmux >/dev/null 2>&1; then
run_root apt-get install -y tmux
fi
}
@@ -354,8 +350,14 @@ install_linux_photon() {
run_root tdnf install -y "${packages[@]}"
fi
if ! command -v zellij >/dev/null 2>&1; then
fail "zellij is missing on Photon OS and no native install path is scripted here."
if ! command -v tmux >/dev/null 2>&1; then
if command -v tdnf >/dev/null 2>&1; then
run_root tdnf install -y tmux
fi
fi
if ! command -v tmux >/dev/null 2>&1; then
fail "tmux is missing on Photon OS and no native install path is scripted here."
fi
}
+6 -5
View File
@@ -8,22 +8,23 @@ while [[ -L "$script_source" ]]; do
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
source "$script_dir/zellij-ai-common.sh"
source "$script_dir/constant-common.sh"
warn_deprecated() {
printf 'Warning: %s\n' "$*" >&2
}
usage() {
cat <<'EOF'
Usage: zellij-ai-remote-tab.sh [options]
local script_name="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
cat <<EOF
Usage: ${script_name} [options]
Open or attach the local AI Zellij session for one machine.
Open or attach the local Constant machine session for one machine.
Options:
--label NAME Display label for logs and tab naming
--target TARGET SSH target, or "local" for the current machine
--session NAME Remote/local Zellij session name
--session NAME Remote/local Constant tmux session name
default: constant
--repo-dir DIR Repository path on the target machine
default: $HOME/constant
+2 -226
View File
@@ -8,230 +8,6 @@ while [[ -L "$script_source" ]]; do
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
repo_dir="$(cd "$script_dir/.." && pwd -P)"
source "$script_dir/zellij-ai-common.sh"
usage() {
local script_name="${CONSTANT_SCRIPT_NAME:-$(basename "$0")}"
cat <<EOF
Usage: ${script_name} [options]
Start a 4-pane Constant machine session:
- left pane: Claude on the host
- top-right pane: Codex on the host
- middle-right pane: Copilot CLI on the host
- bottom-right pane: Mistral Vibe on the host
Options:
--workspace DIR Workspace used by all panes
default: current directory
--session NAME Session name
default: $(zellij_ai_default_session)
--codex-home DIR Persistent CODEX_HOME for the Codex pane
default: $(zellij_ai_default_codex_home)
--codex-label LABEL Display label for the Codex pane
default: codex
--copilot-label LABEL Display label for the Copilot pane
default: copilot
--vibe-label LABEL Display label for the Vibe pane
default: vibe
--claude-config DIR Optional CLAUDE_CONFIG_DIR override for the Claude pane
--zellij-config-dir DIR
Optional isolated Zellij config dir for session creation
default: a fresh temporary config dir per new session
--codex-image IMAGE Deprecated, ignored
--codex1-home DIR Deprecated alias for --codex-home
--codex2-home DIR Deprecated, ignored
--codex1-label LABEL Deprecated alias for --codex-label
--codex2-label LABEL Deprecated, ignored
--recreate Recreate the session and purge stale Zellij state first
-h, --help Show this help
EOF
}
warn_deprecated() {
printf 'Warning: %s\n' "$*" >&2
}
workspace="$PWD"
session="$(zellij_ai_default_session)"
codex_home="$(zellij_ai_default_codex_home)"
codex_label="codex"
copilot_label="copilot"
vibe_label="vibe"
claude_config_dir=""
zellij_config_dir=""
recreate=false
while [[ $# -gt 0 ]]; do
case "$1" in
--workspace)
workspace="$2"
shift 2
;;
--session)
session="$2"
shift 2
;;
--codex-home)
codex_home="$2"
shift 2
;;
--codex-label)
codex_label="$2"
shift 2
;;
--copilot-label)
copilot_label="$2"
shift 2
;;
--vibe-label)
vibe_label="$2"
shift 2
;;
--codex-image)
warn_deprecated "--codex-image is deprecated and ignored; Codex now runs on the host."
shift 2
;;
--codex1-home)
warn_deprecated "--codex1-home is deprecated; use --codex-home."
codex_home="$2"
shift 2
;;
--codex2-home)
warn_deprecated "--codex2-home is deprecated and ignored."
shift 2
;;
--codex1-label)
warn_deprecated "--codex1-label is deprecated; use --codex-label."
codex_label="$2"
shift 2
;;
--codex2-label)
warn_deprecated "--codex2-label is deprecated and ignored."
shift 2
;;
--claude-config)
claude_config_dir="$2"
shift 2
;;
--zellij-config-dir)
zellij_config_dir="$2"
shift 2
;;
--recreate)
recreate=true
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown option: $1" >&2
usage >&2
exit 2
;;
esac
done
workspace="$(cd "$workspace" && pwd -P)"
codex_home="$(mkdir -p "$(zellij_ai_expand_home_path "$codex_home")" && cd "$(zellij_ai_expand_home_path "$codex_home")" && pwd -P)"
machine_name="$(zellij_ai_current_machine_name)"
state_dir="$(zellij_ai_session_state_dir "$session")"
bus_dir="$state_dir/bus"
bootstrap_file="$state_dir/bootstrapped"
layout_file="$state_dir/layout.kdl"
clipboard_config_file="$state_dir/zellij.config.kdl"
if [[ ! -d "$workspace" ]]; then
echo "Workspace not found: $workspace" >&2
exit 1
fi
for required in \
"$script_dir/zellij-ai-claude-pane.sh" \
"$script_dir/zellij-ai-codex-pane.sh" \
"$script_dir/zellij-ai-copilot-pane.sh" \
"$script_dir/zellij-ai-vibe-pane.sh" \
"$script_dir/zellij-ai-bootstrap.sh" \
"$script_dir/zellij-ai-common.sh"
do
if [[ ! -x "$required" ]]; then
echo "Helper script not executable: $required" >&2
exit 1
fi
done
mkdir -p "$bus_dir/messages"
if [[ "${ZELLIJ_AI_FORCE_OSC52:-false}" == "true" || "${ZELLIJ_AI_FORCE_OSC52:-0}" == "1" ]]; then
cat >"$clipboard_config_file" <<'EOF'
copy_clipboard "system"
EOF
elif [[ "$(uname -s)" == "Darwin" && -x /usr/bin/pbcopy ]]; then
cat >"$clipboard_config_file" <<'EOF'
copy_command "pbcopy"
copy_clipboard "system"
EOF
else
cat >"$clipboard_config_file" <<'EOF'
copy_clipboard "system"
EOF
fi
export ZELLIJ_AI_WORKSPACE="$workspace"
export ZELLIJ_AI_CODEX_HOME="$codex_home"
export ZELLIJ_AI_CODEX_LABEL="$codex_label"
export ZELLIJ_AI_COPILOT_LABEL="$copilot_label"
export ZELLIJ_AI_VIBE_LABEL="$vibe_label"
export ZELLIJ_AI_MAIN_CODEX_CONFIG="$HOME/.codex/config.toml"
export ZELLIJ_AI_BOOTSTRAP_FILE="$bootstrap_file"
export ZELLIJ_AI_REPO_DIR="$repo_dir"
export ZELLIJ_AI_SESSION="$session"
export ZELLIJ_AI_STATE_DIR="$state_dir"
export ZELLIJ_AI_BUS_DIR="$bus_dir"
export ZELLIJ_AI_MACHINE_NAME="$machine_name"
if [[ -n "$claude_config_dir" ]]; then
export ZELLIJ_AI_CLAUDE_CONFIG_DIR="$claude_config_dir"
fi
zellij_attach_cmd=(zellij --config "$clipboard_config_file")
if $recreate && zellij_ai_session_exists "$session"; then
zellij delete-session -f "$session" >/dev/null 2>&1 || true
fi
if zellij_ai_session_exists "$session"; then
exec "${zellij_attach_cmd[@]}" attach "$session"
fi
zellij delete-session "$session" >/dev/null 2>&1 || true
mkdir -p "$state_dir"
rm -f "$bootstrap_file"
bootstrap_path="$(zellij_ai_kdl_escape "$script_dir/zellij-ai-bootstrap.sh")"
cat >"$layout_file" <<EOF
layout {
tab name="Constant" {
pane size=1 borderless=true {
plugin location="tab-bar"
}
pane command="$bootstrap_path"
pane size=2 borderless=true {
plugin location="status-bar"
}
}
}
EOF
if [[ -z "$zellij_config_dir" ]]; then
zellij_config_dir="$(mktemp -d "${TMPDIR:-/tmp}/constant-zellij-${session}.XXXXXX")"
else
zellij_config_dir="$(zellij_ai_expand_home_path "$zellij_config_dir")"
mkdir -p "$zellij_config_dir"
fi
exec zellij --config "$clipboard_config_file" --config-dir "$zellij_config_dir" --new-session-with-layout "$layout_file" --session "$session"
export CONSTANT_SCRIPT_NAME="$(basename "$0")"
exec "$script_dir/constant-tmux-machine.sh" "$@"
+1 -1
View File
@@ -8,7 +8,7 @@ while [[ -L "$script_source" ]]; do
[[ "$script_source" != /* ]] && script_source="$script_dir/$script_source"
done
script_dir="$(cd "$(dirname "$script_source")" && pwd -P)"
source "$script_dir/zellij-ai-common.sh"
source "$script_dir/constant-common.sh"
workspace="${ZELLIJ_AI_WORKSPACE:-$PWD}"
machine_name="${ZELLIJ_AI_MACHINE_NAME:-unknown}"