Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion GhosttyTabs.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "set -euo pipefail\nDEST=\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}\"\nGHOSTTY_DEST=\"${DEST}/ghostty\"\nTERMINFO_DEST=\"${DEST}/terminfo\"\nCMUX_SHELL_DEST=\"${DEST}/shell-integration\"\nSRC_SHARE=\"${SRCROOT}/ghostty/zig-out/share\"\nGHOSTTY_SRC=\"${SRC_SHARE}/ghostty\"\nTERMINFO_SRC=\"${SRC_SHARE}/terminfo\"\nFALLBACK_GHOSTTY=\"${SRCROOT}/Resources/ghostty\"\nFALLBACK_TERMINFO=\"${SRCROOT}/Resources/ghostty/terminfo\"\nTERMINFO_OVERLAY=\"${SRCROOT}/Resources/terminfo-overlay\"\nCMUX_SHELL_SRC=\"${SRCROOT}/Resources/shell-integration\"\nif [ -d \"$GHOSTTY_SRC\" ]; then\n mkdir -p \"$GHOSTTY_DEST\"\n rsync -a --delete \"$GHOSTTY_SRC/\" \"$GHOSTTY_DEST/\"\nelif [ -d \"$FALLBACK_GHOSTTY\" ]; then\n mkdir -p \"$GHOSTTY_DEST\"\n rsync -a --delete \"$FALLBACK_GHOSTTY/\" \"$GHOSTTY_DEST/\"\nfi\nif [ -d \"$TERMINFO_SRC\" ]; then\n mkdir -p \"$TERMINFO_DEST\"\n rsync -a --delete \"$TERMINFO_SRC/\" \"$TERMINFO_DEST/\"\nelif [ -d \"$FALLBACK_TERMINFO\" ]; then\n mkdir -p \"$TERMINFO_DEST\"\n rsync -a --delete \"$FALLBACK_TERMINFO/\" \"$TERMINFO_DEST/\"\nfi\n# Overlay any cmux-specific terminfo adjustments.\n# This intentionally does not use --delete so we only patch specific entries.\nif [ -d \"$TERMINFO_OVERLAY\" ]; then\n mkdir -p \"$TERMINFO_DEST\"\n rsync -a \"$TERMINFO_OVERLAY/\" \"$TERMINFO_DEST/\"\nfi\nif [ -d \"$CMUX_SHELL_SRC\" ]; then\n mkdir -p \"$CMUX_SHELL_DEST\"\n # Use '/.' so dotfiles like .zshenv/.zprofile are copied too.\n rsync -a \"$CMUX_SHELL_SRC/.\" \"$CMUX_SHELL_DEST/\"\nfi\nINFO_PLIST=\"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}\"\nCOMMIT=\"$(git -C \"${SRCROOT}\" rev-parse --short=9 HEAD 2>/dev/null || true)\"\nif [ -n \"$COMMIT\" ] && [ -f \"$INFO_PLIST\" ]; then\n /usr/libexec/PlistBuddy -c \"Set :CMUXCommit $COMMIT\" \"$INFO_PLIST\" >/dev/null 2>&1 || /usr/libexec/PlistBuddy -c \"Add :CMUXCommit string $COMMIT\" \"$INFO_PLIST\" >/dev/null 2>&1 || true\nfi\n";
shellScript = "set -euo pipefail\nDEST=\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}\"\nGHOSTTY_DEST=\"${DEST}/ghostty\"\nTERMINFO_DEST=\"${DEST}/terminfo\"\nCMUX_SHELL_DEST=\"${DEST}/shell-integration\"\nSRC_SHARE=\"${SRCROOT}/ghostty/zig-out/share\"\nGHOSTTY_SRC=\"${SRC_SHARE}/ghostty\"\nTERMINFO_SRC=\"${SRC_SHARE}/terminfo\"\nFALLBACK_GHOSTTY=\"${SRCROOT}/Resources/ghostty\"\nFALLBACK_TERMINFO=\"${SRCROOT}/Resources/ghostty/terminfo\"\nTERMINFO_OVERLAY=\"${SRCROOT}/Resources/terminfo-overlay\"\nCMUX_SHELL_SRC=\"${SRCROOT}/Resources/shell-integration\"\nCMUX_GHOSTTY_ZSH_SRC=\"${SRCROOT}/ghostty/src/shell-integration/zsh/ghostty-integration\"\nif [ -d \"$GHOSTTY_SRC\" ]; then\n mkdir -p \"$GHOSTTY_DEST\"\n rsync -a --delete \"$GHOSTTY_SRC/\" \"$GHOSTTY_DEST/\"\nelif [ -d \"$FALLBACK_GHOSTTY\" ]; then\n mkdir -p \"$GHOSTTY_DEST\"\n rsync -a --delete \"$FALLBACK_GHOSTTY/\" \"$GHOSTTY_DEST/\"\nfi\nif [ -d \"$TERMINFO_SRC\" ]; then\n mkdir -p \"$TERMINFO_DEST\"\n rsync -a --delete \"$TERMINFO_SRC/\" \"$TERMINFO_DEST/\"\nelif [ -d \"$FALLBACK_TERMINFO\" ]; then\n mkdir -p \"$TERMINFO_DEST\"\n rsync -a --delete \"$FALLBACK_TERMINFO/\" \"$TERMINFO_DEST/\"\nfi\n# Overlay any cmux-specific terminfo adjustments.\n# This intentionally does not use --delete so we only patch specific entries.\nif [ -d \"$TERMINFO_OVERLAY\" ]; then\n mkdir -p \"$TERMINFO_DEST\"\n rsync -a \"$TERMINFO_OVERLAY/\" \"$TERMINFO_DEST/\"\nfi\nif [ -d \"$CMUX_SHELL_SRC\" ]; then\n mkdir -p \"$CMUX_SHELL_DEST\"\n # Use '/.' so dotfiles like .zshenv/.zprofile are copied too.\n rsync -a \"$CMUX_SHELL_SRC/.\" \"$CMUX_SHELL_DEST/\"\nfi\nif [ -f \"$CMUX_GHOSTTY_ZSH_SRC\" ]; then\n mkdir -p \"$CMUX_SHELL_DEST\"\n rsync -a \"$CMUX_GHOSTTY_ZSH_SRC\" \"$CMUX_SHELL_DEST/ghostty-integration.zsh\"\nfi\nINFO_PLIST=\"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}\"\nCOMMIT=\"$(git -C \"${SRCROOT}\" rev-parse --short=9 HEAD 2>/dev/null || true)\"\nif [ -n \"$COMMIT\" ] && [ -f \"$INFO_PLIST\" ]; then\n /usr/libexec/PlistBuddy -c \"Set :CMUXCommit $COMMIT\" \"$INFO_PLIST\" >/dev/null 2>&1 || /usr/libexec/PlistBuddy -c \"Add :CMUXCommit string $COMMIT\" \"$INFO_PLIST\" >/dev/null 2>&1 || true\nfi\n";
};
/* End PBXShellScriptBuildPhase section */

Expand Down
9 changes: 7 additions & 2 deletions Resources/shell-integration/.zshenv
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,13 @@ fi
#
# We can't rely on GHOSTTY_ZSH_ZDOTDIR here because Ghostty's own zsh
# bootstrap unsets it before chaining into this cmux wrapper.
if [[ "${CMUX_LOAD_GHOSTTY_ZSH_INTEGRATION:-0}" == "1" && -n "${GHOSTTY_RESOURCES_DIR:-}" ]]; then
builtin typeset _cmux_ghostty="$GHOSTTY_RESOURCES_DIR/shell-integration/zsh/ghostty-integration"
if [[ "${CMUX_LOAD_GHOSTTY_ZSH_INTEGRATION:-0}" == "1" ]]; then
if [[ -n "${CMUX_SHELL_INTEGRATION_DIR:-}" ]]; then
builtin typeset _cmux_ghostty="$CMUX_SHELL_INTEGRATION_DIR/ghostty-integration.zsh"
fi
if [[ ! -r "${_cmux_ghostty:-}" && -n "${GHOSTTY_RESOURCES_DIR:-}" ]]; then
builtin typeset _cmux_ghostty="$GHOSTTY_RESOURCES_DIR/shell-integration/zsh/ghostty-integration"
fi
[[ -r "$_cmux_ghostty" ]] && builtin source -- "$_cmux_ghostty"
fi

Expand Down
11 changes: 7 additions & 4 deletions docs/ghostty-fork.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,15 @@ touch the same stale-frame mitigation path and tend to conflict in the same file

### 6) zsh Pure-style multiline prompt redraws

- Commit: `0cf559581` (zsh: fix Pure-style multiline prompt redraws)
- Commits:
- `0cf559581` (zsh: fix Pure-style multiline prompt redraws)
- `312c7b23a` (zsh: avoid extra Pure continuation markers)
- Files:
- `src/shell-integration/zsh/ghostty-integration`
- Summary:
- Handles multiline prompts that use `\n%{\r%}` to return to column 0 before the visible prompt line.
- Places the continuation marker after Pure's hidden carriage return so async redraws do not leave stale preprompt lines behind.
- Keeps redraw-safe prompt-start markers for async themes.
- Avoids inserting an explicit continuation marker after Pure's hidden carriage return, because Ghostty already tracks the newline as prompt continuation and the extra marker duplicates the preprompt row.

The fork branch HEAD is now the section 6 zsh redraw commit.

Expand All @@ -103,7 +106,7 @@ These files change frequently upstream; be careful when rebasing the fork:

- `src/shell-integration/zsh/ghostty-integration`
- Prompt marker handling is easy to regress when upstream adjusts zsh redraw behavior. Keep the
`OSC 133;A` vs `OSC 133;P` split intact for redraw-heavy themes, and preserve the special
handling for Pure-style `\n%{\r%}` prompt newlines.
`OSC 133;A` vs `OSC 133;P` split intact for redraw-heavy themes. Pure-style `\n%{\r%}`
prompt newlines should not get an extra explicit continuation marker after the hidden CR.

If you resolve a conflict, update this doc with what changed.
2 changes: 1 addition & 1 deletion ghostty
1 change: 1 addition & 0 deletions scripts/ghosttykit-checksums.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
7dd589824d4c9bda8265355718800cccaf7189a0 3915af4256850a0a7bee671c3ba0a47cbfee5dbfc6d71caf952acefdf2ee4207
a50579bd5ddec81c6244b9b349d4bf781f667cec f7e9c0597468a263d6b75eaf815ccecd90c7933f3cf4ae58929569ff23b2666d
0cf5595817794466e3a60abe6bf97f8494dedcfe 1c6ae53ea549740bd45e59fe92714a292fb0d71a41ff915eb6b2e644468152de
312c7b23a7c8dc0704431940d76ba5dc32a46afb ae73cb18a9d6efec42126a1d99e0e9d12022403d7dc301dfa21ed9f7c89c9e30
189 changes: 189 additions & 0 deletions scripts/launch-tagged-automation.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
#!/usr/bin/env bash
set -euo pipefail

usage() {
cat <<'EOF'
Usage: ./scripts/launch-tagged-automation.sh <tag> [options]

Options:
--mode <mode> Socket mode override. Default: automation
--shell-log <path> Set GHOSTTY_ZSH_INTEGRATION_LOG for shells in the tagged app.
--wait-socket <s> Wait for the tagged socket to appear. Default: 10
--env KEY=VALUE Extra environment variable to inject at launch. Repeatable.
-h, --help Show this help.
EOF
}

