-
-
Save woodward54/8cc8d011cf82dda3146aceeb0a860aa9 to your computer and use it in GitHub Desktop.
Claude Tree Helper Commands
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ct — Claude worktree helpers for parallel work | |
| # https://gist.github.com/craigsc/46979018ee28788602d3452fb08ef814 | |
| # | |
| # Lightweight zsh functions for managing git worktrees, designed for | |
| # running multiple Claude Code sessions in parallel on the same repo. | |
| # | |
| # Each worktree gets its own branch and directory, with configurable | |
| # symlinks so you don't have to reinstall dependencies every time. | |
| # | |
| # Functions: | |
| # ct <name> — Create a worktree, symlink shared files, launch Claude | |
| # ct --cursor <name> — Same as above, but open in Cursor instead of CLI Claude | |
| # ctcd <name> — cd into an existing worktree | |
| # ctrm [name] — Remove a worktree and its branch (no args = current worktree) | |
| # ctls — List all worktrees | |
| # ctiso — Replace symlinked node_modules with a real install | |
| # | |
| # Names can use spaces: `ct my feature` becomes branch `my-feature` | |
| # and directory `<repo>-my-feature` alongside your main repo. | |
| # ── Configuration ───────────────────────────────────────────────────── | |
| # Point this at your main (non-worktree) repo checkout: | |
| CT_REPO_ROOT=~/dev/merlin | |
| # Files/directories to symlink from the main repo into each new worktree. | |
| # Remove or add entries to match your project: | |
| CT_SYMLINKS=() | |
| # Command to run after creating a worktree (set to "" to disable): | |
| CT_AUTO_COMMAND="claude" | |
| # ────────────────────────────────────────────────────────────────────── | |
| # Derived — no need to edit these | |
| _ct_repo_name="${CT_REPO_ROOT:t}" # e.g. "pastmaps" | |
| _ct_parent_dir="${CT_REPO_ROOT:h}" # e.g. ~/Projects | |
| # Create a worktree with a new branch and launch Claude | |
| # Usage: ct [--cursor] feature-name OR ct feature name with spaces | |
| ct() { | |
| local use_cursor=false | |
| if [[ "$1" == "--cursor" ]]; then | |
| use_cursor=true | |
| shift | |
| fi | |
| if [ -z "$1" ]; then | |
| echo "Usage: ct [--cursor] <name> (spaces are auto-hyphenated)" | |
| return 1 | |
| fi | |
| local feature="${(j:-:)@}" | |
| local dir_name="${feature//\//-}" | |
| local worktree_dir="$_ct_parent_dir/${_ct_repo_name}-${dir_name}" | |
| git -C "$CT_REPO_ROOT" worktree add "$worktree_dir" -b "$feature" || return 1 | |
| cd "$worktree_dir" | |
| # Symlink shared files/directories from the main repo | |
| for item in $CT_SYMLINKS; do | |
| if [ -e "$CT_REPO_ROOT/$item" ]; then | |
| ln -sf "$CT_REPO_ROOT/$item" "$item" | |
| fi | |
| done | |
| # Install dependencies | |
| cd frontend | |
| pnpm i | |
| cd .. | |
| echo "Worktree ready: $worktree_dir" | |
| if $use_cursor; then | |
| # Open in Cursor and run Claude Code in the integrated terminal | |
| cursor "$worktree_dir" | |
| elif [ -n "$CT_AUTO_COMMAND" ]; then | |
| eval "$CT_AUTO_COMMAND" | |
| fi | |
| } | |
| # cd into an existing worktree | |
| # Usage: ctcd feature-name OR ctcd feature name with spaces | |
| ctcd() { | |
| if [ -z "$1" ]; then | |
| echo "Usage: ctcd <name> (spaces are auto-hyphenated)" | |
| return 1 | |
| fi | |
| local feature="${(j:-:)@}" | |
| local dir_name="${feature//\//-}" | |
| local worktree_dir="$_ct_parent_dir/${_ct_repo_name}-${dir_name}" | |
| if [ ! -d "$worktree_dir" ]; then | |
| echo "Worktree not found: $worktree_dir" | |
| echo "Run 'ctls' to see available worktrees." | |
| return 1 | |
| fi | |
| cd "$worktree_dir" | |
| [ -f .dev.vars ] && source .dev.vars | |
| } | |
| # Remove a worktree and its branch | |
| # Usage: ctrm feature-name OR ctrm (no args = current worktree) | |
| ctrm() { | |
| local feature worktree_dir | |
| if [ -z "$1" ]; then | |
| # No argument — remove current worktree if we're in one | |
| if [[ "$PWD" == "$_ct_parent_dir/${_ct_repo_name}"-* ]]; then | |
| worktree_dir="$PWD" | |
| # Get the actual branch name from git (reliable even with / in names) | |
| feature="$(git -C "$PWD" rev-parse --abbrev-ref HEAD)" | |
| cd "$CT_REPO_ROOT" | |
| else | |
| echo "Usage: ctrm <name> (or run with no args from inside a worktree)" | |
| return 1 | |
| fi | |
| else | |
| feature="${(j:-:)@}" | |
| local dir_name="${feature//\//-}" | |
| worktree_dir="$_ct_parent_dir/${_ct_repo_name}-${dir_name}" | |
| fi | |
| if [ ! -d "$worktree_dir" ]; then | |
| echo "Worktree not found: $worktree_dir" | |
| return 1 | |
| fi | |
| git -C "$CT_REPO_ROOT" worktree remove "$worktree_dir" && \ | |
| git -C "$CT_REPO_ROOT" branch -d "$feature" 2>/dev/null | |
| } | |
| # List all worktrees (excluding the main repo) | |
| ctls() { | |
| git -C "$CT_REPO_ROOT" worktree list | tail -n +2 | |
| } | |
| # Isolate node_modules — replace the symlink with a real install | |
| # (useful when you need to modify dependencies in a worktree) | |
| ctiso() { | |
| if [ -L "node_modules" ]; then | |
| echo "Removing symlink and running npm install..." | |
| rm node_modules | |
| npm install | |
| else | |
| echo "node_modules is not a symlink — already isolated." | |
| fi | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment