Skip to content
Merged
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
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ The `install.sh` script automates initial setup (clone, build, create container,

1. **Git identity** — name and email (skipped if already configured)
2. **GitHub CLI auth** — persisted to `/workspace/.squarebox/gh` across rebuilds
3. **AI coding assistant** — Claude Code, OpenCode, or both
3. **AI coding assistant** — Claude Code, GitHub Copilot CLI, Google Gemini CLI, OpenAI Codex CLI, OpenCode (any combination)
4. **Text editors** — micro, edit (Microsoft), fresh, helix, nvim (nano is always available)
5. **SDKs** — Node.js (via nvm), Python (via uv), Go, .NET

Expand Down
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,18 @@ What's included

### AI Coding Assistants (optional)

Installed during first-run setup. Choose one or both:
Installed during first-run setup. Choose any combination:

| Name | Language | Description |
|------|----------|-------------|
| [Claude Code](https://github.com/anthropics/claude-code) | TypeScript | AI coding assistant |
| [GitHub Copilot CLI](https://github.com/githubnext/github-copilot-cli) | TypeScript | GitHub Copilot in the terminal * |
| [Google Gemini CLI](https://github.com/google-gemini/gemini-cli) | TypeScript | Google Gemini in the terminal * |
| [OpenAI Codex CLI](https://github.com/openai/codex) | TypeScript | OpenAI Codex in the terminal * |
| [opencode](https://github.com/anomalyco/opencode) | Go | AI coding TUI |

\* Requires Node.js (auto-installed if needed).

### Text Editors (optional)

Installed during first-run setup. Nano is always available as the default editor.
Expand Down Expand Up @@ -102,7 +107,7 @@ Inspired by [Omarchy](https://omarchy.com).
| `..` | `cd ..` | Go up one directory |
| `...` | `cd ../..` | Go up two directories |
| `....` | `cd ../../..` | Go up three directories |
| `c` | `claude` or `opencode` | Launch selected AI assistant |
| `c` | first selected AI tool | Launch selected AI assistant |
| `g` | `git` | Git shorthand |
| `gcm` | `git commit -m` | Commit with message |
| `gcam` | `git commit -a -m` | Stage all and commit |
Expand Down Expand Up @@ -179,6 +184,9 @@ First-run selections add to that:
| Component | Adds |
|-----------|------|
| Claude Code | ~300 MB |
| GitHub Copilot CLI | ~50 MB |
| Google Gemini CLI | ~50 MB |
| OpenAI Codex CLI | ~50 MB |
| OpenCode | ~30 MB |
| micro / edit | ~12 / ~7 MB |
| fresh / helix / nvim | ~10 / ~80 / ~45 MB |
Expand All @@ -195,7 +203,8 @@ Security

All binary tools are pinned to specific versions and verified against SHA256
checksums at build time. Third-party install scripts (Claude Code, uv, .NET)
manage their own binary verification.
manage their own binary verification. npm-based AI tools (Copilot CLI, Gemini
CLI, Codex CLI) use npm's built-in integrity verification.
Comment on lines 204 to +207

Copilot AI Apr 6, 2026

Copy link

Choose a reason for hiding this comment

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

The Security section still states “All binary tools are pinned to specific versions…”, but the new npm-based AI tools are installed without version pinning (e.g. npm install -g @google/gemini-cli). This makes the statement misleading. Either pin npm package versions (and document update process) or narrow the wording to distinguish pinned checksum-verified downloads from npm-installed packages.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback


For the full trust model (what `install.sh` does on your machine, how each
layer is verified, and how to inspect the script before running it) see
Expand Down
246 changes: 163 additions & 83 deletions setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -112,62 +112,81 @@ mkdir -p /workspace/.squarebox ~/.local/bin
ai_prev=""
if [ -f "$AI_CONFIG" ]; then
ai_prev=$(cat "$AI_CONFIG")
fi

if $INTERACTIVE; then
# Migrate legacy single-choice values
case "$ai_prev" in
claude) ai_default_label="Claude Code" ;;
opencode) ai_default_label="OpenCode" ;;
both) ai_default_label="Both" ;;
*) ai_default_label="" ;;
both) ai_prev="claude,opencode" ;;
esac
fi

if $INTERACTIVE; then
echo
if $HAS_GUM; then
gum_args=(--header "Choose your AI coding assistant:")
[ -n "$ai_default_label" ] && gum_args+=(--selected "$ai_default_label")
ai_label=$(gum choose "${gum_args[@]}" \
"Claude Code" "OpenCode" "Both") || true
case "$ai_label" in
"Claude Code") ai_choice="claude" ;;
"OpenCode") ai_choice="opencode" ;;
"Both") ai_choice="both" ;;
*) echo "No selection, defaulting to Claude Code"; ai_choice="claude" ;;
esac
# Build --selected from previously saved AI tools
gum_selected=""
for ai in $(echo "$ai_prev" | tr ',' ' '); do
case "$ai" in
claude) gum_selected="${gum_selected:+$gum_selected,}Claude Code" ;;
copilot) gum_selected="${gum_selected:+$gum_selected,}GitHub Copilot CLI" ;;
gemini) gum_selected="${gum_selected:+$gum_selected,}Google Gemini CLI" ;;
codex) gum_selected="${gum_selected:+$gum_selected,}OpenAI Codex CLI" ;;
opencode) gum_selected="${gum_selected:+$gum_selected,}OpenCode" ;;
esac
done
gum_args=(--no-limit --header "Select AI coding assistants (space=toggle, enter=confirm):")
[ -n "$gum_selected" ] && gum_args+=(--selected "$gum_selected")
selected=$(gum choose "${gum_args[@]}" \
"Claude Code" "GitHub Copilot CLI" "Google Gemini CLI" \
"OpenAI Codex CLI" "OpenCode") || true
ai_choice=""
while IFS= read -r line; do
case "$line" in
"Claude Code") ai_choice="${ai_choice:+$ai_choice,}claude" ;;
"GitHub Copilot CLI") ai_choice="${ai_choice:+$ai_choice,}copilot" ;;
"Google Gemini CLI") ai_choice="${ai_choice:+$ai_choice,}gemini" ;;
"OpenAI Codex CLI") ai_choice="${ai_choice:+$ai_choice,}codex" ;;
"OpenCode") ai_choice="${ai_choice:+$ai_choice,}opencode" ;;
esac
done <<< "$selected"
else
echo "Choose your AI coding assistant:"
if [ "$ai_prev" = "claude" ]; then echo " 1) Claude Code [current]"; else echo " 1) Claude Code"; fi
if [ "$ai_prev" = "opencode" ]; then echo " 2) OpenCode [current]"; else echo " 2) OpenCode"; fi
if [ "$ai_prev" = "both" ]; then echo " 3) Both [current]"; else echo " 3) Both"; fi
read -rp "Selection [1/2/3]: " selection
if [ -z "$selection" ] && [ -n "$ai_prev" ]; then
echo "Select AI coding assistants (comma-separated, 'all', or press Enter to skip):"
for ai_item in "1:claude:Claude Code" "2:copilot:GitHub Copilot CLI" "3:gemini:Google Gemini CLI" "4:codex:OpenAI Codex CLI" "5:opencode:OpenCode"; do
num="${ai_item%%:*}"; rest="${ai_item#*:}"; key="${rest%%:*}"; label="${rest#*:}"
if [[ ",$ai_prev," == *",${key},"* ]]; then
echo " ${num}) ${label} [installed]"
else
echo " ${num}) ${label}"
fi
done
read -rp "Selection [1,2,3,4,5/all/skip]: " ai_selection
if [ -z "$ai_selection" ] && [ -n "$ai_prev" ]; then
ai_choice="$ai_prev"
else
case "$selection" in
1) ai_choice="claude" ;;
2) ai_choice="opencode" ;;
3) ai_choice="both" ;;
*) echo "Invalid selection, defaulting to Claude Code"; ai_choice="claude" ;;
esac
ai_choice=""
if [ "$ai_selection" = "all" ]; then
ai_choice="claude,copilot,gemini,codex,opencode"
elif [ -n "$ai_selection" ]; then
for item in $(echo "$ai_selection" | tr ',' ' '); do
case "$item" in
1) ai_choice="${ai_choice:+$ai_choice,}claude" ;;
2) ai_choice="${ai_choice:+$ai_choice,}copilot" ;;
3) ai_choice="${ai_choice:+$ai_choice,}gemini" ;;
4) ai_choice="${ai_choice:+$ai_choice,}codex" ;;
5) ai_choice="${ai_choice:+$ai_choice,}opencode" ;;
esac
done
fi
fi
fi
echo "$ai_choice" > "$AI_CONFIG"
elif [ -n "$ai_prev" ]; then
ai_choice="$ai_prev"
echo "Installing AI tool: $ai_choice (from previous selection)"
echo "Installing AI tools: $ai_choice (from previous selection)"
else
echo "Defaulting to Claude Code (non-interactive)"
ai_choice="claude"
echo "$ai_choice" > "$AI_CONFIG"
fi

if [ "$ai_choice" = "claude" ] || [ "$ai_choice" = "both" ]; then
echo "Installing Claude Code..."
# Trust boundary: the Claude Code install script manages its own binary
# fetching and verification. We rely on HTTPS for script integrity.
curl -fsSL https://claude.ai/install.sh | bash
fi

# Pinned versions — update via: scripts/update-versions.sh
OPENCODE_VERSION="1.3.15"
MICRO_VERSION="2.0.15"
Expand All @@ -185,27 +204,115 @@ for _var in OPENCODE_VERSION MICRO_VERSION EDIT_VERSION EDIT_ASSET_VERSION FRESH
fi
done

if [ "$ai_choice" = "opencode" ] || [ "$ai_choice" = "both" ]; then
if command -v opencode &>/dev/null; then
echo "OpenCode already installed, skipping."
else
echo "Installing OpenCode v${OPENCODE_VERSION}..."
sb_install opencode "$OPENCODE_VERSION"
# Pinned SDK versions needed early (for npm-based AI tools)
NVM_VERSION="0.40.3"

# SDK path setup file (create if missing, preserve on retry)
touch ~/.squarebox-sdk-paths

install_node() {
if command -v node &>/dev/null; then echo "Node.js already installed, skipping."; return 0; fi
rm -rf "$HOME/.nvm"
echo "Installing Node.js (via nvm v${NVM_VERSION})..."
curl -fsSo /tmp/nvm-install.sh "https://raw.githubusercontent.com/nvm-sh/nvm/v${NVM_VERSION}/install.sh"
verify_checksum /tmp/nvm-install.sh "nvm-install-v${NVM_VERSION}.sh"
bash /tmp/nvm-install.sh
rm /tmp/nvm-install.sh
export NVM_DIR="$HOME/.nvm"
# shellcheck source=/dev/null
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
# Node.js binary verification is handled by nvm
nvm install --lts
if ! grep -q 'NVM_DIR' ~/.squarebox-sdk-paths 2>/dev/null; then
cat <<'PATHS' >> ~/.squarebox-sdk-paths
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
PATHS
fi
fi
if ! command -v node &>/dev/null; then
echo "Error: Node.js binary not found after installation" >&2
exit 1
fi
}

# Set aliases based on selection
{
if [ "$ai_choice" = "claude" ]; then
echo "alias c='claude'"
echo "alias claude-yolo='claude --dangerously-skip-permissions'"
elif [ "$ai_choice" = "opencode" ]; then
echo "alias c='opencode'"
echo "alias opencode-yolo='opencode --dangerously-skip-permissions'"
# Ensure Node.js is available for npm-based AI tools
ensure_node_for_npm() {
if command -v node &>/dev/null; then return 0; fi
echo "Installing Node.js (required for npm-based AI tools)..."
install_node
# Ensure node/npm are available in this session
export NVM_DIR="$HOME/.nvm"
# shellcheck source=/dev/null
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
# Persist Node.js in SDK config so it survives rebuilds
local sdk_cfg="/workspace/.squarebox/sdks"
if [ -f "$sdk_cfg" ]; then
local sdk_current
sdk_current=$(cat "$sdk_cfg")
if [[ ",$sdk_current," != *",node,"* ]] && [ "$sdk_current" != "node" ]; then
echo "${sdk_current:+$sdk_current,}node" > "$sdk_cfg"
fi
Comment on lines +252 to +254

Copilot AI Apr 6, 2026

Copy link

Choose a reason for hiding this comment

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

ensure_node_for_npm mutates /workspace/.squarebox/sdks here, but later in the script the SDK selection flow overwrites the same file based on user input. That can drop the persisted node entry even when npm-based AI tools were selected/installed. Consider merging node into the final sdk_list when any npm-based AI tool is chosen, or defer writing until after the SDK prompt to avoid clobbering.

Copilot uses AI. Check for mistakes.
else
echo "alias claude-yolo='claude --dangerously-skip-permissions'"
echo "alias opencode-yolo='opencode --dangerously-skip-permissions'"
echo "node" > "$sdk_cfg"
fi
}

install_copilot() {
if command -v github-copilot-cli &>/dev/null; then echo "GitHub Copilot CLI already installed, skipping."; return 0; fi
ensure_node_for_npm
echo "Installing GitHub Copilot CLI..."
npm install -g @githubnext/github-copilot-cli
}

install_gemini() {
if command -v gemini &>/dev/null; then echo "Google Gemini CLI already installed, skipping."; return 0; fi
ensure_node_for_npm
echo "Installing Google Gemini CLI..."
npm install -g @google/gemini-cli
}

install_codex() {
if command -v codex &>/dev/null; then echo "OpenAI Codex CLI already installed, skipping."; return 0; fi
ensure_node_for_npm
echo "Installing OpenAI Codex CLI..."
npm install -g @openai/codex
}

for ai_tool in $(echo "$ai_choice" | tr ',' ' '); do
case "$ai_tool" in
claude)
echo "Installing Claude Code..."
# Trust boundary: the Claude Code install script manages its own binary
# fetching and verification. We rely on HTTPS for script integrity.
curl -fsSL https://claude.ai/install.sh | bash
;;
opencode)
if command -v opencode &>/dev/null; then
echo "OpenCode already installed, skipping."
else
echo "Installing OpenCode v${OPENCODE_VERSION}..."
sb_install opencode "$OPENCODE_VERSION"
fi
;;
copilot) install_copilot ;;
gemini) install_gemini ;;
codex) install_codex ;;
esac
done

# Set aliases based on selection — c maps to first selected tool in priority order
{
c_target=""
for ai_tool in claude copilot gemini codex opencode; do
if [[ ",$ai_choice," == *",$ai_tool,"* ]]; then
[ -z "$c_target" ] && c_target="$ai_tool"
case "$ai_tool" in
claude) echo "alias claude-yolo='claude --dangerously-skip-permissions'" ;;
opencode) echo "alias opencode-yolo='opencode --dangerously-skip-permissions'" ;;
esac
fi
done
[ -n "$c_target" ] && echo "alias c='$c_target'"
} > ~/.squarebox-ai-aliases
Comment on lines +314 to 316

Copilot AI Apr 6, 2026

Copy link

Choose a reason for hiding this comment

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

c_target is set to the config key (e.g. copilot), but Copilot is installed/detected as github-copilot-cli (command -v github-copilot-cli). If Copilot is the first selected tool, alias c='copilot' will point to a non-existent command. Map AI keys to their actual executable names (e.g. make c point to github-copilot-cli), or install/provide a copilot wrapper executable for consistency.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@copilot can you open a github issue for this so i can explore the best fix

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I wasn't able to create the issue programmatically (the token doesn't have issues: write permission), but here's the content ready to paste into a new issue:

Title: Fix: 'c' alias for GitHub Copilot CLI points to non-existent 'copilot' command

Body:

## Bug

When GitHub Copilot CLI is selected as the highest-priority AI tool, `setup.sh` generates:

    alias c='copilot'

But the executable installed by `npm install -g @githubnext/github-copilot-cli` is
`github-copilot-cli`, not `copilot`. Running `c` will fail with "command not found: copilot".

## Root Cause

`setup.sh` lines 305–315 use the internal config key (`copilot`) directly as the alias target,
without mapping it to the real executable name.

## Fix Options

1. **Map keys to executables** – add a lookup so `copilot` → `github-copilot-cli`
   (e.g. a `case` statement before `echo "alias c='$c_target'"`).
2. **Install a thin `copilot` wrapper** – create `/usr/local/bin/copilot` that delegates
   to `github-copilot-cli "$@"`.
3. **Detect at shell startup** – generate the alias lazily with `command -v` so it resolves
   the right binary at runtime.

## Steps to Reproduce

1. Run `setup.sh` and choose only GitHub Copilot CLI (or rank it first).
2. Open a new shell.
3. Type `c` — it fails with "command not found: copilot".

Once the issue is open, option 1 (a case statement mapping keys to executable names) is the most straightforward fix and I'm happy to implement it.


# Text editors
Expand Down Expand Up @@ -468,44 +575,17 @@ else
echo "$sdk_list" > "$SDK_CONFIG"
fi

# SDK path setup file (create if missing, preserve on retry)
touch ~/.squarebox-sdk-paths

# Pinned versions — update via: scripts/update-versions.sh
NVM_VERSION="0.40.3"
GO_VERSION="go1.26.1"

for _var in NVM_VERSION GO_VERSION; do
for _var in GO_VERSION; do
if [ -z "${!_var:-}" ]; then
echo "Error: ${_var} is empty or unset" >&2
exit 1
fi
done

install_node() {
if command -v node &>/dev/null; then echo "Node.js already installed, skipping."; return 0; fi
rm -rf "$HOME/.nvm"
echo "Installing Node.js (via nvm v${NVM_VERSION})..."
curl -fsSo /tmp/nvm-install.sh "https://raw.githubusercontent.com/nvm-sh/nvm/v${NVM_VERSION}/install.sh"
verify_checksum /tmp/nvm-install.sh "nvm-install-v${NVM_VERSION}.sh"
bash /tmp/nvm-install.sh
rm /tmp/nvm-install.sh
export NVM_DIR="$HOME/.nvm"
# shellcheck source=/dev/null
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
# Node.js binary verification is handled by nvm
nvm install --lts
if ! grep -q 'NVM_DIR' ~/.squarebox-sdk-paths 2>/dev/null; then
cat <<'PATHS' >> ~/.squarebox-sdk-paths
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
PATHS
fi
if ! command -v node &>/dev/null; then
echo "Error: Node.js binary not found after installation" >&2
exit 1
fi
}
# install_node is defined earlier (needed by npm-based AI tools)

install_python() {
if command -v uv &>/dev/null; then echo "uv already installed, skipping."; return 0; fi
Expand Down
Loading