Skip to content

xydacshell upgrade path: profiles, x command, safe installer#1

Merged
xydac merged 12 commits into
masterfrom
upgrade-path
Apr 12, 2026
Merged

xydacshell upgrade path: profiles, x command, safe installer#1
xydac merged 12 commits into
masterfrom
upgrade-path

Conversation

@xydac

@xydac xydac commented Apr 12, 2026

Copy link
Copy Markdown
Owner

Summary

Turns xydacshell into a maintained tool without breaking any existing classic-profile user. Adds a modern profile (starship + nvim + modern CLI tools), introduces the short x command (subcommands: install, update, switch, doctor, rollback, storage, uninstall), and rewrites the installer to be idempotent, safety-rail-guarded, and interactive where it matters.

Safety guarantees for existing users

  • Existing ~/.zshrc -> ~/.xydacshell/zshrc.file symlinks keep working with zero action required. Missing profile file → dispatcher defaults to classic, which loads the original oh-my-zsh + materialshell-electro setup byte-for-byte.
  • ~/.xydacshell/zshrc.custom and vimrc.custom are hash-verified before and after every install run.
  • Legacy backup/.zshrc and backup/.vimrc (pre-install originals) are never touched. New backups go to backup/<timestamp>/.
  • Installer refuses to run with uncommitted local edits to tracked files.
  • Profile switch requires explicit confirmation (or --force).
  • Broken %{…%} git-status escape sequences in materialshell-electro.zsh-theme fixed (lines 68-73 — benefits classic users immediately on next pull).

The x command

  • x install [--profile classic|modern] [--dry-run] [--force]
  • x update — git pull + submodule sync + reinstall current profile
  • x switch <classic|modern> — profile flip with dry-run preview + confirmation
  • x doctor [--no-prompt] — diagnostic (profile, symlinks, custom file sizes, PM, tool presence, latest backup, git state). When on classic with clean repo and interactive tty, offers to preview and apply a switch to modern. This makes x doctor the friendly entry point for existing users after git pull.
  • x rollback [--stamp TS] — restore from a timestamped backup
  • x storage [--caches] [--top N] [--clean] — disk-usage report: filesystems (via duf/df), $HOME top dirs (via dust/du), package-manager caches (brew/npm/pnpm/cargo/pip/uv), docker, trash. --clean prompts per-cache for native cleanup commands.
  • x uninstall — removes our symlinks, restores legacy backups. Does not delete the repo.

bin/x is the primary command; bin/xydacshell is a symlink. Both work. Installer warns on PATH conflicts (another x already on PATH or shadowed by a shell alias).

Modern profile

  • Starship prompt (two-line, echoes classic's material aesthetic)
  • Small Neovim init.lua (backtick leader to preserve classic muscle memory; no plugin manager)
  • Graceful fallbacks for optional tools: fzf, zoxide, lsd, bat, ncdu, dust, duf

Interactive tool installer: detects OS + package manager (brew / apt / dnf / pacman / apk), prompts per missing tool with the exact command it will run. Uses the native PM where possible; falls back to official curl installers (starship, zoxide) or cargo install (dust on apt). Missing tools degrade gracefully.

Upgrade story for existing users

cd ~/.xydacshell
git stash                     # only if hand-edited
git pull --rebase
git stash pop                 # only if stashed
exec zsh                      # new shell picks up `x` on PATH

x doctor                      # report + optional switch to modern

Classic users who decline the switch see zero change. Future updates run via x update.

CI

Added GitHub Actions workflow: shellcheck on all shell scripts, zsh -n syntax check on all zshrc files, nvim --headless load-check on the modern init.lua.

Demo

vhs/demo.tape scripts a ~25s showcase: x help → x storage --top 5x doctor --no-prompt. Render locally with vhs vhs/demo.tape (needs brew install vhs). The rendered vhs/demo.gif is referenced at the top of the README — commit it after first render.

Deferred for a follow-up

  • tmux integration (design discussion owed — prefix key? TPM? resurrect/continuum?)
  • Cron management (x cron list | edit | add | remove | history | restore)
  • Prompt polish (fix missing ] in starship second line; add contextual modules: cmd_duration, language detectors, k8s/aws context)
  • Cold-box bootstrap (curl | bash one-liner that installs Homebrew, git, zsh, clones the repo, and runs install.sh)
  • Classic retirement (future major): drop oh-my-zsh, amix/vimrc, k submodules once the user-base has migrated.

Test plan

  • Dry-run fresh classic install
  • Dry-run fresh modern install
  • Dry-run profile switch on a simulated existing-install (verify zshrc.custom hash unchanged, legacy backup preserved)
  • x doctor on classic offers the upgrade; --no-prompt skips it
  • x storage reports sane values for brew/npm/cargo/etc
  • x rollback restores from the most recent backup
  • x uninstall --dry-run previews cleanly
  • CI (shellcheck + zsh/nvim syntax) passes

Commits (8)

  1. Add profiles, safe idempotent installer, modern stack, CI
  2. Interactive installer for modern-profile tools
  3. Swap eza for lsd in modern profile
  4. Add xydacshell command with doctor/rollback/storage/uninstall
  5. Rename dispatcher to 'x', warn on PATH conflicts
  6. Make 'x doctor' the upgrade path for classic users
  7. Simplify first-upgrade recipe
  8. Add VHS tape for demo gif

xydac added 12 commits April 12, 2026 09:02
- Split shell config into profiles (classic + modern); root zshrc.file and
  vimrc.file become dispatchers that read ~/.xydacshell/profile. Existing
  ~/.zshrc symlinks keep working with no action needed; missing profile
  defaults to classic, which loads the original setup unchanged.
- Rewrite install.sh as idempotent, profile-aware, with --dry-run, --force,
  and --profile flags. New backups go to backup/<timestamp>/ so the legacy
  pre-install backup/.zshrc and backup/.vimrc stay untouched. The installer
  refuses to run with uncommitted local edits, and hash-verifies that
  zshrc.custom and vimrc.custom are not modified during a run.
- Add modern profile: starship config echoing the classic two-line prompt,
  a small nvim init.lua keeping the backtick leader for muscle memory, and
  a zshrc that uses fzf/zoxide/eza/bat when present with graceful fallbacks.
- Fix broken %{...%} escape sequences in materialshell-electro.zsh-theme
  (lines 68-73) that leaked raw escapes on dirty repos.
- Add CI: shellcheck, zsh -n syntax check, nvim headless load-check.
- Rewrite README, add CHANGELOG.

All five submodules are retained; classic still needs them. Plan for a
future major cut is captured in CHANGELOG.
Replace the static 'here are install hints' block with OS and
package-manager detection plus per-tool install prompts. Supports
brew/apt/dnf/pacman/apk; uses official curl installers for tools
the pm does not ship (starship, zoxide); flags eza as cargo-install
on apt.

--force auto-accepts; --dry-run previews without running. Missing
tools continue to degrade gracefully in the modern zshrc.
lsd is more broadly packaged (in apt since Debian 11; no cargo
fallback needed), so it drops cleanly into every supported package
manager. Updates the installer tool list, zshrc aliases (ls/ll/la/tree),
README and CHANGELOG.
Introduces bin/xydacshell as a subcommand dispatcher for day-to-day
operations. Both profile zshrcs prepend $XYDACSHELL_HOME/bin to PATH
so 'xydacshell <verb>' works after install.

Subcommands:
- install        forwards to install.sh (unchanged)
- update         git pull + submodule sync + reinstall current profile
- switch         shorthand for install --profile <profile>
- doctor         diagnostic: profile, symlinks, custom file sizes, PM,
                 tool presence, latest backup, git state
- rollback       restore from backup/<timestamp>/; --stamp picks one
- storage        disk-usage report; covers local filesystems, $HOME
                 top dirs, package-manager caches (brew, npm, pnpm,
                 cargo, pip, uv), docker, trash; --clean prompts to
                 prune each cache with its native command
- uninstall      remove our symlinks, restore legacy pre-install
                 backup/.zshrc /.vimrc, do not delete the repo itself

Also: add ncdu, dust, duf to the modern-profile installer tool list
(with cargo-install-du-dust fallback on apt/apk). Add LICENSE file
(MIT, matching what README already declared). Move xs_prompt_yn from
modern-tools.sh to util.sh so every command can use it.
Primary command is now 'x' (short, fast to type) with 'xydacshell' kept
as a symlink alias for discoverability, tab completion, and existing
muscle memory. Both work identically.

During install, check PATH for an existing 'x' that isn't ours and print
a clear warning listing the conflicting path(s), with a reminder that
shell aliases (which override PATH in interactive shells) won't be
visible from install.sh — the user needs to run 'alias | grep ^x='
themselves. If the user wants to keep their existing 'x', the
'xydacshell' symlink still works.
After printing the diagnostic, doctor now checks: is the user on the
classic profile, is the repo clean, and is stdin a tty? If all three,
it offers to preview (dry-run) and then apply a switch to the modern
profile. Declining leaves everything untouched.

This turns 'x doctor' into the friendly post-git-pull entry point for
existing users. The upgrade flow becomes:

  cd ~/.xydacshell
  git stash && git pull --rebase && git submodule update --init --recursive && git stash pop
  exec zsh            # new shell picks up the x command on PATH
  x doctor            # report + optionally switch to modern

Classic users who say no never see a change. Their shell remains
byte-for-byte what it was.

Also: --no-prompt / --report flag for doctor to skip the offer (useful
for scripts and CI).
Drop the submodule update step from the README upgrade instructions.
No submodule refs change in this PR, so it would be a no-op for the
first upgrade. For subsequent pulls that may bump submodules, 'x update'
handles it in one step (and install.sh itself does submodule update
for the classic profile).
vhs/demo.tape scripts a ~25s showcase: 'x' help, 'x storage --top 5'
(the real win), 'x doctor --no-prompt'. Renders to vhs/demo.gif.

Rendering requires 'brew install vhs' (or equivalent on other platforms).
Chrome sandbox restrictions in some Linux environments prevent vhs from
rendering there; run it on macOS or a box with working Chrome sandbox.

README embeds vhs/demo.gif so the gif appears at top once committed
after the first render.
Two changes:

1. Add '# shellcheck shell=bash' directive to profiles/{classic,modern}/zshrc.
   These files are sourced by the dispatcher (no shebang); shellcheck
   refused to lint them without a shell hint (SC2148 error).

2. Set severity=error on the shellcheck action so the pipeline doesn't
   fail on stylistic notes/warnings — unused ZSH_THEME/plugins/SAVEHIST
   variables (oh-my-zsh and zsh read them implicitly, but shellcheck
   can't see that), SC1091 for sourced files at runtime paths, SC2086
   quoting notes inside zsh idioms, etc.

Real errors still fail the build.
~25s showcase: 'x' help → 'x storage --top 5' → 'x doctor --no-prompt'.
Rendered with asciinema + agg on Linux (vhs failed here due to Chrome
sandbox restrictions). vhs/demo.tape stays as the canonical script for
future rerenders; use 'brew install vhs && vhs vhs/demo.tape' on macOS
to rebuild.
Use 'git pull --rebase --autostash' (built in since git 2.9) so the
upgrade flow compresses to one line and works whether or not the user
has local changes:

  cd ~/.xydacshell && git pull --rebase --autostash && git submodule update --init --recursive && exec zsh

Then 'x doctor' in the new shell. Removed the multi-step stash / pull /
pop recipe and the half-written upgrade.sh helper.
The user-facing help and doctor output no longer lead with the word
'xydacshell'. The header reads 'x — your terminal setup, managed.',
doctor's banner is 'x doctor', and the uninstall line refers to
'installation' instead of 'xydacshell'. A small footer line retains
'(also available as xydacshell.)' for discoverability.

The repo and project are still called xydacshell; only the interactive
command-line surface favors 'x'. The xydacshell symlink still works.

Re-renders vhs/demo.gif with the updated help output.
@xydac xydac merged commit b75ad80 into master Apr 12, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant