From 7226f6c20e7c1ed96eb90e1c3f2dac92ba9e5236 Mon Sep 17 00:00:00 2001 From: Brett Kinny Date: Fri, 17 Apr 2026 18:14:31 +1000 Subject: [PATCH 1/2] feat: add experimental fish shell option to setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends the shell picker in setup.sh with a third choice (fish), alongside the existing bash and zsh options. Also drops "(experimental)" from the Shell section header since the tag now lives on the individual options. Fish install: - apt-get install fish - Writes ~/.config/fish/config.fish mirroring the default bashrc in fish-native syntax (starship, zoxide, aliases, EDITOR, PATH). - Translates the user's ~/.squarebox-ai-aliases, -editor-aliases, -tui-aliases, and -sdk-paths bash files into a single ~/.config/fish/conf.d/squarebox-selections.fish at install time via a small bash→fish line translator (export PATH=… → set -x PATH …, other exports → set -x, aliases pass through, unsupported constructs like nvm sourcing are dropped). Handoff: - New ~/.squarebox-use-fish marker (parallel to the zsh one) triggers `exec fish -l` from bashrc. SQUAREBOX_IN_FISH guards re-exec loops; SQUAREBOX_NO_FISH forces bash for a single session. Docs + test: - README Shell section documents fish and flags the known nvm-in-fish limitation (nvm.fish plugin required). - squarebox-setup.sh help text mentions fish. - e2e-test.sh clears both markers before asserting selection state. Co-Authored-By: Claude Opus 4.7 (1M context) --- Dockerfile | 7 ++ README.md | 23 +++++-- scripts/e2e-test.sh | 4 +- scripts/squarebox-setup.sh | 2 +- setup.sh | 136 ++++++++++++++++++++++++++++++++++--- 5 files changed, 152 insertions(+), 20 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8905554..dc4873d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -170,6 +170,13 @@ if [ -f ~/.squarebox-use-zsh ] && [ -z "${SQUAREBOX_IN_ZSH:-}" ] && [ -z "${SQUA export SQUAREBOX_IN_ZSH=1 exec zsh -l fi +# Hand off to fish if the user opted in via setup.sh (experimental). +# SQUAREBOX_IN_FISH guards against re-exec loops; SQUAREBOX_NO_FISH lets +# users force bash for one shell without removing the marker. +if [ -f ~/.squarebox-use-fish ] && [ -z "${SQUAREBOX_IN_FISH:-}" ] && [ -z "${SQUAREBOX_NO_FISH:-}" ] && command -v fish >/dev/null 2>&1; then + export SQUAREBOX_IN_FISH=1 + exec fish -l +fi EOFRC # Display MOTD on interactive shell login diff --git a/README.md b/README.md index 1a02af7..44919c7 100644 --- a/README.md +++ b/README.md @@ -179,7 +179,9 @@ Installed during first-run setup. Choose either, both, or neither: ### Shell (Experimental) By default, squarebox uses Bash. During first-run setup you can opt in to -**Zsh** instead, which installs: +**Zsh** or **Fish** instead. + +**Zsh** installs: | Name | Description | |------|-------------| @@ -191,12 +193,21 @@ By default, squarebox uses Bash. During first-run setup you can opt in to The generated `~/.zshrc` mirrors the default bashrc — same aliases, starship prompt, zoxide, and AI/editor/SDK sourcing — layered on top of Oh My Zsh. -> **Experimental:** the marker file `~/.squarebox-use-zsh` causes `~/.bashrc` -> to `exec zsh -l` on every interactive login, so the next shell start picks -> up the new shell. Set `SQUAREBOX_NO_ZSH=1` to force bash for a single +**Fish** installs [fish](https://fishshell.com) (via apt), which ships with +autosuggestions and syntax highlighting built in. The generated +`~/.config/fish/config.fish` mirrors the default bashrc in fish-native syntax; +AI/editor/TUI/SDK selections are translated from their bash files into +`~/.config/fish/conf.d/squarebox-selections.fish` at setup time. + +> **Experimental:** the marker file `~/.squarebox-use-zsh` (or +> `~/.squarebox-use-fish`) causes `~/.bashrc` to `exec` the chosen shell on +> every interactive login, so the next shell start picks up the new shell. +> Set `SQUAREBOX_NO_ZSH=1` or `SQUAREBOX_NO_FISH=1` to force bash for a single > session, or re-run `sqrbx-setup shell` to switch back permanently. Tooling -> is primarily tested against bash, so a few edge cases (custom alias files, -> rebuilds) may need polish — please file an issue if you hit one. +> is primarily tested against bash, so a few edge cases may need polish — +> please file an issue if you hit one. Known fish limitation: nvm (Node.js) +> is not wired into fish; install [nvm.fish](https://github.com/jorgebucaran/nvm.fish) +> separately if you need it. ### SDKs diff --git a/scripts/e2e-test.sh b/scripts/e2e-test.sh index 200ac7b..0869b9b 100755 --- a/scripts/e2e-test.sh +++ b/scripts/e2e-test.sh @@ -170,9 +170,9 @@ suite_setup_editors() { # install (apt zsh + Oh My Zsh + two plugin clones) is network-heavy and # would significantly slow the CI suite, so it's not pre-seeded by default. echo "bash" > /workspace/.squarebox/shell - # Ensure a stale marker from a previous run is cleared so the assertion + # Ensure stale markers from a previous run are cleared so the assertion # below reflects the current selection, not leftover state. - rm -f ~/.squarebox-use-zsh + rm -f ~/.squarebox-use-zsh ~/.squarebox-use-fish # Pre-configure git identity git config --global user.name "E2E Test" 2>/dev/null || true diff --git a/scripts/squarebox-setup.sh b/scripts/squarebox-setup.sh index b2d814e..fbcec10 100644 --- a/scripts/squarebox-setup.sh +++ b/scripts/squarebox-setup.sh @@ -36,7 +36,7 @@ usage() { tuis TUI tools (lazygit, gh-dash, yazi) multiplexers Terminal multiplexers (tmux, zellij) sdks SDKs (node, python, go, dotnet) - shell Default shell (bash, zsh — experimental) + shell Default shell (bash, zsh/fish — experimental) ${BOLD}Examples:${RESET} sqrbx-setup ai editors Re-run AI assistant and editor selection diff --git a/setup.sh b/setup.sh index 97aee91..8938c78 100755 --- a/setup.sh +++ b/setup.sh @@ -1293,7 +1293,7 @@ for sdk in $(echo "$sdk_list" | tr ',' ' '); do done fi # should_run sdks -# Shell (experimental) — offer Zsh + Oh My Zsh as an alternative to Bash +# Shell (experimental) — offer Zsh + Oh My Zsh or Fish as alternatives to Bash if should_run shell; then SHELL_CONFIG="/workspace/.squarebox/shell" @@ -1304,24 +1304,29 @@ fi if $INTERACTIVE; then echo - section_header "Shell (experimental)" + section_header "Shell" if $HAS_GUM; then gum_selected="" case "$shell_prev" in - zsh) gum_selected="zsh (experimental)" ;; + zsh) gum_selected="zsh (experimental)" ;; + fish) gum_selected="fish (experimental)" ;; bash) gum_selected="bash" ;; esac gum_args=(--header "Select default shell:") [ -n "$gum_selected" ] && gum_args+=(--selected "$gum_selected") - shell_pick=$(gum choose "${gum_args[@]}" "bash" "zsh (experimental)") || shell_pick="" + shell_pick=$(gum choose "${gum_args[@]}" "bash" "zsh (experimental)" "fish (experimental)") || shell_pick="" case "$shell_pick" in - "zsh (experimental)") shell_choice="zsh" ;; - "bash") shell_choice="bash" ;; - *) shell_choice="$shell_prev" ;; + "zsh (experimental)") shell_choice="zsh" ;; + "fish (experimental)") shell_choice="fish" ;; + "bash") shell_choice="bash" ;; + *) shell_choice="$shell_prev" ;; esac else echo "Select default shell:" - for sh_item in "1:bash:GNU Bash (default)" "2:zsh:Zsh + Oh My Zsh + autosuggestions + syntax highlighting (experimental)"; do + for sh_item in \ + "1:bash:GNU Bash (default)" \ + "2:zsh:Zsh + Oh My Zsh + autosuggestions + syntax highlighting (experimental)" \ + "3:fish:Fish shell with built-in autosuggestions and syntax highlighting (experimental)"; do num="${sh_item%%:*}"; rest="${sh_item#*:}"; key="${rest%%:*}"; desc="${rest#*:}" if [ "$key" = "$shell_prev" ]; then echo " ${num}) ${key} — ${desc} [current]" @@ -1329,13 +1334,14 @@ if $INTERACTIVE; then echo " ${num}) ${key} — ${desc}" fi done - read -rp "Selection [1,2/skip]: " shell_selection + read -rp "Selection [1,2,3/skip]: " shell_selection if [ -z "$shell_selection" ] && [ -n "$shell_prev" ]; then shell_choice="$shell_prev" else case "$shell_selection" in 1) shell_choice="bash" ;; 2) shell_choice="zsh" ;; + 3) shell_choice="fish" ;; *) shell_choice="${shell_prev:-bash}" ;; esac fi @@ -1344,7 +1350,10 @@ if $INTERACTIVE; then echo "$shell_choice" > "$SHELL_CONFIG" elif [ -n "$shell_prev" ]; then shell_choice="$shell_prev" - [ "$shell_choice" = "zsh" ] && echo "Configuring shell: zsh (from previous selection)" + case "$shell_choice" in + zsh) echo "Configuring shell: zsh (from previous selection)" ;; + fish) echo "Configuring shell: fish (from previous selection)" ;; + esac else shell_choice="bash" echo "$shell_choice" > "$SHELL_CONFIG" @@ -1423,18 +1432,123 @@ install_zsh() { run_with_spinner "Installing Zsh + Oh My Zsh..." _install_zsh_inner } +# Translate one bash-syntax line from a ~/.squarebox-* alias/path file into +# its fish equivalent on stdout. Handles: +# export PATH="A:B:$PATH" → set -x PATH A B $PATH +# export NAME='value' → set -x NAME value +# alias name='cmd' → passed through (fish accepts this form) +# Bash-only constructs (nvm sourcing, [ -s ... ] && . ..., etc.) are dropped; +# users who need nvm in fish should install a fish plugin (e.g. nvm.fish). +_squarebox_bash_line_to_fish() { + local line="$1" + case "$line" in + "export PATH="*) + local val="${line#export PATH=}" + val="${val#\"}"; val="${val%\"}" + val="${val#\'}"; val="${val%\'}" + echo "set -x PATH ${val//:/ }" + ;; + "export "*=*) + local rest="${line#export }" + local name="${rest%%=*}" + local val="${rest#*=}" + val="${val#\"}"; val="${val%\"}" + val="${val#\'}"; val="${val%\'}" + echo "set -x $name $val" + ;; + "alias "*=*) + echo "$line" + ;; + esac +} + +_install_fish_inner() { + sudo apt-get update -qq && sudo apt-get install -y -qq fish >/dev/null 2>&1 || return 1 + command -v fish >/dev/null 2>&1 || return 1 + mkdir -p "$HOME/.config/fish/conf.d" || return 1 + # Generate ~/.config/fish/config.fish mirroring the default bashrc in + # fish-native syntax. Fish has built-in autosuggestions and syntax + # highlighting, so no plugins are needed. + cat > "$HOME/.config/fish/config.fish" <<-'FISHRC' || return 1 + # squarebox fish config (experimental) — mirrors ~/.bashrc + status is-interactive; or exit 0 + + starship init fish | source + zoxide init fish --cmd cd | source + + alias ls='eza --icons' + alias ll='eza -la --icons' + alias lsa='ls -a' + alias lt='eza --tree --level=2 --long --icons --git' + alias lta='lt -a' + alias cat='bat --paging=never' + alias ff="fzf --preview 'bat --style=numbers --color=always {}'" + alias eff='$EDITOR (ff)' + alias ..='cd ..' + alias ...='cd ../..' + alias ....='cd ../../..' + alias g='git' + alias gcm='git commit -m' + alias gcam='git commit -a -m' + alias gcad='git commit -a --amend' + + set -x EDITOR nano + fish_add_path -g $HOME/.local/bin + + # User selections translated from bash files at install time. + test -f $HOME/.config/fish/conf.d/squarebox-selections.fish + and source $HOME/.config/fish/conf.d/squarebox-selections.fish + + test -x ~/motd.sh; and ~/motd.sh + FISHRC + [ -f "$HOME/.config/fish/config.fish" ] || return 1 + + # Translate AI/editor/TUI/SDK bash-syntax files into a single fish + # conf.d snippet. Regenerated each install to reflect current selections. + local sel_out="$HOME/.config/fish/conf.d/squarebox-selections.fish" + { + echo "# Generated by setup.sh from ~/.squarebox-* bash files." + for src in \ + "$HOME/.squarebox-ai-aliases" \ + "$HOME/.squarebox-editor-aliases" \ + "$HOME/.squarebox-tui-aliases" \ + "$HOME/.squarebox-sdk-paths"; do + [ -f "$src" ] || continue + echo "# --- from $(basename "$src") ---" + while IFS= read -r _sq_line; do + _squarebox_bash_line_to_fish "$_sq_line" + done < "$src" + done + } > "$sel_out" || return 1 +} + +install_fish() { + run_with_spinner "Installing Fish..." _install_fish_inner +} + case "$shell_choice" in zsh) if install_zsh; then touch ~/.squarebox-use-zsh + rm -f ~/.squarebox-use-fish echo "Zsh will take over at the end of this setup (next interactive shell)." else echo "Warning: Zsh installation failed; staying on bash." + rm -f ~/.squarebox-use-zsh ~/.squarebox-use-fish + fi + ;; + fish) + if install_fish; then + touch ~/.squarebox-use-fish rm -f ~/.squarebox-use-zsh + echo "Fish will take over at the end of this setup (next interactive shell)." + else + echo "Warning: Fish installation failed; staying on bash." + rm -f ~/.squarebox-use-zsh ~/.squarebox-use-fish fi ;; bash|*) - rm -f ~/.squarebox-use-zsh + rm -f ~/.squarebox-use-zsh ~/.squarebox-use-fish ;; esac fi # should_run shell From 18e397340a3a8965071936182653a496c422183a Mon Sep 17 00:00:00 2001 From: Brett Kinny Date: Fri, 17 Apr 2026 18:19:50 +1000 Subject: [PATCH 2/2] fix: address copilot review comments on fish setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - setup.sh: use `status is-interactive; or return` instead of `exit 0` in the generated config.fish. `exit` terminates the whole shell for non-interactive invocations like `fish -c '…'`; `return` only exits the current sourced config context. - scripts/e2e-test.sh: add a 3.12c assertion that the bash selection leaves no fish handoff marker, matching the existing zsh marker check. Co-Authored-By: Claude Opus 4.7 (1M context) --- scripts/e2e-test.sh | 10 +++++++++- setup.sh | 4 +++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/scripts/e2e-test.sh b/scripts/e2e-test.sh index 0869b9b..5fdef9f 100755 --- a/scripts/e2e-test.sh +++ b/scripts/e2e-test.sh @@ -217,7 +217,7 @@ suite_setup_editors() { run_test "3.11e sdks config saved" test -f /workspace/.squarebox/sdks run_test "3.11f shell config saved" test -f /workspace/.squarebox/shell - # 3.12 shell section: bash selection leaves no zsh handoff marker + # 3.12 shell section: bash selection leaves no zsh/fish handoff markers run_test_grep "3.12a shell config is bash" "bash" cat /workspace/.squarebox/shell TEST_NUM=$((TEST_NUM + 1)) if [ ! -e ~/.squarebox-use-zsh ]; then @@ -227,6 +227,14 @@ suite_setup_editors() { FAIL_COUNT=$((FAIL_COUNT + 1)) echo "not ok ${TEST_NUM} - 3.12b no zsh marker for bash selection" fi + TEST_NUM=$((TEST_NUM + 1)) + if [ ! -e ~/.squarebox-use-fish ]; then + PASS_COUNT=$((PASS_COUNT + 1)) + echo "ok ${TEST_NUM} - 3.12c no fish marker for bash selection" + else + FAIL_COUNT=$((FAIL_COUNT + 1)) + echo "not ok ${TEST_NUM} - 3.12c no fish marker for bash selection" + fi # 4.4 EDITOR set to first selected editor (micro) run_test_grep "4.4 EDITOR set to micro" "micro" cat ~/.squarebox-editor-aliases diff --git a/setup.sh b/setup.sh index 8938c78..606c73b 100755 --- a/setup.sh +++ b/setup.sh @@ -1471,7 +1471,9 @@ _install_fish_inner() { # highlighting, so no plugins are needed. cat > "$HOME/.config/fish/config.fish" <<-'FISHRC' || return 1 # squarebox fish config (experimental) — mirrors ~/.bashrc - status is-interactive; or exit 0 + # Use `return` (not `exit`) so non-interactive invocations like + # `fish -c '…'` skip this config without terminating the shell. + status is-interactive; or return starship init fish | source zoxide init fish --cmd cd | source