diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..3a9790b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,48 @@ +name: ci + +on: + push: + branches: [main, master, upgrade-path] + pull_request: + +jobs: + shellcheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: { submodules: false } + - name: Run shellcheck + uses: ludeeus/action-shellcheck@master + with: + severity: error + scandir: "./" + ignore_paths: | + oh-my-zsh + zsh-autosuggestions + zsh-syntax-highlighting + k + vimrc + backup + + syntax-zsh: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: { submodules: false } + - name: Install zsh + run: sudo apt-get update && sudo apt-get install -y zsh + - name: Parse-check profile zshrc files + run: | + for f in profiles/classic/zshrc profiles/modern/zshrc zshrc.file; do + zsh -n "$f" + done + + syntax-nvim: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: { submodules: false } + - name: Install Neovim + run: sudo apt-get update && sudo apt-get install -y neovim + - name: Load-check init.lua + run: nvim --headless -u profiles/modern/nvim/init.lua +qa diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..15f5514 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,37 @@ +# Changelog + +## Unreleased — upgrade-path branch + +### Added +- **`x doctor` offers profile upgrades.** When a user is on classic with a clean repo and an interactive tty, doctor prints the report and then offers to preview (`install.sh --dry-run --profile modern`) and, if accepted, apply the switch. Skippable with `--no-prompt`/`--report`. Intended as the friendly post-`git pull` entry point for existing classic users. +- **`x` command** (with `xydacshell` as a symlink alias for discoverability). PATH-installed dispatcher (`bin/x`) with subcommands: `install`, `update`, `switch`, `doctor`, `rollback`, `storage`, `uninstall`. Both profile zshrcs prepend `$XYDACSHELL_HOME/bin` to PATH so it "just works" after install. The installer warns if another `x` is already on PATH (or may be shadowed by a shell alias). +- **`xydacshell doctor`.** One-command diagnostic: current profile, managed symlinks, sacred custom file sizes, detected OS + package manager, modern-tool presence, most recent backup, git state. +- **`xydacshell rollback`.** Restore files from a timestamped backup dir (most recent by default; `--stamp` to pick one). Prompts before writing; `--dry-run` previews. +- **`xydacshell storage`.** Disk-usage report: local filesystems (via `duf`/`df`), top `$HOME` directories (via `dust`/`du`), package-manager caches (brew, npm, pnpm, cargo, pip, uv), docker, trash. `--caches` to focus, `--top N` to expand, `--clean` to prompt per-cache cleanup. +- **`xydacshell uninstall`.** Removes our symlinks, restores legacy `backup/.zshrc` / `backup/.vimrc` pre-install files when present. Does not delete the repo itself. +- **More modern tools in the installer.** `ncdu`, `dust`, `duf` added alongside starship/nvim/fzf/zoxide/lsd/bat. `dust` uses `cargo install du-dust` as fallback on apt/apk. +- **`LICENSE` file.** Formalizes the MIT license already declared in the README. +- **Modern-profile tool installer.** The installer now detects the OS (macOS / Linux) and an available package manager (brew / apt / dnf / pacman / apk), lists missing tools (starship, nvim, fzf, zoxide, lsd, bat) with their install commands, and prompts per tool. Uses the native package manager where possible; falls back to official curl installers (starship, zoxide) or `cargo install` (lsd on apt) where the pm doesn't ship the package. `--force` auto-accepts; `--dry-run` previews. Missing tools degrade gracefully — the profile still works without them. +- **Profiles.** Two installable profiles: + - `classic` — the original oh-my-zsh + materialshell-electro + amix/vimrc stack. Default for existing users. + - `modern` — starship prompt, Neovim with a small `init.lua`, graceful use of fzf/zoxide/lsd/bat when present, no oh-my-zsh dependency. +- **Profile dispatcher** in `zshrc.file` and `vimrc.file` — reads `~/.xydacshell/profile` and loads the active profile. Missing file → defaults to `classic`. Your `~/.zshrc` symlink keeps working; no action required for existing users. +- **Idempotent installer.** Re-running `install.sh` is safe. Creates a new `backup//` directory per run; never touches existing backups. +- **Profile-switch flow.** `install.sh --profile modern` flips profile with confirmation. +- **Dry-run mode.** `install.sh --dry-run` prints everything it would do. +- **Safety preflight.** Installer refuses to run if there are uncommitted local edits to tracked files. Sacred files (`zshrc.custom`, `vimrc.custom`) are hash-verified before and after. +- **CI.** `shellcheck` on all shell scripts; `zsh -n` syntax-check on all zshrc files; `nvim --headless` load-check on the modern `init.lua`. + +### Fixed +- **Broken git-status escape sequences** in `materialshell-electro.zsh-theme` (lines 68–73). Previously missing `%` before `%{$reset_color%}` would leak raw escape characters into the prompt. Classic users running a dirty repo would see garbled output. + +### Preserved (intentionally unchanged) +- Existing users' `~/.xydacshell/backup/.zshrc` and `backup/.vimrc` (their pre-install configs) remain untouched. +- `zshrc.custom` and `vimrc.custom` are never rewritten by the installer. +- Every submodule. Classic still needs them. +- The `~/.zshrc` / `~/.vimrc` symlink targets stay the same paths (`zshrc.file` / `vimrc.file`) — existing symlinks continue to work without modification. + +### Planned for a future major +- Retire the `classic` profile; drop `oh-my-zsh`, `amix/vimrc`, and `k` submodules. +- Make the modern profile fully submodule-free (source zsh plugins from `brew`/`apt`). +- Publish as a Homebrew tap for brew-managed upgrades. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d606178 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2010-2026 Deepak Seth + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index bc0e349..d6417bf 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,139 @@ +# xydacshell -# Xydac Shell -Opinionated Awesome Shell with cherry picked awesomeness. +An opinionated terminal setup. Two profiles, one managed toolchain, safe to re-run. -## Includes -* [oh-my-zsh](https://github.com/robbyrussell/oh-my-zsh) -* [vim rc](https://github.com/amix/vimrc) -* [zsh-syntax-highlighting](https://github.com/zsh-users/zsh-syntax-highlighting) -* [zsh-autosuggestions](https://github.com/zsh-users/zsh-autosuggestions) -* [k](https://github.com/supercrabtree/k) +![demo](vhs/demo.gif) -## Features -A lovely and Customized terminal System that declutters your shell customzations +- **classic** — oh-my-zsh with the `materialshell-electro` theme, amix/vimrc, and a handful of zsh plugins. The original xydacshell stack. +- **modern** — starship prompt, Neovim with a small `init.lua`, and graceful use of fzf / zoxide / lsd / bat / ncdu / dust / duf when they're installed. +Existing users: your setup still works. You stay on `classic` until you opt into `modern`. -## Screenshots -Prompt Preview -![Prompt Theme](https://raw.githubusercontent.com/xydac/xydacshell/master/screenshots/screenshot-prompt.png) -VIM Preview -![VI](https://raw.githubusercontent.com/xydac/xydacshell/master/screenshots/screenshot-vi.png) +## Install -## Installation, Updates, Uninstallation -### Installation : -``` -git clone https://github.com/xydac/xydacshell.git ~/.xydacshell && cd ~/.xydacshell && bash install.sh +```bash +git clone --recurse-submodules https://github.com/xydac/xydacshell.git ~/.xydacshell +cd ~/.xydacshell +bash install.sh # fresh: defaults to classic +bash install.sh --profile modern # opt into modern ``` -### Update: + +### Upgrading from a pre-2026 install + +Existing classic-profile installs keep working after a `git pull`. The updated +dispatcher defaults to classic when no profile file is present, so your shell +stays byte-for-byte the same. The only visible change is that the `x` command +appears on your `PATH` in new shells. + +One-liner: + +```bash +cd ~/.xydacshell && git pull --rebase --autostash && git submodule update --init --recursive && exec zsh ``` -cd ~/.xydacshell && git pull --rebase + +Then: + +```bash +x doctor ``` -### Uninstall -Restores Backup + +`--autostash` stashes local hand-edits before the rebase and pops them after, so this works whether or not you have uncommitted changes. `x doctor` detects that you're on classic, checks that the repo is clean, and offers to dry-run and then apply a switch to the modern profile. Say no and nothing changes. + +From then on, `x update` handles future pulls (git pull + submodules + reinstall) in one step. + +The installer is idempotent — running it twice is safe. After it finishes, open a new shell. The `x` command (and its `xydacshell` alias) is on your `PATH`. + +> **Heads-up on the name `x`:** the installer warns you if another `x` command already exists on your PATH (or via shell alias). Our `x` will take precedence in new shells. If you'd rather keep your existing `x`, use the `xydacshell` symlink instead — it's the same command. + +## Usage + +Once installed, everything runs through the `x` command (`xydacshell` is an alias): + +```bash +x # help +x install [--profile X] # run the installer (same as bash install.sh) +x update # git pull + submodule sync + reinstall +x switch modern # flip profile +x doctor # diagnose current install state +x rollback # restore from the most recent backup +x storage # disk-usage report, per-cache cleanup prompts +x uninstall # remove cleanly, restore legacy backups ``` -rm ~/.zshrc ~/.vimrc && mv ~/.xydacshell/backup/.zshrc ~/.zshrc && ~/.xydacshell/backup/.vimrc ~/.vimrc + +Every command supports `--dry-run` and `--force`. + +## Storage analytics + +```bash +x storage # filesystems + $HOME top dirs + pkg caches +x storage --caches # only package-manager caches +x storage --top 20 # more $HOME entries +x storage --clean # after the report, prompt per-cache to prune ``` -## Tweaks -* Alias : ```c``` --> Clears Screen -* Alias : ```gitlog``` --> One Liner Git Logs -### VI Tweaks -* Leader Key : ``` ` ``` -* Shortcut : ``` ` + ``` --> Move Panes -* Shortcut : ``` ` + ``` --> Recent Files +The report covers: +- local filesystems (via `duf` if installed, else `df -h`) +- top directories in `$HOME` (via `dust` if installed, else `du | sort`) +- package-manager caches: brew · npm · pnpm · cargo · pip · uv +- docker (`docker system df`) +- trash +Clean-up runs the documented cleanup command for each cache (e.g. `brew cleanup -s`, `pnpm store prune`, `docker system prune -f`), guarded by per-cache y/n prompts. `--dry-run` previews. + +## Customize + +Edit these files — they outlive any profile switch or upgrade and are never touched by the installer. + +- zsh: `~/.xydacshell/zshrc.custom` +- vim (classic): `~/.xydacshell/vimrc.custom` +- nvim (modern): `~/.xydacshell/nvim.custom.lua` + +## Modern profile — optional tool install + +The installer detects your OS + package manager and offers to install each missing tool, one at a time. Missing tools degrade gracefully — the modern profile still works without them. + +```bash +# What the installer offers on macOS: +brew install starship neovim fzf zoxide lsd bat ncdu dust duf + +# On Debian/Ubuntu the installer falls back to official scripts for +# tools apt doesn't ship (starship, zoxide, dust). +``` + +## Uninstall + +```bash +x uninstall # removes our symlinks, restores legacy backups +rm -rf ~/.xydacshell # removes the repo itself +``` + +## Repository layout + +``` +xydacshell/ +├── bin/x # dispatcher on your PATH +├── bin/xydacshell # symlink → x +├── install.sh # idempotent, profile-aware +├── lib/ +│ ├── util.sh # shared shell helpers +│ ├── modern-tools.sh # OS + PM detection, tool installer +│ └── cmds/ # one file per `xydacshell ` +├── profiles/ +│ ├── classic/ { zshrc, vimrc } # the original setup +│ └── modern/ { zshrc, starship.toml, nvim/init.lua } +├── zshrc.file, vimrc.file # dispatchers (read the profile, load config) +├── materialshell-electro.zsh-theme # classic prompt theme +├── backup/ # timestamped backups per install run +└── .github/workflows/ci.yml # shellcheck + zsh/nvim syntax checks +``` -## Further Customization -* Vim Customization : update your custom tweaks in ```~/.xydacshell/vimrc.custom``` -* ZSH Customization : update your custom tweaks in ```~/.xydacshell/zshrc.custom``` +## Compatibility -## Minimum Requirement -* zsh -* git +- `zsh` required. +- `git` required. +- `classic` profile: submodules are used (oh-my-zsh, amix/vimrc, etc.). +- `modern` profile: Neovim recommended; starship, fzf, zoxide, lsd, bat are optional with fallbacks. ## License -MIT -- Pull Request Welcome +MIT. Pull requests welcome. diff --git a/bin/x b/bin/x new file mode 100755 index 0000000..b0afe8d --- /dev/null +++ b/bin/x @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# xydacshell subcommand dispatcher. +# Installed as `x` (primary) with an `xydacshell` symlink for discoverability. +# Add $XYDACSHELL_HOME/bin to PATH (the profile zshrcs do this for you). + +set -euo pipefail + +: ${XYDACSHELL_HOME:=$HOME/.xydacshell} +export XYDACSHELL_HOME + +if [ ! -f "$XYDACSHELL_HOME/lib/util.sh" ]; then + echo "x: cannot find $XYDACSHELL_HOME/lib/util.sh — is XYDACSHELL_HOME correct?" >&2 + exit 1 +fi + +# shellcheck source=../lib/util.sh +. "$XYDACSHELL_HOME/lib/util.sh" + +usage() { + cat <<'EOF' +x — your terminal setup, managed. + +usage: x [args] + +commands + install [flags] run the installer (same as bash install.sh) + update git pull + submodule sync + reinstall + switch switch profile + doctor diagnose current install state + rollback [--stamp YYYYMMDDTHHMMSSZ] + restore from a timestamped backup + storage [--caches] [--top N] [--clean] + disk usage report; --clean prompts to prune + uninstall remove installation, restore legacy backups + +global flags + --dry-run preview (supported by install/update/switch/rollback/storage/uninstall) + --force auto-accept prompts + --help, -h show this message or per-command help + +run 'x --help' for per-command help. +(also available as `xydacshell`.) +EOF +} + +cmd="${1:-help}" +shift || true + +case "$cmd" in + install|update|switch|doctor|rollback|storage|uninstall) + src="$XYDACSHELL_HOME/lib/cmds/$cmd.sh" + if [ ! -f "$src" ]; then + xs_err "command file missing: $src" + exit 1 + fi + # shellcheck source=/dev/null + . "$src" + "xs_cmd_$cmd" "$@" + ;; + help|--help|-h|"") + usage + ;; + *) + xs_err "unknown command: $cmd" + usage >&2 + exit 2 + ;; +esac diff --git a/bin/xydacshell b/bin/xydacshell new file mode 120000 index 0000000..c1b0730 --- /dev/null +++ b/bin/xydacshell @@ -0,0 +1 @@ +x \ No newline at end of file diff --git a/install.sh b/install.sh old mode 100644 new mode 100755 index e8e4c55..15d3982 --- a/install.sh +++ b/install.sh @@ -1,35 +1,225 @@ -#!/bin/bash -if ! [ -x "$(command -v git)" ]; then - echo 'Error: git is not installed.' >&2 +#!/usr/bin/env bash +# xydacshell installer. +# Idempotent, profile-aware, non-destructive. Safe to re-run. +# +# Usage: +# bash install.sh interactive +# bash install.sh --profile classic pin to the classic profile +# bash install.sh --profile modern switch to the modern profile +# bash install.sh --dry-run preview without touching the filesystem +# bash install.sh --force skip all confirmations (profile switch + tool installs) +# bash install.sh --help print this help +# +# Safety guarantees (for existing users): +# * Never touches $XYDACSHELL_HOME/zshrc.custom or vimrc.custom if they exist. +# * Never touches the legacy single-file backups (backup/.zshrc, backup/.vimrc). +# * New backups go to backup// — never collide with anything older. +# * Refuses to run if the xydacshell repo has uncommitted local edits. +# * Switching profiles requires explicit confirmation (or --force). + +set -euo pipefail + +XYDACSHELL_HOME="${XYDACSHELL_HOME:-$HOME/.xydacshell}" +export XYDACSHELL_HOME + +# shellcheck source=lib/util.sh +. "$XYDACSHELL_HOME/lib/util.sh" +# shellcheck source=lib/modern-tools.sh +. "$XYDACSHELL_HOME/lib/modern-tools.sh" + +XS_DRY_RUN=0 +REQUESTED_PROFILE="" +FORCE=0 + +usage() { + sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//' +} + +while [ $# -gt 0 ]; do + case "$1" in + --profile) REQUESTED_PROFILE="${2:-}"; shift 2 ;; + --profile=*) REQUESTED_PROFILE="${1#--profile=}"; shift ;; + --dry-run) XS_DRY_RUN=1; export XS_DRY_RUN; shift ;; + --force) FORCE=1; shift ;; + -h|--help) usage; exit 0 ;; + *) xs_err "unknown flag: $1"; usage; exit 2 ;; + esac +done + +# Preflight checks. +for bin in git zsh; do + if ! xs_command_exists "$bin"; then + xs_err "required command missing: $bin" exit 1 + fi +done + +if [ "$PWD" != "$XYDACSHELL_HOME" ]; then + xs_err "please run from $XYDACSHELL_HOME (current: $PWD)" + exit 1 fi -if ! [ -x "$(command -v zsh)" ]; then - echo 'Error: zsh is not installed.' >&2 + +# Refuse to run if the user has made uncommitted changes to tracked files. +# (Unstaged changes to tracked files = potentially custom edits to zshrc.file/vimrc.file +# that git pull would clobber.) +if [ -d "$XYDACSHELL_HOME/.git" ]; then + dirty="$(git -C "$XYDACSHELL_HOME" status --porcelain -- ':!zshrc.custom' ':!vimrc.custom' ':!backup' ':!profile' 2>/dev/null || true)" + if [ -n "$dirty" ]; then + xs_err "xydacshell repo has uncommitted local changes:" + printf '%s\n' "$dirty" >&2 + xs_err "commit, stash, or discard them before re-running install.sh" exit 1 + fi fi -if [ "$PWD" != "$HOME/.xydacshell" ]; then - echo " Error: Please run from $HOME/.xydacshell directory."; - exit 1 +# Detect existing install. +is_existing_install() { + [ -f "$XYDACSHELL_HOME/profile" ] || \ + { [ -L "$HOME/.zshrc" ] && [[ "$(readlink "$HOME/.zshrc")" == "$XYDACSHELL_HOME"/* ]]; } || \ + [ -f "$XYDACSHELL_HOME/backup/.zshrc" ] +} + +current_profile="" +if is_existing_install; then + current_profile="$(xs_profile_read "$XYDACSHELL_HOME")" + xs_info "detected existing install; current profile: $current_profile" +fi + +# Choose target profile. +if [ -z "$REQUESTED_PROFILE" ]; then + if [ -n "$current_profile" ]; then + target_profile="$current_profile" + else + target_profile="classic" + xs_info "fresh install; defaulting to profile: classic" + xs_dim " pass --profile modern to try the modern stack (starship + nvim)" + fi +else + case "$REQUESTED_PROFILE" in + classic|modern) target_profile="$REQUESTED_PROFILE" ;; + *) xs_err "unknown profile: $REQUESTED_PROFILE (expected classic|modern)"; exit 2 ;; + esac +fi + +# Profile-switch confirmation. +if [ -n "$current_profile" ] && [ "$current_profile" != "$target_profile" ]; then + xs_warn "switching profile: $current_profile → $target_profile" + xs_dim " your zshrc.custom and vimrc.custom will be preserved." + xs_dim " your previous symlinks will be backed up to backup//." + if [ "$FORCE" != "1" ] && [ "$XS_DRY_RUN" != "1" ]; then + printf 'proceed? [y/N] ' + read -r ans + case "${ans:-n}" in + y|Y|yes) ;; + *) xs_err "aborted."; exit 1 ;; + esac + fi fi -echo "Checking out awesome stuffs" -git submodule update --init --recursive +xs_info "installing profile: $target_profile" +[ "$XS_DRY_RUN" = "1" ] && xs_warn "DRY RUN — no filesystem changes will be made" -chmod -R go-w ./ +# Update submodules (classic depends on them; modern ignores them). +if [ "$target_profile" = "classic" ]; then + xs_info "syncing classic-profile submodules" + xs_run git -C "$XYDACSHELL_HOME" submodule update --init --recursive +fi + +# Sanity snapshot of sacred files (custom overrides). We verify post-run. +snapshot_hash() { + local f="$1" + if [ -f "$f" ]; then + if command -v shasum >/dev/null 2>&1; then + shasum "$f" | awk '{print $1}' + elif command -v sha1sum >/dev/null 2>&1; then + sha1sum "$f" | awk '{print $1}' + fi + else + printf 'absent' + fi +} +pre_zshrc_custom="$(snapshot_hash "$XYDACSHELL_HOME/zshrc.custom")" +pre_vimrc_custom="$(snapshot_hash "$XYDACSHELL_HOME/vimrc.custom")" + +# Timestamped backup dir for this run. Created lazily by xs_backup_file. +stamp="$(xs_timestamp)" +backup_dir="$XYDACSHELL_HOME/backup/$stamp" + +# ~/.zshrc always points to our dispatcher. +xs_symlink "$XYDACSHELL_HOME/zshrc.file" "$HOME/.zshrc" "$backup_dir" + +case "$target_profile" in + classic) + xs_symlink "$XYDACSHELL_HOME/vimrc.file" "$HOME/.vimrc" "$backup_dir" + ;; + modern) + nvim_config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/nvim" + xs_run mkdir -p "$nvim_config_dir" + xs_symlink "$XYDACSHELL_HOME/profiles/modern/nvim/init.lua" "$nvim_config_dir/init.lua" "$backup_dir" + + starship_config_dir="${XDG_CONFIG_HOME:-$HOME/.config}" + xs_run mkdir -p "$starship_config_dir" + xs_symlink "$XYDACSHELL_HOME/profiles/modern/starship.toml" "$starship_config_dir/starship.toml" "$backup_dir" + + # Detect missing modern tools and offer to install them. + # User is prompted per tool; --force accepts all; --dry-run previews without running. + FORCE="$FORCE" xs_modern_tools_offer starship nvim fzf zoxide lsd bat ncdu dust duf + ;; +esac + +# Custom override files — create empty ONLY if absent. Never touched otherwise. +for custom in zshrc.custom vimrc.custom; do + target="$XYDACSHELL_HOME/$custom" + if [ ! -e "$target" ]; then + if [ "$XS_DRY_RUN" = "1" ]; then + xs_dim " would create empty $target" + else + : > "$target" + xs_ok "created empty $target" + fi + else + xs_dim " preserving existing $target (not touched)" + fi +done -echo "Creating Backups now "; -# Create Backups -if [ -f ~/.zshrc ]; then - mv ~/.zshrc ~/.xydacshell/backup/.zshrc +# Write the profile file last. +xs_profile_write "$XYDACSHELL_HOME" "$target_profile" + +# Warn if another `x` is on PATH that isn't ours — it may shadow xydacshell's +# command in scripts or non-interactive shells. Aliases in zsh override PATH +# for interactive shells, so also prompt the user to check. +existing_x="$(type -ap x 2>/dev/null | grep -v "^${XYDACSHELL_HOME}/bin/x\$" || true)" +if [ -n "$existing_x" ]; then + xs_warn "another 'x' command is on your PATH:" + printf '%s\n' "$existing_x" | sed 's/^/ /' >&2 + xs_dim " after this install, xydacshell's 'x' will take precedence in new" + xs_dim " shells because \$XYDACSHELL_HOME/bin is prepended to PATH." + xs_dim " also check for shell aliases that would shadow it:" + xs_dim " alias | grep '^x='" + xs_dim " if you want to keep your existing 'x', use 'xydacshell' instead" + xs_dim " (it's a symlink to the same command)." fi -if [ -f ~/.vimrc ]; then - mv ~/.vimrc ~/.xydacshell/backup/.vimrc + +# Verify sacred files are unchanged. +if [ "$XS_DRY_RUN" != "1" ]; then + post_zshrc_custom="$(snapshot_hash "$XYDACSHELL_HOME/zshrc.custom")" + post_vimrc_custom="$(snapshot_hash "$XYDACSHELL_HOME/vimrc.custom")" + + if [ "$pre_zshrc_custom" != "absent" ] && [ "$pre_zshrc_custom" != "$post_zshrc_custom" ]; then + xs_err "internal error: zshrc.custom content changed during install. check backup/$stamp/" + exit 1 + fi + if [ "$pre_vimrc_custom" != "absent" ] && [ "$pre_vimrc_custom" != "$post_vimrc_custom" ]; then + xs_err "internal error: vimrc.custom content changed during install. check backup/$stamp/" + exit 1 + fi fi -# Copy config files -echo "Creating Symlinks now" -ln -s ~/.xydacshell/vimrc.file ~/.vimrc -ln -s ~/.xydacshell/zshrc.file ~/.zshrc +if [ -d "$backup_dir" ]; then + xs_info "new backup files for this run: $backup_dir" +fi +if [ -f "$XYDACSHELL_HOME/backup/.zshrc" ] || [ -f "$XYDACSHELL_HOME/backup/.vimrc" ]; then + xs_dim "legacy pre-install backups are preserved at $XYDACSHELL_HOME/backup/.zshrc / .vimrc" +fi -echo "All Done !!" +xs_ok "done. start a new shell: exec zsh" diff --git a/lib/cmds/doctor.sh b/lib/cmds/doctor.sh new file mode 100644 index 0000000..4ea5002 --- /dev/null +++ b/lib/cmds/doctor.sh @@ -0,0 +1,168 @@ +#!/usr/bin/env bash +# x doctor — report current install state, and offer profile upgrades +# when appropriate (classic + clean repo + interactive tty). + +xs_cmd_doctor() { + local xh="$XYDACSHELL_HOME" + local flag_no_prompt=0 flag_report=0 + + while [ $# -gt 0 ]; do + case "$1" in + --no-prompt|--report) flag_no_prompt=1; flag_report=1; shift ;; + --dry-run) XS_DRY_RUN=1; export XS_DRY_RUN; shift ;; + --force) FORCE=1; export FORCE; shift ;; + -h|--help) + cat <<'EOF' +usage: x doctor [--no-prompt|--report] + +Reports the current install state (profile, symlinks, custom files, PM, +tool presence, backups, git state). + +When on the classic profile with a clean repo and an interactive terminal, +doctor offers to preview and switch to the modern profile. Pass --no-prompt +(alias: --report) to skip that and just print the diagnostic. +EOF + return 0 + ;; + *) xs_err "unknown flag: $1"; return 2 ;; + esac + done + + printf '\n%s\n' "x doctor" + printf '=========\n' + + # Paths. + printf '\npaths\n' + printf ' XYDACSHELL_HOME %s\n' "$xh" + printf ' dispatcher %s\n' "$xh/bin/xydacshell" + + # Profile. + local profile + if [ -f "$xh/profile" ]; then + profile="$(cat "$xh/profile")" + else + profile="(unset — dispatcher defaults to classic)" + fi + printf '\nprofile %s\n' "$profile" + + # Symlinks. + printf '\nsymlinks\n' + local f + for f in "$HOME/.zshrc" "$HOME/.vimrc" \ + "${XDG_CONFIG_HOME:-$HOME/.config}/nvim/init.lua" \ + "${XDG_CONFIG_HOME:-$HOME/.config}/starship.toml"; do + if [ -L "$f" ]; then + printf ' %-44s → %s\n' "$f" "$(readlink "$f")" + elif [ -e "$f" ]; then + printf ' %-44s (regular file, not managed by x)\n' "$f" + fi + done + + # Sacred custom files. + printf '\ncustom files (never touched by installer)\n' + local c size="-" + for c in "$xh/zshrc.custom" "$xh/vimrc.custom" "$xh/nvim.custom.lua"; do + if [ -f "$c" ]; then + size="$(wc -c < "$c" | tr -d ' ')" + printf ' %-44s %s bytes\n' "$c" "$size" + else + printf ' %-44s (absent)\n' "$c" + fi + done + + # Environment + tool status. + # shellcheck source=../modern-tools.sh + . "$xh/lib/modern-tools.sh" + xs_detect_pm + printf '\nenvironment\n' + printf ' OS %s\n' "$XS_OS" + printf ' package manager %s\n' "$XS_PM" + + printf '\ntools\n' + local t + for t in starship nvim fzf zoxide lsd bat ncdu dust duf; do + if xs_command_exists "$t"; then + printf ' %-10s ✓\n' "$t" + else + printf ' %-10s missing\n' "$t" + fi + done + + # Backups. + printf '\nbackups\n' + if [ -d "$xh/backup" ]; then + local latest + latest="$(find "$xh/backup" -maxdepth 1 -type d -name '[0-9]*T[0-9]*Z' 2>/dev/null | sort | tail -1)" + if [ -n "$latest" ]; then + printf ' latest timestamped %s\n' "$(basename "$latest")" + else + printf ' latest timestamped (none)\n' + fi + local legacy=0 + [ -f "$xh/backup/.zshrc" ] && legacy=$((legacy + 1)) + [ -f "$xh/backup/.vimrc" ] && legacy=$((legacy + 1)) + printf ' legacy pre-install %d file(s)\n' "$legacy" + else + printf ' (no backup directory)\n' + fi + + # Git state. + if [ -d "$xh/.git" ]; then + printf '\ngit\n' + printf ' branch %s\n' "$(git -C "$xh" rev-parse --abbrev-ref HEAD 2>/dev/null || echo '?')" + printf ' head %s\n' "$(git -C "$xh" rev-parse --short HEAD 2>/dev/null || echo '?')" + local dirty + dirty="$(git -C "$xh" status --porcelain 2>/dev/null | wc -l | tr -d ' ')" + printf ' uncommitted %s file(s)\n' "$dirty" + fi + + printf '\n' + + # Optional: offer a profile upgrade if conditions are right. + if [ "$flag_no_prompt" != 1 ]; then + _xs_doctor_maybe_offer_upgrade "$profile" + fi +} + +# If the user is on classic, the repo is clean, and stdin is a tty, offer +# to preview and switch to modern. Otherwise return silently. +_xs_doctor_maybe_offer_upgrade() { + local current="$1" + local xh="$XYDACSHELL_HOME" + + [ "$current" = classic ] || return 0 + [ -t 0 ] || return 0 + + if [ -d "$xh/.git" ]; then + local dirty + dirty="$(git -C "$xh" status --porcelain 2>/dev/null | wc -l | tr -d ' ')" + if [ "$dirty" != 0 ]; then + xs_dim "the modern profile is available, but the repo has uncommitted" + xs_dim "changes. commit or stash them, then re-run 'x doctor' to see the" + xs_dim "switch option." + return 0 + fi + fi + + xs_info "you're on the classic profile." + xs_dim " modern swaps in starship + nvim + fzf/zoxide/lsd/bat." + xs_dim " your zshrc.custom and vimrc.custom stay untouched." + xs_dim " revertible any time with 'x switch classic'." + + if ! xs_prompt_yn "preview a switch to modern?" "n"; then + xs_dim "staying on classic." + return 0 + fi + + printf '\n' + xs_info "preview (dry run):" + (cd "$xh" && XS_DRY_RUN=1 bash "$xh/install.sh" --dry-run --profile modern) || return 0 + + printf '\n' + if xs_prompt_yn "switch to modern now?" "n"; then + printf '\n' + (cd "$xh" && bash "$xh/install.sh" --profile modern --force) + else + xs_dim "staying on classic." + fi +} diff --git a/lib/cmds/install.sh b/lib/cmds/install.sh new file mode 100644 index 0000000..703b484 --- /dev/null +++ b/lib/cmds/install.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +# x install — delegates to the root install.sh. + +xs_cmd_install() { + (cd "$XYDACSHELL_HOME" && bash "$XYDACSHELL_HOME/install.sh" "$@") +} diff --git a/lib/cmds/rollback.sh b/lib/cmds/rollback.sh new file mode 100644 index 0000000..6687f94 --- /dev/null +++ b/lib/cmds/rollback.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# x rollback — restore files from a timestamped backup. + +xs_cmd_rollback() { + local xh="$XYDACSHELL_HOME" + local stamp="" + + while [ $# -gt 0 ]; do + case "$1" in + --stamp) stamp="${2:-}"; shift 2 ;; + --stamp=*) stamp="${1#--stamp=}"; shift ;; + --dry-run) XS_DRY_RUN=1; export XS_DRY_RUN; shift ;; + --force) FORCE=1; export FORCE; shift ;; + -h|--help) + cat <<'EOF' +usage: x rollback [--stamp YYYYMMDDTHHMMSSZ] [--dry-run] [--force] + +Restore files from a timestamped backup created by install.sh. If no +--stamp is given, the most recent backup is used. Prompts before writing. +EOF + return 0 + ;; + *) xs_err "unknown flag: $1"; return 2 ;; + esac + done + + if [ -z "$stamp" ]; then + stamp="$(find "$xh/backup" -maxdepth 1 -type d -name '[0-9]*T[0-9]*Z' 2>/dev/null | sort | tail -1 | xargs -n1 basename 2>/dev/null || true)" + fi + + if [ -z "$stamp" ]; then + xs_err "no timestamped backups found in $xh/backup/" + return 1 + fi + + local dir="$xh/backup/$stamp" + if [ ! -d "$dir" ]; then + xs_err "backup dir not found: $dir" + return 1 + fi + + xs_info "rolling back from: $dir" + xs_dim "contents:" + ls -la "$dir" 2>/dev/null | sed 's/^/ /' >&2 + + if ! xs_prompt_yn "restore these files to their original locations?" "n"; then + xs_dim "aborted." + return 0 + fi + + local f name target + for f in "$dir"/*; do + [ -e "$f" ] || continue + name="$(basename "$f")" + case "$name" in + .zshrc) target="$HOME/.zshrc" ;; + .vimrc) target="$HOME/.vimrc" ;; + init.lua) target="${XDG_CONFIG_HOME:-$HOME/.config}/nvim/init.lua" ;; + starship.toml) target="${XDG_CONFIG_HOME:-$HOME/.config}/starship.toml" ;; + *) xs_warn " don't know where to restore '$name', skipping"; continue ;; + esac + if [ -e "$target" ] || [ -L "$target" ]; then + xs_run rm -f "$target" + fi + xs_run cp -a "$f" "$target" + xs_ok "restored $target" + done +} diff --git a/lib/cmds/storage.sh b/lib/cmds/storage.sh new file mode 100644 index 0000000..5124c73 --- /dev/null +++ b/lib/cmds/storage.sh @@ -0,0 +1,129 @@ +#!/usr/bin/env bash +# x storage — disk-usage report with optional cleanup prompts. + +xs_cmd_storage() { + local flag_caches_only=0 flag_clean=0 top=10 + + while [ $# -gt 0 ]; do + case "$1" in + --caches) flag_caches_only=1; shift ;; + --clean) flag_clean=1; shift ;; + --top) top="${2:-10}"; shift 2 ;; + --top=*) top="${1#--top=}"; shift ;; + --dry-run) XS_DRY_RUN=1; export XS_DRY_RUN; shift ;; + --force) FORCE=1; export FORCE; shift ;; + -h|--help) + cat <<'EOF' +usage: x storage [--caches] [--top N] [--clean] [--dry-run] [--force] + +Disk-usage report covering filesystems, $HOME top directories, package-manager +caches, containers, and trash. + + --caches only show package-manager caches (skip filesystems and $HOME) + --top N number of $HOME top dirs to show (default 10) + --clean after the report, prompt per-cache to run its cleanup command + --dry-run preview (affects --clean) + --force accept all prompts (affects --clean) +EOF + return 0 + ;; + *) xs_err "unknown flag: $1"; return 2 ;; + esac + done + + if [ "$flag_caches_only" != 1 ]; then + _xs_storage_filesystems + _xs_storage_home "$top" + fi + _xs_storage_caches + _xs_storage_containers + _xs_storage_trash + + if [ "$flag_clean" = 1 ]; then + _xs_storage_clean + fi +} + +# Report filesystems (prefers duf when available, else df -h). +_xs_storage_filesystems() { + printf '\nfilesystems\n' + if xs_command_exists duf; then + duf --only local 2>/dev/null | sed 's/^/ /' + else + df -h 2>/dev/null | head -12 | sed 's/^/ /' + fi +} + +# Top N $HOME directories by size. Uses dust when available, else du | sort. +_xs_storage_home() { + local n="${1:-10}" + printf '\n$HOME top %s directories\n' "$n" + if xs_command_exists dust; then + dust -d 1 -n "$n" "$HOME" 2>/dev/null | sed 's/^/ /' + else + du -sh "$HOME"/* 2>/dev/null | sort -hr | head -"$n" | sed 's/^/ /' + fi +} + +# Return human-readable size of a path, or '-' if absent/empty. +_xs_size() { + local p="$1" + if [ -n "$p" ] && { [ -d "$p" ] || [ -f "$p" ]; }; then + du -sh "$p" 2>/dev/null | awk '{print $1}' + else + printf '-' + fi +} + +_xs_storage_caches() { + printf '\npackage-manager caches\n' + + local brew_cache="" npm_cache="" pnpm_cache="" cargo_cache="$HOME/.cargo" pip_cache="" + xs_command_exists brew && brew_cache="$(brew --cache 2>/dev/null || true)" + xs_command_exists npm && npm_cache="$(npm config get cache 2>/dev/null || true)" + xs_command_exists pnpm && pnpm_cache="$(pnpm store path 2>/dev/null || true)" + if xs_command_exists pip; then + pip_cache="$(pip cache dir 2>/dev/null || true)" + elif xs_command_exists pip3; then + pip_cache="$(pip3 cache dir 2>/dev/null || true)" + fi + + local uv_cache="" + xs_command_exists uv && uv_cache="$(uv cache dir 2>/dev/null || true)" + + _row() { printf ' %-8s %-8s %s\n' "$1" "$(_xs_size "$2")" "$3"; } + _row brew "$brew_cache" "brew cleanup -s" + _row npm "$npm_cache" "npm cache clean --force" + _row pnpm "$pnpm_cache" "pnpm store prune" + _row cargo "$cargo_cache" "cargo cache --autoclean (needs cargo-cache)" + _row pip "$pip_cache" "pip cache purge" + [ -n "$uv_cache" ] && _row uv "$uv_cache" "uv cache clean" +} + +_xs_storage_containers() { + if xs_command_exists docker; then + printf '\ncontainers (docker system df)\n' + docker system df 2>/dev/null | sed 's/^/ /' | head -5 + fi +} + +_xs_storage_trash() { + local t="" + case "$(uname -s)" in + Darwin) t="$HOME/.Trash" ;; + Linux) t="$HOME/.local/share/Trash" ;; + esac + if [ -n "$t" ] && [ -d "$t" ]; then + printf '\ntrash\n %s %s\n' "$(_xs_size "$t")" "$t" + fi +} + +_xs_storage_clean() { + printf '\ncleanup (interactive)\n' + xs_command_exists brew && xs_prompt_yn " brew cleanup -s?" n && xs_run brew cleanup -s + xs_command_exists npm && xs_prompt_yn " npm cache clean?" n && xs_run npm cache clean --force + xs_command_exists pnpm && xs_prompt_yn " pnpm store prune?" n && xs_run pnpm store prune + xs_command_exists pip && xs_prompt_yn " pip cache purge?" n && xs_run pip cache purge + xs_command_exists uv && xs_prompt_yn " uv cache clean?" n && xs_run uv cache clean + xs_command_exists docker && xs_prompt_yn " docker system prune -f?" n && xs_run docker system prune -f +} diff --git a/lib/cmds/switch.sh b/lib/cmds/switch.sh new file mode 100644 index 0000000..3b012c5 --- /dev/null +++ b/lib/cmds/switch.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# x switch — shorthand for install --profile . + +xs_cmd_switch() { + local profile="${1:-}" + case "$profile" in + classic|modern) ;; + -h|--help|"") + cat <<'EOF' +usage: x switch [--dry-run] [--force] + +Switches the active xydacshell profile. Equivalent to: + bash install.sh --profile +EOF + [ -z "$profile" ] && return 2 || return 0 + ;; + *) xs_err "unknown profile: $profile (expected classic|modern)"; return 2 ;; + esac + shift + (cd "$XYDACSHELL_HOME" && bash "$XYDACSHELL_HOME/install.sh" --profile "$profile" "$@") +} diff --git a/lib/cmds/uninstall.sh b/lib/cmds/uninstall.sh new file mode 100644 index 0000000..f90956a --- /dev/null +++ b/lib/cmds/uninstall.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# x uninstall — remove our symlinks and restore legacy backups. +# Never touches the xydacshell repo itself; user deletes manually. + +xs_cmd_uninstall() { + local xh="$XYDACSHELL_HOME" + + while [ $# -gt 0 ]; do + case "$1" in + --dry-run) XS_DRY_RUN=1; export XS_DRY_RUN; shift ;; + --force) FORCE=1; export FORCE; shift ;; + -h|--help) + cat <<'EOF' +usage: x uninstall [--dry-run] [--force] + +Remove xydacshell symlinks and restore the original pre-install files +if they're in backup/.zshrc / backup/.vimrc. Does NOT delete the +xydacshell repo itself. +EOF + return 0 + ;; + *) xs_err "unknown flag: $1"; return 2 ;; + esac + done + + xs_warn "about to uninstall xydacshell" + xs_dim " will remove our symlinks at ~/.zshrc, ~/.vimrc, and the" + xs_dim " modern-profile starship/nvim configs if they point into $xh." + xs_dim " will restore $xh/backup/.zshrc and /.vimrc (pre-install originals)" + xs_dim " if they exist." + xs_dim " will NOT delete $xh itself — rm -rf it manually when ready." + + if ! xs_prompt_yn "proceed?" "n"; then + xs_dim "aborted." + return 0 + fi + + _restore_or_remove() { + local link="$1" legacy="$2" + if [ -L "$link" ] && [[ "$(readlink "$link")" == "$xh"/* ]]; then + xs_run rm -f "$link" + if [ -f "$legacy" ]; then + xs_run mv "$legacy" "$link" + xs_ok "restored $link from $legacy" + else + xs_ok "removed $link" + fi + fi + } + + _restore_or_remove "$HOME/.zshrc" "$xh/backup/.zshrc" + _restore_or_remove "$HOME/.vimrc" "$xh/backup/.vimrc" + + local nvim="${XDG_CONFIG_HOME:-$HOME/.config}/nvim/init.lua" + if [ -L "$nvim" ] && [[ "$(readlink "$nvim")" == "$xh"/* ]]; then + xs_run rm -f "$nvim" + xs_ok "removed $nvim" + fi + + local starship="${XDG_CONFIG_HOME:-$HOME/.config}/starship.toml" + if [ -L "$starship" ] && [[ "$(readlink "$starship")" == "$xh"/* ]]; then + xs_run rm -f "$starship" + xs_ok "removed $starship" + fi + + printf '\n' + xs_info "done. to complete removal: rm -rf $xh" +} diff --git a/lib/cmds/update.sh b/lib/cmds/update.sh new file mode 100644 index 0000000..280e5b6 --- /dev/null +++ b/lib/cmds/update.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# x update — git pull + submodule sync + reinstall. + +xs_cmd_update() { + local xh="$XYDACSHELL_HOME" + + if [ ! -d "$xh/.git" ]; then + xs_err "$xh is not a git checkout; cannot update." + return 1 + fi + + xs_info "pulling latest" + xs_run git -C "$xh" pull --rebase + + xs_info "syncing submodules" + xs_run git -C "$xh" submodule update --init --recursive + + xs_info "reinstalling current profile" + (cd "$xh" && bash "$xh/install.sh" "$@") +} diff --git a/lib/modern-tools.sh b/lib/modern-tools.sh new file mode 100644 index 0000000..654a3e5 --- /dev/null +++ b/lib/modern-tools.sh @@ -0,0 +1,152 @@ +#!/usr/bin/env bash +# xydacshell — modern profile tool installer. +# Detects OS + package manager, offers to install missing tools. +# Sourced by install.sh; not stand-alone. Depends on lib/util.sh. + +# Detect OS and a primary package manager. +# Sets: XS_OS, XS_PM. Values for XS_PM: brew | apt | dnf | pacman | apk | unknown +xs_detect_pm() { + case "$(uname -s)" in + Darwin) XS_OS=macos ;; + Linux) XS_OS=linux ;; + *) XS_OS=unknown ;; + esac + + if [ "$XS_OS" = macos ]; then + if xs_command_exists brew; then XS_PM=brew; else XS_PM=unknown; fi + export XS_OS XS_PM + return 0 + fi + + for pm in apt-get dnf pacman apk; do + if xs_command_exists "$pm"; then + case "$pm" in + apt-get) XS_PM=apt ;; + *) XS_PM="$pm" ;; + esac + export XS_OS XS_PM + return 0 + fi + done + XS_PM=unknown + export XS_OS XS_PM +} + +# xs_pkg_for : echo the package name for that pm, or empty if unsupported. +xs_pkg_for() { + local tool="$1" pm="$2" + case "$tool:$pm" in + starship:brew) echo starship ;; + starship:pacman) echo starship ;; + starship:apt|starship:dnf|starship:apk) echo "" ;; # use fallback curl installer + nvim:brew) echo neovim ;; + nvim:apt|nvim:dnf|nvim:pacman|nvim:apk) echo neovim ;; + fzf:brew|fzf:apt|fzf:dnf|fzf:pacman|fzf:apk) echo fzf ;; + zoxide:brew|zoxide:pacman|zoxide:dnf) echo zoxide ;; + zoxide:apt|zoxide:apk) echo "" ;; + lsd:brew|lsd:apt|lsd:dnf|lsd:pacman|lsd:apk) echo lsd ;; + bat:brew|bat:pacman|bat:dnf) echo bat ;; + bat:apt) echo bat ;; # on Debian the binary is called batcat — user needs to alias + bat:apk) echo bat ;; + ncdu:brew|ncdu:apt|ncdu:dnf|ncdu:pacman|ncdu:apk) echo ncdu ;; + dust:brew|dust:pacman|dust:dnf) echo dust ;; + dust:apt|dust:apk) echo "" ;; # fallback: cargo install du-dust + duf:brew|duf:apt|duf:dnf|duf:pacman|duf:apk) echo duf ;; + *) echo "" ;; + esac +} + +# xs_fallback_for : echo a single-line install command to run when the pm has no pkg. +xs_fallback_for() { + local tool="$1" + case "$tool" in + starship) echo 'curl -sS https://starship.rs/install.sh | sh -s -- --yes' ;; + zoxide) echo 'curl -sSfL https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | sh' ;; + dust) echo 'cargo install du-dust # requires Rust toolchain' ;; + *) echo "" ;; + esac +} + +# xs_install_cmd : emit the install command for that pm. +xs_install_cmd() { + local pkg="$1" pm="$2" + case "$pm" in + brew) echo "brew install $pkg" ;; + apt) echo "sudo apt-get update && sudo apt-get install -y $pkg" ;; + dnf) echo "sudo dnf install -y $pkg" ;; + pacman) echo "sudo pacman -S --noconfirm $pkg" ;; + apk) echo "sudo apk add $pkg" ;; + *) echo "" ;; + esac +} + +# xs_modern_tools_offer: for each tool in $1..., if missing, show install option and install if accepted. +# Returns 0 regardless of individual outcomes (modern profile degrades gracefully). +xs_modern_tools_offer() { + local tools=("$@") + xs_detect_pm + + xs_info "modern-profile tools" + xs_dim " OS: $XS_OS · package manager: $XS_PM" + + local present=() missing=() + local t + for t in "${tools[@]}"; do + if xs_command_exists "$t"; then + present+=("$t") + xs_ok " $t (installed)" + else + missing+=("$t") + fi + done + + if [ "${#missing[@]}" -eq 0 ]; then + xs_ok "all modern tools are installed" + return 0 + fi + + xs_warn "missing: ${missing[*]}" + + if [ "$XS_PM" = "unknown" ]; then + xs_warn "no supported package manager detected; skipping install prompts" + xs_dim " install manually when ready — the profile degrades gracefully" + return 0 + fi + + for t in "${missing[@]}"; do + local pkg cmd + pkg="$(xs_pkg_for "$t" "$XS_PM")" + if [ -n "$pkg" ]; then + cmd="$(xs_install_cmd "$pkg" "$XS_PM")" + else + cmd="$(xs_fallback_for "$t")" + fi + + if [ -z "$cmd" ]; then + xs_warn " $t: no install recipe for $XS_PM on $XS_OS" + continue + fi + + printf '\n' + xs_info "install $t?" + xs_dim " will run: $cmd" + if xs_prompt_yn " install $t" "n"; then + if [ "${XS_DRY_RUN:-0}" = 1 ]; then + xs_dim " would run: $cmd" + else + if sh -c "$cmd"; then + xs_ok " $t installed" + else + xs_err " $t install failed (continuing — profile tolerates missing tools)" + fi + fi + else + xs_dim " skipped $t" + fi + done + + # Post-install note for bat on Debian/Ubuntu where the binary is `batcat`. + if [ "$XS_PM" = apt ] && xs_command_exists batcat && ! xs_command_exists bat; then + xs_dim " note: Debian/Ubuntu ships 'bat' as 'batcat'. Add to your zshrc.custom: alias bat=batcat" + fi +} diff --git a/lib/util.sh b/lib/util.sh new file mode 100644 index 0000000..38bca0c --- /dev/null +++ b/lib/util.sh @@ -0,0 +1,130 @@ +#!/usr/bin/env bash +# xydacshell — shared shell helpers. Sourced by install.sh. Not stand-alone. + +# Output helpers. Colors only when stdout is a tty. +if [ -t 1 ]; then + XS_DIM=$'\033[2m' + XS_BOLD=$'\033[1m' + XS_RED=$'\033[31m' + XS_GREEN=$'\033[32m' + XS_YELLOW=$'\033[33m' + XS_BLUE=$'\033[34m' + XS_RESET=$'\033[0m' +else + XS_DIM="" + XS_BOLD="" + XS_RED="" + XS_GREEN="" + XS_YELLOW="" + XS_BLUE="" + XS_RESET="" +fi + +xs_log() { printf '%s\n' "$*"; } +xs_info() { printf '%s%s%s\n' "$XS_BLUE" "$*" "$XS_RESET"; } +xs_ok() { printf '%s✓%s %s\n' "$XS_GREEN" "$XS_RESET" "$*"; } +xs_warn() { printf '%s!%s %s\n' "$XS_YELLOW" "$XS_RESET" "$*" >&2; } +xs_err() { printf '%s✗%s %s\n' "$XS_RED" "$XS_RESET" "$*" >&2; } +xs_dim() { printf '%s%s%s\n' "$XS_DIM" "$*" "$XS_RESET"; } + +# xs_run : run a command, honoring $XS_DRY_RUN. Echoes the command dimly when dry. +xs_run() { + if [ "${XS_DRY_RUN:-0}" = "1" ]; then + xs_dim " would run: $*" + else + "$@" + fi +} + +# xs_timestamp: ISO-like stamp safe for filesystem paths. +xs_timestamp() { date -u +"%Y%m%dT%H%M%SZ"; } + +# xs_command_exists : 0 if on PATH, 1 otherwise. +xs_command_exists() { command -v "$1" >/dev/null 2>&1; } + +# xs_backup_file : move a real file into a timestamped backup dir. +# Skips if the path is a symlink into XYDACSHELL_HOME (our own install) or missing. +xs_backup_file() { + local path="$1" backup_dir="$2" xhome="${XYDACSHELL_HOME:-$HOME/.xydacshell}" + + if [ ! -e "$path" ] && [ ! -L "$path" ]; then + return 0 + fi + + if [ -L "$path" ]; then + local target + target="$(readlink "$path")" + case "$target" in + "$xhome"/*) + xs_dim " $path is our symlink, not backing up" + return 0 + ;; + esac + fi + + xs_run mkdir -p "$backup_dir" + xs_run mv "$path" "$backup_dir/$(basename "$path")" + xs_ok "backed up $path → $backup_dir/" +} + +# xs_symlink : create an idempotent symlink. Backs up an existing non-symlink. +xs_symlink() { + local src="$1" dest="$2" backup_dir="$3" + + if [ -L "$dest" ] && [ "$(readlink "$dest")" = "$src" ]; then + xs_dim " $dest already links to $src" + return 0 + fi + + if [ -e "$dest" ] || [ -L "$dest" ]; then + xs_backup_file "$dest" "$backup_dir" + fi + + xs_run ln -s "$src" "$dest" + xs_ok "linked $dest → $src" +} + +# xs_prompt_yn : yes/no prompt. Honors FORCE and XS_DRY_RUN. +# Returns 0 for yes, 1 for no. --force skips ask and returns yes. +# --dry-run also returns yes so previews show what would happen. +xs_prompt_yn() { + local q="$1" default="${2:-n}" + if [ "${FORCE:-0}" = 1 ]; then return 0; fi + if [ "${XS_DRY_RUN:-0}" = 1 ]; then return 0; fi + + local hint + case "$default" in + y|Y) hint="[Y/n]" ;; + *) hint="[y/N]" ;; + esac + + printf '%s %s ' "$q" "$hint" + read -r ans + : "${ans:=$default}" + case "$ans" in + y|Y|yes|YES) return 0 ;; + *) return 1 ;; + esac +} + +# xs_profile_read : echo the active profile, or "classic" if the file is missing. +xs_profile_read() { + local xhome="$1" file="$1/profile" + if [ -f "$file" ]; then + cat "$file" + else + printf 'classic\n' + fi +} + +# xs_profile_write : write the profile file atomically. +xs_profile_write() { + local xhome="$1" profile="$2" + xs_run mkdir -p "$xhome" + if [ "${XS_DRY_RUN:-0}" = "1" ]; then + xs_dim " would write profile=$profile to $xhome/profile" + else + printf '%s\n' "$profile" > "$xhome/profile" + fi + xs_ok "profile set to $profile" +} diff --git a/materialshell-electro.zsh-theme b/materialshell-electro.zsh-theme index b9ac035..de720ba 100644 --- a/materialshell-electro.zsh-theme +++ b/materialshell-electro.zsh-theme @@ -65,12 +65,12 @@ ZSH_THEME_GIT_PROMPT_SUFFIX=" %{$reset_color%}" ZSH_THEME_GIT_PROMPT_DIRTY=" %{$red%}✗%{$reset_color%}" ZSH_THEME_GIT_PROMPT_CLEAN=" %{$green%}✔%{$reset_color%}" -ZSH_THEME_GIT_PROMPT_ADDED=" %{$green%}✚{$reset_color%}" -ZSH_THEME_GIT_PROMPT_MODIFIED=" %{$yellow%}⚑{$reset_color%} " -ZSH_THEME_GIT_PROMPT_DELETED=" %{$red%}✖{$reset_color%}" -ZSH_THEME_GIT_PROMPT_RENAMED=" %{$blue%}▴{$reset_color% " -ZSH_THEME_GIT_PROMPT_UNMERGED=" %{$cyan%}§{$reset_color%}" -ZSH_THEME_GIT_PROMPT_UNTRACKED=" %{$grey%}◒{$reset_color%}" +ZSH_THEME_GIT_PROMPT_ADDED=" %{$green%}✚%{$reset_color%}" +ZSH_THEME_GIT_PROMPT_MODIFIED=" %{$yellow%}⚑%{$reset_color%}" +ZSH_THEME_GIT_PROMPT_DELETED=" %{$red%}✖%{$reset_color%}" +ZSH_THEME_GIT_PROMPT_RENAMED=" %{$blue%}▴%{$reset_color%}" +ZSH_THEME_GIT_PROMPT_UNMERGED=" %{$cyan%}§%{$reset_color%}" +ZSH_THEME_GIT_PROMPT_UNTRACKED=" %{$grey%}◒%{$reset_color%}" # Format for git_prompt_long_sha() and git_prompt_short_sha() ZSH_THEME_GIT_PROMPT_SHA_BEFORE="%{$cyan%} " diff --git a/profiles/classic/vimrc b/profiles/classic/vimrc new file mode 100644 index 0000000..67ea5a5 --- /dev/null +++ b/profiles/classic/vimrc @@ -0,0 +1,43 @@ +" xydacshell — classic profile vimrc +" Mirrors the original pre-upgrade vimrc, minus the trailing +" `source ... vimrc.custom` (the dispatcher at the repo root handles that). +" +" Do not edit this file directly. +" For customizations, edit: ~/.xydacshell/vimrc.custom + +set runtimepath+=~/.xydacshell/vimrc + +source ~/.xydacshell/vimrc/vimrcs/basic.vim +source ~/.xydacshell/vimrc/vimrcs/filetypes.vim +source ~/.xydacshell/vimrc/vimrcs/plugins_config.vim +source ~/.xydacshell/vimrc/vimrcs/extended.vim + +" Automatically start NERDTree. +autocmd vimenter * NERDTree +" Close vim if the only other open buffer is NERDTree. +autocmd bufenter * if (winnr("$") == 1 && exists("b:NERDTree") && b:NERDTree.isTabTree()) | q | endif +" Toggle NERDTree with Ctrl-n. +map :NERDTreeToggle +" Keep cursor out of the NERDTree pane on startup. +autocmd VimEnter * wincmd p + +" Go-specific keymaps. +au FileType go nmap r (go-run) +au FileType go nmap b (go-build) + +set mouse=a +set number +set autoindent +let g:NERDTreeWinPos = "left" + +let mapleader= "`" + +map :wincmd h +map :wincmd j +map :wincmd k +map :wincmd l +map :MRU + +nnoremap :if &modifiable && !&readonly && &modified :write :endif:bnext +nnoremap :if &modifiable && !&readonly && &modified :write :endif:bprevious +set nofoldenable diff --git a/profiles/classic/zshrc b/profiles/classic/zshrc new file mode 100644 index 0000000..df5287f --- /dev/null +++ b/profiles/classic/zshrc @@ -0,0 +1,40 @@ +# xydacshell — classic profile +# shellcheck shell=bash +# This file mirrors the original pre-upgrade zshrc, minus the trailing +# `source ... zshrc.custom` (the dispatcher at the repo root handles that). +# +# Do not edit this file directly. +# For customizations, edit: ~/.xydacshell/zshrc.custom + +# Path to your oh-my-zsh installation. +export ZSH=$HOME/.xydacshell/oh-my-zsh +ZSH_CUSTOM=$HOME/.xydacshell/ + +# Source plugin files. +source $HOME/.xydacshell/zsh-autosuggestions/zsh-autosuggestions.zsh +source $HOME/.xydacshell/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh +source $HOME/.xydacshell/k/k.sh + +# ZSH theme. +ZSH_THEME='materialshell-electro' + +# ZSH plugins. +plugins=(git zsh-autosuggestions zsh-syntax-highlighting k) +source $ZSH/oh-my-zsh.sh + +# Helpers. +commandexists(){ type "$1" &> /dev/null; } +switchuser(){ sudo su -s /usr/bin/zsh $1; } + +# Aliases. +alias c=clear +alias gitlog="git log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit" + +if commandexists nnn; then + alias n=nnn + export NNN_USE_EDITOR=1 +fi + +if commandexists screenfetch; then + clear && screenfetch +fi diff --git a/profiles/modern/nvim/init.lua b/profiles/modern/nvim/init.lua new file mode 100644 index 0000000..cbab470 --- /dev/null +++ b/profiles/modern/nvim/init.lua @@ -0,0 +1,60 @@ +-- xydacshell — modern profile init.lua. +-- Small, no plugin manager, sensible defaults. +-- Preserves the classic leader key (backtick) so muscle memory carries over. + +-- Leader: backtick (same as classic). +vim.g.mapleader = "`" +vim.g.maplocalleader = "`" + +-- Basics. +vim.opt.number = true +vim.opt.relativenumber = true +vim.opt.mouse = "a" +vim.opt.autoindent = true +vim.opt.smartindent = true +vim.opt.expandtab = true +vim.opt.shiftwidth = 2 +vim.opt.tabstop = 2 +vim.opt.termguicolors = true +vim.opt.signcolumn = "yes" +vim.opt.splitright = true +vim.opt.splitbelow = true +vim.opt.ignorecase = true +vim.opt.smartcase = true +vim.opt.undofile = true +vim.opt.clipboard = "unnamedplus" +vim.opt.updatetime = 250 +vim.opt.scrolloff = 8 + +-- Search ergonomics. +vim.opt.incsearch = true +vim.opt.hlsearch = true + +-- Pane switching (parity with classic). +vim.keymap.set("n", "", "h", { silent = true }) +vim.keymap.set("n", "", "j", { silent = true }) +vim.keymap.set("n", "", "k", { silent = true }) +vim.keymap.set("n", "", "l", { silent = true }) + +-- Buffer navigation. +vim.keymap.set("n", "", ":bnext", { silent = true }) +vim.keymap.set("n", "", ":bprevious", { silent = true }) + +-- Quick escape. +vim.keymap.set("i", "jk", "", { silent = true }) + +-- Strip trailing whitespace on save. +vim.api.nvim_create_autocmd("BufWritePre", { + pattern = "*", + callback = function() + local save = vim.fn.winsaveview() + vim.cmd([[%s/\s\+$//e]]) + vim.fn.winrestview(save) + end, +}) + +-- Source user customizations if present. Mirrors vimrc.custom from classic. +local custom = vim.fn.expand("$HOME/.xydacshell/nvim.custom.lua") +if vim.fn.filereadable(custom) == 1 then + dofile(custom) +end diff --git a/profiles/modern/starship.toml b/profiles/modern/starship.toml new file mode 100644 index 0000000..cc5efcf --- /dev/null +++ b/profiles/modern/starship.toml @@ -0,0 +1,50 @@ +# xydacshell — modern profile starship config. +# Two-line prompt, material-ish accents that echo the classic theme's vibe. +# https://starship.rs/config/ + +format = """ +[┌─](bold cyan)[ ](bold cyan)$username[ Ξ ](dimmed)$hostname[ Ξ ](dimmed)$directory$git_branch$git_status[ ](bold cyan)[]](bold cyan) +[└─](bold cyan)[ ](bold cyan)$status$cmd_duration[]](bold cyan) $character""" + +add_newline = false + +[username] +style_user = "green" +style_root = "red bold" +format = "[$user]($style)" +show_always = true + +[hostname] +style = "cyan" +format = "[$hostname]($style)" +ssh_only = false + +[directory] +style = "green" +format = "[$path]($style)" +truncation_length = 3 +truncation_symbol = "…/" + +[git_branch] +symbol = " Ξ " +style = "cyan" +format = "[$symbol$branch]($style)" + +[git_status] +style = "red" +format = "[ $all_status$ahead_behind]($style)" + +[status] +disabled = false +symbol = "[✗](bold red)" +success_symbol = "[✓](bold green)" +format = "$symbol" + +[character] +success_symbol = "[ॐ](bold blue)" +error_symbol = "[ϟ](bold red)" + +[cmd_duration] +min_time = 1000 +style = "yellow" +format = " [took $duration]($style)" diff --git a/profiles/modern/zshrc b/profiles/modern/zshrc new file mode 100644 index 0000000..e07024e --- /dev/null +++ b/profiles/modern/zshrc @@ -0,0 +1,81 @@ +# xydacshell — modern profile zshrc. +# shellcheck shell=bash +# Starship prompt · plain zsh (no oh-my-zsh) · graceful fallbacks for optional tools. +# +# Do not edit this file directly. +# For customizations, edit: ~/.xydacshell/zshrc.custom + +: ${XYDACSHELL_HOME:=$HOME/.xydacshell} + +# History: sane defaults. +HISTFILE="$HOME/.zsh_history" +HISTSIZE=50000 +SAVEHIST=50000 +setopt SHARE_HISTORY HIST_IGNORE_DUPS HIST_IGNORE_SPACE HIST_VERIFY INC_APPEND_HISTORY + +# Better completions. +autoload -Uz compinit && compinit -C +zstyle ':completion:*' menu select +zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}' 'r:|=*' 'l:|=* r:|=*' +setopt AUTO_CD INTERACTIVE_COMMENTS PROMPT_SUBST + +# vi-style editing feels cleaner for a "modern" profile; flip to emacs if you prefer. +bindkey -e + +# Helpers — mirror classic for compat with user customizations that use them. +commandexists(){ type "$1" &> /dev/null; } + +# Plugin sourcing. We still use the submodules (same assets as classic). +[ -f "$XYDACSHELL_HOME/zsh-autosuggestions/zsh-autosuggestions.zsh" ] && \ + source "$XYDACSHELL_HOME/zsh-autosuggestions/zsh-autosuggestions.zsh" +[ -f "$XYDACSHELL_HOME/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh" ] && \ + source "$XYDACSHELL_HOME/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh" + +# Prompt: starship when available, a quiet fallback when not. +if commandexists starship; then + eval "$(starship init zsh)" +else + PROMPT='%F{cyan}%n%f %F{blue}%~%f %# ' +fi + +# Modern CLI tools — used when available. +if commandexists zoxide; then + eval "$(zoxide init zsh)" + alias cd=z 2>/dev/null || true +fi +if commandexists lsd; then + alias ls='lsd --group-dirs=first' + alias ll='lsd -l --group-dirs=first --git' + alias la='lsd -la --group-dirs=first --git' + alias tree='lsd --tree' +fi +if commandexists bat; then + alias cat='bat --paging=never' +fi +if commandexists fzf; then + # Load key-bindings and completion from either brew or apt layouts. + for p in \ + /opt/homebrew/opt/fzf/shell \ + /usr/local/opt/fzf/shell \ + /usr/share/fzf \ + /usr/share/doc/fzf/examples; do + [ -f "$p/key-bindings.zsh" ] && source "$p/key-bindings.zsh" + [ -f "$p/completion.zsh" ] && source "$p/completion.zsh" + done +fi + +# Aliases (carry forward the useful ones from classic). +alias c=clear +alias gitlog="git log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit" + +# Editor. +if commandexists nvim; then + export EDITOR=nvim + alias vim=nvim +fi + +# nnn helper, preserved. +if commandexists nnn; then + alias n=nnn + export NNN_USE_EDITOR=1 +fi diff --git a/vhs/README.md b/vhs/README.md new file mode 100644 index 0000000..8dfe72c --- /dev/null +++ b/vhs/README.md @@ -0,0 +1,23 @@ +# vhs/ + +Scripted terminal recordings built with [VHS](https://github.com/charmbracelet/vhs). + +## Render + +```bash +# install VHS once (macOS) +brew install vhs + +# render the demo to a gif +cd ~/.xydacshell # or wherever this repo lives +vhs vhs/demo.tape # outputs vhs/demo.gif + +# optional: render an mp4 alongside +vhs vhs/demo.tape --publish # or edit the tape to add Output demo.mp4 +``` + +The tape uses a real zsh instance and your actual `$HOME` to produce an honest demo. If you'd rather hide your home-dir contents, edit the `Setup` block in `demo.tape` to `cd /tmp` or set a different working dir first. + +## Tapes + +- `demo.tape` — ~25 s showcase: `x` help → `x storage --top 5` → `x doctor --no-prompt`. diff --git a/vhs/demo.gif b/vhs/demo.gif new file mode 100644 index 0000000..dd1f2d9 Binary files /dev/null and b/vhs/demo.gif differ diff --git a/vhs/demo.tape b/vhs/demo.tape new file mode 100644 index 0000000..a0ad77a --- /dev/null +++ b/vhs/demo.tape @@ -0,0 +1,61 @@ +# xydacshell demo — rendered with https://github.com/charmbracelet/vhs +# +# Build: vhs vhs/demo.tape +# Output: vhs/demo.gif + +Output vhs/demo.gif + +Set Shell "zsh" +Set FontSize 14 +Set Width 1100 +Set Height 700 +Set Padding 22 +Set TypingSpeed 40ms +Set Theme "Catppuccin Mocha" + +# Setup: put $XYDACSHELL_HOME/bin on PATH so `x` resolves in this demo shell. +# Hidden from the recording — the viewer sees a clean terminal to start. +Hide +Type "export XYDACSHELL_HOME=$(pwd)" +Enter +Type "export PATH=$XYDACSHELL_HOME/bin:$PATH" +Enter +Type "clear" +Enter +Sleep 500ms +Show + +# Act 1: the command surface. +Type "# xydacshell — one command, short and sharp:" +Enter +Sleep 1200ms +Type "x" +Enter +Sleep 5s + +Type "clear" +Enter +Sleep 400ms + +# Act 2: the storage report — the real win. +Type "# where did all my disk go?" +Enter +Sleep 1200ms +Type "x storage --top 5" +Enter +Sleep 7s + +Type "clear" +Enter +Sleep 400ms + +# Act 3: doctor — self-diagnose + upgrade offer. +Type "# what's my install state?" +Enter +Sleep 1200ms +Type "x doctor --no-prompt" +Enter +Sleep 6s + +# Fade out. +Sleep 1s diff --git a/vimrc.file b/vimrc.file index 8c2e9bf..38fb459 100644 --- a/vimrc.file +++ b/vimrc.file @@ -1,52 +1,23 @@ -" """""""""""""""""""" -" Please Donot Edit This File directly -" For any custom Tweaks use below file -" ~/.xydacshell/vimrc.custom -" -" "" - - -set runtimepath+=~/.xydacshell/vimrc - -source ~/.xydacshell/vimrc/vimrcs/basic.vim -source ~/.xydacshell/vimrc/vimrcs/filetypes.vim -source ~/.xydacshell/vimrc/vimrcs/plugins_config.vim -source ~/.xydacshell/vimrc/vimrcs/extended.vim - -" Automatically start NerdTree -autocmd vimenter * NERDTree -" Close Vim if the only other tab open is of NerdTree -autocmd bufenter * if (winnr("$") == 1 && exists("b:NERDTree") && b:NERDTree.isTabTree()) | q | endif -" Add Toggle for NerdTree to Ctrl+N -map :NERDTreeToggle -" Stop Cursor from coming to NerdTree Pane at Vim Start -autocmd VimEnter * wincmd p - -" GO Specific Keymapping -au FileType go nmap r (go-run) -au FileType go nmap b (go-build) -" Activate Mouse -set mouse=a -" Active Line Numbers -set number -" Set Autoindent on -set autoindent -" Open NerTree always in left -let g:NERDTreeWinPos = "left" - -" Assign , as Leader -let mapleader= "`" - -"e Assign Pane Switching Shortcuts -map :wincmd h -map :wincmd j -map :wincmd k -map :wincmd l -map :MRU - -nnoremap :if &modifiable && !&readonly && &modified :write :endif:bnext -nnoremap :if &modifiable && !&readonly && &modified :write :endif:bprevious -set nofoldenable - -source ~/.xydacshell/vimrc.custom - +" xydacshell — vim dispatcher (classic profile only; modern uses nvim). +" Existing users: your ~/.vimrc still symlinks to this file. +" If you've switched to the modern profile, the installer writes a +" Neovim init.lua to ~/.config/nvim/ instead and leaves ~/.vimrc alone. + +let s:xydacshell_home = expand('$HOME/.xydacshell') +let s:profile_file = s:xydacshell_home . '/profile' +let s:profile = 'classic' +if filereadable(s:profile_file) + let s:profile = readfile(s:profile_file)[0] +endif + +let s:profile_vimrc = s:xydacshell_home . '/profiles/' . s:profile . '/vimrc' +if filereadable(s:profile_vimrc) + execute 'source ' . s:profile_vimrc +elseif filereadable(s:xydacshell_home . '/profiles/classic/vimrc') + execute 'source ' . s:xydacshell_home . '/profiles/classic/vimrc' +endif + +" User customizations — sacred. Never overwritten by install.sh. +if filereadable(s:xydacshell_home . '/vimrc.custom') + execute 'source ' . s:xydacshell_home . '/vimrc.custom' +endif diff --git a/zshrc.file b/zshrc.file index 9ef27df..4f9d9e1 100644 --- a/zshrc.file +++ b/zshrc.file @@ -1,49 +1,44 @@ -# ########################### -# Please do not edit this file for any custom tweaks -# For any Customization Please add to below file -# ~/.xydacshell/zshrc.custom +# xydacshell — zsh dispatcher. +# Existing users: your ~/.zshrc still symlinks to this file. +# What changed: this file now reads the active profile from +# ~/.xydacshell/profile and sources the right config. If that file is +# missing (as it was pre-upgrade), we default to 'classic', which loads +# the original oh-my-zsh + materialshell-electro setup — exactly what +# you had before. # -# ########################## - - -# Path to your oh-my-zsh installation. -export ZSH=$HOME/.xydacshell/oh-my-zsh -ZSH_CUSTOM=$HOME/.xydacshell/ - -# Source Plugin Files -source $HOME/.xydacshell/zsh-autosuggestions/zsh-autosuggestions.zsh -source $HOME/.xydacshell/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh -source $HOME/.xydacshell/k/k.sh - -# Set ZSH Theme -ZSH_THEME='materialshell-electro' - -# Set ZSH Plugins - -plugins=(git zsh-autosuggestions zsh-syntax-highlighting k ) -source $ZSH/oh-my-zsh.sh - -# Custom Functions -commandexists(){ - type "$1" &> /dev/null; -} -switchuser(){ - sudo su -s /usr/bin/zsh $1 -} - -# Custom Alias -alias c=clear -alias gitlog="git log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit" - - -if commandexists nnn ; then - alias n=nnn - export NNN_USE_EDITOR=1 +# Your personal customizations live in ~/.xydacshell/zshrc.custom and are +# sourced at the end, after the profile loads. That hasn't changed. + +: ${XYDACSHELL_HOME:=$HOME/.xydacshell} +export XYDACSHELL_HOME + +# Put our bin/ on PATH so `xydacshell` works as a command. +case ":$PATH:" in + *":$XYDACSHELL_HOME/bin:"*) ;; + *) export PATH="$XYDACSHELL_HOME/bin:$PATH" ;; +esac + +if [ -f "$XYDACSHELL_HOME/profile" ]; then + XYDACSHELL_PROFILE="$(cat "$XYDACSHELL_HOME/profile")" +else + XYDACSHELL_PROFILE=classic fi - - -if commandexists screenfetch ; then - clear && screenfetch +export XYDACSHELL_PROFILE + +_xs_profile_zshrc="$XYDACSHELL_HOME/profiles/$XYDACSHELL_PROFILE/zshrc" + +if [ -f "$_xs_profile_zshrc" ]; then + source "$_xs_profile_zshrc" +else + # Fallback: if the selected profile can't load, try classic, then give up quietly. + # This preserves shell usability even if someone yanks a file. + if [ -f "$XYDACSHELL_HOME/profiles/classic/zshrc" ]; then + source "$XYDACSHELL_HOME/profiles/classic/zshrc" + fi fi -source $HOME/.xydacshell/zshrc.custom +unset _xs_profile_zshrc +# User customizations — sacred. Never overwritten by install.sh. +if [ -f "$XYDACSHELL_HOME/zshrc.custom" ]; then + source "$XYDACSHELL_HOME/zshrc.custom" +fi