# Shell Nushell as the login shell, bash unchanged as `/bin/sh` and `/bin/bash`. Companion to [STACK.md](STACK.md) and [CONSOLE.md](CONSOLE.md). ## Thesis Alloy's audience writes their own scripts and reaches for the shell as a tool-glue surface, not just an interactive prompt. That reframes the shell pick away from "safe universal default" and toward "which shell makes daily tool-glue actually good." Nushell wins that on structural grounds (structured pipelines with typed values instead of stringly-typed byte streams), and every daily interaction compounds the advantage. The pick is coherent with the rest of Alloy: `alloy console` treats configs as typed data, `alloy_tui` treats widgets as functions over state, and now the shell treats commands as functions over typed pipelines. Same principle at three layers. ## Architecture: nu is login-only The pick that keeps the ecosystem tax bounded: - **Login shell:** `nu`. What the user sees when they open rio, when they SSH in, when they open a scratch pane. - **`/bin/sh`:** bash, unchanged. Every `curl | sh` invokes this. Untouched. - **`/bin/bash`:** bash, unchanged. Every `#!/bin/bash` script runs here. Untouched. - **`/etc/shells`:** contains both nu and bash. `chsh -s /bin/bash` reverts a user to bash cleanly and reversibly. **Consequence: `curl -fsSL https://example.com/install.sh | sh` works identically to any other distro.** The login shell is not in the invocation path. This was the biggest concern about picking a non-POSIX shell, and it turns out to be a misread of how `curl | sh` actually resolves. **Consequence: every `#!/bin/bash` or `#!/usr/bin/env bash` script the user writes or downloads runs in bash.** Their own tool-glue scripts can be nu (`#!/usr/bin/env nu`), which is where the structured-pipeline advantage lives, but nothing forces them to. ## What Alloy ships `etc/skel/.config/nushell/` in the Alloy repo: - `config.nu`: main nu configuration. Table style, history (sqlite backend), fuzzy completions, Alloy color palette applied to nu's own output, integration hooks for starship / zoxide / direnv (conditional on presence). - `env.nu`: environment. `EDITOR=hx`, `VISUAL=hx`, PATH additions for `~/.local/bin` and `~/.cargo/bin`, cache directory prep for integration inits. - `aliases.nu`: a small curated set (`ll`, `la`, `..`, `...`, git shortcuts). No plugin framework. The Alloy image installs these into `~/.config/nushell/` on first boot (or symlinks; packaging decision at v0). ## Interoperability Where daily-use friction shows up, and how Alloy handles it: - **Tool init lines.** `eval "$(starship init bash)"` and equivalents. Starship, zoxide, direnv, atuin, and carapace all ship `--init nu`. For the long tail of tools that don't, Alloy ships small wrapper snippets in `etc/skel/.config/nushell/integrations/` as they're needed, added on demand rather than preemptively. - **Interactive one-liners.** `FOO=bar cmd && cmd2` becomes `FOO=bar; cmd; cmd2` (or `try / catch` for the conditional flavor). Muscle-memory tax; not a real barrier. - **`export`.** `$env.FOO = "bar"` in nu, `export FOO=bar` in bash. Both work in their respective contexts. - **Piped stdin.** Third-party CLIs that expect line-oriented text stdin: nu's default output is a table, but piping to an external command auto-flattens to text. Explicit control via `| to text` when needed. - **Command-substitution.** `` `foo` `` becomes `(foo)` in nu. Small learn. ## Accepted costs - **Pre-1.0 velocity.** Nushell is currently in the 0.100.x range. Breaking changes across releases have slowed dramatically since 0.75+ (era of "every release rewrites your config" is over), but a couple of key renames per year still happen. Alloy accepts drift maintenance for `config.nu` the same way it does for `userChrome.css`. - **Documentation tax.** Google results for "how do I X on the command line" assume bash. Fish translations are usually a nearby SO answer; nu translations often require a trip to nushell.sh's book. Real cost, absorbed by the "audience that reads docs deliberately" targeting. - **Command name collisions.** Nu's built-in `ls`, `cd`, `cp`, `ps` behave differently from GNU equivalents (structured output, different flags). Users learn the mapping. Strictly nicer for interactive use once learned. - **Ecosystem depth.** Fewer third-party CLIs ship `--init nu` than `--init fish` or `--init zsh`. Alloy backfills as needed via `etc/skel/.config/nushell/integrations/`. ## Prompt **starship** (Rust, cross-shell). Boring, works, ships `starship init nu` first-class. Alloy configures starship's own palette to match the tokens. A custom nu prompt as a small design-system exercise is a legitimate future project (nushell's prompt hook is rich enough to author an Alloy prompt directly), but not v0 scope. Deferred. ## Rejected - **fish.** Excellent shell, safer pick. Rejected because the audience is script-writers, not paste-and-forget users, so nu's structural advantages earn their cost. If nu turns out to be the wrong call in practice, fish is the fallback and the switch is small (login-shell only, `chsh -s /usr/bin/fish`). - **zsh.** Mostly-POSIX and powerful, but raw zsh is unpleasant and requires a config framework (oh-my-zsh, zinit) to be tolerable. Adopting a framework means importing someone else's opinions, which Alloy avoids everywhere else. - **bash.** No opinion, works. Best UX-out-of-box is worst-in-class. Alloy is opinionated everywhere; being un-opinionated at the shell layer specifically feels like leaving value on the table. - **elvish, xonsh, oil.** Interesting, small communities. Adopting them would put Alloy in a "maintaining half the ecosystem" position. ## Open questions - [x] **Root login shell: bash.** Recovery is the wrong time to hit a shell surprise. Nu is in `/etc/shells` and reachable via `chsh -s /usr/bin/nu` for users who want consistency. - [x] **History file backend: sqlite.** Better interactive search; atuin (if adopted later) reads sqlite natively, so no migration on the future path. - [x] **atuin default: defer.** Encrypted history sync is a real feature but sits adjacent to CONTINUITY.md's Tailscale/Syncthing story and wants its own design pass. Users install atuin themselves in the meantime.