Skip to content

Claude Code

Documentation for Claude Code, an AI-powered development tool integrated into this workflow.

Overview

Claude Code provides AI-assisted development capabilities for code generation, analysis, and problem-solving. This setup uses AWS Bedrock for model inference, enabling local-first Claude Code execution.

Prerequisites: Shell Environment Required

Claude Code depends on a properly configured shell environment with git and GitHub working correctly. Before installing Claude Code, ensure your shell is set up properly. If you need help with shell configuration, SSH keys, or Git setup, see our Shell Setup Guide for automated configuration.

Setup

1. Configure AWS Credentials and Region

Add your AWS credentials to ~/.aws/credentials:

1
2
3
[bedrock]
aws_access_key_id = XXXXXXXXXXX
aws_secret_access_key = YYYYYYYYYYYYYYYYY

Add the Bedrock profile to ~/.aws/config:

1
2
[profile bedrock]
region = us-west-2

Use AWS CLI to configure credentials

You can use the AWS CLI to set up your credentials conveniently. See the AWS documentation for detailed instructions on configuring AWS CLI.

2. Install Claude Code

Install Claude Code using the official binary installer:

1
curl -fsSL https://claude.ai/install.sh | bash -s latest

Having issues?

If this installation doesn't work for some reason, see the legacy npm-based installation at the end of this guide.

3. Install Claude Code Wrapper

Install the wrapper script that provides easy model switching and proper permission handling:

1
curl -o- "https://raw.githubusercontent.com/dirkpetersen/dok/main/scripts/claude-wrapper.sh?$(date +%s)" | bash

This wrapper script will: - Automatically find your Claude Code installation - Install itself to ~/bin/claude-wrapper.sh - Create a symlink ~/bin/claude pointing to the wrapper - Configure AWS Bedrock integration - Enable easy model switching (haiku/sonnet/opus) - Set appropriate permissions for the current directory

View script contents
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
#!/bin/bash
# claude-wrapper.sh
# Wrapper script for Claude Code with AWS Bedrock integration
# Provides easy model switching and proper permission handling

SCRIPT_NAME="claude-wrapper.sh"
INSTALL_DIR="$HOME/bin"
WRAPPER_PATH="$INSTALL_DIR/$SCRIPT_NAME"
SYMLINK_PATH="$INSTALL_DIR/claude"

# Colors for output
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color

# Function to verify PATH configuration
verify_path_configuration() {
  local home_bin="$HOME/bin"
  local home_local_bin="$HOME/.local/bin"
  local current_shell="${SHELL##*/}"
  local rc_file=""

  # Determine which rc file to use
  if [[ "$current_shell" == "zsh" ]]; then
    rc_file="~/.zshrc"
  else
    rc_file="~/.bashrc"
  fi

  # Check if ~/bin is in PATH
  if [[ ":$PATH:" != *":$home_bin:"* ]]; then
    echo -e "${RED}✗ Error: $home_bin is not in PATH${NC}" >&2
    echo "" >&2
    echo "To fix this, add the following to $rc_file:" >&2
    echo "" >&2
    echo "  export PATH=\$HOME/bin:\$HOME/.local/bin:\$PATH" >&2
    echo "" >&2
    echo "Then reload your shell:" >&2
    echo "  . $rc_file" >&2
    echo "" >&2
    return 1
  fi

  # Check if ~/.local/bin is in PATH
  if [[ ":$PATH:" != *":$home_local_bin:"* ]]; then
    echo -e "${RED}✗ Error: $home_local_bin is not in PATH${NC}" >&2
    echo "" >&2
    echo "To fix this, add the following to $rc_file:" >&2
    echo "" >&2
    echo "  export PATH=\$HOME/bin:\$HOME/.local/bin:\$PATH" >&2
    echo "" >&2
    echo "Then reload your shell:" >&2
    echo "  . $rc_file" >&2
    echo "" >&2
    return 1
  fi

  # Check if ~/bin comes before ~/.local/bin
  # Add colons to beginning and end for easier matching
  local normalized_path=":$PATH:"

  # Find the position of first occurrence of each directory
  local bin_pos="${normalized_path%%:$home_bin:*}"
  local local_bin_pos="${normalized_path%%:$home_local_bin:*}"

  # If local_bin appears before bin, the prefix will be shorter
  if [[ ${#local_bin_pos} -lt ${#bin_pos} ]]; then
    echo -e "${RED}✗ Error: $home_bin must come before $home_local_bin in PATH${NC}" >&2
    echo "" >&2
    echo "Current PATH order:" >&2
    echo "  $home_local_bin comes first" >&2
    echo "  $home_bin comes second" >&2
    echo "" >&2
    echo "To fix this, ensure $rc_file has:" >&2
    echo "" >&2
    echo "  export PATH=\$HOME/bin:\$HOME/.local/bin:\$PATH" >&2
    echo "" >&2
    echo "Then reload your shell:" >&2
    echo "  . $rc_file" >&2
    echo "" >&2
    return 1
  fi

  return 0
}

# Function to find the real claude binary (not in ~/bin)
find_claude_binary() {
  # Get all claude executables in PATH
  local claude_paths=$(which -a claude 2>/dev/null || true)

  # Find first claude that is NOT in ~/bin
  local home_bin_expanded="$HOME/bin"
  local real_claude=""

  while IFS= read -r claude_path; do
    # Expand ~ in path for comparison
    local expanded_path="${claude_path/#\~/$HOME}"

    # Skip if it's in ~/bin (could be our symlink)
    if [[ "$expanded_path" != "$home_bin_expanded"* ]]; then
      real_claude="$expanded_path"
      break
    fi
  done <<< "$claude_paths"

  # If we found a real claude binary, return it
  if [[ -n "$real_claude" ]]; then
    echo "$real_claude"
    return 0
  fi

  # No Claude Code found outside ~/bin, try to install it
  echo -e "${YELLOW}Claude Code not found in PATH${NC}" >&2
  echo "" >&2
  echo "Installing Claude Code..." >&2
  echo "Running: curl -fsSL https://claude.ai/install.sh | bash -s latest" >&2
  echo "" >&2
  if curl -fsSL https://claude.ai/install.sh | bash -s latest; then
    echo -e "${GREEN}${NC} Claude Code installed successfully" >&2
    echo "" >&2
    # Re-check PATH after installation
    claude_paths=$(which -a claude 2>/dev/null || true)

    # Try to find it again
    while IFS= read -r claude_path; do
      local expanded_path="${claude_path/#\~/$HOME}"
      if [[ "$expanded_path" != "$home_bin_expanded"* ]]; then
        echo "$expanded_path"
        return 0
      fi
    done <<< "$claude_paths"

    # Still not found after install
    echo -e "${RED}✗ Claude Code still not found in PATH after installation${NC}" >&2
    echo "Please reload your shell and try again:" >&2
    local current_shell="${SHELL##*/}"
    if [[ "$current_shell" == "zsh" ]]; then
      echo "  . ~/.zshrc" >&2
    else
      echo "  . ~/.bashrc" >&2
    fi
    return 1
  else
    echo -e "${RED}✗ Failed to install Claude Code${NC}" >&2
    return 1
  fi
}

# Function to install the wrapper
install_wrapper() {
  echo -e "${YELLOW}Installing Claude Code wrapper...${NC}"

  # Find the real claude binary (this will auto-install if not found)
  local real_claude=$(find_claude_binary)
  local find_result=$?

  if [[ $find_result -ne 0 ]] || [[ -z "$real_claude" ]]; then
    # Claude Code installation failed or not found
    echo -e "${RED}✗ Cannot install wrapper without Claude Code binary${NC}" >&2
    echo "" >&2
    echo "Please ensure Claude Code is installed and in your PATH." >&2
    echo "You may need to reload your shell:" >&2
    local current_shell="${SHELL##*/}"
    if [[ "$current_shell" == "zsh" ]]; then
      echo "  . ~/.zshrc" >&2
    else
      echo "  . ~/.bashrc" >&2
    fi
    exit 1
  fi

  # Create ~/bin if it doesn't exist
  mkdir -p "$INSTALL_DIR"

  # Copy or download wrapper script to ~/bin
  # When script is piped (curl | bash), $0 will be "bash"
  # Always download from GitHub in this case to ensure latest version
  if [[ "$0" == "bash" ]] || [[ ! -f "$0" ]]; then
    echo -e "${YELLOW}Downloading wrapper script from GitHub...${NC}"
    if curl -f -s -o "$WRAPPER_PATH" https://raw.githubusercontent.com/dirkpetersen/dok/main/scripts/claude-wrapper.sh; then
      echo -e "${GREEN}${NC} Downloaded wrapper script"
    else
      echo -e "${RED}✗ Failed to download wrapper script${NC}" >&2
      exit 1
    fi
    chmod +x "$WRAPPER_PATH"
    echo -e "${GREEN}${NC} Installed wrapper to ~/bin/$SCRIPT_NAME"
  elif [[ ! -f "$WRAPPER_PATH" ]] || [[ "$(readlink -f "$0" 2>/dev/null || echo "")" != "$(readlink -f "$WRAPPER_PATH" 2>/dev/null)" ]]; then
    # Script is being run from a file, copy it if needed
    echo -e "${YELLOW}Copying wrapper script...${NC}"
    if cp "$0" "$WRAPPER_PATH"; then
      echo -e "${GREEN}${NC} Copied wrapper script"
    else
      echo -e "${RED}✗ Failed to copy wrapper script${NC}" >&2
      exit 1
    fi
    chmod +x "$WRAPPER_PATH"
    echo -e "${GREEN}${NC} Installed wrapper to ~/bin/$SCRIPT_NAME"
  fi

  # Create symlink if it doesn't exist or points elsewhere
  if [[ -L "$SYMLINK_PATH" ]]; then
    local current_target=$(readlink "$SYMLINK_PATH")
    if [[ "$current_target" != "$SCRIPT_NAME" ]]; then
      rm "$SYMLINK_PATH"
      ln -s "$SCRIPT_NAME" "$SYMLINK_PATH"
      echo -e "${GREEN}${NC} Updated symlink ~/bin/claude"
    else
      echo -e "${GREEN}${NC} Wrapper already correctly configured"
    fi
  elif [[ -e "$SYMLINK_PATH" ]]; then
    echo -e "${RED}✗ ~/bin/claude exists but is not a symlink${NC}"
    echo "Please remove it manually and run this script again"
    exit 1
  else
    ln -s "$SCRIPT_NAME" "$SYMLINK_PATH"
    echo -e "${GREEN}${NC} Created symlink ~/bin/claude"
  fi

  echo ""
  echo -e "${GREEN}=== Installation Complete! ===${NC}"
  echo ""
  echo "You can now run the claude wrapper from anywhere:"
  echo ""
  echo "  claude                # Launch with Haiku (fast/default)"
  echo "  claude sonnet         # Launch with Sonnet (balanced)"
  echo "  claude opus           # Launch with Opus (most capable)"
  echo ""

  exit 0
}

# Verify PATH configuration first, before doing anything
verify_path_configuration
if [[ $? -ne 0 ]]; then
  exit 1
fi

# Check if this is an installation run
if [[ "$1" == "--install" ]]; then
  install_wrapper
fi

# Check if wrapper is already installed
# If we're being run from the installed location, skip installation checks
CURRENT_SCRIPT_PATH="$(readlink -f "$0" 2>/dev/null || echo "")"
INSTALLED_WRAPPER_PATH="$(readlink -f "$WRAPPER_PATH" 2>/dev/null || echo "")"

if [[ -n "$CURRENT_SCRIPT_PATH" && "$CURRENT_SCRIPT_PATH" == "$INSTALLED_WRAPPER_PATH" ]]; then
  # We're running from the installed location - proceed to wrapper functionality
  :
elif [[ -L "$SYMLINK_PATH" && -f "$WRAPPER_PATH" ]]; then
  # Wrapper is installed but we're running from a different location (e.g., git repo)
  echo -e "${GREEN}${NC} Wrapper already installed. Reinstalling to ensure latest version..."
  install_wrapper
  exit 0
else
  # Script is not installed yet, auto-install or prompt
  echo -e "${YELLOW}Claude Code wrapper is not installed yet.${NC}"

  # Check if stdin is a terminal (interactive) or pipe (non-interactive)
  if [[ -t 0 ]]; then
    # Interactive mode - prompt user
    read -p "Install to ~/bin/claude? (y/n): " install_confirm

    if [[ "$install_confirm" == "y" || "$install_confirm" == "Y" ]]; then
      install_wrapper
      # install_wrapper exits, so we won't reach here
    else
      echo "Installation cancelled. Run with --install to install later."
      exit 1
    fi
  else
    # Non-interactive mode (piped) - auto-install
    echo -e "${YELLOW}Running in non-interactive mode. Auto-installing...${NC}"
    install_wrapper
    # install_wrapper exits, so we won't reach here
  fi
fi

# ============================================================================
# WRAPPER FUNCTIONALITY
# ============================================================================

# Check if ANTHROPIC_BASE_URL is set (for local LLM usage)
if [[ -n "$ANTHROPIC_BASE_URL" ]]; then
  echo -e "${GREEN}Using custom ANTHROPIC_BASE_URL: $ANTHROPIC_BASE_URL${NC}" >&2

  # Set ANTHROPIC_API_KEY to dummy value if not set or invalid
  if [[ -z "$ANTHROPIC_API_KEY" ]] || [[ ! "$ANTHROPIC_API_KEY" =~ ^sk-ant- ]]; then
    export ANTHROPIC_API_KEY="sk-ant-dummy"
    echo -e "${YELLOW}Set ANTHROPIC_API_KEY to dummy value for local LLM${NC}" >&2
  fi

  # Find the real claude binary
  REAL_CLAUDE=$(find_claude_binary)
  if [[ $? -ne 0 ]]; then
    exit 1
  fi

  # Execute Claude Code with custom base URL (skip all Bedrock/AWS config)
  # Note: Using --dangerously-skip-permissions for unrestricted access
  exec "$REAL_CLAUDE" --dangerously-skip-permissions "$@"
fi

# AWS Bedrock Configuration
export CLAUDE_CODE_USE_BEDROCK=1
export AWS_REGION=us-west-2
export AWS_PROFILE=bedrock

# Model Configuration
export ANTHROPIC_DEFAULT_HAIKU_MODEL="us.anthropic.claude-haiku-4-5-20251001-v1:0"
export ANTHROPIC_DEFAULT_SONNET_MODEL="us.anthropic.claude-sonnet-4-5-20250929-v1:0"
export ANTHROPIC_DEFAULT_OPUS_MODEL="global.anthropic.claude-opus-4-5-20251101-v1:0"
export ANTHROPIC_SMALL_FAST_MODEL="${ANTHROPIC_DEFAULT_HAIKU_MODEL}"

# Set default model to Haiku
mymodel="${ANTHROPIC_DEFAULT_HAIKU_MODEL}"

# Check first argument for model selection
if [[ "$1" == "opus" ]]; then
  mymodel="${ANTHROPIC_DEFAULT_OPUS_MODEL}"
  shift
elif [[ "$1" == "sonnet" ]]; then
  mymodel="${ANTHROPIC_DEFAULT_SONNET_MODEL}"
  shift
elif [[ "$1" == "haiku" ]]; then
  mymodel="${ANTHROPIC_DEFAULT_HAIKU_MODEL}"
  shift
fi

# Set the model environment variable
export ANTHROPIC_MODEL="$mymodel"

# Find the real claude binary
REAL_CLAUDE=$(find_claude_binary)
if [[ $? -ne 0 ]]; then
  exit 1
fi

# Execute the real Claude Code with model selection
# Note: Using --dangerously-skip-permissions for unrestricted access
# Remove this flag if you want to use permission restrictions
exec "$REAL_CLAUDE" --model "$mymodel" --dangerously-skip-permissions "$@"

4. Test Claude Code

Create a test project and launch Claude Code:

1
2
3
4
5
6
7
# Create a test directory
mkdir -p ~/test-project
cd ~/test-project
git init

# Launch Claude Code with default Haiku model
claude

First Time Setup: Reload Shell if Needed

If ~/bin was not already in your PATH before running the wrapper installation, you'll need to reload your shell first:

1
2
. ~/.bashrc   # For Bash
. ~/.zshrc    # For Zsh

You can check if reload is needed by running: echo $PATH | grep -q "$HOME/bin" && echo "Ready!" || echo "Please reload shell"

When Claude Code launches for the first time, it will: 1. Ask for your SSH keychain passphrase (cached for 4 hours) 2. Initialize the Claude Code environment 3. Start the interactive session in the git repository

Important: Git Repository Requirement

Claude Code must always be initialized inside a git repository. This is a requirement for the tool to function properly.

Setting Up a Git Repository

You have two options:

Option 1: Create a Local Repository

1
2
3
mkdir my-project
cd my-project
git init

Then run Claude Code inside this directory:

1
claude

Option 2: Create a Repository on GitHub

  1. Visit github.com/new to create a new repository
  2. Clone it to your local machine using SSH:
1
2
git clone git@github.com:YOUR_USERNAME/my-project.git
cd my-project

SSH vs HTTPS

This example uses SSH for secure authentication. SSH requires setting up an SSH key pair. See the SSH setup guide for detailed instructions on generating and configuring SSH keys for GitHub.

  1. Run Claude Code:
1
claude

Model Comparison

Understanding the differences between the three available models helps you choose the right tool for each task:

Aspect Haiku Sonnet Opus
Speed ⚡ Fast ⚡⚡ Medium 🐌 Slow
Cost 💰 $1.00/MTok 💰💰 $3.00/MTok 💰💰💰 $5.00/MTok
Performance Excellent Very Good Superior
Complex Tasks ⭐⭐⭐ Medium ⭐⭐⭐⭐ High ⭐⭐⭐⭐⭐ Excellent
Coding Skills ⭐⭐⭐⭐ High ⭐⭐⭐⭐⭐ Excellent ⭐⭐⭐⭐⭐ Excellent

Usage

Default (Haiku - Fast)

Run Claude Code with the default fast Haiku model for quick fixes and simple tasks:

1
claude /path/to/project

Best for: - Quick bug fixes - Simple code generation - Refactoring small sections - Cost-conscious tasks

Sonnet (Balanced)

Run with the balanced Sonnet model for most development work:

1
claude sonnet /path/to/project

Best for: - Complex feature development - Detailed code analysis - Medium-complexity problems - Good balance of speed and capability

Opus (Most Capable)

Run with the most capable Opus model for challenging problems:

1
claude opus /path/to/project

Best for: - Difficult architectural decisions - Complex problem-solving - Full-project analysis - Maximum capability required

Configuration Notes

  • Bedrock Integration: Uses AWS Bedrock for model inference
  • Context Window: Defaults to 1M context window for Sonnet
  • No Confirmation: Configured to skip permission prompts for streamlined workflow
  • Model Selection: Easy switching between Haiku (fast), Sonnet (balanced), and Opus (capable)

Tips and Best Practices

  • Use Haiku for quick fixes and simple tasks
  • Use Sonnet for complex development and analysis
  • Use Opus for challenging problems requiring maximum capability
  • Ensure AWS credentials are properly configured before use
  • The wrapper script automatically handles model selection and AWS environment setup

Alternative: npm-based Installation (Legacy)

If the binary installer doesn't work or you prefer npm, follow these steps:

Check and Install npm

Before installing Claude Code via npm, verify that npm is available and properly configured.

Copy and paste this command into your terminal:

1
curl -o- "https://raw.githubusercontent.com/dirkpetersen/dok/main/scripts/nodejs-install-check.sh?$(date +%s)" | bash
View script contents
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#!/bin/bash
# nodejs-install-check.sh
# Script to check and install Node.js/npm with proper configuration
# Works on both standard Linux/macOS and HPC systems with Lmod

# Note: NOT using set -e because we want to handle errors gracefully

# Colors for output
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color

# Function to detect user's login shell and add nvm initialization to shell rc
add_nvm_to_shell() {
  local shell_rc=""
  local shell_name=""
  local login_shell=""

  # Get the user's login shell from the SHELL environment variable
  login_shell="${SHELL##*/}"  # Extract shell name from path (e.g., /bin/zsh -> zsh)

  case "$login_shell" in
    zsh)
      shell_rc="$HOME/.zshrc"
      shell_name="Zsh"
      ;;
    bash)
      shell_rc="$HOME/.bashrc"
      shell_name="Bash"
      ;;
    *)
      echo -e "${YELLOW}Warning: Detected shell '$login_shell'. Please manually add NVM initialization.${NC}"
      return 1
      ;;
  esac

  # Create shell rc file if it doesn't exist
  if [[ ! -f "$shell_rc" ]]; then
    touch "$shell_rc"
    echo -e "${YELLOW}Created new $shell_name configuration file: $shell_rc${NC}"
  fi

  # Check if NVM initialization already exists (uncommented)
  if grep -q '^[^#]*NVM_DIR.*nvm.sh' "$shell_rc" 2>/dev/null; then
    echo -e "${GREEN}${NC} NVM already configured in $shell_name configuration"
    return 0
  fi

  # Remove commented-out NVM lines if they exist
  if grep -q '#.*NVM_DIR.*nvm.sh' "$shell_rc" 2>/dev/null; then
    echo -e "${YELLOW}Removing commented NVM configuration...${NC}"
    sed -i.bak '/^#.*NVM_DIR/d; /^#.*nvm.sh/d; /^#.*bash_completion/d' "$shell_rc"
    rm -f "${shell_rc}.bak"
  fi

  # Add NVM initialization to shell RC file
  # Use a separator comment to mark our additions
  echo "" >> "$shell_rc"
  echo "# ========== NVM (Node Version Manager) initialization ==========" >> "$shell_rc"
  echo "export NVM_DIR=\"\$HOME/.nvm\"" >> "$shell_rc"
  echo "[ -s \"\$NVM_DIR/nvm.sh\" ] && \\. \"\$NVM_DIR/nvm.sh\"" >> "$shell_rc"
  echo "[ -s \"\$NVM_DIR/bash_completion\" ] && \\. \"\$NVM_DIR/bash_completion\"" >> "$shell_rc"
  echo "# ================================================================" >> "$shell_rc"

  echo -e "${GREEN}${NC} Added NVM initialization to $shell_name configuration"
  return 0
}

# Check if npm is already installed
if command -v npm &> /dev/null; then
  echo -e "${GREEN}${NC} npm is already installed"
  npm --version
  exit 0
fi

echo -e "${YELLOW}npm not found. Installing Node.js via NVM...${NC}"
echo ""

# Try loading nodejs module first (for HPC systems that have it)
if command -v module &> /dev/null; then
  echo -e "${YELLOW}Attempting to load nodejs module...${NC}"
  if module load nodejs 2>/dev/null; then
    echo -e "${GREEN}${NC} Successfully loaded nodejs module"
    if command -v npm &> /dev/null; then
      echo -e "${GREEN}${NC} npm is now available"
      npm --version
      exit 0
    fi
  else
    echo -e "${YELLOW}${NC} nodejs module not found or failed to load. Proceeding with NVM installation..."
  fi
fi

# Install NVM (Node Version Manager)
echo -e "${YELLOW}Installing NVM (Node Version Manager)...${NC}"

# Check if NVM already installed
if [[ ! -d "$HOME/.nvm" ]]; then
  curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash

  if [[ ! -f "$HOME/.nvm/nvm.sh" ]]; then
    echo -e "${RED}✗ Failed to install NVM${NC}"
    exit 1
  fi
  echo -e "${GREEN}${NC} NVM installed successfully"
else
  echo -e "${GREEN}${NC} NVM already exists at $HOME/.nvm"
fi

# Add NVM initialization to shell configuration
echo -e "${YELLOW}Configuring shell...${NC}"
add_nvm_to_shell
if [[ $? -ne 0 ]]; then
  echo -e "${RED}✗ Failed to configure shell${NC}"
  exit 1
fi

# Load nvm in current shell session
export NVM_DIR="$HOME/.nvm"
if [[ ! -s "$NVM_DIR/nvm.sh" ]]; then
  echo -e "${RED}✗ NVM script not found at $NVM_DIR/nvm.sh${NC}"
  exit 1
fi

# Source NVM in current shell
. "$NVM_DIR/nvm.sh" 2>/dev/null || true

# Verify NVM is loaded
if ! command -v nvm &> /dev/null; then
  echo -e "${RED}✗ Failed to load NVM in current shell${NC}"
  echo -e "${YELLOW}Try reloading your shell: source ~/.bashrc${NC}"
  exit 1
fi

echo -e "${GREEN}${NC} NVM loaded in current shell"

# Install Node.js (version 24, latest LTS)
echo -e "${YELLOW}Installing Node.js v24...${NC}"
nvm install 24

if ! command -v npm &> /dev/null; then
  echo -e "${RED}✗ Failed to install Node.js${NC}"
  exit 1
fi

echo -e "${GREEN}${NC} Node.js installed successfully"

# Verify versions
echo ""
echo -e "${GREEN}Installation Summary:${NC}"
node --version
npm --version

# Final instructions
echo ""
echo "========================================"
echo "Node.js/npm installation complete!"
echo "========================================"
echo ""
echo "To apply the NVM configuration in new shell sessions:"
echo ""
if [[ -n "$ZSH_VERSION" ]]; then
  echo "  source ~/.zshrc"
elif [[ -n "$BASH_VERSION" ]]; then
  echo "  source ~/.bashrc"
else
  case "$SHELL" in
    */zsh)
      echo "  source ~/.zshrc"
      ;;
    */bash)
      echo "  source ~/.bashrc"
      ;;
  esac
fi
echo ""
echo "Then install Claude Code:"
echo "  npm install -g @anthropic-ai/claude-code"
echo ""

This script will:

  • Check if npm is installed
  • Attempt to load nodejs module (for HPC systems)
  • Install nvm and Node.js 24 if needed
  • Configure npm to install global packages in your home directory
  • Set up the correct PATH configuration

Install Claude Code via npm

Once npm is set up, install Claude Code globally:

1
npm i -g @anthropic-ai/claude-code

Legacy Method

This is a fallback if the primary binary installation doesn't work for you.

Next: Complete Project Development Workflow

Ready to develop a full project with Claude Code? Follow the Claude Code Tutorial for the complete step-by-step workflow using the recommended Haiku → Sonnet → Opus escalation strategy.