ghost-tab 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +151 -0
  2. package/VERSION +1 -0
  3. package/bin/ghost-tab +360 -0
  4. package/bin/npx-ghost-tab.js +141 -0
  5. package/ghostty/config +5 -0
  6. package/lib/ai-select-tui.sh +66 -0
  7. package/lib/ai-tools.sh +19 -0
  8. package/lib/config-tui.sh +95 -0
  9. package/lib/ghostty-config.sh +26 -0
  10. package/lib/input.sh +39 -0
  11. package/lib/install.sh +224 -0
  12. package/lib/loading.sh +190 -0
  13. package/lib/menu-tui.sh +189 -0
  14. package/lib/notification-setup.sh +210 -0
  15. package/lib/process.sh +13 -0
  16. package/lib/project-actions-tui.sh +29 -0
  17. package/lib/project-actions.sh +9 -0
  18. package/lib/projects.sh +18 -0
  19. package/lib/settings-json.sh +178 -0
  20. package/lib/settings-menu-tui.sh +32 -0
  21. package/lib/setup.sh +16 -0
  22. package/lib/statusline-setup.sh +60 -0
  23. package/lib/statusline.sh +31 -0
  24. package/lib/tab-title-watcher.sh +118 -0
  25. package/lib/terminal-select-tui.sh +119 -0
  26. package/lib/terminals/adapter.sh +19 -0
  27. package/lib/terminals/ghostty.sh +58 -0
  28. package/lib/terminals/iterm2.sh +51 -0
  29. package/lib/terminals/kitty.sh +40 -0
  30. package/lib/terminals/registry.sh +37 -0
  31. package/lib/terminals/wezterm.sh +50 -0
  32. package/lib/tmux-session.sh +49 -0
  33. package/lib/tui.sh +80 -0
  34. package/lib/update.sh +52 -0
  35. package/package.json +42 -0
  36. package/templates/ccstatusline-settings.json +29 -0
  37. package/templates/statusline-command.sh +30 -0
  38. package/templates/statusline-wrapper.sh +40 -0
  39. package/terminals/ghostty/config +5 -0
  40. package/terminals/kitty/config +1 -0
  41. package/terminals/wezterm/config.lua +4 -0
  42. package/wrapper.sh +222 -0
package/lib/tui.sh ADDED
@@ -0,0 +1,80 @@
1
+ #!/bin/bash
2
+ # Shared TUI helpers — colors, logging, cursor utilities.
3
+
4
+ # Basic colors (with terminal capability fallback)
5
+ if [ -t 1 ] && [ "$(tput colors 2>/dev/null)" -ge 8 ] 2>/dev/null; then
6
+ _GREEN='\033[0;32m'
7
+ _YELLOW='\033[0;33m'
8
+ _RED='\033[0;31m'
9
+ _BLUE='\033[0;34m'
10
+ _BOLD='\033[1m'
11
+ _NC='\033[0m'
12
+ else
13
+ _GREEN='' _YELLOW='' _RED='' _BLUE='' _BOLD='' _NC=''
14
+ fi
15
+
16
+ # Logging helpers
17
+ success() { echo -e "${_GREEN}✓${_NC} $1"; }
18
+ warn() { echo -e "${_YELLOW}!${_NC} $1"; }
19
+ error() { echo -e "${_RED}✗${_NC} $1"; }
20
+ info() { echo -e "${_BLUE}→${_NC} $1"; }
21
+ header() { echo -e "\n${_BOLD}$1${_NC}"; }
22
+
23
+ # Set terminal/tab title. With tool: "project · tool", without: "project"
24
+ set_tab_title() {
25
+ local project="$1"
26
+ local tool="${2:-}"
27
+ if [ -n "$tool" ]; then
28
+ printf '\033]0;%s · %s\007' "$project" "$tool"
29
+ else
30
+ printf '\033]0;%s\007' "$project"
31
+ fi
32
+ }
33
+
34
+ # Set terminal/tab title with ● prefix (waiting state).
35
+ # Same format as set_tab_title but prepends "● ".
36
+ set_tab_title_waiting() {
37
+ local project="$1"
38
+ local tool="${2:-}"
39
+ if [ -n "$tool" ]; then
40
+ printf '\033]0;● %s · %s\007' "$project" "$tool"
41
+ else
42
+ printf '\033]0;● %s\007' "$project"
43
+ fi
44
+ }
45
+
46
+ # Extended TUI variables for interactive full-screen UIs.
47
+ # Call this before using any of the extended variables.
48
+ tui_init_interactive() {
49
+ _CYAN=$'\033[0;36m'
50
+ _DIM=$'\033[2m'
51
+ _INVERSE=$'\033[7m'
52
+ _BG_BLUE=$'\033[48;5;27m'
53
+ _BG_RED=$'\033[48;5;160m'
54
+ _WHITE=$'\033[1;37m'
55
+ _HIDE_CURSOR=$'\033[?25l'
56
+ _SHOW_CURSOR=$'\033[?25h'
57
+ _MOUSE_ON=$'\033[?1000h\033[?1006h'
58
+ _MOUSE_OFF=$'\033[?1000l\033[?1006l'
59
+ }
60
+
61
+ # Move cursor to row;col
62
+ moveto() { printf '\033[%d;%dH' "$1" "$2"; }
63
+
64
+ # Print N spaces
65
+ pad() { printf "%*s" "$1" ""; }
66
+
67
+ # Draw logo using ghost-tab-tui binary
68
+ # Usage: draw_logo tool_name [row] [col]
69
+ # If row/col provided, they are ignored (for compatibility)
70
+ draw_logo() {
71
+ # shellcheck disable=SC2034 # tool parameter reserved for future use
72
+ local tool="$1"
73
+
74
+ if command -v ghost-tab-tui &>/dev/null; then
75
+ ghost-tab-tui show-logo 2>/dev/null || true
76
+ else
77
+ # Fallback: no logo if binary missing
78
+ return 0
79
+ fi
80
+ }
package/lib/update.sh ADDED
@@ -0,0 +1,52 @@
1
+ #!/bin/bash
2
+ # Git-based auto-update for ghost-tab.
3
+
4
+ # Show update notification if a previous background update wrote a flag file.
5
+ # Deletes the flag after displaying.
6
+ notify_if_updated() {
7
+ local config_home="${XDG_CONFIG_HOME:-$HOME/.config}"
8
+ local flag="${config_home}/ghost-tab/updated"
9
+ [ -f "$flag" ] || return 0
10
+
11
+ local version
12
+ version="$(cat "$flag")"
13
+ rm -f "$flag"
14
+ echo " ↑ Updated to v${version}"
15
+ }
16
+
17
+ # Run a background git fetch + pull in share_dir.
18
+ # If a new version is pulled, downloads the ghost-tab-tui binary and writes a flag file.
19
+ # Args: share_dir
20
+ check_for_update() {
21
+ local share_dir="$1"
22
+ local config_home="${XDG_CONFIG_HOME:-$HOME/.config}"
23
+ local flag="${config_home}/ghost-tab/updated"
24
+
25
+ # Only works if share_dir is a git repo
26
+ [ -d "$share_dir/.git" ] || return 0
27
+
28
+ (
29
+ local local_ref remote_ref
30
+ git -C "$share_dir" fetch origin main --quiet 2>/dev/null || return
31
+
32
+ local_ref="$(git -C "$share_dir" rev-parse HEAD 2>/dev/null)"
33
+ remote_ref="$(git -C "$share_dir" rev-parse origin/main 2>/dev/null)"
34
+ [ "$local_ref" = "$remote_ref" ] && return
35
+
36
+ git -C "$share_dir" pull --rebase --quiet origin main 2>/dev/null || return
37
+
38
+ local new_version arch
39
+ new_version="$(tr -d '[:space:]' < "$share_dir/VERSION" 2>/dev/null)" || return
40
+ [ -n "$new_version" ] || return
41
+
42
+ arch="$(uname -m)"
43
+ local bin_url="https://github.com/JackUait/ghost-tab/releases/download/v${new_version}/ghost-tab-tui-darwin-${arch}"
44
+ mkdir -p "$HOME/.local/bin"
45
+ curl -fsSL -o "$HOME/.local/bin/ghost-tab-tui" "$bin_url" 2>/dev/null && \
46
+ chmod +x "$HOME/.local/bin/ghost-tab-tui" || true
47
+
48
+ mkdir -p "${config_home}/ghost-tab"
49
+ echo "$new_version" > "$flag"
50
+ ) &
51
+ disown
52
+ }
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "ghost-tab",
3
+ "version": "2.6.0",
4
+ "description": "Terminal + tmux wrapper for AI coding tools (Claude Code, Codex CLI, Copilot CLI, OpenCode)",
5
+ "bin": {
6
+ "ghost-tab": "bin/npx-ghost-tab.js"
7
+ },
8
+ "files": [
9
+ "bin/ghost-tab",
10
+ "bin/npx-ghost-tab.js",
11
+ "lib/",
12
+ "templates/",
13
+ "ghostty/",
14
+ "terminals/",
15
+ "wrapper.sh",
16
+ "VERSION"
17
+ ],
18
+ "os": [
19
+ "darwin"
20
+ ],
21
+ "keywords": [
22
+ "ghost-tab",
23
+ "tmux",
24
+ "terminal",
25
+ "claude",
26
+ "codex",
27
+ "copilot",
28
+ "opencode",
29
+ "ai",
30
+ "coding"
31
+ ],
32
+ "author": "JackUait",
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/JackUait/ghost-tab.git"
37
+ },
38
+ "homepage": "https://github.com/JackUait/ghost-tab",
39
+ "engines": {
40
+ "node": ">=16"
41
+ }
42
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "version": 3,
3
+ "lines": [
4
+ [
5
+ {
6
+ "id": "1",
7
+ "type": "context-percentage",
8
+ "color": "yellow",
9
+ "bold": true,
10
+ "rawValue": true
11
+ }
12
+ ],
13
+ [],
14
+ []
15
+ ],
16
+ "flexMode": "full-minus-40",
17
+ "compactThreshold": 60,
18
+ "colorLevel": 2,
19
+ "inheritSeparatorColors": false,
20
+ "globalBold": false,
21
+ "powerline": {
22
+ "enabled": false,
23
+ "separators": [""],
24
+ "separatorInvertBackground": [false],
25
+ "startCaps": [],
26
+ "endCaps": [],
27
+ "autoAlign": false
28
+ }
29
+ }
@@ -0,0 +1,30 @@
1
+ #!/bin/bash
2
+ input=$(cat)
3
+ cwd=$(echo "$input" | sed -n 's/.*"current_dir":"\([^"]*\)".*/\1/p')
4
+
5
+ if git -C "$cwd" rev-parse --git-dir > /dev/null 2>&1; then
6
+ repo_name=$(basename "$cwd")
7
+ branch=$(git -C "$cwd" --no-optional-locks rev-parse --abbrev-ref HEAD 2>/dev/null)
8
+
9
+ # Session line diff: compare working tree against baseline SHA
10
+ baseline_file="${GHOST_TAB_BASELINE_FILE:-}"
11
+ if [ -n "$baseline_file" ] && [ -f "$baseline_file" ]; then
12
+ baseline_sha=$(head -1 "$baseline_file" 2>/dev/null)
13
+ if [ -n "$baseline_sha" ]; then
14
+ diff_stats=$(git -C "$cwd" --no-optional-locks diff "$baseline_sha" --numstat 2>/dev/null \
15
+ | awk '{a+=$1; d+=$2} END {print a+0, d+0}')
16
+ added=$(echo "$diff_stats" | cut -d' ' -f1)
17
+ deleted=$(echo "$diff_stats" | cut -d' ' -f2)
18
+ fi
19
+ fi
20
+
21
+ if [ -n "${added:-}" ]; then
22
+ printf '\033[01;36m%s\033[00m | \033[01;32m%s\033[00m | \033[01;32m+%s\033[00m / \033[01;31m-%s\033[00m' \
23
+ "$repo_name" "$branch" "$added" "$deleted"
24
+ else
25
+ printf '\033[01;36m%s\033[00m | \033[01;32m%s\033[00m' \
26
+ "$repo_name" "$branch"
27
+ fi
28
+ else
29
+ printf '\033[01;36m%s\033[00m' "$(basename "$cwd")"
30
+ fi
@@ -0,0 +1,40 @@
1
+ #!/bin/bash
2
+ # shellcheck source=../lib/statusline.sh
3
+ source "$(dirname "$0")/../lib/statusline.sh" 2>/dev/null \
4
+ || source ~/.claude/statusline-helpers.sh 2>/dev/null \
5
+ || true
6
+
7
+ input=$(cat)
8
+ git_info=$(echo "$input" | bash ~/.claude/statusline-command.sh)
9
+ context_pct=$(echo "$input" | npx ccstatusline 2>/dev/null)
10
+
11
+ # Find parent Claude Code process and get total tree memory usage
12
+ pid=$PPID
13
+ mem_label=""
14
+ while [ -n "$pid" ] && [ "$pid" != "1" ]; do
15
+ comm=$(ps -o comm= -p "$pid" 2>/dev/null | xargs basename 2>/dev/null)
16
+ if [ "$comm" = "claude" ]; then
17
+ if type get_tree_rss_kb &>/dev/null; then
18
+ mem_kb=$(get_tree_rss_kb "$pid")
19
+ else
20
+ mem_kb=$(ps -o rss= -p "$pid" 2>/dev/null | tr -d ' ')
21
+ fi
22
+ if [ -n "$mem_kb" ] && [ "$mem_kb" -gt 0 ] 2>/dev/null; then
23
+ mem_mb=$((mem_kb / 1024))
24
+ if [ "$mem_mb" -ge 1024 ]; then
25
+ mem_gb=$(echo "scale=1; $mem_mb / 1024" | bc)
26
+ mem_label="${mem_gb}G"
27
+ else
28
+ mem_label="${mem_mb}M"
29
+ fi
30
+ fi
31
+ break
32
+ fi
33
+ pid=$(ps -o ppid= -p "$pid" 2>/dev/null | tr -d ' ')
34
+ done
35
+
36
+ if [ -n "$mem_label" ]; then
37
+ printf '%s | %s | \033[01;35m%s\033[00m' "$git_info" "$context_pct" "$mem_label"
38
+ else
39
+ printf '%s | %s' "$git_info" "$context_pct"
40
+ fi
@@ -0,0 +1,5 @@
1
+ keybind = cmd+shift+left=previous_tab
2
+ keybind = cmd+shift+right=next_tab
3
+ keybind = cmd+t=new_tab
4
+ macos-option-as-alt = left
5
+ command = ~/.config/ghost-tab/wrapper.sh
@@ -0,0 +1 @@
1
+ shell ~/.config/ghost-tab/wrapper.sh
@@ -0,0 +1,4 @@
1
+ local wezterm = require 'wezterm'
2
+ local config = wezterm.config_builder()
3
+ config.default_prog = { '~/.config/ghost-tab/wrapper.sh' }
4
+ return config
package/wrapper.sh ADDED
@@ -0,0 +1,222 @@
1
+ #!/bin/bash
2
+ export PATH="$HOME/.local/bin:/opt/homebrew/bin:/usr/local/bin:$PATH"
3
+
4
+ SHARE_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/ghost-tab"
5
+
6
+ # shellcheck source=/dev/null
7
+ [ -f "$SHARE_DIR/lib/update.sh" ] && source "$SHARE_DIR/lib/update.sh"
8
+
9
+ notify_if_updated
10
+ check_for_update "$SHARE_DIR"
11
+
12
+ # Show animated loading screen immediately in interactive mode (no args)
13
+ _wrapper_dir_early="$(cd "$(dirname "$0")" && pwd)"
14
+ if [ -z "$1" ] && [ -f "$_wrapper_dir_early/lib/loading.sh" ]; then
15
+ # shellcheck disable=SC1091 # Dynamic path
16
+ source "$_wrapper_dir_early/lib/loading.sh"
17
+ # Mirrors AI_TOOL_PREF_FILE (defined after libs load); duplicated here because loading.sh runs before modules
18
+ _ai_tool="$(cat "${XDG_CONFIG_HOME:-$HOME/.config}/ghost-tab/ai-tool" 2>/dev/null | tr -d '[:space:]')"
19
+ show_loading_screen "${_ai_tool:-}"
20
+ fi
21
+
22
+ # Check if ghost-tab-tui binary is available
23
+ if ! command -v ghost-tab-tui &>/dev/null; then
24
+ printf '\033[31mError:\033[0m ghost-tab-tui binary not found.\n' >&2
25
+ printf 'Run \033[1mghost-tab\033[0m to reinstall.\n' >&2
26
+ printf 'Press any key to exit...\n' >&2
27
+ read -rsn1
28
+ exit 1
29
+ fi
30
+
31
+ # Load shared library functions
32
+ _WRAPPER_DIR="$(cd "$(dirname "$0")" && pwd)"
33
+
34
+ if [ ! -d "$_WRAPPER_DIR/lib" ]; then
35
+ printf '\033[31mError:\033[0m Ghost Tab libraries not found at %s/lib\n' "$_WRAPPER_DIR" >&2
36
+ printf 'Run \033[1mghost-tab\033[0m to reinstall.\n' >&2
37
+ printf 'Press any key to exit...\n' >&2
38
+ read -rsn1
39
+ exit 1
40
+ fi
41
+
42
+ _gt_libs=(ai-tools projects process input tui menu-tui project-actions project-actions-tui tmux-session settings-menu-tui settings-json notification-setup tab-title-watcher)
43
+ for _gt_lib in "${_gt_libs[@]}"; do
44
+ if [ ! -f "$_WRAPPER_DIR/lib/${_gt_lib}.sh" ]; then
45
+ printf '\033[31mError:\033[0m Missing library %s/lib/%s.sh\n' "$_WRAPPER_DIR" "$_gt_lib" >&2
46
+ printf 'Run \033[1mghost-tab\033[0m to reinstall.\n' >&2
47
+ printf 'Press any key to exit...\n' >&2
48
+ read -rsn1
49
+ exit 1
50
+ fi
51
+ # shellcheck disable=SC1090 # Dynamic module loading
52
+ source "$_WRAPPER_DIR/lib/${_gt_lib}.sh"
53
+ done
54
+ unset _gt_libs _gt_lib
55
+
56
+ TMUX_CMD="$(command -v tmux)"
57
+ LAZYGIT_CMD="$(command -v lazygit)"
58
+ BROOT_CMD="$(command -v broot)"
59
+ CLAUDE_CMD="$(command -v claude)"
60
+ CODEX_CMD="$(command -v codex)"
61
+ COPILOT_CMD="$(command -v copilot)"
62
+ OPENCODE_CMD="$(command -v opencode)"
63
+
64
+ # AI tool preference
65
+ AI_TOOL_PREF_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/ghost-tab/ai-tool"
66
+ AI_TOOLS_AVAILABLE=()
67
+ [ -n "$CLAUDE_CMD" ] && AI_TOOLS_AVAILABLE+=("claude")
68
+ [ -n "$CODEX_CMD" ] && AI_TOOLS_AVAILABLE+=("codex")
69
+ [ -n "$COPILOT_CMD" ] && AI_TOOLS_AVAILABLE+=("copilot")
70
+ [ -n "$OPENCODE_CMD" ] && AI_TOOLS_AVAILABLE+=("opencode")
71
+
72
+ # Read saved preference, default to first available
73
+ SELECTED_AI_TOOL=""
74
+ if [ -f "$AI_TOOL_PREF_FILE" ]; then
75
+ SELECTED_AI_TOOL="$(cat "$AI_TOOL_PREF_FILE" 2>/dev/null | tr -d '[:space:]')"
76
+ fi
77
+ # Validate saved preference is still installed
78
+ validate_ai_tool "$AI_TOOL_PREF_FILE"
79
+
80
+ # Load user projects from config file if it exists
81
+ PROJECTS_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/ghost-tab/projects"
82
+
83
+ # Select working directory
84
+ if [ -n "$1" ] && [ -d "$1" ]; then
85
+ cd "$1" || exit 1
86
+ shift
87
+ elif [ -z "$1" ]; then
88
+ # Use TUI for project selection
89
+ printf '\033]0;👻 Ghost Tab\007'
90
+
91
+ # Stop loading animation before TUI takes over
92
+ type stop_loading_screen &>/dev/null && stop_loading_screen
93
+
94
+ while true; do
95
+ if select_project_interactive "$PROJECTS_FILE"; then
96
+ # Update AI tool if user cycled it in the menu (for all actions)
97
+ if [[ -n "${_selected_ai_tool:-}" ]]; then
98
+ SELECTED_AI_TOOL="$_selected_ai_tool"
99
+ fi
100
+ # shellcheck disable=SC2154
101
+ case "$_selected_project_action" in
102
+ select-project|open-once)
103
+ PROJECT_NAME="$_selected_project_name"
104
+ # shellcheck disable=SC2154
105
+ cd "$_selected_project_path" || exit 1
106
+ break
107
+ ;;
108
+ plain-terminal)
109
+ exec "$SHELL"
110
+ ;;
111
+ add-worktree)
112
+ # Loop back to menu — worktrees refresh on reload
113
+ continue
114
+ ;;
115
+ *)
116
+ # settings or unknown — loop back to menu
117
+ continue
118
+ ;;
119
+ esac
120
+ else
121
+ # User quit (ESC/Ctrl-C)
122
+ exit 0
123
+ fi
124
+ done
125
+ fi
126
+
127
+ PROJECT_DIR="$(pwd)"
128
+ export PROJECT_DIR
129
+ export PROJECT_NAME="${PROJECT_NAME:-$(basename "$PROJECT_DIR")}"
130
+ SESSION_NAME="dev-${PROJECT_NAME}-$$"
131
+
132
+ # Capture session baseline for line diff tracking
133
+ GHOST_TAB_BASELINE_FILE="/tmp/ghost-tab-baseline-${SESSION_NAME}"
134
+ if git rev-parse --git-dir > /dev/null 2>&1; then
135
+ git rev-parse HEAD > "$GHOST_TAB_BASELINE_FILE" 2>/dev/null
136
+ fi
137
+
138
+ # Set terminal/tab title based on tab_title setting
139
+ _tab_title_setting="full"
140
+ _settings_file="${XDG_CONFIG_HOME:-$HOME/.config}/ghost-tab/settings"
141
+ if [ -f "$_settings_file" ]; then
142
+ _saved_tab_title=$(grep '^tab_title=' "$_settings_file" 2>/dev/null | cut -d= -f2)
143
+ if [ -n "$_saved_tab_title" ]; then
144
+ _tab_title_setting="$_saved_tab_title"
145
+ fi
146
+ fi
147
+ if [ "$_tab_title_setting" = "full" ]; then
148
+ set_tab_title "$PROJECT_NAME" "$SELECTED_AI_TOOL"
149
+ else
150
+ set_tab_title "$PROJECT_NAME"
151
+ fi
152
+
153
+ # Tab title waiting indicator
154
+ GHOST_TAB_MARKER_FILE="/tmp/ghost-tab-waiting-$$"
155
+ if [ "$SELECTED_AI_TOOL" = "claude" ]; then
156
+ _claude_settings="${HOME}/.claude/settings.json"
157
+ add_waiting_indicator_hooks "$_claude_settings" >/dev/null
158
+ fi
159
+
160
+ # Background watcher: switch to Claude pane once it's ready
161
+ (
162
+ while true; do
163
+ sleep 0.5
164
+ content=$("$TMUX_CMD" capture-pane -t "$SESSION_NAME:0.1" -p 2>/dev/null)
165
+ # All three tools show a prompt character when ready
166
+ if echo "$content" | grep -qE '[>$❯]'; then
167
+ "$TMUX_CMD" select-pane -t "$SESSION_NAME:0.1"
168
+ break
169
+ fi
170
+ done
171
+ ) &
172
+ WATCHER_PID=$!
173
+
174
+ cleanup() {
175
+ stop_tab_title_watcher "$GHOST_TAB_MARKER_FILE"
176
+ # Remove waiting indicator hooks if no other Ghost Tab sessions are running
177
+ if [ "$SELECTED_AI_TOOL" = "claude" ]; then
178
+ # Clean up orphaned markers from dead sessions (e.g., after SIGKILL)
179
+ for marker in /tmp/ghost-tab-waiting-*; do
180
+ [ -f "$marker" ] || continue
181
+ local pid="${marker##*-}"
182
+ if ! kill -0 "$pid" 2>/dev/null; then
183
+ rm -f "$marker"
184
+ fi
185
+ done
186
+ if ! ls /tmp/ghost-tab-waiting-* &>/dev/null; then
187
+ remove_waiting_indicator_hooks "${HOME}/.claude/settings.json" >/dev/null 2>&1 || true
188
+ fi
189
+ fi
190
+ cleanup_tmux_session "$SESSION_NAME" "$WATCHER_PID" "$TMUX_CMD"
191
+ rm -f "$GHOST_TAB_BASELINE_FILE"
192
+ }
193
+ trap cleanup EXIT HUP TERM INT
194
+
195
+ # Build the AI tool launch command
196
+ case "$SELECTED_AI_TOOL" in
197
+ codex|opencode)
198
+ AI_LAUNCH_CMD="$(build_ai_launch_cmd "$SELECTED_AI_TOOL" "$CLAUDE_CMD" "$CODEX_CMD" "$COPILOT_CMD" "$OPENCODE_CMD" "$PROJECT_DIR")"
199
+ ;;
200
+ *)
201
+ AI_LAUNCH_CMD="$(build_ai_launch_cmd "$SELECTED_AI_TOOL" "$CLAUDE_CMD" "$CODEX_CMD" "$COPILOT_CMD" "$OPENCODE_CMD" "$*")"
202
+ ;;
203
+ esac
204
+
205
+ # Start tab title watcher before tmux (which blocks until session ends)
206
+ start_tab_title_watcher "$SESSION_NAME" "$SELECTED_AI_TOOL" "$PROJECT_NAME" "$_tab_title_setting" "$TMUX_CMD" "$GHOST_TAB_MARKER_FILE" "${XDG_CONFIG_HOME:-$HOME/.config}/ghost-tab"
207
+
208
+ "$TMUX_CMD" new-session -s "$SESSION_NAME" -e "PATH=$PATH" -e "GHOST_TAB_BASELINE_FILE=$GHOST_TAB_BASELINE_FILE" -e "GHOST_TAB_MARKER_FILE=$GHOST_TAB_MARKER_FILE" -c "$PROJECT_DIR" \
209
+ "$LAZYGIT_CMD; exec bash" \; \
210
+ set-option status-left " ⬡ ${PROJECT_NAME} " \; \
211
+ set-option status-left-style "fg=white,bg=colour236,bold" \; \
212
+ set-option status-style "bg=colour235" \; \
213
+ set-option status-right "" \; \
214
+ set-option set-titles off \; \
215
+ set-option exit-unattached on \; \
216
+ split-window -h -p 50 -c "$PROJECT_DIR" \
217
+ "$AI_LAUNCH_CMD; exec bash" \; \
218
+ select-pane -t 0 \; \
219
+ split-window -v -p 50 -c "$PROJECT_DIR" \
220
+ "trap exit TERM; while true; do $BROOT_CMD $PROJECT_DIR; done" \; \
221
+ split-window -v -p 30 -c "$PROJECT_DIR" \; \
222
+ select-pane -t 3