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
7 changes: 7 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 17 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,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 |
|------|-------------|
Expand All @@ -195,12 +197,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

Expand Down
14 changes: 11 additions & 3 deletions scripts/e2e-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines +173 to +175
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

The suite now clears both ~/.squarebox-use-zsh and ~/.squarebox-use-fish, but the assertion later in this suite still only checks that the zsh marker is absent for a bash selection. Please also assert that the fish marker is not present, so the test actually catches regressions where bash selection accidentally leaves/creates the fish handoff marker.

Copilot uses AI. Check for mistakes.

# Pre-configure git identity
git config --global user.name "E2E Test" 2>/dev/null || true
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion scripts/squarebox-setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ usage() {
tuis TUI tools (lazygit, gh-dash, yazi)
multiplexers Terminal multiplexers (tmux, zellij)
sdks SDKs (node, python, go, dotnet, rust)
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
Expand Down
138 changes: 127 additions & 11 deletions setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1319,7 +1319,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"

Expand All @@ -1330,38 +1330,44 @@ 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]"
else
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
Expand All @@ -1370,7 +1376,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"
Expand Down Expand Up @@ -1449,18 +1458,125 @@ 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
# 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

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
Expand Down
Loading