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 supports multiple inference backends: AWS Bedrock (default), Microsoft Azure AI Foundry, or a local LLM endpoint.

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

Where do I get these credentials? You have two options:

  • From your sysadmin — If you are in a managed environment (university, company), ask your AWS administrator to create a Bedrock-scoped IAM user for you and send you the access key and secret.
  • Create them yourself — If you have access to the AWS Console, use the Bedrock user setup script to create a bedrock-<yourname> IAM user in under a minute via AWS CloudShell. The script outputs a ready-to-paste credentials block.

See the AWS Clouds section for full details on credential management, profile isolation, and IAM policy best practices.

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 -fsSL "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 - Auto-detect the active backend (Bedrock, Foundry, or local LLM) - Enable easy model switching (haiku/sonnet/opus) - Apply a safe tool allowlist by default; full permissions via ~/.claude/yolo-mode

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
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
#!/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=""
  local path_cmd=""
  local source_cmd=""

  # Determine which rc file and syntax to use based on shell
  if [[ "$current_shell" == "zsh" ]]; then
    rc_file="~/.zshrc"
    path_cmd="export PATH=\$HOME/bin:\$HOME/.local/bin:\$PATH"
    source_cmd=". $rc_file"
  elif [[ "$current_shell" == "tcsh" || "$current_shell" == "csh" ]]; then
    rc_file="~/.tcshrc"
    path_cmd="setenv PATH \$HOME/bin:\$HOME/.local/bin:\$PATH"
    source_cmd="source $rc_file"
  else
    rc_file="~/.bashrc"
    path_cmd="export PATH=\$HOME/bin:\$HOME/.local/bin:\$PATH"
    source_cmd=". $rc_file"
  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 "  $path_cmd" >&2
    echo "" >&2
    echo "Then reload your shell:" >&2
    echo "  $source_cmd" >&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 "  $path_cmd" >&2
    echo "" >&2
    echo "Then reload your shell:" >&2
    echo "  $source_cmd" >&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 "  $path_cmd" >&2
    echo "" >&2
    echo "Then reload your shell:" >&2
    echo "  $source_cmd" >&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" >&2
  echo "" >&2
  local _install_sh
  _install_sh=$(mktemp)
  if ! curl -fsSL -o "$_install_sh" https://claude.ai/install.sh; then
    echo -e "${RED}✗ Failed to download Claude Code installer (HTTP error)${NC}" >&2
    rm -f "$_install_sh"
    return 1
  fi
  if bash "$_install_sh" latest; then
    rm -f "$_install_sh"
    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
    elif [[ "$current_shell" == "tcsh" || "$current_shell" == "csh" ]]; then
      echo "  source ~/.tcshrc" >&2
    else
      echo "  . ~/.bashrc" >&2
    fi
    return 1
  else
    rm -f "$_install_sh"
    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
    elif [[ "$current_shell" == "tcsh" || "$current_shell" == "csh" ]]; then
      echo "  source ~/.tcshrc" >&2
    else
      echo "  . ~/.bashrc" >&2
    fi
    exit 1
  fi

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

  # Force remove old wrapper and symlink
  rm -f "$WRAPPER_PATH" "$SYMLINK_PATH"

  # 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 -fsSL -o "$WRAPPER_PATH" "https://raw.githubusercontent.com/dirkpetersen/dok/main/scripts/claude-wrapper.sh?`date +%s`"; 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"
  else
    # Script is being run from a file, copy it
    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 fresh symlink
  ln -s "$SCRIPT_NAME" "$SYMLINK_PATH"
  echo -e "${GREEN}${NC} Created symlink ~/bin/claude"

  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 "  claude opus-1m        # Launch with Opus in fast mode"
  echo "  claude sonnet-1m      # Launch with Sonnet in fast mode"
  echo "  claude -c opus        # Model name works anywhere in args"
  echo "  claude --models       # Show default Anthropic models"
  echo "  claude update         # Update wrapper and Claude Code"
  echo "  claude --local        # Use local LLM (requires LOCAL_ANTHROPIC_BASE_URL)"
  echo ""

  echo "To skip all permission prompts, run:"
  echo ""
  echo "  touch ~/.claude/yolo-mode"
  echo ""

  exit 0
}

# Create default .claude.json if it doesn't exist (must happen before any claude execution)
if [[ ! -f "$HOME/.claude.json" ]]; then
  cat > "$HOME/.claude.json" <<'EOF'
{
  "numStartups": 1,
  "customApiKeyResponses": {
    "approved": [
      "sk-ant-dummy"
    ],
    "rejected": []
  }
}
EOF
fi

# Source ~/.azure/clauderc if it exists (loads Foundry and other Azure settings)
CLAUDERC_LOADED=0
if [[ -f "$HOME/.azure/clauderc" ]]; then
  CLAUDERC_LOADED=1
  # shellcheck source=/dev/null
  source "$HOME/.azure/clauderc"
fi

# Source ~/.claude/claudelocalrc if it exists (loads local LLM settings)
LOCALRC_LOADED=0
if [[ -f "$HOME/.claude/claudelocalrc" ]]; then
  LOCALRC_LOADED=1
  # shellcheck source=/dev/null
  source "$HOME/.claude/claudelocalrc"
fi

# 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
# ============================================================================

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

