Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions build/docker-entrypoint
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ if [ -n "${CLAUDEBOX_PROJECT_NAME:-}" ]; then
runuser -u DOCKERUSER -- touch "$VENV_FLAG"
else
# Someone else is fixing it, wait for completion
local wait_count=0
wait_count=0
while [ ! -f "$VENV_FLAG" ] && [ $wait_count -lt 60 ]; do
sleep 0.5
((wait_count++)) || true
Expand All @@ -106,7 +106,7 @@ if [ -n "${CLAUDEBOX_PROJECT_NAME:-}" ]; then
runuser -u DOCKERUSER -- touch "$VENV_FLAG"
else
# Someone else is creating it, wait for completion flag
local wait_count=0
wait_count=0
while [ ! -f "$VENV_FLAG" ] && [ $wait_count -lt 60 ]; do
sleep 0.5
((wait_count++)) || true
Expand Down Expand Up @@ -138,7 +138,7 @@ if [ -n "${CLAUDEBOX_PROJECT_NAME:-}" ]; then
if [ -f "$CONFIG_FILE" ] && grep -qE 'python|ml|datascience' "$CONFIG_FILE"; then
if [ ! -f "$PYDEV_FLAG" ] && [ -f "$VENV_FLAG" ] && [ -d "$VENV_DIR" ]; then
# Deploy Python dev tools based on profile
local python_packages=""
python_packages=""

# Base Python profile packages
if grep -q 'python' "$CONFIG_FILE"; then
Expand Down
51 changes: 28 additions & 23 deletions lib/cli.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ readonly SCRIPT_COMMANDS=(shell create slot slots revoke profiles projects profi
# pass_through: Array of args to pass to Claude in container
# Note: Each argument goes into exactly ONE bucket - no duplication
parse_cli_args() {
local all_args=("$@")

# Initialize bucket arrays
host_flags=()
control_flags=()
Expand All @@ -31,7 +29,8 @@ parse_cli_args() {
# Single parsing loop - each arg goes into exactly ONE bucket
local found_script_command=false

for arg in "${all_args[@]}"; do
# Iterate directly over arguments (handles empty $@ with set -u)
for arg in "$@"; do
if [[ " ${HOST_ONLY_FLAGS[*]} " == *" $arg "* ]]; then
# Bucket 1: Host-only flags
host_flags+=("$arg")
Expand All @@ -48,28 +47,34 @@ parse_cli_args() {
fi
done

# Export results for use by main script
export CLI_HOST_FLAGS=("${host_flags[@]}")
export CLI_CONTROL_FLAGS=("${control_flags[@]}")
# Export results for use by main script (handle empty arrays with set -u)
CLI_HOST_FLAGS=("${host_flags[@]+"${host_flags[@]}"}")
export CLI_HOST_FLAGS
CLI_CONTROL_FLAGS=("${control_flags[@]+"${control_flags[@]}"}")
export CLI_CONTROL_FLAGS
export CLI_SCRIPT_COMMAND="$script_command"
export CLI_PASS_THROUGH=("${pass_through[@]}")
CLI_PASS_THROUGH=("${pass_through[@]+"${pass_through[@]}"}")
export CLI_PASS_THROUGH
}

# Process host-only flags and set environment variables
process_host_flags() {
for flag in "${CLI_HOST_FLAGS[@]}"; do
case "$flag" in
--verbose)
export VERBOSE=true
;;
rebuild)
export REBUILD=true
;;
tmux)
export CLAUDEBOX_WRAP_TMUX=true
;;
esac
done
# Handle empty array with set -u compatibility
if [ ${#CLI_HOST_FLAGS[@]} -gt 0 ]; then
for flag in "${CLI_HOST_FLAGS[@]}"; do
case "$flag" in
--verbose)
export VERBOSE=true
;;
rebuild)
export REBUILD=true
;;
tmux)
export CLAUDEBOX_WRAP_TMUX=true
;;
esac
done
fi
}

# Get command requirements - returns one of:
Expand Down Expand Up @@ -126,10 +131,10 @@ requires_slot() {
debug_parsed_args() {
if [[ "${VERBOSE:-false}" == "true" ]]; then
echo "[DEBUG] CLI Parser Results:" >&2
echo "[DEBUG] Host flags: ${CLI_HOST_FLAGS[*]}" >&2
echo "[DEBUG] Control flags: ${CLI_CONTROL_FLAGS[*]}" >&2
echo "[DEBUG] Host flags: ${CLI_HOST_FLAGS[*]+"${CLI_HOST_FLAGS[*]}"}" >&2
echo "[DEBUG] Control flags: ${CLI_CONTROL_FLAGS[*]+"${CLI_CONTROL_FLAGS[*]}"}" >&2
echo "[DEBUG] Script command: ${CLI_SCRIPT_COMMAND}" >&2
echo "[DEBUG] Pass-through: ${CLI_PASS_THROUGH[*]}" >&2
echo "[DEBUG] Pass-through: ${CLI_PASS_THROUGH[*]+"${CLI_PASS_THROUGH[*]}"}" >&2
fi
}

Expand Down
6 changes: 3 additions & 3 deletions lib/commands.core.sh
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,11 @@ _cmd_shell() {
trap cleanup_admin EXIT

if [[ "$VERBOSE" == "true" ]]; then
echo "[DEBUG] Running admin container with flags: ${shell_flags[*]}" >&2
echo "[DEBUG] Running admin container with flags: ${shell_flags[*]+"${shell_flags[*]}"}" >&2
echo "[DEBUG] Remaining args after processing: $*" >&2
fi
# Don't pass any remaining arguments - only shell and the flags
run_claudebox_container "$temp_container" "interactive" shell "${shell_flags[@]}"
run_claudebox_container "$temp_container" "interactive" shell "${shell_flags[@]+"${shell_flags[@]}"}"

# Commit changes back to image
fillbar
Expand All @@ -127,7 +127,7 @@ _cmd_shell() {
success "Changes saved to image!"
else
# Regular shell mode - just run without committing
run_claudebox_container "" "interactive" shell "${shell_flags[@]}"
run_claudebox_container "" "interactive" shell "${shell_flags[@]+"${shell_flags[@]}"}"
fi

exit 0
Expand Down
94 changes: 54 additions & 40 deletions lib/commands.profile.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@ _cmd_profiles() {
for profile in $(get_all_profile_names | tr ' ' '\n' | sort); do
local desc=$(get_profile_description "$profile")
local is_enabled=false
# Check if profile is currently enabled
for enabled in "${current_profiles[@]}"; do
if [[ "$enabled" == "$profile" ]]; then
is_enabled=true
break
fi
done
# Check if profile is currently enabled (guard for empty array)
if [ ${#current_profiles[@]} -gt 0 ]; then
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
for enabled in "${current_profiles[@]}"; do
if [[ "$enabled" == "$profile" ]]; then
is_enabled=true
break
fi
done
fi
printf " ${GREEN}%-15s${NC} " "$profile"
if [[ "$is_enabled" == "true" ]]; then
printf "${GREEN}✓${NC} "
Expand Down Expand Up @@ -144,12 +146,14 @@ _cmd_add() {

# Check if any Python-related profiles were added
local python_profiles_added=false
for profile in "${selected[@]}"; do
if [[ "$profile" == "python" ]] || [[ "$profile" == "ml" ]] || [[ "$profile" == "datascience" ]]; then
python_profiles_added=true
break
fi
done
if [ ${#selected[@]} -gt 0 ]; then
for profile in "${selected[@]}"; do
if [[ "$profile" == "python" ]] || [[ "$profile" == "ml" ]] || [[ "$profile" == "datascience" ]]; then
python_profiles_added=true
break
fi
done
fi

# If Python profiles were added, remove the pydev flag to trigger reinstall
if [[ "$python_profiles_added" == "true" ]]; then
Expand All @@ -162,12 +166,14 @@ _cmd_add() {

# Only show rebuild message for non-Python profiles
local needs_rebuild=false
for profile in "${selected[@]}"; do
if [[ "$profile" != "python" ]] && [[ "$profile" != "ml" ]] && [[ "$profile" != "datascience" ]]; then
needs_rebuild=true
break
fi
done
if [ ${#selected[@]} -gt 0 ]; then
for profile in "${selected[@]}"; do
if [[ "$profile" != "python" ]] && [[ "$profile" != "ml" ]] && [[ "$profile" != "datascience" ]]; then
needs_rebuild=true
break
fi
done
fi

if [[ "$needs_rebuild" == "true" ]]; then
warn "The Docker image will be rebuilt with new profiles on next run."
Expand Down Expand Up @@ -229,29 +235,35 @@ _cmd_remove() {
# Remove specified profiles
local new_profiles=()
local python_profiles_removed=false
for profile in "${current_profiles[@]}"; do
local keep=true
for remove in "${to_remove[@]}"; do
if [[ "$profile" == "$remove" ]]; then
keep=false
# Check if we're removing a Python-related profile
if [[ "$profile" == "python" ]] || [[ "$profile" == "ml" ]] || [[ "$profile" == "datascience" ]]; then
python_profiles_removed=true
fi
break
if [ ${#current_profiles[@]} -gt 0 ]; then
for profile in "${current_profiles[@]}"; do
local keep=true
if [ ${#to_remove[@]} -gt 0 ]; then
for remove in "${to_remove[@]}"; do
if [[ "$profile" == "$remove" ]]; then
keep=false
# Check if we're removing a Python-related profile
if [[ "$profile" == "python" ]] || [[ "$profile" == "ml" ]] || [[ "$profile" == "datascience" ]]; then
python_profiles_removed=true
fi
break
fi
done
fi
[[ "$keep" == "true" ]] && new_profiles+=("$profile")
done
[[ "$keep" == "true" ]] && new_profiles+=("$profile")
done
fi

# Check if any Python-related profiles remain
local has_python_profiles=false
for profile in "${new_profiles[@]}"; do
if [[ "$profile" == "python" ]] || [[ "$profile" == "ml" ]] || [[ "$profile" == "datascience" ]]; then
has_python_profiles=true
break
fi
done
if [ ${#new_profiles[@]} -gt 0 ]; then
for profile in "${new_profiles[@]}"; do
if [[ "$profile" == "python" ]] || [[ "$profile" == "ml" ]] || [[ "$profile" == "datascience" ]]; then
has_python_profiles=true
break
fi
done
fi

# If we removed Python profiles and no Python profiles remain, clean up Python flags
if [[ "$python_profiles_removed" == "true" ]] && [[ "$has_python_profiles" == "false" ]]; then
Expand All @@ -275,9 +287,11 @@ _cmd_remove() {
# Write back the filtered profiles
{
echo "[profiles]"
for profile in "${new_profiles[@]}"; do
echo "$profile"
done
if [ ${#new_profiles[@]} -gt 0 ]; then
for profile in "${new_profiles[@]+"${new_profiles[@]}"}"; do
echo "$profile"
done
fi
echo ""

# Preserve packages section if it exists
Expand Down
41 changes: 26 additions & 15 deletions lib/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ read_profile_section() {
done < <(sed -n "/^\[$section\]/,/^\[/p" "$profile_file" | tail -n +2 | grep -v '^\[')
fi

printf '%s\n' "${result[@]}"
printf '%s\n' "${result[@]+"${result[@]}"}"
}

update_profile_section() {
Expand All @@ -140,20 +140,29 @@ update_profile_section() {
local new_items=("$@")

local existing_items=()
readarray -t existing_items < <(read_profile_section "$profile_file" "$section")
# Bash 3.2 compatible - no readarray
while IFS= read -r line; do
existing_items+=("$line")
done < <(read_profile_section "$profile_file" "$section")

local all_items=()
for item in "${existing_items[@]}"; do
[[ -n "$item" ]] && all_items+=("$item")
done
if [ ${#existing_items[@]} -gt 0 ]; then
for item in "${existing_items[@]+"${existing_items[@]}"}"; do
[[ -n "$item" ]] && all_items+=("$item")
done
fi

for item in "${new_items[@]}"; do
local found=false
for existing in "${all_items[@]}"; do
[[ "$existing" == "$item" ]] && found=true && break
if [ ${#new_items[@]} -gt 0 ]; then
for item in "${new_items[@]+"${new_items[@]}"}"; do
local found=false
if [ ${#all_items[@]} -gt 0 ]; then
for existing in "${all_items[@]+"${all_items[@]}"}"; do
[[ "$existing" == "$item" ]] && found=true && break
done
fi
[[ "$found" == "false" ]] && all_items+=("$item")
done
[[ "$found" == "false" ]] && all_items+=("$item")
done
fi

{
if [[ -f "$profile_file" ]]; then
Expand All @@ -169,9 +178,11 @@ update_profile_section() {
fi

echo "[$section]"
for item in "${all_items[@]}"; do
echo "$item"
done
if [ ${#all_items[@]} -gt 0 ]; then
for item in "${all_items[@]+"${all_items[@]}"}"; do
echo "$item"
done
fi
echo ""
} > "${profile_file}.tmp" && mv "${profile_file}.tmp" "$profile_file"
}
Expand All @@ -186,7 +197,7 @@ get_current_profiles() {
done < <(read_profile_section "$profiles_file" "profiles")
fi

printf '%s\n' "${current_profiles[@]}"
printf '%s\n' "${current_profiles[@]+"${current_profiles[@]}"}"
}

# -------- Profile installation functions for Docker builds -------------------
Expand Down
17 changes: 10 additions & 7 deletions lib/docker.sh
Original file line number Diff line number Diff line change
Expand Up @@ -296,15 +296,18 @@ run_claudebox_container() {
# Set up cleanup trap for temporary MCP config files
cleanup_mcp_files() {
local file
for file in "${mcp_temp_files[@]}"; do
if [[ -f "$file" ]]; then
rm -f "$file"
fi
done
if [[ -n "$user_mcp_file" ]] && [[ -f "$user_mcp_file" ]]; then
# Check if array exists and has elements (set -u safe)
if [[ -n "${mcp_temp_files+set}" ]] && [ ${#mcp_temp_files[@]} -gt 0 ]; then
for file in "${mcp_temp_files[@]}"; do
if [[ -f "$file" ]]; then
rm -f "$file"
fi
done
fi
if [[ -n "${user_mcp_file:-}" ]] && [[ -f "$user_mcp_file" ]]; then
rm -f "$user_mcp_file"
fi
if [[ -n "$project_mcp_file" ]] && [[ -f "$project_mcp_file" ]]; then
if [[ -n "${project_mcp_file:-}" ]] && [[ -f "$project_mcp_file" ]]; then
rm -f "$project_mcp_file"
fi
}
Expand Down
Loading