Skip to content

Harden CLI parsing and centralize command directory structure#108

Open
lsr00ter wants to merge 1 commit into
RchGrav:mainfrom
lsr00ter:chore/cli-hardening-commands-dir
Open

Harden CLI parsing and centralize command directory structure#108
lsr00ter wants to merge 1 commit into
RchGrav:mainfrom
lsr00ter:chore/cli-hardening-commands-dir

Conversation

@lsr00ter
Copy link
Copy Markdown

@lsr00ter lsr00ter commented May 5, 2026

  • Introduce CLAUDEBOX_GLOBAL_CLAUDE_COMMANDS_DIR (~/.claudebox/.claude/commands) and migrate references from ~/.claude/commands; preserve legacy commands by copying them into the new location on setup.
  • Expand SCRIPT_COMMANDS list (update, import, unlink, undo, redo, config, mcp, migrate-installer) and add did-you-mean suggestions for unknown commands via get_script_command_suggestion().
  • Make array expansions safe under set -u using ${arr[@]+"${arr[@]}"} across cli, config, docker, profile, and main flows.
  • Strip trailing whitespace and minor markdown formatting cleanup in README and commands/controlflow.md.

Summary by Sourcery

Centralize Claude command storage under a new global ClaudeBox directory, harden CLI parsing and Docker/mcp flows for unset variables, and improve usability with command suggestions and documentation updates.

New Features:

  • Add a global ClaudeBox Claude configuration and commands directory and migrate shared command handling to use it.
  • Introduce did-you-mean style suggestions for mistyped CLI commands based on known script commands.

Enhancements:

  • Preserve and sync legacy user command files into the new shared commands directory during setup and project sync.
  • Relax Docker/image requirements for additional host-only commands and refine command requirement classification.
  • Improve robustness of array handling and temporary file cleanup across CLI, Docker, config, and profile flows to work correctly with strict unset-variable settings.
  • Adjust Dockerfile template rendering to avoid awk portability issues and ensure placeholder validation.
  • Tighten tmux and project/slot management logic without changing behavior.
  • Expand profile management UX output and info reporting around commands and profiles.

Documentation:

  • Refresh README and controlflow workflow documentation to reflect the new global Claude commands location and clean up markdown formatting.

- Introduce CLAUDEBOX_GLOBAL_CLAUDE_COMMANDS_DIR (~/.claudebox/.claude/commands)
  and migrate references from ~/.claude/commands; preserve legacy commands by
  copying them into the new location on setup.
- Expand SCRIPT_COMMANDS list (update, import, unlink, undo, redo, config, mcp,
  migrate-installer) and add did-you-mean suggestions for unknown commands via
  get_script_command_suggestion().
- Make array expansions safe under set -u using ${arr[@]+"${arr[@]}"} across
  cli, config, docker, profile, and main flows.
- Strip trailing whitespace and minor markdown formatting cleanup in README and
  commands/controlflow.md.
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented May 5, 2026

Reviewer's Guide

Centralizes Claude command storage under a new ~/.claudebox/.claude/commands directory (with migration from legacy paths), hardens bash scripts for set -u and array usage, expands and improves CLI command parsing (including more script commands and did-you-mean suggestions), and performs small documentation/whitespace cleanups.

Class diagram for updated CLI, environment, and command sync modules

classDiagram
    class Env {
        <<module>>
        +USER_ID
        +GROUP_ID
        +PROJECT_DIR
        +CLAUDEBOX_HOME
        +CLAUDEBOX_GLOBAL_CLAUDE_DIR
        +CLAUDEBOX_GLOBAL_CLAUDE_COMMANDS_DIR
    }

    class CLI {
        <<module>>
        +HOST_ONLY_FLAGS
        +CONTROL_FLAGS
        +SCRIPT_COMMANDS
        +CLI_HOST_FLAGS
        +CLI_CONTROL_FLAGS
        +CLI_SCRIPT_COMMAND
        +CLI_PASS_THROUGH
        +parse_cli_args(args)
        +process_host_flags()
        +get_script_command_requirements(cmd, subcommand)
        +requires_docker_image()
        +requires_slot(cmd)
        +debug_parsed_args()
        +get_script_command_suggestion(input)
        +_is_close_command_match(left, right)
    }

    class Main {
        <<module>>
        +main(args)
        +show_first_time_welcome()
        +build_docker_image()
    }

    class State {
        <<module>>
        +update_symlink()
        +setup_shared_commands()
        +setup_claude_agent_command(parent_dir)
        +calculate_docker_layer_checksums(project_dir)
        +needs_docker_rebuild(project_dir, image_name)
        +save_docker_layer_checksums(project_dir)
    }

    class ProjectModule {
        <<module>>
        +init_project_dir(path)
        +get_project_folder_name(path)
        +sync_commands_to_project(project_parent)
        +list_all_projects()
        +list_project_slots(path)
    }

    class ConfigModule {
        <<module>>
        +read_profile_section(profile_file, section)
        +update_profile_section(profile_file, section, new_items)
        +get_current_profiles()
    }

    class DockerModule {
        <<module>>
        +run_claudebox_container(container_name, run_mode, container_args)
        +run_docker_build(dockerfile, build_context)
        +check_container_exists(container_name)
    }

    Env <.. CLI : uses env constants
    Env <.. Main : uses PROJECT_DIR, CLAUDEBOX_HOME
    Env <.. State : uses CLAUDEBOX_HOME\nCLAUDEBOX_GLOBAL_CLAUDE_COMMANDS_DIR
    Env <.. ProjectModule : uses CLAUDEBOX_HOME

    Main --> CLI : calls parse_cli_args()\nprocess_host_flags()\nget_script_command_suggestion()
    Main --> DockerModule : calls run_claudebox_container()\nrun_docker_build()
    Main --> State : calls update_symlink()\nsetup_shared_commands()\nneeds_docker_rebuild()
    Main --> ProjectModule : calls init_project_dir()\nsync_commands_to_project()
    Main --> ConfigModule : calls get_current_profiles()

    State --> ProjectModule : setup_claude_agent_command()\nrelies on project parent
    ProjectModule --> State : uses calculate_docker_layer_checksums()\nvia rebuild logic

    CLI --> ConfigModule : get_command_requirements()\ninteracts via requires_slot()
    DockerModule --> ProjectModule : uses get_slot_index()
