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
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ jobs:
needs: changes
if: needs.changes.outputs.ubuntu_container_e2e == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
timeout-minutes: 25
env:
GITHUB_TOKEN: ${{ github.token }}
container:
image: ubuntu:24.04

Expand Down
29 changes: 16 additions & 13 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ and Ubuntu on WSL. Installation must be simple, maintenance predictable, and the
user experience consistent across supported platforms.

- The immutable `v<version>` Git tag is the release version source of truth.
- `profiles/*.conf` defines built-in package profiles.
- `packages.conf` defines the environment's package membership.
- `dependencies.conf` pins direct downloads and Git dependencies.
- `config/shared/mise.toml` pins mise-managed developer tools.
- `docs/RELEASING.md` is the release procedure.
Expand All @@ -26,7 +26,7 @@ Supported commands are `help`, `version`, `doctor`, `install`, `status`,
`update`, `rollback`, and `uninstall`. Keep their responsibilities narrow:

- the bootstrap installs only the CLI unless `--setup` is explicit;
- `selfishell install` explicitly installs a profile and configuration;
- `selfishell install` explicitly installs the development environment and configuration;
- `update --cli-only` and `update --tools-only` keep release and environment
updates separable;
- rollback uses a retained release without downloading it again;
Expand Down Expand Up @@ -88,8 +88,8 @@ Preserve these lifecycle invariants:
macOS Bash 3.2 unless the product explicitly installs another interpreter.
- Keep Homebrew and Apt operations in `lib/package_managers/`; do not scatter
platform branches through command implementations.
- Keep profile files declarative: only supported `include` and `package`
records, never executable shell code.
- Keep `packages.conf` declarative: only supported `package` records, never
executable shell code.
- Make repeated setup safe and idempotent.
- Download to a temporary location, verify it, and activate it atomically.
- Never execute an unversioned remote release payload as the installer.
Expand All @@ -103,16 +103,19 @@ Preserve these lifecycle invariants:
- Ordinary shell startup must never install updates or block on the network. A
cached release notice may refresh metadata in a non-blocking background job.

## Profiles and Dependencies
## Packages and Dependencies

`developer` is the default profile and includes `minimal` plus the larger
interactive tools, jq, build tools, and language/editor tooling. `minimal` is
the explicit lightweight choice. Ghostty is a separate saved macOS installation
choice.
Selfishell provides one development environment, without selectable profiles.
Ghostty is a separate saved macOS installation choice. The `configured` marker
in the state directory records completed setup, not a package selection.

The developer profile's mise-managed tool membership is declared in
`profiles/developer.conf`; exact versions for those tools are pinned in
Mise-managed tool membership is declared in
`packages.conf`; exact versions for those tools are pinned in
`config/shared/mise.toml`, the source of truth for mise-managed tool versions.
Installer-owned mise operations run from the release's `config/shared`
directory so a caller's project cannot override approved tool versions.
Status and doctor query installed versions, not merely configured requests;
an orphaned mise shim does not count as an external tool installation.

When the tools/configuration phase runs, `--skip-packages` must skip package
and tool installation and apply managed configuration only. A default update
Expand Down Expand Up @@ -162,7 +165,7 @@ instead. After publication, verify all four archives, `SHA256SUMS`, generated
## Verification

Run the smallest relevant tests while iterating, then run the repository gate
for any shell, lifecycle, profile, dependency, or release change:
for any shell, lifecycle, package, dependency, or release change:

```sh
bash scripts/check.sh
Expand Down Expand Up @@ -190,7 +193,7 @@ it; the gate remains required for the change categories listed above.
| --- | --- |
| `bin/`, `lib/` | CLI commands, lifecycle, platform and package adapters |
| `config/` | Managed shared, macOS, and Ubuntu shell/editor configuration |
| `profiles/`, `dependencies.conf` | Declarative profiles and approved dependencies |
| `packages.conf`, `dependencies.conf` | Declarative packages and approved dependencies |
| `tests/` | Isolated unit and lifecycle coverage |
| `scripts/` | Validation, benchmarks, dependency discovery, release builds |
| `.github/` | CI, dependency automation, and release publication |
Expand Down
26 changes: 9 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Install the CLI:
curl -fsSL https://raw.githubusercontent.com/jiminu/selfishell/main/install.sh | bash
```

Then install the default `developer` environment and verify it:
Then install the development environment and verify it:

```bash
selfishell install
Expand All @@ -32,35 +32,27 @@ Ghostty, and platform notes.

- A consistent Zsh environment across supported macOS and Ubuntu systems.
- A Starship prompt, useful aliases, completions, and managed shell defaults.
- `minimal` and `developer` profiles, with Neovim and development tooling in
the developer profile.
- Neovim, mise-managed runtimes, CLI tools, and build tooling.
- Checksum-verified release updates and a retained release for offline rollback.
- User-owned shell files: personal aliases, exports, functions, and project
tooling stay outside Selfishell-managed blocks.

![Selfishell Neovim workspace showing the file explorer, buffer tabs, shell-script syntax highlighting, and compact statusline](img/nvim.png)

## Profiles
## Development Environment

`developer` is the default profile; choose `minimal` explicitly for a lighter
shell setup.

| Profile | Includes |
| --- | --- |
| `minimal` | Core Zsh, Git, Vim, Starship, Zinit, and shell configuration |
| `developer` | Everything in `minimal`, plus Neovim, mise-managed runtimes, CLI/editor tools, and build tooling |

See [Profiles](docs/PROFILES.md) for the complete package list and profile
behavior.
`selfishell install` sets up one consistent environment: Zsh, Git, Vim,
Starship, Zinit, Neovim, mise-managed runtimes, CLI tools, and build tooling.
See [Environment](docs/ENVIRONMENT.md) for tool management and editor behavior.

## Everyday Commands

| Command | Use it to |
| --- | --- |
| `selfishell status` | Show the active profile, managed resources, and CLI/rollback version. |
| `selfishell status` | Show installed tools, managed resources, and CLI/rollback version. |
| `selfishell doctor` | Diagnose the current installation. |
| `selfishell version --available` | Check the latest published release. |
| `selfishell update` | Update the CLI, profile tools, and configuration. |
| `selfishell update` | Update the CLI, tools, and configuration. |
| `selfishell rollback` | Return to the previous retained release offline. |
| `selfishell uninstall --restore --dry-run` | Preview restoring backed-up configuration. |

Expand All @@ -80,7 +72,7 @@ startup.

- [Installation](docs/INSTALLATION.md) — setup, uninstallation, Ghostty, and
platform notes.
- [Profiles](docs/PROFILES.md) — package choices and Neovim workflow.
- [Environment](docs/ENVIRONMENT.md) — managed tools and Neovim workflow.
- [Python development](docs/PYTHON.md) — Python tooling and project setup.
- [Updates and rollback](docs/UPDATES.md) — release updates and recovery.
- [Troubleshooting](docs/TROUBLESHOOTING.md) — common installation and shell
Expand Down
4 changes: 2 additions & 2 deletions bin/selfishell
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ source "$SELFISHELL_ROOT/lib/platform.sh"
source "$SELFISHELL_ROOT/lib/resources.sh"
source "$SELFISHELL_ROOT/lib/dependencies.sh"
source "$SELFISHELL_ROOT/lib/tool_status.sh"
source "$SELFISHELL_ROOT/lib/profile_scan.sh"
source "$SELFISHELL_ROOT/lib/package_scan.sh"
source "$SELFISHELL_ROOT/lib/managed.sh"
source "$SELFISHELL_ROOT/lib/profiles.sh"
source "$SELFISHELL_ROOT/lib/package_manifest.sh"
source "$SELFISHELL_ROOT/lib/package_managers/apt.sh"
source "$SELFISHELL_ROOT/lib/package_managers/homebrew.sh"
source "$SELFISHELL_ROOT/lib/installers.sh"
Expand Down
9 changes: 7 additions & 2 deletions config/shared/mise.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
[tools]
starship = "1.26.0"
fzf = "0.74.4"
zoxide = "0.10.0"
ripgrep = "15.2.0"
eza = "0.23.5"
bat = "0.26.1"
jq = "1.8.2"
node = "24.18.0"
python = "3.13.14"
neovim = "0.12.5"
Expand All @@ -8,5 +15,3 @@ gh = "2.100.0"

[settings]
not_found_auto_install = false


2 changes: 1 addition & 1 deletion config/shared/nvim/lua/plugins/ui.lua
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ return {
},
},
opts = {
-- The developer profile guarantees ripgrep, but not fd.
-- Selfishell installs ripgrep, but not fd.
picker = {
ui_select = false,
sources = {
Expand Down
2 changes: 1 addition & 1 deletion config/shared/zsh/aliases.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ if _selfishell_command_path nvim >/dev/null; then
alias view='nvim -R'
fi

# Git is a required dependency in every profile, so these aliases need no
# Git is a required dependency, so these aliases need no
# _selfishell_command_path guard.
alias gst='git status -s'
alias gf='git fetch'
Expand Down
4 changes: 3 additions & 1 deletion config/shared/zsh/interactive.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@ fi
unset _selfishell_zoxide_bin

if _selfishell_fzf_bin="$(command -v fzf)"; then
# Ubuntu 24.04's fzf supports "16", but not the newer "base16" alias.
# Scheme 16 keeps fzf to the terminal's own colors, as the prompt does by
# naming colors. The environment wins, so this is a default, not a policy,
# and it reaches only Ctrl-T and Ctrl-R.
export FZF_DEFAULT_OPTS="${FZF_DEFAULT_OPTS:---color=16}"

_selfishell_fzf_cache="$SELFISHELL_CACHE_DIR/fzf-init.zsh"
Expand Down
4 changes: 0 additions & 4 deletions dependencies.conf
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ download mise 2026.9.7 linux amd64 https://github.com/jdx/mise/releases/download
download mise 2026.9.7 linux arm64 https://github.com/jdx/mise/releases/download/v2026.9.7/mise-v2026.9.7-linux-arm64 1227565aeff505b0b0735d91b63109e1e1e44a237c06dc9aa274f2f3bf732af7 .local/bin/mise raw
download mise 2026.9.7 macos amd64 https://github.com/jdx/mise/releases/download/v2026.9.7/mise-v2026.9.7-macos-x64 06f763e37615966f0d660f0fd7d4d23568d1b63d5bdb65e2f7a98af2fdc36075 .local/bin/mise raw
download mise 2026.9.7 macos arm64 https://github.com/jdx/mise/releases/download/v2026.9.7/mise-v2026.9.7-macos-arm64 3c3f377e7123a466274a20f01502ddd8c58f76028907f471c9bc42fbf83846e1 .local/bin/mise raw
download starship 1.26.0 linux amd64 https://github.com/starship/starship/releases/download/v1.26.0/starship-x86_64-unknown-linux-gnu.tar.gz 321f0dd7af8340a5f2e6a8fec6538a04f617486f9ec70d878f91c09cd8deef22 .local/bin/starship starship
download starship 1.26.0 linux arm64 https://github.com/starship/starship/releases/download/v1.26.0/starship-aarch64-unknown-linux-musl.tar.gz dc30189378d2f2e287384e8a692d3f95ad1df64cf0e8c36aa9201516028aed6b .local/bin/starship starship
download starship 1.26.0 macos amd64 https://github.com/starship/starship/releases/download/v1.26.0/starship-x86_64-apple-darwin.tar.gz 5548f406a4b6f5695903bdea83f77ce47ec12c8c0e62dabd33122d8f133e4207 .local/bin/starship starship
download starship 1.26.0 macos arm64 https://github.com/starship/starship/releases/download/v1.26.0/starship-aarch64-apple-darwin.tar.gz c40b27b11f580411e068f2fa6c1be7830a387c0bc47a94d1d37f32b054c5361d .local/bin/starship starship
git zinit v3.17.0 all all https://github.com/zdharma-continuum/zinit.git - .local/share/zinit/zinit.git zinit.zsh
zsh-plugin zsh-users/zsh-completions de02bb84ab0af51e328c6ae85ab5555397c31277 all all https://github.com/zsh-users/zsh-completions.git - - -
zsh-plugin Aloxaf/fzf-tab 24105b15714bfec37989ed5c5b6e60f572253019 all all https://github.com/Aloxaf/fzf-tab.git - - -
Expand Down
4 changes: 2 additions & 2 deletions docs/COMPANY.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ Recommended deployment controls:
3. Provision Homebrew separately if executing its upstream bootstrap is not an
acceptable trust decision.
4. Never place credentials, tokens, kubeconfigs, internal URLs, or certificate
private keys in a profile or this repository.
5. Validate the selected profile on a clean managed image before broad rollout.
private keys in package configuration or this repository.
5. Validate the environment on a clean managed image before broad rollout.
57 changes: 24 additions & 33 deletions docs/PROFILES.md → docs/ENVIRONMENT.md
Original file line number Diff line number Diff line change
@@ -1,53 +1,44 @@
# Profiles
# Development Environment

Profiles are cumulative. Choose the smallest profile that covers the machine's
role.
Selfishell installs one development environment. `packages.conf` declares
its packages: Zsh, Git, Vim, Starship, Zinit, Neovim, CLI tools, language
runtimes, compiler tooling, and optional macOS terminal fonts.

| Profile | Purpose |
| --- | --- |
| `minimal` | Core shell, Zinit, Vim, and macOS terminal fonts |
| `developer` | Minimal plus Neovim, Tree-sitter CLI, Node.js, Python, uv, GitHub CLI, FZF, Zoxide, Ripgrep, Eza, Bat, jq, and compiler tooling |

`developer` is selected when `--profile` is omitted. Choose `minimal`
explicitly for a lightweight shell setup without the larger development
toolchain.

The `developer` profile installs a pinned mise binary and activates it for
Selfishell installs a pinned mise binary and activates it for
interactive Zsh. Selfishell keeps its defaults in
`${XDG_CONFIG_HOME:-$HOME/.config}/selfishell/mise/selfishell.toml` (which is symlinked to `~/.config/mise/conf.d/selfishell.toml` so it is automatically loaded by `mise`); a project's
`mise.toml` can select different tool versions.

Built-in mise tools use exact reviewed versions pinned in `config/shared/mise.toml`,
the single source of truth for these versions. Projects remain free to
override them in a local `mise.toml`. Updating these defaults requires a normal
Developer tools managed by mise use exact reviewed versions pinned in
`config/shared/mise.toml`, the single source of truth for these versions. This
includes Starship, FZF, Zoxide, Ripgrep, Eza, Bat, jq, Neovim, Tree-sitter CLI, Node.js,
Python, uv, and GitHub CLI on both macOS and Ubuntu. Eza and Bat remain optional;
failure to install either does not stop setup. Projects remain free to override
the defaults in a local `mise.toml`. Updating the defaults requires a normal
Selfishell release and never happens during shell startup.

Mise manages the Starship executable and its version; Selfishell still manages
`starship.toml` and prompt initialization.

Preview without changing the machine:

```sh
selfishell install --dry-run
```

Install or change the selected profile explicitly:
Install the environment:

```sh
selfishell install --profile minimal --yes
selfishell install --yes
```

The active profile is recorded in the XDG state directory. `selfishell update`
uses that recorded profile to install missing Apt, Homebrew, and directly
managed tools before updating configuration. Apt and Homebrew retain
responsibility for versions of packages they already manage.

Changing from `developer` to `minimal` changes future tool synchronization;
it does not remove previously installed tools or configuration. Existing
Neovim and mise configuration links remain active, and `selfishell status`
continues to check all tracked paths, including retained developer resources.
To remove managed configuration before setting up a smaller profile, run
`selfishell uninstall --restore`, then `selfishell install --profile minimal`.
Uninstall leaves installed packages and user-created files in place.
`selfishell update` uses `packages.conf` to install missing Apt, Homebrew, and directly
managed tools and synchronize mise tools before updating configuration. Apt and
Homebrew retain responsibility for packages still declared through them.
Copies of a tool left from an older release are not removed
automatically; after mise activation, its pinned tool version takes precedence.

Profile package requirements have two failure policies:
Package requirements have two failure policies:

- `required` packages must be available and install successfully;
- `optional` packages are recommended and attempted automatically, but an
Expand All @@ -62,7 +53,7 @@ choice is saved and reused by `selfishell update`.

## Neovim workflow

The `developer` profile includes a pinned Neovim configuration whose leader key
Selfishell includes a pinned Neovim configuration whose leader key
is `Space`. In Normal mode, press `Space` and pause to open which-key. The popup
shows actions available in the current context; continue typing to narrow the
list. Every Selfishell mapping has a description, so which-key remains aligned
Expand All @@ -86,5 +77,5 @@ applied. Bufferline shows open buffers across the top; use `[b` and `]b` to move
between them, and `Space b d` to close the current buffer without closing its
editor window.

In the `developer` profile, `vim` resolves to Neovim while `vi` remains the
When Neovim is available, `vim` resolves to Neovim while `vi` remains the
system editor.
11 changes: 5 additions & 6 deletions docs/INSTALLATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ curl -fsSL https://raw.githubusercontent.com/jiminu/selfishell/main/install.sh |
selfishell install
```

`selfishell install` selects the recommended `developer` profile. Use
`selfishell install --profile minimal` for a lightweight shell setup.
`selfishell install` sets up the complete development environment.

## Verification coverage

Expand Down Expand Up @@ -41,9 +40,9 @@ The bootstrap installs only the CLI unless `--setup` is explicitly supplied.
Version discovery prefers the latest stable release and otherwise uses the
newest version tag only after its exact `VERSION` release asset is published.

### Install CLI and default profile together
### Install CLI and environment together

Install the CLI and default `developer` profile non-interactively:
Install the CLI and development environment non-interactively:

```sh
curl -fsSL https://raw.githubusercontent.com/jiminu/selfishell/main/install.sh |
Expand All @@ -60,7 +59,7 @@ Use an exact release in controlled environments:
```sh
curl -fsSL https://raw.githubusercontent.com/jiminu/selfishell/main/install.sh |
bash -s -- --version <version>
selfishell install --profile developer --yes
selfishell install --yes
```

The archive is downloaded to a temporary directory, checked against the
Expand All @@ -74,7 +73,7 @@ release for offline rollback and removes older inactive releases.
For configuration-only installation after the CLI is provisioned:

```sh
selfishell install --profile developer --skip-packages --yes
selfishell install --skip-packages --yes
```

`--skip-packages` performs configuration-only installation without package or
Expand Down
13 changes: 5 additions & 8 deletions docs/PERFORMANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,20 @@ cannot reintroduce host tools. A normal local run therefore does not read the
developer's mise configuration or execute or modify the developer's plugin
checkout.

### Full-profile mode
### Full-environment mode

Full mode additionally provisions the pinned mise, Starship, and Zinit -- with
Full mode additionally provisions the pinned mise, Starship, fzf, zoxide, and Zinit -- with
its pinned Zsh plugins -- into the benchmark's own isolated `HOME`, via the
same code path the real installer uses, so `interactive-cached` reflects a
real developer-profile startup rather than whatever happens to already be on
real full-environment startup rather than whatever happens to already be on
the runner's `PATH`. It:

- uses an isolated, temporary `HOME`; the real user `HOME` is never read or
changed;
- uses that home as its working directory and gives mise an isolated global
config;
- installs the pinned mise, Starship, and Zinit (with its pinned plugins)
into that isolated `HOME`;
- measures fzf and zoxide only if they are already on `PATH` -- installing
packages is out of scope for this script, so provision them via the
platform package manager first;
- installs the pinned mise and Zinit (with its pinned plugins), plus Starship,
fzf, and zoxide through mise using the release's exact pins;
- needs network access to provision those tools, so it is not part of the
regular (network-free) unit test suite, or run in CI -- run it locally
when needed.
Expand Down
2 changes: 1 addition & 1 deletion docs/TROUBLESHOOTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ selfishell doctor
selfishell status
```

`status` reports tools from the active profile as Selfishell-managed, Homebrew,
`status` reports tools from the environment as Selfishell-managed, Homebrew,
apt, external, or missing. Package-manager versions are reported without an
exact approved version because those repositories control resolution. It
returns nonzero when required tools are missing or managed configuration is
Expand Down
Loading