Skip to content
Open
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
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ Once restarted, you'll see a confirmation message and notifications will appear

- [Warp terminal](https://warp.dev) (macOS, Linux, or Windows)
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) CLI
- `jq` for JSON parsing (install via `brew install jq` or your package manager)
- `jq` for JSON parsing — install via your package manager:
- macOS: `brew install jq`
- Linux: `sudo apt install jq` (Debian/Ubuntu) or `sudo dnf install jq` (Fedora)
- Windows: `scoop install jq` or `choco install jq`

## How It Works

Expand Down
8 changes: 8 additions & 0 deletions plugins/warp/scripts/build-payload.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@
# The function extracts common fields (session_id, cwd, project) from the
# hook's stdin JSON (passed as $1), then merges any extra jq args you pass.

# Bail out early if jq is not available — every path through this script
# requires it, and a missing jq produces confusing "command not found" errors
# in hook output that Claude Code surfaces as hook errors.
if ! command -v jq &>/dev/null; then
echo "jq is required but not found. Install it via your package manager." >&2
exit 1
fi

# The current protocol version this plugin knows how to produce.
PLUGIN_CURRENT_PROTOCOL_VERSION=1

Expand Down
20 changes: 17 additions & 3 deletions plugins/warp/scripts/emit-terminal-sequence.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@
# write to /dev/tty instead.
#
# Decision tree:
# 1. CLAUDE_CODE_VERSION known, >= 2.1.141 → emit terminalSequence JSON
# 2. CLAUDE_CODE_VERSION known, < 2.1.141 → write /dev/tty; give up if missing
# 3. CLAUDE_CODE_VERSION unknown → try /dev/tty, fall back to JSON
# 1. Windows (MSYS/MINGW/Cygwin) → always use terminalSequence JSON
# 2. CLAUDE_CODE_VERSION known, >= 2.1.141 → emit terminalSequence JSON
# 3. CLAUDE_CODE_VERSION known, < 2.1.141 → write /dev/tty; give up if missing
# 4. CLAUDE_CODE_VERSION unknown (non-Windows) → try /dev/tty, fall back to JSON
#
# Usage:
# source "$SCRIPT_DIR/emit-terminal-sequence.sh"
Expand All @@ -24,6 +25,13 @@
# The first Claude Code version that supports the terminalSequence output field.
TERMINAL_SEQUENCE_MIN_VERSION="2.1.141"

# Returns 0 (true) if running under Windows (MSYS, MINGW, or Cygwin).
_is_windows() {
local kernel
kernel="$(uname -s 2>/dev/null)"
[[ "$kernel" == MINGW* || "$kernel" == MSYS* || "$kernel" == CYGWIN* ]]
}

# Compare two dotted version strings (e.g. "2.1.141" >= "2.1.141").
# Returns 0 (true) if $1 >= $2, 1 (false) otherwise.
_version_at_least() {
Expand Down Expand Up @@ -59,6 +67,12 @@ emit_terminal_sequence() {
local seq="$1"
[ -z "$seq" ] && return 0

# Windows has no /dev/tty — always use the JSON terminalSequence path.
if _is_windows; then
jq -nc --arg seq "$seq" '{terminalSequence: $seq}'
return 0
fi

# Classify the running Claude Code version, if we can.
local raw="${CLAUDE_CODE_VERSION:-}"
local ver=""
Expand Down
52 changes: 52 additions & 0 deletions plugins/warp/tests/test-hooks.sh
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,58 @@ OUTPUT=$(emit_terminal_sequence "test-seq")
assert_json_field "new CC outputs terminalSequence" "$OUTPUT" ".terminalSequence" "test-seq"
unset CLAUDE_CODE_VERSION

echo ""
echo "--- _is_windows ---"

# _is_windows should return 0 on Windows (MSYS/MINGW/Cygwin), 1 otherwise.
# The test result depends on the host OS, so we assert against uname.
_kernel="$(uname -s 2>/dev/null)"
_is_windows
if [[ "$_kernel" == MINGW* || "$_kernel" == MSYS* || "$_kernel" == CYGWIN* ]]; then
assert_eq "Windows kernel returns true" "0" "$?"
else
assert_eq "non-Windows kernel returns false" "1" "$?"
fi

echo ""
echo "--- Windows: emit_terminal_sequence skips /dev/tty ---"

# On non-Windows we can't fully test the Windows branch, but we can verify
# that when _is_windows is true (mocked), the function outputs terminalSequence JSON
# instead of trying /dev/tty. We mock _is_windows by temporarily overriding it.
_is_windows_orig="$(declare -f _is_windows)"
_is_windows_mock() { return 0; }
# Replace the function
eval "_is_windows() { return 0; }"

unset CLAUDE_CODE_VERSION
OUTPUT=$(emit_terminal_sequence "win-test-seq" 2>&1)
assert_json_field "Windows outputs terminalSequence JSON" "$OUTPUT" ".terminalSequence" "win-test-seq"

# Also test with old CC version on Windows — should still use JSON, not /dev/tty
export CLAUDE_CODE_VERSION="2.1.100"
OUTPUT=$(emit_terminal_sequence "win-old-cc-seq" 2>&1)
assert_json_field "Windows + old CC outputs terminalSequence JSON" "$OUTPUT" ".terminalSequence" "win-old-cc-seq"
unset CLAUDE_CODE_VERSION

# Restore original _is_windows
eval "$_is_windows_orig"

echo ""
echo "--- jq guard in build-payload.sh ---"

# When jq is missing, sourcing build-payload.sh should exit 1 with a clear message.
# We set PATH to only include the bash binary's directory (no jq).
SCRIPT_DIR_TEST="$(cd "$(dirname "${BASH_SOURCE[0]}")/../scripts" && pwd)"
_minimal_path="$(dirname "$(command -v bash)")"
OUTPUT=$(PATH="$_minimal_path" bash -c "source '$SCRIPT_DIR_TEST/build-payload.sh'" 2>&1)
_rc=$?
if [ "$_rc" -ne 0 ] && echo "$OUTPUT" | grep -qi "jq"; then
assert_eq "build-payload.sh fails gracefully when jq missing" "0" "0"
else
assert_eq "build-payload.sh fails gracefully when jq missing" "1" "0 (exit=$_rc, output=$OUTPUT)"
fi

# --- Routing tests ---
# These test the hook scripts as subprocesses to verify routing behavior.
# We override /dev/tty writes since they'd fail in CI.
Expand Down