Loading

File-Level Changes

Change Details Files
Introduce centralized global Claude commands directory under ~/.claudebox and migrate legacy command locations into it, wiring this into project sync and import flows and user-facing info/help text.
  • Define CLAUDEBOX_GLOBAL_CLAUDE_DIR and CLAUDEBOX_GLOBAL_CLAUDE_COMMANDS_DIR in env.sh and export them for other modules.
  • Update setup_shared_commands to point to the new global commands directory, and copy user commands from legacy ~/.claude/commands and ~/.claudebox/commands into the new directory without overwriting existing files.
  • Switch sync_commands_to_project to treat the new global commands directory as the user source, including checksum tracking and sync behavior.
  • Update _cmd_import to import from the new global commands directory and adjust user-facing instructions accordingly.
  • Adjust _cmd_info and controlflow.md/README.md documentation to refer to the new commands path and count commands from there.
lib/env.sh
lib/state.sh
lib/project.sh
lib/commands.info.sh
lib/commands.system.sh
commands/controlflow.md
README.md
Harden CLI parsing and command dispatch around bash strict mode and improve UX with a broader script command set and did-you-mean suggestions for mistyped commands.
  • Expand SCRIPT_COMMANDS to include update, import, unlink, undo, redo, config, mcp, and migrate-installer so they are parsed as script commands instead of pass-through args.
  • Make parse_cli_args and related consumers use safe Bash array expansions (${arr[@]+"${arr[@]}"}) and avoid exporting arrays directly, storing them in global variables instead.
  • Add _is_close_command_match and get_script_command_suggestion helpers to perform a simple edit-distance-1 match over known script commands, skipping help aliases.
  • In main(), after initial parse, detect unknown top-level commands and, if they are close to a known script command, emit a helpful error with a suggested claudebox command.
  • Ensure calls to dispatch_command, preflight_check, parse_cli_args, and run_claudebox_container pass arrays using safe expansions compatible with set -u.
lib/cli.sh
main.sh
Stabilize Docker build and container runtime behavior, especially around MCP config temp-file cleanup and profile-driven Dockerfile rendering, making logic safe for macOS Bash/awk and strict mode.
  • Rewrite build_docker_image Dockerfile templating to line-by-line shell processing instead of awk with multiline variables, ensuring robustness on macOS awk and with strict mode.
  • Fix a bug in the unreplaced-placeholders check in build_docker_image by separating the two grep calls with a logical OR.
  • In run_claudebox_container, track and clean up temporary MCP config files explicitly via a local cleanup function, and avoid relying solely on a global EXIT trap.
  • Wrap the docker run invocation with set +e / set -e to capture exit codes reliably while still cleaning up temporary files.
  • Annotate cleanup functions and MCP-related arrays with safe array iteration and expansions to avoid errors under set -u.
main.sh
lib/docker.sh
General robustness and style cleanups across command/system/profile/project helpers to support strict mode and better readability without changing behavior.
  • Normalize whitespace and spacing across many shell functions (kill, tmux, project, special, profiles, project listing/slots) to remove trailing spaces and improve consistency.
  • Introduce local loop variables where needed and adopt safe array iteration patterns in config/profile helpers (e.g., read_profile_section, update_profile_section, get_current_profiles).
  • Ensure loops over CLI_HOST_FLAGS, current_profiles, and related arrays use the ${arr[@]+"${arr[@]}"} idiom to avoid unbound variable errors when arrays are empty.
lib/commands.system.sh
lib/project.sh
lib/commands.profile.sh
lib/config.sh
lib/state.sh
README.md
commands/controlflow.md

Possibly linked issues

  • #MCP cleanup causes exit error with unbound variable: PR removes EXIT trap, centralizes MCP temp-file cleanup via tracked array, and adds set -u-safe expansions, fixing the unbound variable error.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • In build_docker_image, the guard that checks for unreplaced Dockerfile placeholders (grep -q '{{PROFILE_INSTALLATIONS}}' and grep -q '{{LABELS}}') is missing a command separator or logical operator between the two grep invocations, which will cause a syntax/runtime error; this should be rewritten with || or similar so both checks run correctly.
  • In setup_shared_commands, the for file in "$legacy_commands"/*; do loop will iterate over the literal pattern when the legacy directory is empty, which can generate spurious errors; consider enabling nullglob locally or checking for at least one regular file before entering the loop to make the migration more robust.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `build_docker_image`, the guard that checks for unreplaced Dockerfile placeholders (`grep -q '{{PROFILE_INSTALLATIONS}}'` and `grep -q '{{LABELS}}'`) is missing a command separator or logical operator between the two `grep` invocations, which will cause a syntax/runtime error; this should be rewritten with `||` or similar so both checks run correctly.
- In `setup_shared_commands`, the `for file in "$legacy_commands"/*; do` loop will iterate over the literal pattern when the legacy directory is empty, which can generate spurious errors; consider enabling `nullglob` locally or checking for at least one regular file before entering the loop to make the migration more robust.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant