← back to Iterm Project Grouper
claude-proj
87 lines
#!/usr/bin/env bash
# claude-proj — open a Claude Code session as a TAB inside its project's
# dedicated iTerm2 window (creating the window the first time). Groups every
# session of the same project (git repo root) into ONE window, going forward,
# without ever touching existing live sessions.
#
# Usage:
# claude-proj [dir] # group by the git root of dir (default: $PWD)
# claude-proj # current directory's project
# CLAUDE_PROJ_CMD='...' claude-proj [dir] # override the launched command (testing)
# claude-proj --list # show the project→window registry
# claude-proj --forget PROJ # drop a stale registry entry
#
# Registry: ~/.claude/iterm-proj-windows.tsv (lines: <project>\t<iterm-window-id>)
# Non-destructive: only ever CREATES tabs/windows; never closes or moves live ones.
set -euo pipefail
REG="$HOME/.claude/iterm-proj-windows.tsv"
mkdir -p "$(dirname "$REG")"; touch "$REG"
case "${1:-}" in
--list) printf 'project\twindow-id\n'; cat "$REG"; exit 0 ;;
--forget) awk -F'\t' -v p="${2:-__none__}" '$1!=p' "$REG" > "$REG.tmp" && mv "$REG.tmp" "$REG"; echo "forgot: ${2:-}"; exit 0 ;;
esac
DIR="${1:-$PWD}"
[ -d "$DIR" ] || { echo "claude-proj: no such dir: $DIR" >&2; exit 1; }
DIR="$(cd "$DIR" && pwd -P)"
ROOT="$(git -C "$DIR" rev-parse --show-toplevel 2>/dev/null || echo "$DIR")"
PROJ="$(basename "$ROOT")"
CMD="${CLAUDE_PROJ_CMD:-claude}"
# look up an existing window id for this project
WINID="$(awk -F'\t' -v p="$PROJ" '$1==p{print $2; exit}' "$REG")"
# ask iTerm2 to place the tab; returns the window id actually used.
# CP_* env vars are read inside AppleScript via `system attribute` — no quoting hell.
USED_ID="$(CP_DIR="$DIR" CP_CMD="$CMD" CP_PROJ="$PROJ" CP_WINID="${WINID:-0}" osascript <<'APPLESCRIPT'
on envv(k)
try
return (do shell script "printf %s \"$" & k & "\"")
on error
return ""
end try
end envv
set theDir to envv("CP_DIR")
set theCmd to envv("CP_CMD")
set theProj to envv("CP_PROJ")
set wantId to envv("CP_WINID")
set runLine to "cd " & quoted form of theDir & " && " & theCmd
tell application "iTerm2"
set targetWin to missing value
if wantId is not "" and wantId is not "0" then
repeat with w in windows
try
if (id of w as string) is wantId then set targetWin to w
end try
end repeat
end if
if targetWin is missing value then
-- no live window for this project → make one
set targetWin to (create window with default profile)
tell current session of targetWin to write text runLine
tell current session of targetWin to set name to theProj
else
-- reuse the project's window: add a tab
tell targetWin
set newTab to (create tab with default profile)
end tell
tell current session of newTab to write text runLine
tell current session of newTab to set name to theProj
end if
return (id of targetWin as string)
end tell
APPLESCRIPT
)"
# persist / refresh the registry mapping
if [ -n "$USED_ID" ]; then
grep -v -E "^${PROJ} " "$REG" > "$REG.tmp" 2>/dev/null || true
printf '%s\t%s\n' "$PROJ" "$USED_ID" >> "$REG.tmp"
mv "$REG.tmp" "$REG"
echo "claude-proj: '$PROJ' → iTerm2 window $USED_ID (dir: $DIR)"
else
echo "claude-proj: failed to place tab (is iTerm2 running?)" >&2; exit 1
fi