Commit 6e5008e2ba53

Vincent Demeester <vincent@sbr.pm>
2026-01-30 10:59:08
feat(shpool-ssh-wrapper): add copilot, opencode, oco tools and model selection
- Add support for copilot/*, opencode/*, oco/* session patterns - Add model selection via :model syntax (e.g., claude:opus/home) - Add review-<tool>/* pattern to choose review tool (cr, copilot, opencode, oco) - Refactor to use generic run-in-dir helper with optional --model flag - Update claude/* to use cr command instead of inline claude-vertex script Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 2461fc0
Changed files (1)
tools
shpool-ssh-wrapper
tools/shpool-ssh-wrapper/default.nix
@@ -1,33 +1,48 @@
 { pkgs }:
 
 let
-  # Helper script to start Claude Code with Vertex AI environment
-  # Takes required directory argument: claude-vertex <directory>
-  claude-vertex = pkgs.writeShellScript "claude-vertex" ''
-    export CLAUDE_CODE_USE_VERTEX=1
-    export CLOUD_ML_REGION=us-east5
-    export ANTHROPIC_VERTEX_PROJECT_ID=itpc-gcp-pnd-pe-eng-claude
+  # Helper script to run a command in a specific directory with proper environment
+  # Usage: run-in-dir <command> <directory> [model]
+  # If model is provided, adds --model <model> to the command
+  run-in-dir = pkgs.writeShellScript "run-in-dir" ''
+    CMD="''${1:-}"
+    DIR="''${2:-}"
+    MODEL="''${3:-}"
+
     # Set TERM for proper color support
     export TERM=xterm-256color
 
+    # Command is required
+    if [ -z "$CMD" ]; then
+      echo "Error: command argument is required" >&2
+      exit 1
+    fi
+
     # Directory is required
-    if [ -z "''${1:-}" ]; then
+    if [ -z "$DIR" ]; then
       echo "Error: directory argument is required" >&2
       exit 1
     fi
 
     # Check if directory exists
-    if [ ! -d "$1" ]; then
-      echo "Error: directory does not exist: $1" >&2
+    if [ ! -d "$DIR" ]; then
+      echo "Error: directory does not exist: $DIR" >&2
       exit 1
     fi
 
     # Change to specified directory
-    cd "$1"
+    cd "$DIR"
+
+    # Build command with optional model flag
+    if [ -n "$MODEL" ]; then
+      FULL_CMD="$CMD --model $MODEL"
+    else
+      FULL_CMD="$CMD"
+    fi
 
     # Execute through zsh with proper environment
     # zshenv sets up PATH and other environment variables
-    exec ${pkgs.zsh}/bin/zsh -c "source ~/.zshenv; exec /etc/profiles/per-user/vincent/bin/claude"
+    exec ${pkgs.zsh}/bin/zsh -c "source ~/.zshenv; exec $FULL_CMD"
   '';
 
 in
@@ -35,7 +50,24 @@ in
 pkgs.writeScriptBin "shpool-ssh-wrapper" ''
   #!${pkgs.bash}/bin/bash
   # Shpool SSH wrapper - automatically runs commands for specific session patterns
-  # Supports syntax: session-name=directory (e.g., "claude/home=src/home")
+  #
+  # Syntax: session-name=directory (e.g., "claude/home=src/home")
+  #
+  # Supported session patterns:
+  #   claude/*        - Claude Code with Vertex AI (cr command)
+  #   copilot/*       - GitHub Copilot CLI
+  #   opencode/*      - OpenCode CLI
+  #   oco/*           - OpenCode with Vertex AI
+  #   review-<tool>/* - Review sessions with specified tool (cr, copilot, opencode, oco)
+  #
+  # Model selection (optional):
+  #   claude:opus/home      - Use opus model
+  #   oco:sonnet/project    - Use sonnet model
+  #
+  # Examples:
+  #   ssh host/claude/home                    # Claude in ~/src/home
+  #   ssh host/claude:opus/home               # Claude with opus in ~/src/home
+  #   ssh host/review-copilot/pr-123="prompt" # Review with copilot
 
   set -euo pipefail
 
@@ -57,36 +89,140 @@ pkgs.writeScriptBin "shpool-ssh-wrapper" ''
       exec ${pkgs.shpool}/bin/shpool attach -f -c "$cmd $*" "$session"
   }
 
+  # Function to parse tool:model from prefix
+  # e.g., "claude:opus" -> TOOL=claude, MODEL=opus
+  #       "claude" -> TOOL=claude, MODEL=""
+  parse_tool_model() {
+      local prefix="$1"
+      if [[ "$prefix" =~ ^([^:]+):(.+)$ ]]; then
+          TOOL="''${BASH_REMATCH[1]}"
+          MODEL="''${BASH_REMATCH[2]}"
+      else
+          TOOL="$prefix"
+          MODEL=""
+      fi
+  }
+
+  # Function to get default directory from session suffix
+  # e.g., "claude/home" -> "src/home", "claude:opus/home" -> "src/home"
+  get_default_dir() {
+      local session="$1"
+      local prefix="$2"
+      # Remove prefix (which may include :model)
+      local suffix="''${session#$prefix/}"
+      echo "src/$suffix"
+  }
+
+  # Function to run an AI tool session
+  # Args: session_name, tool_prefix, tool_command
+  run_ai_session() {
+      local session="$1"
+      local prefix_pattern="$2"
+      local default_cmd="$3"
+
+      # Extract the prefix part (before first /)
+      local prefix="''${session%%/*}"
+
+      # Parse tool:model from prefix
+      parse_tool_model "$prefix"
+
+      # Use default command if TOOL matches expected pattern
+      local cmd="$default_cmd"
+
+      # Get work directory
+      if [ -z "$WORK_DIR" ]; then
+          WORK_DIR=$(get_default_dir "$session" "$prefix")
+      fi
+
+      run_with_command "$session" "${run-in-dir}" "$cmd" "$WORK_DIR" "$MODEL"
+  }
+
   # Check session name patterns and run appropriate commands
   case "$SESSION_NAME" in
-      claude/*)
-          # Claude Code session - run with Vertex AI environment
-          # If no directory specified, default to src/{session-name}
-          if [ -z "$WORK_DIR" ]; then
-              # Extract session name part after "claude/"
-              SESSION_SUFFIX="''${SESSION_NAME#claude/}"
-              WORK_DIR="src/$SESSION_SUFFIX"
-          fi
-          # Use helper script since shpool -c expects a binary, not a shell command
-          run_with_command "$SESSION_NAME" "${claude-vertex}" "$WORK_DIR"
+      claude:*/*)
+          # Claude Code with model selection
+          run_ai_session "$SESSION_NAME" "claude" "cr"
           ;;
-      review-*)
-          # Claude Code review session - run with prompt
-          # Usage: ssh host/review-pull-123="Review https://github.com/..."
-          # The WORK_DIR variable holds the prompt (after =)
-          # Uses 'cr' script from PATH (pkgs/my/scripts/bin/cr)
+      claude/*)
+          # Claude Code session - run with Vertex AI environment via cr
+          run_ai_session "$SESSION_NAME" "claude" "cr"
+          ;;
+      copilot:*/*)
+          # GitHub Copilot CLI with model selection
+          run_ai_session "$SESSION_NAME" "copilot" "copilot"
+          ;;
+      copilot/*)
+          # GitHub Copilot CLI session
+          run_ai_session "$SESSION_NAME" "copilot" "copilot"
+          ;;
+      opencode:*/*)
+          # OpenCode CLI with model selection
+          run_ai_session "$SESSION_NAME" "opencode" "opencode"
+          ;;
+      opencode/*)
+          # OpenCode CLI session
+          run_ai_session "$SESSION_NAME" "opencode" "opencode"
+          ;;
+      oco:*/*)
+          # OpenCode with Vertex AI and model selection
+          run_ai_session "$SESSION_NAME" "oco" "oco"
+          ;;
+      oco/*)
+          # OpenCode with Vertex AI session
+          run_ai_session "$SESSION_NAME" "oco" "oco"
+          ;;
+      review-cr/*|review-claude/*)
+          # Review with Claude Code (cr)
           if [ -z "$WORK_DIR" ]; then
-              # No prompt - check if session already exists and just attach
               if ${pkgs.shpool}/bin/shpool list 2>/dev/null | grep -q "^$SESSION_NAME "; then
                   exec ${pkgs.shpool}/bin/shpool attach -f "$SESSION_NAME"
               else
                   echo "Error: new review sessions require a prompt (use session=prompt syntax)" >&2
-                  echo "Example: ssh host/review-pull-123=\"Review https://github.com/...\"" >&2
+                  echo "Example: ssh host/review-cr/pr-123=\"Review https://github.com/...\"" >&2
                   exit 1
               fi
           fi
           run_with_command "$SESSION_NAME" "cr" "$WORK_DIR"
           ;;
+      review-copilot/*)
+          # Review with GitHub Copilot
+          if [ -z "$WORK_DIR" ]; then
+              if ${pkgs.shpool}/bin/shpool list 2>/dev/null | grep -q "^$SESSION_NAME "; then
+                  exec ${pkgs.shpool}/bin/shpool attach -f "$SESSION_NAME"
+              else
+                  echo "Error: new review sessions require a prompt (use session=prompt syntax)" >&2
+                  echo "Example: ssh host/review-copilot/pr-123=\"Review https://github.com/...\"" >&2
+                  exit 1
+              fi
+          fi
+          run_with_command "$SESSION_NAME" "copilot" "$WORK_DIR"
+          ;;
+      review-opencode/*)
+          # Review with OpenCode
+          if [ -z "$WORK_DIR" ]; then
+              if ${pkgs.shpool}/bin/shpool list 2>/dev/null | grep -q "^$SESSION_NAME "; then
+                  exec ${pkgs.shpool}/bin/shpool attach -f "$SESSION_NAME"
+              else
+                  echo "Error: new review sessions require a prompt (use session=prompt syntax)" >&2
+                  echo "Example: ssh host/review-opencode/pr-123=\"Review https://github.com/...\"" >&2
+                  exit 1
+              fi
+          fi
+          run_with_command "$SESSION_NAME" "opencode" "$WORK_DIR"
+          ;;
+      review-oco/*)
+          # Review with OpenCode Vertex
+          if [ -z "$WORK_DIR" ]; then
+              if ${pkgs.shpool}/bin/shpool list 2>/dev/null | grep -q "^$SESSION_NAME "; then
+                  exec ${pkgs.shpool}/bin/shpool attach -f "$SESSION_NAME"
+              else
+                  echo "Error: new review sessions require a prompt (use session=prompt syntax)" >&2
+                  echo "Example: ssh host/review-oco/pr-123=\"Review https://github.com/...\"" >&2
+                  exit 1
+              fi
+          fi
+          run_with_command "$SESSION_NAME" "oco" "$WORK_DIR"
+          ;;
       *)
           # If work directory specified, cd into it before attaching
           if [ -n "$WORK_DIR" ]; then