22set -eu
33
44# Claude Code CLI Local Feature Install Script
5- # Based on: https://github.com/anthropics/devcontainer-features/pull/25
6- # Combines CLI installation with configuration directory setup
5+ # Installs Claude Code via pixi and sets up configuration directories
76
8- # Function to install Claude Code CLI
9- install_claude_code () {
10- echo " Installing Claude Code CLI globally ..."
7+ # Function to install pixi if not found
8+ install_pixi () {
9+ echo " Installing pixi ..."
1110
12- # Verify Node.js and npm are available (should be installed via dependsOn)
13- if ! command -v node > /dev/null || ! command -v npm > /dev/null; then
14- cat << EOF
11+ # Detect architecture
12+ case " $( uname -m) " in
13+ x86_64|amd64) ARCH=" x86_64" ;;
14+ aarch64|arm64) ARCH=" aarch64" ;;
15+ * ) echo " Unsupported architecture: $( uname -m) " >&2 ; exit 1 ;;
16+ esac
1517
16- ERROR: Node.js and npm are required but not found!
18+ # Download and install pixi
19+ curl -fsSL " https://github.com/prefix-dev/pixi/releases/latest/download/pixi-${ARCH} -unknown-linux-musl" -o /usr/local/bin/pixi
20+ chmod +x /usr/local/bin/pixi
1721
18- This should not happen as the Node.js feature is declared in 'dependsOn'.
22+ echo " pixi installed successfully"
23+ pixi --version
24+ }
1925
20- Please check:
21- 1. The devcontainer feature specification is correct
22- 2. The Node.js feature (ghcr.io/devcontainers/features/node) is available
23- 3. Your devcontainer build logs for errors
26+ # Function to install Claude Code CLI via pixi
27+ install_claude_code () {
28+ echo " Installing Claude Code CLI via pixi..."
2429
25- EOF
26- exit 1
30+ # Install pixi if not available
31+ if ! command -v pixi > /dev/null; then
32+ install_pixi
2733 fi
2834
29- # Install with npm
30- npm install -g @anthropic-ai/claude-code
35+ # Determine target user for pixi global install
36+ local target_user=" ${_REMOTE_USER:- vscode} "
37+ local target_home=" ${_REMOTE_USER_HOME:-/ home/ ${target_user} } "
38+
39+ # Install with pixi global from blooop channel
40+ # Run as target user so it installs to their home directory
41+ if [ " $( id -u) " -eq 0 ] && [ " $target_user " != " root" ]; then
42+ su - " $target_user " -c " pixi global install --channel https://prefix.dev/blooop claude-shim"
43+ else
44+ pixi global install --channel https://prefix.dev/blooop claude-shim
45+ fi
3146
32- # Verify installation
33- if command -v claude > /dev/null; then
47+ # Add pixi paths to user's profile if not already there
48+ local profile=" $target_home /.profile"
49+ local pixi_path_line=' export PATH="$HOME/.pixi/envs/claude-shim/bin:$HOME/.pixi/bin:$PATH"'
50+ if [ -f " $profile " ] && ! grep -q " \.pixi/envs/claude-shim/bin" " $profile " ; then
51+ echo " $pixi_path_line " >> " $profile "
52+ elif [ ! -f " $profile " ]; then
53+ echo " $pixi_path_line " > " $profile "
54+ chown " $target_user :$target_user " " $profile " 2> /dev/null || true
55+ fi
56+
57+ # Verify installation by checking the binary exists
58+ local pixi_bin_path=" $target_home /.pixi/bin"
59+ local claude_bin=" $pixi_bin_path /claude"
60+ if [ -x " $claude_bin " ]; then
3461 echo " Claude Code CLI installed successfully!"
35- claude --version
62+ " $claude_bin " --version
3663 return 0
3764 else
38- echo " ERROR: Claude Code CLI installation failed!"
65+ echo " ERROR: Claude Code CLI installation failed! Binary not found at $claude_bin "
3966 return 1
4067 fi
4168}
4269
4370# Function to create Claude configuration directories
44- # These directories will be mounted from the host, but we create them
45- # in the container to ensure they exist and have proper permissions
4671create_claude_directories () {
4772 echo " Creating Claude configuration directories..."
4873
4974 # Determine the target user's home directory
50- # $_REMOTE_USER is set by devcontainer, fallback to 'vscode'
5175 local target_user=" ${_REMOTE_USER:- vscode} "
5276 local target_home=" ${_REMOTE_USER_HOME:-/ home/ ${target_user} } "
5377
54- # Be defensive: if the resolved home does not exist, fall back to $HOME,
55- # then to /home/${target_user}. If neither is available, fail clearly.
78+ # Be defensive: if the resolved home does not exist, fall back
5679 if [ ! -d " $target_home " ]; then
5780 if [ -n " ${HOME:- } " ] && [ -d " $HOME " ]; then
5881 echo " Warning: target_home '$target_home ' does not exist, falling back to \$ HOME: $HOME " >&2
@@ -61,26 +84,21 @@ create_claude_directories() {
6184 echo " Warning: target_home '$target_home ' does not exist, falling back to /home/${target_user} " >&2
6285 target_home=" /home/${target_user} "
6386 else
64- echo " Error: No suitable home directory found for '${target_user} '. Tried:" >&2
65- echo " - _REMOTE_USER_HOME='${_REMOTE_USER_HOME:- } '" >&2
66- echo " - \$ HOME='${HOME:- } '" >&2
67- echo " - /home/${target_user} " >&2
68- echo " Please set _REMOTE_USER_HOME to a valid, writable directory." >&2
87+ echo " Error: No suitable home directory found for '${target_user} '." >&2
6988 exit 1
7089 fi
7190 fi
7291
7392 echo " Target home directory: $target_home "
7493 echo " Target user: $target_user "
7594
76- # Create the main .claude directory
95+ # Create the main .claude directory and subdirectories
7796 mkdir -p " $target_home /.claude"
7897 mkdir -p " $target_home /.claude/agents"
7998 mkdir -p " $target_home /.claude/commands"
8099 mkdir -p " $target_home /.claude/hooks"
81100
82101 # Create empty config files if they don't exist
83- # This ensures the bind mounts won't fail if files are missing on host
84102 if [ ! -f " $target_home /.claude/.credentials.json" ]; then
85103 echo " {}" > " $target_home /.claude/.credentials.json"
86104 chmod 600 " $target_home /.claude/.credentials.json"
@@ -92,26 +110,28 @@ create_claude_directories() {
92110 fi
93111
94112 # Set proper ownership
95- # Note: These will be overridden by bind mounts from the host,
96- # but this ensures the directories exist with correct permissions
97- # if the mounts fail or for non-mounted directories
98113 if [ " $( id -u) " -eq 0 ]; then
99114 chown -R " $target_user :$target_user " " $target_home /.claude" || true
100115 fi
101116
102117 echo " Claude directories created successfully"
103118}
104119
105- # Main script starts here
120+ # Main script
106121main () {
107122 echo " ========================================="
108123 echo " Activating feature 'claude-code' (local)"
109124 echo " ========================================="
110125
111- # Install Claude Code CLI (or verify it's already installed)
112- if command -v claude > /dev/null; then
126+ # Determine target paths
127+ local target_user=" ${_REMOTE_USER:- vscode} "
128+ local target_home=" ${_REMOTE_USER_HOME:-/ home/ ${target_user} } "
129+ local claude_bin=" $target_home /.pixi/bin/claude"
130+
131+ # Install Claude Code CLI
132+ if [ -x " $claude_bin " ]; then
113133 echo " Claude Code CLI is already installed"
114- claude --version
134+ " $claude_bin " --version
115135 else
116136 install_claude_code || exit 1
117137 fi
@@ -123,27 +143,11 @@ main() {
123143 echo " Claude Code feature activated successfully!"
124144 echo " ========================================="
125145 echo " "
126- echo " Configuration files mounted from host:"
127- echo " Read-Write (auth & state):"
128- echo " - ~/.claude/.credentials.json (OAuth tokens)"
129- echo " - ~/.claude/.claude.json (account, setup tracking)"
130- echo " "
131- echo " Read-Only (security-protected):"
132- echo " - ~/.claude/CLAUDE.md"
133- echo " - ~/.claude/settings.json"
134- echo " - ~/.claude/agents/"
135- echo " - ~/.claude/commands/"
136- echo " - ~/.claude/hooks/"
137- echo " "
138- echo " Authentication:"
139- echo " - If you're already authenticated on your host, credentials are shared"
140- echo " - Otherwise, run 'claude' and follow the OAuth flow"
141- echo " - The OAuth callback may open in your host browser"
142- echo " - Credentials are stored on your host at ~/.claude/.credentials.json"
146+ echo " Configuration is stored in a Docker volume (claude-config)"
147+ echo " and persists between container rebuilds."
143148 echo " "
144- echo " To modify config files, edit on your host machine and rebuild the container ."
149+ echo " To authenticate, run 'claude' and follow the OAuth flow ."
145150 echo " "
146151}
147152
148- # Execute main function
149153main
0 commit comments