# Check if --models flag is used
if [[ "$1" == "--models" ]]; then
  echo ""
  echo "Default Anthropic Models:"
  echo ""
  echo "  Haiku:  ${ANTHROPIC_DEFAULT_HAIKU_MODEL:-us.anthropic.claude-haiku-4-5-20251001-v1:0}"
  echo "  Sonnet: ${ANTHROPIC_DEFAULT_SONNET_MODEL:-global.anthropic.claude-sonnet-4-6}"
  echo "  Opus:   ${ANTHROPIC_DEFAULT_OPUS_MODEL:-global.anthropic.claude-opus-4-7}"
  echo ""
  echo "Fast mode models (append [1m] to base model):"
  echo "  Sonnet: ${ANTHROPIC_DEFAULT_SONNET_MODEL:-global.anthropic.claude-sonnet-4-6}[1m]"
  echo "  Opus:   ${ANTHROPIC_DEFAULT_OPUS_MODEL:-global.anthropic.claude-opus-4-7}[1m]"
  echo ""

  # Show Foundry configuration section
  if [[ "${CLAUDE_CODE_USE_FOUNDRY:-0}" == "1" && -n "$ANTHROPIC_FOUNDRY_BASE_URL" && -n "$ANTHROPIC_FOUNDRY_API_KEY" ]]; then
    echo "Foundry Configuration (active via CLAUDE_CODE_USE_FOUNDRY=1):"
    echo ""
    echo "  Base URL: $ANTHROPIC_FOUNDRY_BASE_URL"
    echo "  Haiku:    ${ANTHROPIC_DEFAULT_HAIKU_MODEL:-claude-haiku-4-5}"
    echo "  Sonnet:   ${ANTHROPIC_DEFAULT_SONNET_MODEL:-claude-sonnet-4-6}"
    echo "  Opus:     ${ANTHROPIC_DEFAULT_OPUS_MODEL:-claude-opus-4-7}"
    echo ""
  elif [[ "${CLAUDE_CODE_USE_FOUNDRY:-0}" == "1" ]]; then
    echo "Foundry Configuration (CLAUDE_CODE_USE_FOUNDRY=1 set but incomplete):"
    echo ""
    [[ -z "$ANTHROPIC_FOUNDRY_BASE_URL" ]] && echo "  Missing: ANTHROPIC_FOUNDRY_BASE_URL"
    [[ -z "$ANTHROPIC_FOUNDRY_API_KEY" ]]  && echo "  Missing: ANTHROPIC_FOUNDRY_API_KEY"
    echo ""
  fi

  # Always show local LLM configuration section
  if [[ -n "$LOCAL_ANTHROPIC_BASE_URL" ]]; then
    echo "Local LLM Configuration (use 'claude --local'):"
    echo ""
    echo "  Base URL: $LOCAL_ANTHROPIC_BASE_URL"
    echo "  Haiku:    ${LOCAL_ANTHROPIC_DEFAULT_HAIKU_MODEL:-hc/minimax-m2p5}"
    echo "  Sonnet:   ${LOCAL_ANTHROPIC_DEFAULT_SONNET_MODEL:-hc/minimax-m2p5}"
    echo "  Opus:     ${LOCAL_ANTHROPIC_DEFAULT_OPUS_MODEL:-hc/minimax-m2p5}"
    echo ""
  else
    echo "Local LLM Configuration (not configured):"
    echo ""
    echo "  Set LOCAL_ANTHROPIC_BASE_URL to activate local models"
    echo "  Example: export LOCAL_ANTHROPIC_BASE_URL=\"http://llm.example.com/v1\""
    echo ""
    echo "  Default models (hc/minimax-m2p5) can be overridden with:"
    echo "    LOCAL_ANTHROPIC_DEFAULT_HAIKU_MODEL"
    echo "    LOCAL_ANTHROPIC_DEFAULT_SONNET_MODEL"
    echo "    LOCAL_ANTHROPIC_DEFAULT_OPUS_MODEL"
    echo ""
  fi

  exit 0
fi

# Check if update/upgrade is requested
if [[ "$1" == "update" || "$1" == "upgrade" ]]; then
  echo -e "${YELLOW}Updating claude-wrapper...${NC}" >&2

  # Checksum before download
  MD5_BEFORE=$(md5sum "$WRAPPER_PATH" 2>/dev/null | cut -d' ' -f1)

  # Download new wrapper to temp file
  TEMP_WRAPPER=$(mktemp)
  if curl -fsSL -o "$TEMP_WRAPPER" "https://raw.githubusercontent.com/dirkpetersen/dok/main/scripts/claude-wrapper.sh?`date +%s`"; then
    MD5_AFTER=$(md5sum "$TEMP_WRAPPER" 2>/dev/null | cut -d' ' -f1)
    # Replace the installed wrapper
    if mv "$TEMP_WRAPPER" "$WRAPPER_PATH" && chmod +x "$WRAPPER_PATH"; then
      if [[ "$MD5_BEFORE" != "$MD5_AFTER" ]]; then
        echo -e "${GREEN}${NC} Wrapper updated successfully" >&2
      else
        echo -e "${GREEN}${NC} Wrapper already up to date" >&2
      fi
      echo "" >&2
    else
      echo -e "${RED}✗ Failed to replace wrapper${NC}" >&2
      rm -f "$TEMP_WRAPPER"
    fi
  else
    echo -e "${RED}✗ Failed to download wrapper${NC}" >&2
    rm -f "$TEMP_WRAPPER"
  fi

  # Now update Claude Code itself
  echo -e "${YELLOW}Updating Claude Code...${NC}" >&2
  mkdir -p "$HOME/.claude"
  date +%s > "$HOME/.claude/wrapper-last-update"
  exec "$REAL_CLAUDE" update
fi

# Auto-update if it has been more than 7 days since last update
AUTO_UPDATE_STAMP="$HOME/.claude/wrapper-last-update"
_now=$(date +%s)
_last=0
[[ -f "$AUTO_UPDATE_STAMP" ]] && _last=$(cat "$AUTO_UPDATE_STAMP" 2>/dev/null || echo 0)
if (( _now - _last > 604800 )); then
  echo -e "${YELLOW}Auto-updating claude-wrapper (last update was >7 days ago)...${NC}" >&2
  TEMP_WRAPPER=$(mktemp)
  MD5_BEFORE=$(md5sum "$WRAPPER_PATH" 2>/dev/null | cut -d' ' -f1)
  if curl -fsSL -o "$TEMP_WRAPPER" "https://raw.githubusercontent.com/dirkpetersen/dok/main/scripts/claude-wrapper.sh?`date +%s`"; then
    MD5_AFTER=$(md5sum "$TEMP_WRAPPER" 2>/dev/null | cut -d' ' -f1)
    if mv "$TEMP_WRAPPER" "$WRAPPER_PATH" && chmod +x "$WRAPPER_PATH"; then
      if [[ "$MD5_BEFORE" != "$MD5_AFTER" ]]; then
        echo -e "${GREEN}${NC} Wrapper updated" >&2
      else
        echo -e "${GREEN}${NC} Wrapper already up to date" >&2
      fi
    else
      echo -e "${RED}✗ Failed to replace wrapper${NC}" >&2
      rm -f "$TEMP_WRAPPER"
    fi
  else
    echo -e "${YELLOW}⚠ Auto-update skipped (no network?)${NC}" >&2
    rm -f "$TEMP_WRAPPER"
  fi
  mkdir -p "$HOME/.claude"
  date +%s > "$AUTO_UPDATE_STAMP"
fi

# Check if --local flag is used
if [[ "$1" == "--local" ]]; then
  shift  # Remove --local from arguments

  # Check if LOCAL_ANTHROPIC_BASE_URL is set
  if [[ -z "$LOCAL_ANTHROPIC_BASE_URL" ]]; then
    echo -e "${RED}✗ Error: --local flag used but LOCAL_ANTHROPIC_BASE_URL is not set${NC}" >&2
    echo "" >&2
    echo "To use --local, set the LOCAL_ANTHROPIC_BASE_URL environment variable:" >&2
    echo "  export LOCAL_ANTHROPIC_BASE_URL=\"http://llm.dev-ai.university.edu/cc/v1\"" >&2
    echo "" >&2
    echo "Optionally, also set local model names:" >&2
    echo "  export LOCAL_ANTHROPIC_DEFAULT_HAIKU_MODEL=\"hc/minimax-m2p5\"" >&2
    echo "  export LOCAL_ANTHROPIC_DEFAULT_SONNET_MODEL=\"hc/minimax-m2p5\"" >&2
    echo "  export LOCAL_ANTHROPIC_DEFAULT_OPUS_MODEL=\"hc/minimax-m2p5\"" >&2
    exit 1
  fi

  # Set ANTHROPIC_BASE_URL from LOCAL_ANTHROPIC_BASE_URL
  export ANTHROPIC_BASE_URL="$LOCAL_ANTHROPIC_BASE_URL"
  export ANTHROPIC_API_KEY="sk-ant-dummy"

  # Set model configurations if LOCAL_* variants exist
  [[ -n "$LOCAL_ANTHROPIC_DEFAULT_HAIKU_MODEL" ]] && export ANTHROPIC_DEFAULT_HAIKU_MODEL="$LOCAL_ANTHROPIC_DEFAULT_HAIKU_MODEL"
  [[ -n "$LOCAL_ANTHROPIC_DEFAULT_SONNET_MODEL" ]] && export ANTHROPIC_DEFAULT_SONNET_MODEL="$LOCAL_ANTHROPIC_DEFAULT_SONNET_MODEL"
  [[ -n "$LOCAL_ANTHROPIC_DEFAULT_OPUS_MODEL" ]] && export ANTHROPIC_DEFAULT_OPUS_MODEL="$LOCAL_ANTHROPIC_DEFAULT_OPUS_MODEL"
  USING_LOCAL=1

  # Offer to persist settings to ~/.claude/claudelocalrc if it doesn't exist yet
  if [[ ! -f "$HOME/.claude/claudelocalrc" ]] && [[ -t 0 ]]; then
    echo "" >&2
    echo -e "${YELLOW}Local LLM vars are set but ~/.claude/claudelocalrc does not exist.${NC}" >&2
    read -p "Save these settings to ~/.claude/claudelocalrc for future sessions? (Y/n): " _save_local
    if [[ -z "$_save_local" || "$_save_local" == "y" || "$_save_local" == "Y" ]]; then
      mkdir -p "$HOME/.claude"
      {
        echo "export LOCAL_ANTHROPIC_BASE_URL=\"$LOCAL_ANTHROPIC_BASE_URL\""
        [[ -n "$LOCAL_ANTHROPIC_DEFAULT_HAIKU_MODEL" ]]  && echo "export LOCAL_ANTHROPIC_DEFAULT_HAIKU_MODEL=\"$LOCAL_ANTHROPIC_DEFAULT_HAIKU_MODEL\""
        [[ -n "$LOCAL_ANTHROPIC_DEFAULT_SONNET_MODEL" ]] && echo "export LOCAL_ANTHROPIC_DEFAULT_SONNET_MODEL=\"$LOCAL_ANTHROPIC_DEFAULT_SONNET_MODEL\""
        [[ -n "$LOCAL_ANTHROPIC_DEFAULT_OPUS_MODEL" ]]   && echo "export LOCAL_ANTHROPIC_DEFAULT_OPUS_MODEL=\"$LOCAL_ANTHROPIC_DEFAULT_OPUS_MODEL\""
      } > "$HOME/.claude/claudelocalrc"
      echo -e "${GREEN}${NC} Saved to ~/.claude/claudelocalrc" >&2
    fi
    echo "" >&2
  fi

# Check if ANTHROPIC_BASE_URL is already set (for local LLM usage without --local flag)
elif [[ -n "$ANTHROPIC_BASE_URL" ]]; then
  # 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"
  fi

# Foundry Configuration - use Azure AI Foundry if CLAUDE_CODE_USE_FOUNDRY=1
elif [[ "${CLAUDE_CODE_USE_FOUNDRY:-0}" == "1" ]]; then
  if [[ -z "$ANTHROPIC_FOUNDRY_BASE_URL" || -z "$ANTHROPIC_FOUNDRY_API_KEY" ]]; then
    echo -e "${RED}✗ Error: CLAUDE_CODE_USE_FOUNDRY=1 but required variables are not set${NC}" >&2
    echo "" >&2
    echo "Both of these must be set in your ~/.profile (or ~/.bashrc / ~/.zshrc):" >&2
    echo "" >&2
    echo "  export ANTHROPIC_FOUNDRY_BASE_URL=\"https://<your-endpoint>.openai.azure.com/...\"" >&2
    echo "  export ANTHROPIC_FOUNDRY_API_KEY=\"<your-foundry-api-key>\"" >&2
    echo "" >&2
    [[ -z "$ANTHROPIC_FOUNDRY_BASE_URL" ]] && echo "  Missing: ANTHROPIC_FOUNDRY_BASE_URL" >&2
    [[ -z "$ANTHROPIC_FOUNDRY_API_KEY" ]]  && echo "  Missing: ANTHROPIC_FOUNDRY_API_KEY" >&2
    echo "" >&2
    exit 1
  fi
  export ANTHROPIC_BASE_URL="$ANTHROPIC_FOUNDRY_BASE_URL"
  export ANTHROPIC_API_KEY="sk-ant-dummy"
  USING_FOUNDRY=1

  # Offer to persist settings to ~/.azure/clauderc if it doesn't exist yet
  if [[ ! -f "$HOME/.azure/clauderc" ]] && [[ -t 0 ]]; then
    echo "" >&2
    echo -e "${YELLOW}Azure Foundry vars are set but ~/.azure/clauderc does not exist.${NC}" >&2
    read -p "Save these settings to ~/.azure/clauderc for future sessions? (Y/n): " _save_confirm
    if [[ -z "$_save_confirm" || "$_save_confirm" == "y" || "$_save_confirm" == "Y" ]]; then
      mkdir -p "$HOME/.azure"
      cat > "$HOME/.azure/clauderc" <<EOF
export CLAUDE_CODE_USE_FOUNDRY=1
export ANTHROPIC_FOUNDRY_BASE_URL="$ANTHROPIC_FOUNDRY_BASE_URL"
export ANTHROPIC_FOUNDRY_API_KEY="$ANTHROPIC_FOUNDRY_API_KEY"
EOF
      echo -e "${GREEN}${NC} Saved to ~/.azure/clauderc" >&2
    fi
    echo "" >&2
  fi

# Default: AWS Bedrock Configuration - only enable if bedrock is configured
elif grep -q "bedrock" "$HOME/.aws/config" 2>/dev/null; then
  export CLAUDE_CODE_USE_BEDROCK=1
  export AWS_DEFAULT_REGION=us-west-2
  export AWS_PROFILE=bedrock

# No valid configuration found
else
  echo -e "${RED}✗ Error: No valid configuration found${NC}" >&2
  echo "" >&2
  echo "Options to fix this:" >&2
  echo "" >&2
  echo "1. Use Azure AI Foundry — paste these exports into your shell (or add to ~/.bashrc):" >&2
  echo "   export CLAUDE_CODE_USE_FOUNDRY=1" >&2
  echo "   export ANTHROPIC_FOUNDRY_BASE_URL=https://xxxxxxxxxxxxx.azure-api.net/anthropic" >&2
  echo "   export ANTHROPIC_FOUNDRY_API_KEY=xxxxxxxxxxxxxxxxxx" >&2
  echo "" >&2
  echo "2. Configure AWS Bedrock — get AWS creds and run:" >&2
  echo "   aws configure --profile bedrock" >&2
  echo "" >&2
  echo "3. Use --local flag with a local LLM endpoint:" >&2
  echo "   claude --local" >&2
  echo "" >&2
  echo "4. Bypass this wrapper and run Claude Code directly:" >&2
  echo "   ~/.local/bin/claude" >&2
  exit 1
fi

# Model Configuration — Foundry uses plain model names; Bedrock uses prefixed IDs
if [[ "${USING_FOUNDRY:-0}" == "1" ]]; then
  export ANTHROPIC_DEFAULT_SONNET_MODEL="${ANTHROPIC_DEFAULT_SONNET_MODEL:-claude-sonnet-4-6}"
  export ANTHROPIC_DEFAULT_HAIKU_MODEL="${ANTHROPIC_DEFAULT_HAIKU_MODEL:-claude-haiku-4-5}"
  export ANTHROPIC_DEFAULT_OPUS_MODEL="${ANTHROPIC_DEFAULT_OPUS_MODEL:-claude-opus-4-7}"
else
  export ANTHROPIC_DEFAULT_HAIKU_MODEL="${ANTHROPIC_DEFAULT_HAIKU_MODEL:-us.anthropic.claude-haiku-4-5-20251001-v1:0}"
  export ANTHROPIC_DEFAULT_SONNET_MODEL="${ANTHROPIC_DEFAULT_SONNET_MODEL:-global.anthropic.claude-sonnet-4-6}"
  export ANTHROPIC_DEFAULT_OPUS_MODEL="${ANTHROPIC_DEFAULT_OPUS_MODEL:-global.anthropic.claude-opus-4-7}"
fi
export ANTHROPIC_SMALL_FAST_MODEL="${ANTHROPIC_DEFAULT_HAIKU_MODEL}"

# Default model is Haiku
mymodel="${ANTHROPIC_DEFAULT_HAIKU_MODEL}"
model_name="haiku"

# Scan all arguments for model selection (allows e.g. "claude -c opus")
wdebug=0
new_args=()
for arg in "$@"; do
  case "$arg" in
    opus-1m)
      mymodel="${ANTHROPIC_DEFAULT_OPUS_MODEL}[1m]"
      model_name="opus-1m"
      ;;
    opus)
      mymodel="${ANTHROPIC_DEFAULT_OPUS_MODEL}"
      model_name="opus"
      ;;
    sonnet-1m)
      mymodel="${ANTHROPIC_DEFAULT_SONNET_MODEL}[1m]"
      model_name="sonnet-1m"
      ;;
    sonnet)
      mymodel="${ANTHROPIC_DEFAULT_SONNET_MODEL}"
      model_name="sonnet"
      ;;
    haiku)
      mymodel="${ANTHROPIC_DEFAULT_HAIKU_MODEL}"
      model_name="haiku"
      ;;
    --wdebug)
      wdebug=1
      ;;
    *)
      new_args+=("$arg")
      ;;
  esac
done
set -- "${new_args[@]}"

export ANTHROPIC_MODEL="$mymodel"

# Show status message for Foundry / local / custom base URL
if [[ "${USING_FOUNDRY:-0}" == "1" ]]; then
  _msg="Foundry Model: $mymodel, URL: $ANTHROPIC_BASE_URL"
  [[ "$CLAUDERC_LOADED" == "1" ]] && _msg="Reading ~/.azure/clauderc, $_msg"
  echo -e "${GREEN}${_msg}${NC}" >&2
elif [[ "${USING_LOCAL:-0}" == "1" ]]; then
  _msg="Local Model: $mymodel, URL: $ANTHROPIC_BASE_URL"
  [[ "$LOCALRC_LOADED" == "1" ]] && _msg="Reading ~/.claude/claudelocalrc, $_msg"
  echo -e "${GREEN}${_msg}${NC}" >&2
elif [[ -n "$ANTHROPIC_BASE_URL" ]]; then
  echo -e "${GREEN}Local Model: $mymodel, URL: $ANTHROPIC_BASE_URL${NC}" >&2
fi

# Show debug info if --wdebug was requested
if [[ "$wdebug" -eq 1 ]]; then
  echo -e "${YELLOW}=== claude-wrapper debug ===${NC}" >&2
  echo "" >&2
  echo "Environment variables set by wrapper:" >&2
  for var in ANTHROPIC_MODEL ANTHROPIC_BASE_URL ANTHROPIC_API_KEY \
             ANTHROPIC_DEFAULT_HAIKU_MODEL ANTHROPIC_DEFAULT_SONNET_MODEL \
             ANTHROPIC_DEFAULT_OPUS_MODEL ANTHROPIC_SMALL_FAST_MODEL \
             CLAUDE_CODE_USE_FOUNDRY ANTHROPIC_FOUNDRY_BASE_URL \
             CLAUDE_CODE_USE_BEDROCK AWS_DEFAULT_REGION AWS_PROFILE; do
    if [[ -n "${!var+x}" ]]; then
      echo "  $var=${!var}" >&2
    fi
  done
  echo "" >&2
  if [[ -f "$HOME/.claude/yolo-mode" ]]; then
    echo "Command: $REAL_CLAUDE --model $mymodel --dangerously-skip-permissions $*" >&2
  else
    echo "Command: $REAL_CLAUDE --model $mymodel --allowedTools <list> --disallowedTools <list> $*" >&2
  fi
  echo "" >&2
fi

# Block running Claude Code directly in the home directory
if [[ "$PWD" == "$HOME" ]]; then
  echo -e "${RED}✗ Error: Do not run Claude Code directly in your home directory${NC}" >&2
  echo "" >&2
  echo "Please create a project folder first, for example:" >&2
  echo "" >&2
  echo "  mkdir ~/myproject && cd ~/myproject && git init" >&2
  echo "" >&2
  exit 1
fi

# Check if current directory is inside a git repository
if ! git rev-parse --git-dir > /dev/null 2>&1; then
  echo -e "${YELLOW}⚠ Warning: Current directory is not a git repository${NC}" >&2
  echo "" >&2
  echo "You can do one of these two things:" >&2
  echo "" >&2
  echo "  1. Clone an existing repository from GitHub:" >&2
  echo "     git clone https://github.com/username/repo-name" >&2
  echo "     cd repo-name" >&2
  echo "" >&2
  echo "  2. Initialize a new local git repository (for a quick test):" >&2
  echo "     git init" >&2
  echo "" >&2
  exit 1
fi

# Allowed/disallowed tools for non-yolo mode
ALLOWED_TOOLS=(
  "Read" "Edit" "WebFetch" "Agent"
  "Bash(ls *)" "Bash(cat *)" "Bash(head *)" "Bash(tail *)" "Bash(less *)"
  "Bash(find *)" "Bash(tree *)" "Bash(file *)" "Bash(stat *)" "Bash(wc *)"
  "Bash(du *)" "Bash(df *)" "Bash(grep *)" "Bash(awk *)" "Bash(sed *)"
  "Bash(sort *)" "Bash(uniq *)" "Bash(cut *)" "Bash(tr *)" "Bash(diff *)"
  "Bash(jq *)" "Bash(yq *)" "Bash(xargs *)" "Bash(tee *)"
  "Bash(cp *)" "Bash(mv *)" "Bash(mkdir *)" "Bash(touch *)" "Bash(chmod *)"
  "Bash(chown *)" "Bash(ln *)" "Bash(tar *)" "Bash(zip *)" "Bash(unzip *)"
  "Bash(gzip *)" "Bash(gunzip *)"
  "Bash(curl *)" "Bash(wget *)" "Bash(ping *)" "Bash(dig *)"
  "Bash(nslookup *)" "Bash(host *)" "Bash(ss *)" "Bash(netstat *)"
  "Bash(ip *)" "Bash(ifconfig *)" "Bash(traceroute *)" "Bash(nmap *)"
  "Bash(ssh *)" "Bash(scp *)" "Bash(rsync *)"
  "Bash(ps *)" "Bash(top *)" "Bash(htop *)" "Bash(kill *)" "Bash(killall *)"
  "Bash(uptime *)" "Bash(free *)" "Bash(uname *)" "Bash(hostname *)"
  "Bash(whoami *)" "Bash(id *)" "Bash(w *)" "Bash(who *)" "Bash(lsof *)"
  "Bash(strace *)" "Bash(systemctl *)" "Bash(journalctl *)" "Bash(service *)"
  "Bash(apt *)" "Bash(apt-get *)" "Bash(yum *)" "Bash(dnf *)" "Bash(brew *)"
  "Bash(snap *)" "Bash(pip *)" "Bash(pip3 *)" "Bash(npm *)" "Bash(npx *)"
  "Bash(docker *)" "Bash(docker-compose *)" "Bash(kubectl *)" "Bash(helm *)"
  "Bash(terraform *)" "Bash(ansible *)"
  "Bash(aws *)" "Bash(az *)" "Bash(gcloud *)" "Bash(git *)" "Bash(gh *)"
  "Bash(appmo *)"
  "Bash(python *)" "Bash(python3 *)" "Bash(node *)"
  "Bash(bash *)" "Bash(sh *)" "Bash(env *)" "Bash(export *)"
  "Bash(echo *)" "Bash(printf *)" "Bash(date *)"
  "Bash(cron *)" "Bash(crontab *)"
  "Bash(mount *)" "Bash(umount *)" "Bash(fdisk *)" "Bash(lsblk *)" "Bash(blkid *)"
  "Bash(openssl *)" "Bash(ssh-keygen *)" "Bash(iptables *)" "Bash(ufw *)"
  "Bash(sudo *)"
  "Bash(* --version)" "Bash(* --help)" "Bash(* --help *)"
  "Bash(which *)" "Bash(type *)" "Bash(man *)"
)
DISALLOWED_TOOLS=(
  "Bash(rm -rf /)"
  "Bash(rm -rf /*)"
  "Bash(mkfs *)"
  "Bash(dd *)"
  "Bash(:(){ :|:& };:)"
)

# Execute Claude Code (skip permissions only if ~/.claude/yolo-mode exists)
if [[ -f "$HOME/.claude/yolo-mode" ]]; then
  exec "$REAL_CLAUDE" --model "$mymodel" --dangerously-skip-permissions "$@"
else
  exec "$REAL_CLAUDE" --model "$mymodel" \
    --allowedTools "${ALLOWED_TOOLS[@]}" \
    --disallowedTools "${DISALLOWED_TOOLS[@]}" \
    "$@"
fi

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

4. (Optional) Using a Local LLM with --local Flag

If you have a local LLM endpoint available, you can use the --local flag to bypass AWS Bedrock and connect to your local server.

Set the variables and run once — the wrapper will offer to save them to ~/.claude/claudelocalrc:

1
2
3
4
5
6
7
8
export LOCAL_ANTHROPIC_BASE_URL="http://llm.dev-ai.university.edu/cc/v1"

# Optionally set model names (defaults to whatever the endpoint provides)
export LOCAL_ANTHROPIC_DEFAULT_HAIKU_MODEL="hc/glm-4.7"
export LOCAL_ANTHROPIC_DEFAULT_SONNET_MODEL="hc/glm-4.7"
export LOCAL_ANTHROPIC_DEFAULT_OPUS_MODEL="hc/glm-4.7"

claude --local

On first use the wrapper prompts:

1
2
Local LLM vars are set but ~/.claude/claudelocalrc does not exist.
Save these settings to ~/.claude/claudelocalrc for future sessions? (Y/n):

After saving, every subsequent launch sources ~/.claude/claudelocalrc automatically and shows:

1
Reading ~/.claude/claudelocalrc, Local Model: hc/glm-4.7, URL: http://llm.dev-ai.university.edu/cc/v1

Environment Variables:

  • LOCAL_ANTHROPIC_BASE_URL (required): URL to your local LLM endpoint
  • LOCAL_ANTHROPIC_DEFAULT_HAIKU_MODEL (optional): Model name for fast inference
  • LOCAL_ANTHROPIC_DEFAULT_SONNET_MODEL (optional): Model name for balanced inference
  • LOCAL_ANTHROPIC_DEFAULT_OPUS_MODEL (optional): Model name for maximum capability

Using --local without setting LOCAL_ANTHROPIC_BASE_URL

If you run claude --local without setting LOCAL_ANTHROPIC_BASE_URL, you'll see an error message with setup instructions.

5. (Optional) Using Microsoft Azure AI Foundry Instead of AWS Bedrock

If you have access to Azure AI Foundry, you can use it as a drop-in replacement for AWS Bedrock — no AWS credentials required.

Set the variables and run once — the wrapper will offer to save them to ~/.azure/clauderc:

1
2
3
4
5
export CLAUDE_CODE_USE_FOUNDRY=1
export ANTHROPIC_FOUNDRY_BASE_URL="https://<your-endpoint>.services.ai.azure.com/..."
export ANTHROPIC_FOUNDRY_API_KEY="<your-foundry-api-key>"

claude

On first use the wrapper prompts:

1
2
Azure Foundry vars are set but ~/.azure/clauderc does not exist.
Save these settings to ~/.azure/clauderc for future sessions? (Y/n):

After saving, every subsequent launch sources ~/.azure/clauderc automatically and shows:

1
Reading ~/.azure/clauderc, Foundry Model: claude-haiku-4-5, URL: https://<your-endpoint>.services.ai.azure.com/...

When CLAUDE_CODE_USE_FOUNDRY=1 is active, model defaults switch to plain Anthropic model names (no Bedrock prefixes):

Alias Default model
haiku claude-haiku-4-5
sonnet claude-sonnet-4-6
opus claude-opus-4-7

You can override any of these by adding them to ~/.azure/clauderc:

1
2
export ANTHROPIC_DEFAULT_OPUS_MODEL="claude-opus-4-7"
export ANTHROPIC_DEFAULT_SONNET_MODEL="claude-sonnet-4-6"

Both Foundry variables must be set

If CLAUDE_CODE_USE_FOUNDRY=1 but either ANTHROPIC_FOUNDRY_BASE_URL or ANTHROPIC_FOUNDRY_API_KEY is missing, the wrapper exits immediately with an error listing exactly which variable needs to be set.

Backend priority (highest to lowest):

  1. --local flag (always wins)
  2. ANTHROPIC_BASE_URL already set in environment
  3. CLAUDE_CODE_USE_FOUNDRY=1
  4. AWS Bedrock profile in ~/.aws/config

6. (Optional) Using Claude Code Natively on Windows via PowerShell

If you are running Claude Code directly on Windows (not inside WSL), configure the required environment variables in your PowerShell profile so they are set automatically in every session.

First, check whether the profile file exists — on a fresh Windows install it often does not:

1
Test-Path $PROFILE.CurrentUserAllHosts

If it returns False, create it (run PowerShell as Administrator once):

1
New-Item -Path $PROFILE.CurrentUserAllHosts -ItemType File -Force

Then open it in an editor:

1
notepad $PROFILE.CurrentUserAllHosts

Managing both backends in one profile

The recommended approach is to keep both backend configurations in your profile and comment out whichever one is not active. To switch backends, swap which block is commented out and reload the profile.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# My PowerShell-only environment variables
# ─────────────────────────────────────────
# Switch backends by commenting/uncommenting the relevant block,
# then reload with: . $PROFILE.CurrentUserAllHosts

# ── Option A: AWS Bedrock ──────────────────────────────────────
# $env:CLAUDE_CODE_USE_BEDROCK               = 1
# $env:CLAUDE_CODE_USE_FOUNDRY               = 0
# $env:AWS_REGION                            = "us-west-2"
# $env:AWS_PROFILE                           = "bedrock"
# $env:ANTHROPIC_SMALL_FAST_MODEL_AWS_REGION = $env:AWS_REGION
# $env:ANTHROPIC_DEFAULT_OPUS_MODEL          = "global.anthropic.claude-opus-4-7"
# $env:ANTHROPIC_DEFAULT_SONNET_MODEL        = "global.anthropic.claude-sonnet-4-6"

# ── Option B: Azure AI Foundry ────────────────────────────────
$env:CLAUDE_CODE_USE_FOUNDRY        = 1
$env:CLAUDE_CODE_USE_BEDROCK        = 0
$env:ANTHROPIC_FOUNDRY_BASE_URL     = "https://xxxxxxxxxxxx.azure-api.net/anthropic"
$env:ANTHROPIC_FOUNDRY_API_KEY      = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
$env:ANTHROPIC_DEFAULT_OPUS_MODEL   = "claude-opus-4-7"
$env:ANTHROPIC_DEFAULT_SONNET_MODEL = "claude-sonnet-4-6"

AWS Bedrock notes: credentials must also be present in ~\.aws\credentials under a [bedrock] profile (see step 1). The PowerShell variables handle region and model selection; the credentials file handles authentication.

Azure AI Foundry notes: replace the URL and API key with the values from your Azure AI Foundry deployment. No ~\.aws\credentials file is needed.

Switching backends

1
2
3
4
5
6
7
# 1. Open the profile
notepad $PROFILE.CurrentUserAllHosts

# 2. Comment out the active block, uncomment the other

# 3. Reload without restarting the terminal
. $PROFILE.CurrentUserAllHosts

Installing Claude Code on Windows

Install Claude Code via npm (requires Node.js):

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

Then run it from any directory that contains a git repo:

1
2
cd C:\Users\you\gh\myproject
claude

WSL users

If you are running Claude Code inside WSL, use the Linux-based setup in steps 1–5 above. The PowerShell profile approach is only needed when running claude natively from a Windows terminal. For a one-command WSL appliance setup, see the WSL Coding Appliance guide.

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

Debugging Wrapper Configuration (--wdebug)

Use --wdebug to inspect all environment variables set by the wrapper and the exact command that will be passed to Claude Code, before committing to running it:

1
2
3
claude --wdebug
claude --wdebug sonnet
claude --wdebug opus --resume

The flag can appear anywhere in the argument list. It prints a summary to stderr, then prompts:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
=== claude-wrapper debug ===

Environment variables set by wrapper:
  ANTHROPIC_MODEL=global.anthropic.claude-sonnet-4-6
  ANTHROPIC_DEFAULT_HAIKU_MODEL=us.anthropic.claude-haiku-4-5-20251001-v1:0
  ANTHROPIC_DEFAULT_SONNET_MODEL=global.anthropic.claude-sonnet-4-6
  ANTHROPIC_DEFAULT_OPUS_MODEL=global.anthropic.claude-opus-4-7
  ANTHROPIC_SMALL_FAST_MODEL=us.anthropic.claude-haiku-4-5-20251001-v1:0
  CLAUDE_CODE_USE_BEDROCK=1
  AWS_DEFAULT_REGION=us-west-2
  AWS_PROFILE=bedrock

Command: /home/user/.local/bin/claude --model global.anthropic.claude-sonnet-4-6 --allowedTools <list> --disallowedTools <list>

Execute Claude Code now? (y/n):

Answering y launches Claude Code; anything else exits without running it. This is especially useful when troubleshooting model selection, local LLM routing, or Bedrock credential issues.

Configuration Notes

  • Inference Backend: AWS Bedrock (default), Azure AI Foundry (CLAUDE_CODE_USE_FOUNDRY=1), or local LLM (--local)
  • Permissions: By default the wrapper uses --allowedTools / --disallowedTools to allow a broad but safe set of commands. To grant full unrestricted permissions, create ~/.claude/yolo-mode:
    1
    2
    touch ~/.claude/yolo-mode   # enables --dangerously-skip-permissions
    rm ~/.claude/yolo-mode      # reverts to allowedTools mode
    
  • Model Selection: Easy switching between Haiku (fast), Sonnet (balanced), and Opus (capable)
  • Debug Mode: Use --wdebug to inspect environment variables and the final command before execution

Secure Setup: Sandboxed Claude Code with Bubblewrap

For maximum security when working with sensitive projects, you can run Claude Code in an isolated sandbox using bubblewrap (bwrap). This approach protects your AWS credentials and SSH keys by:

  • Limiting AWS credentials to only the Bedrock profile (not your entire credentials file)
  • Making SSH keys available read-only for authentication without exposing private keys
  • Creating temporary in-memory filesystems for sensitive data that are wiped when the session ends

Prerequisites

Install bubblewrap on your system:

1
2
3
4
5
6
7
8
# Ubuntu/Debian
sudo apt-get install bubblewrap

# macOS (via Homebrew)
brew install bubblewrap

# Fedora/RHEL
sudo dnf install bubblewrap

Setup Steps

1. Extract Bedrock credentials to a separate file:

1
grep -F -A 2 '[bedrock]' ~/.aws/credentials > ~/.aws/credentials.bedrock

This creates a minimal credentials file containing only your Bedrock profile.

2. Run Claude Code in sandbox:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
bwrap \
  --bind / / \
  --tmpfs ~/.ssh \
  --bind ~/.ssh/known_hosts ~/.ssh/known_hosts \
  --bind ~/.ssh/config ~/.ssh/config \
  --tmpfs ~/.aws \
  --bind ~/.aws/credentials.bedrock ~/.aws/credentials \
  --bind ~/.aws/config ~/.aws/config \
  --dev /dev \
  --proc /proc \
  claude

How It Works

  • --bind / / - Bind the entire filesystem as read-only
  • --tmpfs ~/.ssh - Create an in-memory temporary filesystem for SSH (sensitive keys stay in agent memory)
  • --bind ~/.ssh/known_hosts - Mount only the known hosts file (no private keys)
  • --bind ~/.aws/credentials.bedrock ~/.aws/credentials - Mount only Bedrock credentials, not your full credentials file
  • --tmpfs ~/.aws - Create in-memory temporary filesystem for AWS config
  • --dev /dev --proc /proc - Provide necessary system interfaces

Security Benefits

Limited AWS Credentials - Claude Code only sees the Bedrock profile, not other AWS account credentials ✅ SSH Key Protection - Private keys remain in ssh-agent; Claude Code only accesses known hosts ✅ Temporary Storage - Sensitive files exist only in RAM and are wiped when the sandbox exits ✅ Filesystem Isolation - Reduces risk of accidental credential leakage to untrusted projects

Convenience vs Security Trade-off

This sandbox approach provides maximum security but requires more setup. For routine development with trusted projects, the standard setup is sufficient. Use sandboxing for sensitive work or unfamiliar codebases.

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 the correct backend is configured: AWS Bedrock (~/.aws/config), Foundry (~/.azure/clauderc), or local LLM (~/.claude/claudelocalrc)
  • The wrapper auto-detects the active backend and applies the right model IDs and API settings
  • Use --wdebug to troubleshoot configuration issues — it shows every env var set by the wrapper and the exact command before running
  • Use touch ~/.claude/yolo-mode to enable full permissions; remove it to return to the safe allowedTools default

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 -fsSL "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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
#!/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=""
  local is_tcsh=false

  # 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"
      ;;
    tcsh|csh)
      shell_rc="$HOME/.tcshrc"
      shell_name="Tcsh"
      is_tcsh=true
      ;;
    *)
      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' "$shell_rc" 2>/dev/null; then
    echo -e "${GREEN}${NC} NVM already configured in $shell_name configuration"
    return 0
  fi

  # For tcsh, use tcsh-compatible syntax
  if [[ "$is_tcsh" == true ]]; then
    # Add NVM initialization to tcsh RC file
    echo "" >> "$shell_rc"
    echo "# ========== NVM (Node Version Manager) initialization ==========" >> "$shell_rc"
    echo "setenv NVM_DIR \"\$HOME/.nvm\"" >> "$shell_rc"
    echo "# NVM is primarily designed for bash/zsh. For tcsh, we add NVM bin to PATH." >> "$shell_rc"
    echo "# After running 'nvm use <version>' in bash, the node/npm binaries will be available." >> "$shell_rc"
    echo "if ( -d \"\$NVM_DIR/versions/node\" ) then" >> "$shell_rc"
    echo "  set nvm_node_dir = \`ls -1 \$NVM_DIR/versions/node | tail -1\`" >> "$shell_rc"
    echo "  if ( \"\$nvm_node_dir\" != \"\" ) then" >> "$shell_rc"
    echo "    setenv PATH \"\$NVM_DIR/versions/node/\$nvm_node_dir/bin:\$PATH\"" >> "$shell_rc"
    echo "  endif" >> "$shell_rc"
    echo "endif" >> "$shell_rc"
    echo "# ================================================================" >> "$shell_rc"

    echo -e "${GREEN}${NC} Added NVM initialization to $shell_name configuration"
    echo -e "${YELLOW}Note: NVM functions (nvm use, nvm install) require bash. Node/npm will be in PATH.${NC}"
  else
    # Remove commented-out NVM lines if they exist (bash/zsh only)
    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"
  fi
  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 -fsSL -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 ""
case "${SHELL##*/}" in
  zsh)
    echo "  source ~/.zshrc"
    ;;
  tcsh|csh)
    echo "  source ~/.tcshrc"
    ;;
  *)
    echo "  source ~/.bashrc"
    ;;
esac
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.