sanitize_bundle() {
local raw="$1"
local cleaned
cleaned="$(echo "$raw" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/./g; s/^\\.+//; s/\\.+$//; s/\\.+/./g')"

@cubic-dev-ai cubic-dev-ai Bot Mar 13, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: sanitize_bundle over-escapes dot trimming regexes, so invalid dot-only bundle suffixes can be produced.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/launch-tagged-automation.sh, line 20:

<comment>`sanitize_bundle` over-escapes dot trimming regexes, so invalid dot-only bundle suffixes can be produced.</comment>

<file context>
@@ -0,0 +1,189 @@
+sanitize_bundle() {
+  local raw="$1"
+  local cleaned
+  cleaned="$(echo "$raw" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/./g; s/^\\.+//; s/\\.+$//; s/\\.+/./g')"
+  if [[ -z "$cleaned" ]]; then
+    cleaned="agent"
</file context>
Suggested change
cleaned="$(echo "$raw" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/./g; s/^\\.+//; s/\\.+$//; s/\\.+/./g')"
cleaned="$(echo "$raw" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/./g; s/^\.+//; s/\.+$//; s/\.+/./g')"
Fix with Cubic

if [[ -z "$cleaned" ]]; then
cleaned="agent"
fi
echo "$cleaned"
}

sanitize_path() {
local raw="$1"
local cleaned
cleaned="$(echo "$raw" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//; s/-+/-/g')"
if [[ -z "$cleaned" ]]; then
cleaned="agent"
fi
echo "$cleaned"
}

if [[ $# -lt 1 ]]; then
usage
exit 1
fi

TAG=""
MODE="automation"
SHELL_LOG=""
WAIT_SOCKET="10"
EXTRA_ENV=()

while [[ $# -gt 0 ]]; do
case "$1" in
--mode)
MODE="${2:-}"
if [[ -z "$MODE" ]]; then
echo "error: --mode requires a value" >&2
exit 1
fi
shift 2
;;
--env)
if [[ -z "${2:-}" ]]; then
echo "error: --env requires KEY=VALUE" >&2
exit 1
fi
EXTRA_ENV+=("${2}")
shift 2
;;
--shell-log)
SHELL_LOG="${2:-}"
if [[ -z "$SHELL_LOG" ]]; then
echo "error: --shell-log requires a path" >&2
exit 1
fi
shift 2
;;
--wait-socket)
WAIT_SOCKET="${2:-}"
if [[ -z "$WAIT_SOCKET" ]]; then
echo "error: --wait-socket requires seconds" >&2
exit 1
fi
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
if [[ -z "$TAG" ]]; then
TAG="$1"
shift
else
echo "error: unexpected argument $1" >&2
usage
exit 1
fi
;;
esac
done

if [[ -z "$TAG" ]]; then
echo "error: tag is required" >&2
usage
exit 1
fi

TAG_ID="$(sanitize_bundle "$TAG")"
TAG_SLUG="$(sanitize_path "$TAG")"
APP="$HOME/Library/Developer/Xcode/DerivedData/cmux-${TAG_SLUG}/Build/Products/Debug/cmux DEV ${TAG}.app"
BID="com.cmuxterm.app.debug.${TAG_ID}"
SOCK="/tmp/cmux-debug-${TAG_SLUG}.sock"
DSOCK="$HOME/Library/Application Support/cmux/cmuxd-dev-${TAG_SLUG}.sock"
LOG="/tmp/cmux-debug-${TAG_SLUG}.log"

if [[ ! -d "$APP" ]]; then
echo "error: tagged app not found at $APP" >&2
exit 1
fi

/usr/bin/osascript -e "tell application id \"${BID}\" to quit" >/dev/null 2>&1 || true
sleep 0.5
pkill -f "cmux DEV ${TAG}.app/Contents/MacOS/cmux DEV" || true
rm -f "$SOCK" "$DSOCK"
sleep 0.5

OPEN_ENV=(
env
-u CMUX_SOCKET_PATH
-u CMUX_SOCKET_MODE
-u CMUX_TAB_ID
-u CMUX_PANEL_ID
-u CMUX_SURFACE_ID
-u CMUX_WORKSPACE_ID
-u CMUXD_UNIX_PATH
-u CMUX_TAG
-u CMUX_PORT
-u CMUX_PORT_END
-u CMUX_PORT_RANGE
-u CMUX_DEBUG_LOG
-u CMUX_BUNDLE_ID
-u CMUX_SHELL_INTEGRATION
-u CMUX_SHELL_INTEGRATION_DIR
-u CMUX_LOAD_GHOSTTY_ZSH_INTEGRATION
-u GHOSTTY_BIN_DIR
-u GHOSTTY_RESOURCES_DIR
-u GHOSTTY_SHELL_FEATURES
-u GIT_PAGER
-u GH_PAGER
-u TERMINFO
-u XDG_DATA_DIRS
"CMUX_SOCKET_MODE=${MODE}"
"CMUX_SOCKET_PATH=${SOCK}"
"CMUXD_UNIX_PATH=${DSOCK}"
"CMUX_DEBUG_LOG=${LOG}"
)

for kv in "${EXTRA_ENV[@]}"; do
OPEN_ENV+=("${kv}")
done
if [[ -n "$SHELL_LOG" ]]; then
OPEN_ENV+=("GHOSTTY_ZSH_INTEGRATION_LOG=${SHELL_LOG}")
fi

"${OPEN_ENV[@]}" open -g "$APP"

if [[ "$WAIT_SOCKET" != "0" ]]; then
deadline=$((SECONDS + WAIT_SOCKET))
while (( SECONDS < deadline )); do
if [[ -S "$SOCK" ]]; then
break
fi
sleep 0.1
done
fi

echo "app: $APP"
echo "bundle_id: $BID"
echo "socket: $SOCK"
echo "cmuxd_socket: $DSOCK"
echo "log: $LOG"
echo "mode: $MODE"
echo "socket_ready: $(if [[ -S "$SOCK" ]]; then echo yes; else echo no; fi)"
if [[ -n "$SHELL_LOG" ]]; then
echo "shell_log: $SHELL_LOG"
fi
if [[ "${#EXTRA_ENV[@]}" -gt 0 ]]; then
echo "extra_env:"
for kv in "${EXTRA_ENV[@]}"; do
echo " $kv"
done
fi
Loading
Loading