Dotfiles update 2025-12-25 15:45
This commit is contained in:
596
README.md
596
README.md
@@ -1,296 +1,406 @@
|
||||
# ADLee's Dotfiles
|
||||
# Dotfiles Improvements
|
||||
|
||||
Personal configuration for a productive development environment on **Arch Linux** and **CachyOS**.
|
||||
This directory contains suggested improvements for your dotfiles project. These additions enhance functionality, maintainability, and user experience.
|
||||
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://www.zsh.org/)
|
||||
[](https://archlinux.org/)
|
||||
## Summary of Additions
|
||||
|
||||
```
|
||||
┌[alee@battlestation]─[~/.dotfiles ⎇ main]─[⇑3]
|
||||
└%
|
||||
```
|
||||
| Category | Files Added | Description |
|
||||
|----------|-------------|-------------|
|
||||
| Machine Config | `zsh/lib/machines.zsh`, `machines/*.zsh` | Per-machine configuration support |
|
||||
| Performance | `bin/dotfiles-profile.sh` | Startup time profiling |
|
||||
| Notifications | `zsh/functions/notifications.zsh` | Long-running command notifications |
|
||||
| Security | `bin/dotfiles-diff.sh` | Diff, audit, and secret detection |
|
||||
| Project Env | `zsh/functions/project-env.zsh` | Auto-load project environments |
|
||||
| Analytics | `bin/dotfiles-analytics.sh` | Enhanced history analytics |
|
||||
| Testing | `tests/run-tests.zsh`, `tests/test_*.zsh` | Unit testing framework |
|
||||
| First-Run | `bin/dotfiles-tour.sh` | Interactive tour and changelog |
|
||||
| FZF Extras | `zsh/functions/fzf-extras.zsh` | Additional fuzzy finders |
|
||||
| Plugin Mgr | `zsh/lib/plugins.zsh` | Lightweight plugin management |
|
||||
|
||||
## Quick Start
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
### Option 1: Copy All Files
|
||||
|
||||
```bash
|
||||
git clone https://github.com/adlee-was-taken/dotfiles.git ~/.dotfiles
|
||||
cd ~/.dotfiles && ./install.sh
|
||||
# Backup first
|
||||
cp -r ~/.dotfiles ~/.dotfiles.backup.$(date +%Y%m%d)
|
||||
|
||||
# Copy improvements
|
||||
cp -r /path/to/improvements/* ~/.dotfiles/
|
||||
```
|
||||
|
||||
See [INSTALL.md](INSTALL.md) for detailed instructions.
|
||||
### Option 2: Selective Installation
|
||||
|
||||
Copy only the features you want:
|
||||
|
||||
```bash
|
||||
# Machine-specific configs
|
||||
cp zsh/lib/machines.zsh ~/.dotfiles/zsh/lib/
|
||||
mkdir -p ~/.dotfiles/machines
|
||||
cp machines/*.zsh ~/.dotfiles/machines/
|
||||
|
||||
# Notifications
|
||||
cp zsh/functions/notifications.zsh ~/.dotfiles/zsh/functions/
|
||||
|
||||
# Profiling
|
||||
cp bin/dotfiles-profile.sh ~/.dotfiles/bin/
|
||||
chmod +x ~/.dotfiles/bin/dotfiles-profile.sh
|
||||
```
|
||||
|
||||
### Option 3: Integration
|
||||
|
||||
Add to your `.zshrc` to load new features:
|
||||
|
||||
```zsh
|
||||
# In ~/.dotfiles/zsh/.zshrc, add after other sources:
|
||||
|
||||
# Load machine-specific configuration
|
||||
[[ -f "$DOTFILES_DIR/zsh/lib/machines.zsh" ]] && \
|
||||
source "$DOTFILES_DIR/zsh/lib/machines.zsh"
|
||||
|
||||
# Load notifications
|
||||
[[ -f "$DOTFILES_DIR/zsh/functions/notifications.zsh" ]] && \
|
||||
source "$DOTFILES_DIR/zsh/functions/notifications.zsh"
|
||||
|
||||
# Load project environments
|
||||
[[ -f "$DOTFILES_DIR/zsh/functions/project-env.zsh" ]] && \
|
||||
source "$DOTFILES_DIR/zsh/functions/project-env.zsh"
|
||||
|
||||
# Load FZF extras
|
||||
[[ -f "$DOTFILES_DIR/zsh/functions/fzf-extras.zsh" ]] && \
|
||||
source "$DOTFILES_DIR/zsh/functions/fzf-extras.zsh"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
## Feature Details
|
||||
|
||||
| Feature | Description |
|
||||
|---------|-------------|
|
||||
| **Dynamic MOTD** | System info on shell start |
|
||||
| **Two-Line Prompt** | Git status, command timer, update indicator |
|
||||
| **Command Palette** | Fuzzy launcher (`Ctrl+Space`) |
|
||||
| **Tmux Workspaces** | Simple templates + tmuxinator integration |
|
||||
| **Systemd Helpers** | Quick service management |
|
||||
| **Btrfs/Snapper** | Filesystem health + snapshot management |
|
||||
| **Secrets Vault** | Encrypted storage (age/gpg) |
|
||||
| **Password Manager** | LastPass CLI integration |
|
||||
| **Python Templates** | Project scaffolding (Flask, FastAPI, CLI, etc.) |
|
||||
### 1. Machine-Specific Configuration
|
||||
|
||||
**Files:** `zsh/lib/machines.zsh`, `machines/*.zsh`
|
||||
|
||||
Automatically loads different settings based on hostname or machine type.
|
||||
|
||||
```bash
|
||||
# See current machine detection
|
||||
machine-info
|
||||
|
||||
# Create config for this machine
|
||||
machine-create
|
||||
|
||||
# Edit machine config
|
||||
machine-edit
|
||||
|
||||
# List all machine configs
|
||||
machines
|
||||
```
|
||||
|
||||
**Configuration hierarchy:**
|
||||
1. `dotfiles.conf` (base)
|
||||
2. `machines/default.zsh` (shared overrides)
|
||||
3. `machines/type-<type>.zsh` (laptop/desktop/server/virtual)
|
||||
4. `machines/<hostname>.zsh` (machine-specific)
|
||||
5. `~/.zshrc.local` (local, not synced)
|
||||
|
||||
---
|
||||
|
||||
## Dotfiles Management
|
||||
### 2. Startup Profiling
|
||||
|
||||
| Command | Alias | Description |
|
||||
|---------|-------|-------------|
|
||||
| `dotfiles-doctor.sh` | `dfd` | Health check |
|
||||
| `dotfiles-doctor.sh --fix` | `dffix` | Auto-fix issues |
|
||||
| `dotfiles-sync.sh push` | `dfpush` | Push changes |
|
||||
| `dotfiles-sync.sh pull` | `dfpull` | Pull changes |
|
||||
| `dotfiles-update.sh` | `dfu` | Update dotfiles |
|
||||
| `dotfiles-vault.sh` | `vault` | Secrets manager |
|
||||
| `source ~/.zshrc` | `reload` | Reload config |
|
||||
**File:** `bin/dotfiles-profile.sh`
|
||||
|
||||
**Quick Edit:** `v.zshrc`, `v.conf`, `v.alias`, `v.motd`
|
||||
Measure and optimize shell startup time.
|
||||
|
||||
```bash
|
||||
dotfiles-profile.sh # Quick timing (5 runs)
|
||||
dotfiles-profile.sh --detailed # zprof function-level analysis
|
||||
dotfiles-profile.sh --benchmark # Hyperfine benchmark
|
||||
dotfiles-profile.sh --compare # Full vs minimal shell
|
||||
dotfiles-profile.sh --tips # Optimization suggestions
|
||||
dotfiles-profile.sh --all # Run everything
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Systemd Helpers
|
||||
### 3. Long-Running Command Notifications
|
||||
|
||||
**File:** `zsh/functions/notifications.zsh`
|
||||
|
||||
Get notified when commands taking longer than 60 seconds complete.
|
||||
|
||||
```bash
|
||||
# Toggle notifications
|
||||
notify-toggle
|
||||
|
||||
# Test notification
|
||||
notify-test
|
||||
|
||||
# Show status
|
||||
notify-status
|
||||
|
||||
# Adjust threshold
|
||||
df_notify_threshold 120 # 2 minutes
|
||||
```
|
||||
|
||||
**Configuration in `dotfiles.conf`:**
|
||||
```bash
|
||||
DF_NOTIFY_ENABLED="true"
|
||||
DF_NOTIFY_THRESHOLD="60"
|
||||
DF_NOTIFY_METHODS="desktop bell"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. Diff & Security Audit
|
||||
|
||||
**File:** `bin/dotfiles-diff.sh`
|
||||
|
||||
Compare configurations and audit for security issues.
|
||||
|
||||
```bash
|
||||
dotfiles-diff.sh # Show uncommitted changes
|
||||
dotfiles-diff.sh --installed # Compare installed vs source
|
||||
dotfiles-diff.sh --symlinks # Verify symlink integrity
|
||||
dotfiles-diff.sh --secrets # Scan for exposed secrets
|
||||
dotfiles-diff.sh --permissions # Check file permissions
|
||||
dotfiles-diff.sh --audit # Full security audit
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. Project-Local Environments
|
||||
|
||||
**File:** `zsh/functions/project-env.zsh`
|
||||
|
||||
Auto-load project settings when entering directories (like direnv).
|
||||
|
||||
```bash
|
||||
# Create project env file
|
||||
project-env create
|
||||
|
||||
# Edit current project's env
|
||||
project-env edit
|
||||
|
||||
# Show status
|
||||
project-env status
|
||||
|
||||
# Allow/deny files
|
||||
project-env allow .dotfiles-local
|
||||
project-env deny .dotfiles-local
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Auto-loads `.dotfiles-local`, `.envrc`, or `.env.local`
|
||||
- Auto-activates Python virtualenvs
|
||||
- Auto-switches Node versions via `.nvmrc`
|
||||
- Security prompts for untrusted directories
|
||||
|
||||
---
|
||||
|
||||
### 6. Enhanced Shell Analytics
|
||||
|
||||
**File:** `bin/dotfiles-analytics.sh`
|
||||
|
||||
Advanced command history analysis.
|
||||
|
||||
```bash
|
||||
dotfiles-analytics.sh # Dashboard
|
||||
dotfiles-analytics.sh hourly # Commands by hour
|
||||
dotfiles-analytics.sh weekly # Usage by day of week
|
||||
dotfiles-analytics.sh projects # Group by directory
|
||||
dotfiles-analytics.sh trends # 30-day trends
|
||||
dotfiles-analytics.sh complexity # Command complexity
|
||||
dotfiles-analytics.sh tools # Tool usage breakdown
|
||||
dotfiles-analytics.sh suggestions # Alias suggestions
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7. Testing Framework
|
||||
|
||||
**Files:** `tests/run-tests.zsh`, `tests/test_*.zsh`
|
||||
|
||||
Simple unit testing for shell functions.
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
./tests/run-tests.zsh
|
||||
|
||||
# Run specific test file
|
||||
./tests/run-tests.zsh utils
|
||||
|
||||
# Or use alias
|
||||
dftest
|
||||
```
|
||||
|
||||
**Writing tests:**
|
||||
```zsh
|
||||
describe "my function"
|
||||
|
||||
it "should do something"
|
||||
assert_eq "$(my_func)" "expected"
|
||||
|
||||
it "should handle errors"
|
||||
assert_fail "my_func invalid_arg"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8. First-Run Experience & Tour
|
||||
|
||||
**File:** `bin/dotfiles-tour.sh`
|
||||
|
||||
Interactive introduction for new users.
|
||||
|
||||
```bash
|
||||
dotfiles-tour.sh # Interactive tour
|
||||
dotfiles-tour.sh --quick # Quick reference card
|
||||
dotfiles-tour.sh --changelog # Recent changes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 9. FZF Extras
|
||||
|
||||
**File:** `zsh/functions/fzf-extras.zsh`
|
||||
|
||||
Additional fuzzy finders.
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `sc <args>` | `sudo systemctl <args>` |
|
||||
| `scr <service>` | Restart + show status |
|
||||
| `sce <service>` | Enable + start |
|
||||
| `scd <service>` | Disable + stop |
|
||||
| `sclog <service>` | Follow journal logs |
|
||||
| `sc-failed` | Show failed services |
|
||||
| `sc-boot` | Boot time analysis |
|
||||
| `scf` | Interactive manager (fzf) |
|
||||
|
||||
**Aliases:** `scs` (status), `scstart`, `scstop`, `screload`, `jctl`, `jctlf`
|
||||
| `envf` | Browse environment variables |
|
||||
| `pathf` | Explore PATH directories |
|
||||
| `procf` | Process manager |
|
||||
| `killf` | Fuzzy kill processes |
|
||||
| `aliasf` | Browse aliases |
|
||||
| `funcf` | Browse functions |
|
||||
| `histf` | Enhanced history search |
|
||||
| `ff` | Find files |
|
||||
| `fdir` | Find directories |
|
||||
| `gbf` | Git branch switcher |
|
||||
| `glogf` | Git commit browser |
|
||||
|
||||
---
|
||||
|
||||
## Btrfs & Snapper
|
||||
### 10. Plugin Manager
|
||||
|
||||
### Btrfs Commands
|
||||
**File:** `zsh/lib/plugins.zsh`
|
||||
|
||||
| Command | Alias | Description |
|
||||
|---------|-------|-------------|
|
||||
| `btrfs-usage` | `btru` | Filesystem usage |
|
||||
| `btrfs-health` | `btrh` | Quick health check |
|
||||
| `btrfs-scrub` | - | Start integrity check |
|
||||
| `btrfs-balance` | - | Balance operation |
|
||||
| `btrfs-compress` | `btrc` | Compression stats |
|
||||
|
||||
### Snapper Snapshots
|
||||
|
||||
| Command | Alias | Description |
|
||||
|---------|-------|-------------|
|
||||
| `snap-create "desc"` | `snap` | Create snapshot |
|
||||
| `snap-list` | `snapls` | List snapshots |
|
||||
| `snap-check` | `snapcheck` | Verify limine sync |
|
||||
| `sys-update` | - | Update with pre/post snapshot |
|
||||
|
||||
---
|
||||
|
||||
## Tmux Workspaces
|
||||
|
||||
Manage tmux sessions with simple templates or full tmuxinator projects.
|
||||
|
||||
### Quick Commands
|
||||
|
||||
| Command | Alias | Description |
|
||||
|---------|-------|-------------|
|
||||
| `tw <name> [template]` | - | Create/attach workspace |
|
||||
| `tw-list` | `twl` | List active workspaces |
|
||||
| `tw-templates` | `twt` | Show available templates |
|
||||
| `tw-save <name>` | `tws` | Save current layout |
|
||||
| `twf` | - | Fuzzy search workspaces |
|
||||
|
||||
### Built-in Templates
|
||||
|
||||
| Template | Description |
|
||||
|----------|-------------|
|
||||
| `dev` | Editor (50%) + terminal + logs |
|
||||
| `ops` | 4-pane monitoring grid |
|
||||
| `ssh-multi` | 4 panes for multi-server |
|
||||
| `debug` | Main (70%) + helper (30%) |
|
||||
| `review` | Side-by-side comparison |
|
||||
|
||||
### Tmuxinator Integration
|
||||
|
||||
For complex projects with per-pane commands and startup scripts:
|
||||
Lightweight plugin management without heavy frameworks.
|
||||
|
||||
```bash
|
||||
# Install
|
||||
sudo pacman -S tmuxinator
|
||||
# Install a plugin
|
||||
plugin install zsh-users/zsh-autosuggestions
|
||||
|
||||
# Create project from template
|
||||
txi-new myproject dev
|
||||
# List plugins
|
||||
plugin list
|
||||
|
||||
# Edit configuration
|
||||
txi-edit myproject
|
||||
# Update all plugins
|
||||
plugin update
|
||||
|
||||
# Start project
|
||||
txi myproject
|
||||
# Remove a plugin
|
||||
plugin remove zsh-autosuggestions
|
||||
|
||||
# Show recommended plugins
|
||||
plugin recommended
|
||||
|
||||
# Lazy-load a plugin
|
||||
plugin lazy zsh-nvm nvm node npm
|
||||
```
|
||||
|
||||
| Command | Alias | Description |
|
||||
|---------|-------|-------------|
|
||||
| `txi <name>` | - | Start/attach project |
|
||||
| `txi-new <n> [tmpl]` | `txin` | Create project |
|
||||
| `txi-edit <name>` | `txie` | Edit YAML config |
|
||||
| `txi-list` | `txil` | List projects |
|
||||
| `txif` | - | Fuzzy search projects |
|
||||
---
|
||||
|
||||
**Templates:** `dev`, `ops`, `web`, `data`, `minimal`
|
||||
## New Aliases Reference
|
||||
|
||||
The `tw` command auto-detects: running session → tmuxinator project → simple template.
|
||||
| Alias | Command | Description |
|
||||
|-------|---------|-------------|
|
||||
| `dfprofile` | `dotfiles-profile.sh` | Startup profiling |
|
||||
| `dfdiff` | `dotfiles-diff.sh` | Show changes |
|
||||
| `dfaudit` | `dotfiles-diff.sh --audit` | Security audit |
|
||||
| `dftour` | `dotfiles-tour.sh` | Interactive tour |
|
||||
| `dfanalytics` | `dotfiles-analytics.sh` | Enhanced analytics |
|
||||
| `dftest` | `tests/run-tests.zsh` | Run tests |
|
||||
| `quickref` | `dotfiles-tour.sh --quick` | Quick reference |
|
||||
| `profile` | `dotfiles-profile.sh` | Startup profiling |
|
||||
| `audit` | `dotfiles-diff.sh --audit` | Security audit |
|
||||
| `tour` | `dotfiles-tour.sh` | Interactive tour |
|
||||
|
||||
---
|
||||
|
||||
## Command Palette
|
||||
## Configuration Options
|
||||
|
||||
Press **`Ctrl+Space`** for the fuzzy command launcher.
|
||||
|
||||
Searches aliases, functions, history, git commands, bookmarks, and quick actions.
|
||||
|
||||
### Directory Bookmarks
|
||||
|
||||
| Command | Alias | Description |
|
||||
|---------|-------|-------------|
|
||||
| `bookmark <name> [path]` | `bm` | Save bookmark |
|
||||
| `bookmark list` | `bm list` | List bookmarks |
|
||||
| `jump <name>` | `j` | Go to bookmark |
|
||||
|
||||
---
|
||||
|
||||
## Secrets Vault
|
||||
|
||||
Encrypted storage for API keys using `age` or `gpg`.
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `vault init` | Initialize |
|
||||
| `vault set <key>` | Store secret |
|
||||
| `vault get <key>` | Retrieve secret |
|
||||
| `vault list` | List keys |
|
||||
| `vault shell` | Export to environment |
|
||||
|
||||
---
|
||||
|
||||
## Password Manager (LastPass)
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `pw <query>` | Search and copy password |
|
||||
| `pw show <item>` | Show entry details |
|
||||
| `pw list` | List all entries |
|
||||
| `pw gen [len]` | Generate password |
|
||||
| `pwf` | Fuzzy search (fzf) |
|
||||
|
||||
---
|
||||
|
||||
## Python Templates
|
||||
|
||||
| Command | Alias | Description |
|
||||
|---------|-------|-------------|
|
||||
| `py-new <name>` | `pynew` | Basic project |
|
||||
| `py-flask <name>` | `pyflask` | Flask web app |
|
||||
| `py-fastapi <name>` | `pyfast` | FastAPI REST API |
|
||||
| `py-cli <name>` | `pycli` | CLI with Click |
|
||||
| `py-data <name>` | `pydata` | Data science |
|
||||
| `venv` | - | Activate virtualenv |
|
||||
|
||||
---
|
||||
|
||||
## SSH Manager
|
||||
|
||||
| Command | Alias | Description |
|
||||
|---------|-------|-------------|
|
||||
| `ssh-save <n> <user@host>` | `sshs` | Save profile |
|
||||
| `ssh-connect <name>` | `sshc` | Connect (auto tmux) |
|
||||
| `ssh-list` | `sshl` | List profiles |
|
||||
| `sshf` | - | Fuzzy search |
|
||||
|
||||
---
|
||||
|
||||
## Common Aliases
|
||||
|
||||
### Navigation
|
||||
`..`, `...`, `....`, `~`, `c.` (dotfiles dir)
|
||||
|
||||
### Git
|
||||
`g`, `gs` (status), `ga` (add), `gc` (commit), `gp` (push), `gl` (pull), `gd` (diff), `gco` (checkout), `glog`
|
||||
|
||||
### Docker
|
||||
`d`, `dc` (compose), `dps`, `dpa`, `di` (images), `dex` (exec -it)
|
||||
|
||||
### Tools (conditional)
|
||||
- `ls`/`ll`/`la`/`lt` → `eza` (if installed)
|
||||
- `cat` → `bat` (if installed)
|
||||
|
||||
---
|
||||
|
||||
## Zsh Theme
|
||||
|
||||
The `adlee` theme provides:
|
||||
- Two-line prompt with git branch + dirty indicator
|
||||
- Command timer for commands >10s (color-coded by duration)
|
||||
- Package update count indicator
|
||||
- Root detection (red `#` vs blue `%`)
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
Edit `~/.dotfiles/dotfiles.conf`:
|
||||
Add to `dotfiles.conf`:
|
||||
|
||||
```bash
|
||||
# Display
|
||||
DF_WIDTH="74"
|
||||
MOTD_STYLE="compact" # compact, mini, full, none
|
||||
# === NEW: Notification Settings ===
|
||||
DF_NOTIFY_ENABLED="true"
|
||||
DF_NOTIFY_THRESHOLD="60"
|
||||
DF_NOTIFY_METHODS="desktop bell"
|
||||
DF_NOTIFY_SOUND="/usr/share/sounds/freedesktop/stereo/complete.oga"
|
||||
DF_NOTIFY_ONLY_FAILURES="false"
|
||||
|
||||
# Features
|
||||
ENABLE_SMART_SUGGESTIONS="true"
|
||||
ENABLE_COMMAND_PALETTE="true"
|
||||
# === NEW: Project Environment ===
|
||||
DF_PROJECT_ENV_ENABLED="true"
|
||||
DF_PROJECT_ENV_FILES=".dotfiles-local .envrc .env.local"
|
||||
DF_PROJECT_ENV_TRUSTED_DIRS="$HOME/projects $HOME/work"
|
||||
DF_PROJECT_AUTO_VENV="true"
|
||||
DF_PROJECT_AUTO_NVM="true"
|
||||
|
||||
# Tmuxinator
|
||||
TMUXINATOR_ENABLED="auto"
|
||||
TW_PREFER_TMUXINATOR="true"
|
||||
# === NEW: Plugin Manager ===
|
||||
DF_PLUGIN_DIR="$HOME/.dotfiles/zsh/plugins"
|
||||
```
|
||||
|
||||
### Local Overrides
|
||||
|
||||
Create `~/.zshrc.local` for machine-specific settings.
|
||||
|
||||
---
|
||||
|
||||
## Repository Structure
|
||||
## Verification
|
||||
|
||||
After installation, verify everything works:
|
||||
|
||||
```bash
|
||||
# Health check
|
||||
dfd
|
||||
|
||||
# Run tests
|
||||
dftest
|
||||
|
||||
# Profile startup
|
||||
dfprofile
|
||||
|
||||
# Security audit
|
||||
dfaudit
|
||||
|
||||
# Take the tour
|
||||
dftour
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
~/.dotfiles/
|
||||
├── install.sh # Installer
|
||||
├── dotfiles.conf # Configuration
|
||||
├── bin/ # Scripts → ~/.local/bin
|
||||
dotfiles-improvements/
|
||||
├── bin/
|
||||
│ ├── dotfiles-analytics.sh # Enhanced history analytics
|
||||
│ ├── dotfiles-diff.sh # Diff and security audit
|
||||
│ ├── dotfiles-profile.sh # Startup profiling
|
||||
│ └── dotfiles-tour.sh # First-run experience
|
||||
├── machines/
|
||||
│ ├── default.zsh # Shared machine config
|
||||
│ ├── type-laptop.zsh # Laptop-specific
|
||||
│ └── type-server.zsh # Server-specific
|
||||
├── tests/
|
||||
│ ├── run-tests.zsh # Test runner
|
||||
│ ├── test_config.zsh # Config tests
|
||||
│ └── test_utils.zsh # Utils tests
|
||||
├── zsh/
|
||||
│ ├── .zshrc
|
||||
│ ├── aliases.zsh
|
||||
│ ├── lib/ # colors, config, utils, bootstrap
|
||||
│ ├── themes/adlee.zsh-theme
|
||||
│ └── functions/ # Feature modules
|
||||
├── vim/.vimrc
|
||||
├── tmux/.tmux.conf
|
||||
├── espanso/ # Text expansion
|
||||
└── .tmux-templates/ # Workspace layouts
|
||||
│ ├── aliases-extended.zsh # Extended aliases
|
||||
│ ├── functions/
|
||||
│ │ ├── fzf-extras.zsh # Additional fzf utilities
|
||||
│ │ ├── notifications.zsh # Command notifications
|
||||
│ │ └── project-env.zsh # Project environments
|
||||
│ └── lib/
|
||||
│ ├── machines.zsh # Machine detection
|
||||
│ └── plugins.zsh # Plugin manager
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
MIT – See [LICENSE](LICENSE)
|
||||
|
||||
**Author:** Aaron D. Lee
|
||||
**Repository:** https://github.com/adlee-was-taken/dotfiles
|
||||
|
||||
352
bin/dotfiles-analytics.sh
Executable file
352
bin/dotfiles-analytics.sh
Executable file
@@ -0,0 +1,352 @@
|
||||
#!/usr/bin/env bash
|
||||
# ============================================================================
|
||||
# Enhanced Shell Analytics
|
||||
# ============================================================================
|
||||
# Advanced command history analysis with time-based patterns, project grouping,
|
||||
# and actionable insights.
|
||||
#
|
||||
# Usage:
|
||||
# dotfiles-analytics.sh # Dashboard
|
||||
# dotfiles-analytics.sh hourly # Commands by hour
|
||||
# dotfiles-analytics.sh weekly # Usage by day of week
|
||||
# dotfiles-analytics.sh projects # Group by directory
|
||||
# dotfiles-analytics.sh trends # Usage trends over time
|
||||
# ============================================================================
|
||||
|
||||
set -e
|
||||
|
||||
# Source bootstrap
|
||||
source "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null || {
|
||||
DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' DF_RED=$'\033[0;31m'
|
||||
DF_CYAN=$'\033[0;36m' DF_BLUE=$'\033[0;34m' DF_MAGENTA=$'\033[0;35m'
|
||||
DF_NC=$'\033[0m' DF_DIM=$'\033[2m'
|
||||
df_print_header() { echo "=== $1 ==="; }
|
||||
df_print_section() { echo -e "${DF_CYAN}$1:${DF_NC}"; }
|
||||
df_print_indent() { echo " $1"; }
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Configuration
|
||||
# ============================================================================
|
||||
|
||||
HISTORY_FILE="${HISTFILE:-$HOME/.zsh_history}"
|
||||
BASH_HISTORY_FILE="$HOME/.bash_history"
|
||||
|
||||
# ============================================================================
|
||||
# History Parsing
|
||||
# ============================================================================
|
||||
|
||||
# Get zsh history with timestamps
|
||||
get_zsh_history_with_time() {
|
||||
if [[ -f "$HISTORY_FILE" ]]; then
|
||||
# Zsh extended history format: : timestamp:0;command
|
||||
grep "^:" "$HISTORY_FILE" 2>/dev/null | while read -r line; do
|
||||
local timestamp=$(echo "$line" | cut -d':' -f2)
|
||||
local cmd=$(echo "$line" | cut -d';' -f2-)
|
||||
echo "${timestamp}|${cmd}"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
# Get plain history (command only)
|
||||
get_history() {
|
||||
if [[ -f "$HISTORY_FILE" ]]; then
|
||||
grep "^:" "$HISTORY_FILE" 2>/dev/null | cut -d';' -f2- || cat "$HISTORY_FILE"
|
||||
elif [[ -f "$BASH_HISTORY_FILE" ]]; then
|
||||
cat "$BASH_HISTORY_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Analytics Functions
|
||||
# ============================================================================
|
||||
|
||||
# Commands by hour of day
|
||||
show_hourly() {
|
||||
df_print_section "Command Usage by Hour of Day"
|
||||
echo ""
|
||||
|
||||
declare -A hour_counts
|
||||
|
||||
get_zsh_history_with_time | while IFS='|' read -r timestamp cmd; do
|
||||
if [[ -n "$timestamp" && "$timestamp" =~ ^[0-9]+$ ]]; then
|
||||
local hour=$(date -d "@$timestamp" '+%H' 2>/dev/null || date -r "$timestamp" '+%H' 2>/dev/null)
|
||||
if [[ -n "$hour" ]]; then
|
||||
echo "$hour"
|
||||
fi
|
||||
fi
|
||||
done | sort | uniq -c | sort -k2 -n | while read -r count hour; do
|
||||
local bar=""
|
||||
local bar_len=$((count / 50 + 1))
|
||||
for ((i=0; i<bar_len && i<40; i++)); do bar+="█"; done
|
||||
printf " %s:00 %5d ${DF_CYAN}%s${DF_NC}\n" "$hour" "$count" "$bar"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo -e "${DF_DIM}Peak hours indicate your most active coding times${DF_NC}"
|
||||
}
|
||||
|
||||
# Commands by day of week
|
||||
show_weekly() {
|
||||
df_print_section "Command Usage by Day of Week"
|
||||
echo ""
|
||||
|
||||
local days=("Mon" "Tue" "Wed" "Thu" "Fri" "Sat" "Sun")
|
||||
declare -A day_counts
|
||||
|
||||
# Initialize
|
||||
for day in "${days[@]}"; do
|
||||
day_counts[$day]=0
|
||||
done
|
||||
|
||||
get_zsh_history_with_time | while IFS='|' read -r timestamp cmd; do
|
||||
if [[ -n "$timestamp" && "$timestamp" =~ ^[0-9]+$ ]]; then
|
||||
local dow=$(date -d "@$timestamp" '+%a' 2>/dev/null || date -r "$timestamp" '+%a' 2>/dev/null)
|
||||
if [[ -n "$dow" ]]; then
|
||||
echo "$dow"
|
||||
fi
|
||||
fi
|
||||
done | sort | uniq -c | while read -r count day; do
|
||||
local bar=""
|
||||
local bar_len=$((count / 100 + 1))
|
||||
for ((i=0; i<bar_len && i<40; i++)); do bar+="█"; done
|
||||
printf " %-3s %6d ${DF_CYAN}%s${DF_NC}\n" "$day" "$count" "$bar"
|
||||
done
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Commands grouped by working directory
|
||||
show_projects() {
|
||||
df_print_section "Command Usage by Project/Directory"
|
||||
echo ""
|
||||
|
||||
# This requires shell integration that saves PWD with commands
|
||||
# We'll analyze cd commands as a proxy
|
||||
|
||||
df_print_indent "Most visited directories:"
|
||||
echo ""
|
||||
|
||||
get_history | grep "^cd " | awk '{print $2}' | \
|
||||
sed 's|^~|'"$HOME"'|' | \
|
||||
sort | uniq -c | sort -rn | head -15 | while read -r count dir; do
|
||||
# Shorten home paths
|
||||
local short_dir="${dir/#$HOME/~}"
|
||||
printf " %5d ${DF_CYAN}%s${DF_NC}\n" "$count" "$short_dir"
|
||||
done
|
||||
|
||||
echo ""
|
||||
df_print_indent "Git repositories worked on:"
|
||||
echo ""
|
||||
|
||||
get_history | grep -E "^(git|g) " | grep -v "^git config" | \
|
||||
awk '{print $1, $2}' | sort | uniq -c | sort -rn | head -10 | while read -r count cmd subcmd; do
|
||||
printf " %5d ${DF_GREEN}%s %s${DF_NC}\n" "$count" "$cmd" "$subcmd"
|
||||
done
|
||||
}
|
||||
|
||||
# Usage trends over time
|
||||
show_trends() {
|
||||
df_print_section "Usage Trends (Last 30 Days)"
|
||||
echo ""
|
||||
|
||||
local today=$(date +%s)
|
||||
local thirty_days_ago=$((today - 30*24*60*60))
|
||||
|
||||
df_print_indent "Commands per day:"
|
||||
echo ""
|
||||
|
||||
get_zsh_history_with_time | while IFS='|' read -r timestamp cmd; do
|
||||
if [[ -n "$timestamp" && "$timestamp" =~ ^[0-9]+$ ]]; then
|
||||
if (( timestamp >= thirty_days_ago )); then
|
||||
date -d "@$timestamp" '+%Y-%m-%d' 2>/dev/null || date -r "$timestamp" '+%Y-%m-%d' 2>/dev/null
|
||||
fi
|
||||
fi
|
||||
done | sort | uniq -c | tail -30 | while read -r count date; do
|
||||
local bar=""
|
||||
local bar_len=$((count / 20 + 1))
|
||||
for ((i=0; i<bar_len && i<30; i++)); do bar+="▪"; done
|
||||
printf " %s %4d ${DF_CYAN}%s${DF_NC}\n" "$date" "$count" "$bar"
|
||||
done
|
||||
}
|
||||
|
||||
# Command complexity analysis
|
||||
show_complexity() {
|
||||
df_print_section "Command Complexity Analysis"
|
||||
echo ""
|
||||
|
||||
df_print_indent "Simple commands (single word):"
|
||||
local simple=$(get_history | awk 'NF==1' | wc -l)
|
||||
echo " $simple"
|
||||
|
||||
df_print_indent "Piped commands:"
|
||||
local piped=$(get_history | grep -c '|' || echo 0)
|
||||
echo " $piped"
|
||||
|
||||
df_print_indent "Commands with redirects:"
|
||||
local redirects=$(get_history | grep -cE '[<>]' || echo 0)
|
||||
echo " $redirects"
|
||||
|
||||
df_print_indent "Commands with subshells:"
|
||||
local subshell=$(get_history | grep -cE '\$\(' || echo 0)
|
||||
echo " $subshell"
|
||||
|
||||
echo ""
|
||||
df_print_section "Most Complex Commands (by pipe count)"
|
||||
echo ""
|
||||
|
||||
get_history | awk -F'|' 'NF>2 {print NF-1, $0}' | sort -rn | head -5 | while read -r pipes cmd; do
|
||||
local short_cmd="${cmd:0:60}"
|
||||
[[ ${#cmd} -gt 60 ]] && short_cmd="${short_cmd}..."
|
||||
echo -e " ${DF_MAGENTA}$pipes pipes:${DF_NC} $short_cmd"
|
||||
done
|
||||
}
|
||||
|
||||
# Tool usage breakdown
|
||||
show_tools() {
|
||||
df_print_section "Tool Usage Breakdown"
|
||||
echo ""
|
||||
|
||||
local categories=(
|
||||
"Git:git g"
|
||||
"Docker:docker docker-compose d dc"
|
||||
"Package:pacman paru yay npm pip cargo"
|
||||
"Editor:vim nvim vi nano code"
|
||||
"Navigation:cd ls ll la cat less"
|
||||
"System:sudo systemctl journalctl"
|
||||
"Network:curl wget ssh scp"
|
||||
)
|
||||
|
||||
for category in "${categories[@]}"; do
|
||||
local name="${category%%:*}"
|
||||
local tools="${category#*:}"
|
||||
local total=0
|
||||
|
||||
for tool in $tools; do
|
||||
local count=$(get_history | awk '{print $1}' | grep -c "^${tool}$" 2>/dev/null || echo 0)
|
||||
total=$((total + count))
|
||||
done
|
||||
|
||||
if (( total > 0 )); then
|
||||
printf " %-12s ${DF_GREEN}%6d${DF_NC}\n" "$name:" "$total"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Suggestions based on usage
|
||||
show_suggestions() {
|
||||
df_print_section "Optimization Suggestions"
|
||||
echo ""
|
||||
|
||||
# Find frequently typed long commands
|
||||
df_print_indent "Consider creating aliases for:"
|
||||
echo ""
|
||||
|
||||
get_history | awk 'length > 20' | sort | uniq -c | sort -rn | head -5 | while read -r count cmd; do
|
||||
if (( count >= 5 )); then
|
||||
local short_cmd="${cmd:0:50}"
|
||||
[[ ${#cmd} -gt 50 ]] && short_cmd="${short_cmd}..."
|
||||
echo -e " ${DF_YELLOW}$count×${DF_NC} $short_cmd"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
# Check for common mistakes
|
||||
df_print_indent "Common typos detected:"
|
||||
echo ""
|
||||
|
||||
local typos=("gti:git" "sl:ls" "cta:cat" "grpe:grep" "suod:sudo")
|
||||
for typo_pair in "${typos[@]}"; do
|
||||
local typo="${typo_pair%%:*}"
|
||||
local correct="${typo_pair#*:}"
|
||||
local count=$(get_history | grep -c "^${typo} " 2>/dev/null || echo 0)
|
||||
if (( count > 0 )); then
|
||||
echo -e " ${DF_RED}$typo${DF_NC} → $correct (${count}×)"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Full dashboard
|
||||
show_dashboard() {
|
||||
local total=$(get_history | wc -l)
|
||||
local unique=$(get_history | sort -u | wc -l)
|
||||
|
||||
df_print_section "Shell Analytics Dashboard"
|
||||
echo ""
|
||||
echo -e " Total commands: ${DF_GREEN}$total${DF_NC}"
|
||||
echo -e " Unique commands: ${DF_GREEN}$unique${DF_NC}"
|
||||
echo -e " Efficiency ratio: ${DF_CYAN}$(( unique * 100 / (total + 1) ))%${DF_NC}"
|
||||
echo ""
|
||||
|
||||
df_print_section "Top 15 Commands"
|
||||
echo ""
|
||||
|
||||
get_history | awk '{print $1}' | sort | uniq -c | sort -rn | head -15 | while read -r count cmd; do
|
||||
printf " %-20s ${DF_GREEN}%5d${DF_NC}\n" "$cmd" "$count"
|
||||
done
|
||||
|
||||
echo ""
|
||||
show_tools
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Help
|
||||
# ============================================================================
|
||||
|
||||
show_help() {
|
||||
cat << 'EOF'
|
||||
Enhanced Shell Analytics
|
||||
|
||||
Usage: dotfiles-analytics.sh [COMMAND]
|
||||
|
||||
Commands:
|
||||
dashboard Full analytics dashboard (default)
|
||||
hourly Command usage by hour of day
|
||||
weekly Command usage by day of week
|
||||
projects Commands grouped by directory
|
||||
trends Usage trends over last 30 days
|
||||
complexity Command complexity analysis
|
||||
tools Tool usage breakdown
|
||||
suggestions Optimization suggestions
|
||||
help Show this help
|
||||
|
||||
Examples:
|
||||
dotfiles-analytics.sh # Full dashboard
|
||||
dotfiles-analytics.sh hourly # See peak coding hours
|
||||
dotfiles-analytics.sh suggestions # Get alias suggestions
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Main
|
||||
# ============================================================================
|
||||
|
||||
main() {
|
||||
df_print_header "dotfiles-analytics"
|
||||
|
||||
if [[ ! -f "$HISTORY_FILE" && ! -f "$BASH_HISTORY_FILE" ]]; then
|
||||
echo "No history file found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "${1:-dashboard}" in
|
||||
dashboard|d) show_dashboard ;;
|
||||
hourly|h) show_hourly ;;
|
||||
weekly|w) show_weekly ;;
|
||||
projects|p) show_projects ;;
|
||||
trends|t) show_trends ;;
|
||||
complexity|c) show_complexity ;;
|
||||
tools) show_tools ;;
|
||||
suggestions|s) show_suggestions ;;
|
||||
help|--help|-h) show_help ;;
|
||||
*)
|
||||
echo "Unknown command: $1"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
428
bin/dotfiles-diff.sh
Executable file
428
bin/dotfiles-diff.sh
Executable file
@@ -0,0 +1,428 @@
|
||||
#!/usr/bin/env bash
|
||||
# ============================================================================
|
||||
# Dotfiles Diff & Audit Tool
|
||||
# ============================================================================
|
||||
# Compare configurations, audit for issues, and track changes.
|
||||
#
|
||||
# Usage:
|
||||
# dotfiles-diff.sh # Show uncommitted changes
|
||||
# dotfiles-diff.sh --symlinks # Verify symlink integrity
|
||||
# dotfiles-diff.sh --secrets # Audit for exposed secrets
|
||||
# dotfiles-diff.sh --permissions # Check file permissions
|
||||
# dotfiles-diff.sh --audit # Full security audit
|
||||
# ============================================================================
|
||||
|
||||
set -e
|
||||
|
||||
# Source bootstrap
|
||||
source "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null || {
|
||||
DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' DF_RED=$'\033[0;31m'
|
||||
DF_CYAN=$'\033[0;36m' DF_BLUE=$'\033[0;34m' DF_NC=$'\033[0m'
|
||||
DF_DIM=$'\033[2m'
|
||||
DOTFILES_HOME="${DOTFILES_HOME:-$HOME/.dotfiles}"
|
||||
df_print_header() { echo "=== $1 ==="; }
|
||||
df_print_success() { echo -e "${DF_GREEN}✓${DF_NC} $1"; }
|
||||
df_print_error() { echo -e "${DF_RED}✗${DF_NC} $1" >&2; }
|
||||
df_print_warning() { echo -e "${DF_YELLOW}⚠${DF_NC} $1"; }
|
||||
df_print_step() { echo -e "${DF_BLUE}==>${DF_NC} $1"; }
|
||||
df_print_section() { echo -e "${DF_CYAN}$1:${DF_NC}"; }
|
||||
df_print_indent() { echo " $1"; }
|
||||
}
|
||||
|
||||
DOTFILES_DIR="${DOTFILES_HOME:-$HOME/.dotfiles}"
|
||||
|
||||
# ============================================================================
|
||||
# Diff Functions
|
||||
# ============================================================================
|
||||
|
||||
# Show git diff for uncommitted changes
|
||||
show_git_diff() {
|
||||
df_print_section "Uncommitted Changes"
|
||||
|
||||
if [[ ! -d "$DOTFILES_DIR/.git" ]]; then
|
||||
df_print_warning "Not a git repository"
|
||||
return 1
|
||||
fi
|
||||
|
||||
cd "$DOTFILES_DIR"
|
||||
|
||||
local changes=$(git status --porcelain 2>/dev/null)
|
||||
|
||||
if [[ -z "$changes" ]]; then
|
||||
df_print_success "No uncommitted changes"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "$changes" | while read -r status file; do
|
||||
case "$status" in
|
||||
M*|" M") echo -e " ${DF_YELLOW}modified:${DF_NC} $file" ;;
|
||||
A*|"A ") echo -e " ${DF_GREEN}added:${DF_NC} $file" ;;
|
||||
D*|" D") echo -e " ${DF_RED}deleted:${DF_NC} $file" ;;
|
||||
R*) echo -e " ${DF_BLUE}renamed:${DF_NC} $file" ;;
|
||||
\?\?) echo -e " ${DF_DIM}untracked:${DF_NC} $file" ;;
|
||||
*) echo -e " ${status}: $file" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo ""
|
||||
df_print_step "View full diff: git -C $DOTFILES_DIR diff"
|
||||
}
|
||||
|
||||
# Show what's different between repo and installed files
|
||||
show_installed_diff() {
|
||||
df_print_section "Installed File Differences"
|
||||
echo ""
|
||||
|
||||
local files_to_check=(
|
||||
"$HOME/.zshrc:$DOTFILES_DIR/zsh/.zshrc"
|
||||
"$HOME/.gitconfig:$DOTFILES_DIR/git/.gitconfig"
|
||||
"$HOME/.vimrc:$DOTFILES_DIR/vim/.vimrc"
|
||||
"$HOME/.tmux.conf:$DOTFILES_DIR/tmux/.tmux.conf"
|
||||
)
|
||||
|
||||
local has_diff=false
|
||||
|
||||
for pair in "${files_to_check[@]}"; do
|
||||
local installed="${pair%%:*}"
|
||||
local source="${pair#*:}"
|
||||
local name=$(basename "$installed")
|
||||
|
||||
if [[ ! -e "$installed" ]]; then
|
||||
echo -e " ${DF_YELLOW}⚠${DF_NC} $name: not installed"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ -L "$installed" ]]; then
|
||||
local target=$(readlink -f "$installed")
|
||||
if [[ "$target" == "$source" ]] || [[ "$target" == "$(readlink -f "$source")" ]]; then
|
||||
echo -e " ${DF_GREEN}✓${DF_NC} $name: symlink OK"
|
||||
else
|
||||
echo -e " ${DF_YELLOW}⚠${DF_NC} $name: symlink points elsewhere → $target"
|
||||
has_diff=true
|
||||
fi
|
||||
else
|
||||
# Regular file - check if different
|
||||
if diff -q "$installed" "$source" &>/dev/null; then
|
||||
echo -e " ${DF_GREEN}✓${DF_NC} $name: content matches (regular file)"
|
||||
else
|
||||
echo -e " ${DF_RED}✗${DF_NC} $name: differs from source"
|
||||
has_diff=true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$has_diff" == true ]]; then
|
||||
echo ""
|
||||
df_print_warning "Some files differ. Run installer to sync: ./install.sh"
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Symlink Verification
|
||||
# ============================================================================
|
||||
|
||||
check_symlinks() {
|
||||
df_print_section "Symlink Integrity Check"
|
||||
echo ""
|
||||
|
||||
local symlinks=(
|
||||
"$HOME/.zshrc"
|
||||
"$HOME/.gitconfig"
|
||||
"$HOME/.vimrc"
|
||||
"$HOME/.tmux.conf"
|
||||
"$HOME/.config/nvim"
|
||||
)
|
||||
|
||||
local broken=0
|
||||
local missing=0
|
||||
local ok=0
|
||||
|
||||
for link in "${symlinks[@]}"; do
|
||||
local name=$(basename "$link")
|
||||
|
||||
if [[ ! -e "$link" && ! -L "$link" ]]; then
|
||||
echo -e " ${DF_DIM}○${DF_NC} $name: not installed"
|
||||
((missing++))
|
||||
elif [[ -L "$link" ]]; then
|
||||
if [[ -e "$link" ]]; then
|
||||
echo -e " ${DF_GREEN}✓${DF_NC} $name → $(readlink "$link")"
|
||||
((ok++))
|
||||
else
|
||||
echo -e " ${DF_RED}✗${DF_NC} $name: BROKEN → $(readlink "$link")"
|
||||
((broken++))
|
||||
fi
|
||||
else
|
||||
echo -e " ${DF_YELLOW}⚠${DF_NC} $name: regular file (not symlink)"
|
||||
fi
|
||||
done
|
||||
|
||||
# Check bin scripts
|
||||
echo ""
|
||||
df_print_section "Bin Script Symlinks"
|
||||
|
||||
if [[ -d "$HOME/.local/bin" ]]; then
|
||||
for script in "$HOME/.local/bin"/dotfiles-*.sh; do
|
||||
[[ -e "$script" ]] || continue
|
||||
local name=$(basename "$script")
|
||||
|
||||
if [[ -L "$script" ]]; then
|
||||
if [[ -e "$script" ]]; then
|
||||
echo -e " ${DF_GREEN}✓${DF_NC} $name"
|
||||
((ok++))
|
||||
else
|
||||
echo -e " ${DF_RED}✗${DF_NC} $name: BROKEN"
|
||||
((broken++))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo ""
|
||||
df_print_section "Summary"
|
||||
df_print_indent "OK: $ok | Missing: $missing | Broken: $broken"
|
||||
|
||||
if (( broken > 0 )); then
|
||||
echo ""
|
||||
df_print_error "Found $broken broken symlinks!"
|
||||
df_print_indent "Fix with: dffix"
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Security Audit
|
||||
# ============================================================================
|
||||
|
||||
audit_secrets() {
|
||||
df_print_section "Secret Detection Audit"
|
||||
echo ""
|
||||
|
||||
cd "$DOTFILES_DIR"
|
||||
|
||||
local issues=0
|
||||
|
||||
# Patterns that might indicate secrets
|
||||
local patterns=(
|
||||
'api[_-]?key\s*[:=]'
|
||||
'secret[_-]?key\s*[:=]'
|
||||
'password\s*[:=]'
|
||||
'token\s*[:=]'
|
||||
'private[_-]?key'
|
||||
'BEGIN (RSA|DSA|EC|OPENSSH) PRIVATE KEY'
|
||||
'aws_access_key_id'
|
||||
'aws_secret_access_key'
|
||||
)
|
||||
|
||||
df_print_step "Scanning tracked files..."
|
||||
|
||||
for pattern in "${patterns[@]}"; do
|
||||
local matches=$(git grep -l -i -E "$pattern" 2>/dev/null || true)
|
||||
if [[ -n "$matches" ]]; then
|
||||
echo ""
|
||||
df_print_warning "Pattern '$pattern' found in:"
|
||||
echo "$matches" | while read -r file; do
|
||||
df_print_indent " $file"
|
||||
((issues++))
|
||||
done
|
||||
fi
|
||||
done
|
||||
|
||||
if (( issues == 0 )); then
|
||||
df_print_success "No obvious secrets found in tracked files"
|
||||
else
|
||||
echo ""
|
||||
df_print_error "Found potential secrets in $issues location(s)"
|
||||
df_print_indent "Review these files and use the vault for sensitive data"
|
||||
fi
|
||||
|
||||
# Check git history (limited)
|
||||
echo ""
|
||||
df_print_step "Scanning recent git history (last 50 commits)..."
|
||||
|
||||
local history_issues=$(git log -50 --all -p 2>/dev/null | grep -c -i -E 'password|secret|api.?key|token' || echo 0)
|
||||
|
||||
if (( history_issues > 0 )); then
|
||||
df_print_warning "Found $history_issues potential matches in git history"
|
||||
df_print_indent "Consider: git filter-branch or BFG Repo Cleaner"
|
||||
else
|
||||
df_print_success "No obvious secrets in recent history"
|
||||
fi
|
||||
}
|
||||
|
||||
audit_permissions() {
|
||||
df_print_section "File Permission Audit"
|
||||
echo ""
|
||||
|
||||
cd "$DOTFILES_DIR"
|
||||
|
||||
local issues=0
|
||||
|
||||
# Check for world-writable files
|
||||
df_print_step "Checking for world-writable files..."
|
||||
local world_writable=$(find . -type f -perm -o+w 2>/dev/null | grep -v ".git" || true)
|
||||
|
||||
if [[ -n "$world_writable" ]]; then
|
||||
df_print_warning "World-writable files found:"
|
||||
echo "$world_writable" | while read -r file; do
|
||||
df_print_indent "$file"
|
||||
((issues++))
|
||||
done
|
||||
else
|
||||
df_print_success "No world-writable files"
|
||||
fi
|
||||
|
||||
# Check bin scripts are executable
|
||||
echo ""
|
||||
df_print_step "Checking bin script permissions..."
|
||||
|
||||
for script in "$DOTFILES_DIR/bin"/*.sh; do
|
||||
[[ -f "$script" ]] || continue
|
||||
local name=$(basename "$script")
|
||||
|
||||
if [[ -x "$script" ]]; then
|
||||
echo -e " ${DF_GREEN}✓${DF_NC} $name"
|
||||
else
|
||||
echo -e " ${DF_RED}✗${DF_NC} $name: not executable"
|
||||
((issues++))
|
||||
fi
|
||||
done
|
||||
|
||||
# Check sensitive directories
|
||||
echo ""
|
||||
df_print_step "Checking sensitive directories..."
|
||||
|
||||
if [[ -d "$DOTFILES_DIR/vault" ]]; then
|
||||
local vault_perms=$(stat -c %a "$DOTFILES_DIR/vault" 2>/dev/null || stat -f %Lp "$DOTFILES_DIR/vault" 2>/dev/null)
|
||||
if [[ "$vault_perms" == "700" ]]; then
|
||||
df_print_success "vault/ directory: 700 (secure)"
|
||||
else
|
||||
df_print_warning "vault/ directory: $vault_perms (should be 700)"
|
||||
((issues++))
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if (( issues == 0 )); then
|
||||
df_print_success "All permission checks passed"
|
||||
else
|
||||
df_print_warning "Found $issues permission issues"
|
||||
fi
|
||||
}
|
||||
|
||||
full_audit() {
|
||||
df_print_step "Running full security audit..."
|
||||
echo ""
|
||||
|
||||
audit_secrets
|
||||
echo ""
|
||||
audit_permissions
|
||||
echo ""
|
||||
check_symlinks
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Machine Comparison
|
||||
# ============================================================================
|
||||
|
||||
compare_machines() {
|
||||
df_print_section "Machine Configuration Comparison"
|
||||
echo ""
|
||||
|
||||
local machines_dir="$DOTFILES_DIR/machines"
|
||||
|
||||
if [[ ! -d "$machines_dir" ]] || [[ -z "$(ls -A "$machines_dir" 2>/dev/null)" ]]; then
|
||||
df_print_info "No machine configs to compare"
|
||||
df_print_indent "Create with: df_machine_create"
|
||||
return
|
||||
fi
|
||||
|
||||
local configs=("$machines_dir"/*.zsh(N))
|
||||
|
||||
if (( ${#configs[@]} < 2 )); then
|
||||
df_print_info "Need at least 2 machine configs to compare"
|
||||
return
|
||||
fi
|
||||
|
||||
df_print_step "Available configs:"
|
||||
for config in "${configs[@]}"; do
|
||||
df_print_indent "$(basename "$config" .zsh)"
|
||||
done
|
||||
|
||||
echo ""
|
||||
read -p "Compare which two? (e.g., 'laptop server'): " config1 config2
|
||||
|
||||
if [[ -f "$machines_dir/$config1.zsh" && -f "$machines_dir/$config2.zsh" ]]; then
|
||||
echo ""
|
||||
diff -u --color=always "$machines_dir/$config1.zsh" "$machines_dir/$config2.zsh" || true
|
||||
else
|
||||
df_print_error "Config not found"
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Help
|
||||
# ============================================================================
|
||||
|
||||
show_help() {
|
||||
cat << 'EOF'
|
||||
Dotfiles Diff & Audit Tool
|
||||
|
||||
Usage: dotfiles-diff.sh [OPTIONS]
|
||||
|
||||
Options:
|
||||
(none) Show uncommitted git changes
|
||||
--installed Compare installed files with source
|
||||
--symlinks Verify symlink integrity
|
||||
--secrets Scan for exposed secrets
|
||||
--permissions Check file permissions
|
||||
--audit Full security audit (secrets + permissions + symlinks)
|
||||
--machines Compare machine configurations
|
||||
--help Show this help
|
||||
|
||||
Examples:
|
||||
dotfiles-diff.sh # Quick git status
|
||||
dotfiles-diff.sh --symlinks # Verify all symlinks are valid
|
||||
dotfiles-diff.sh --audit # Full security audit
|
||||
dotfiles-diff.sh --machines # Compare laptop vs server configs
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Main
|
||||
# ============================================================================
|
||||
|
||||
main() {
|
||||
df_print_header "dotfiles-diff"
|
||||
|
||||
case "${1:-}" in
|
||||
--installed|-i)
|
||||
show_installed_diff
|
||||
;;
|
||||
--symlinks|-s)
|
||||
check_symlinks
|
||||
;;
|
||||
--secrets)
|
||||
audit_secrets
|
||||
;;
|
||||
--permissions|-p)
|
||||
audit_permissions
|
||||
;;
|
||||
--audit|-a)
|
||||
full_audit
|
||||
;;
|
||||
--machines|-m)
|
||||
compare_machines
|
||||
;;
|
||||
--help|-h)
|
||||
show_help
|
||||
;;
|
||||
*)
|
||||
show_git_diff
|
||||
echo ""
|
||||
show_installed_diff
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
300
bin/dotfiles-profile.sh
Executable file
300
bin/dotfiles-profile.sh
Executable file
@@ -0,0 +1,300 @@
|
||||
#!/usr/bin/env bash
|
||||
# ============================================================================
|
||||
# Dotfiles Startup Profiler
|
||||
# ============================================================================
|
||||
# Measures and analyzes shell startup time to identify slow components.
|
||||
#
|
||||
# Usage:
|
||||
# dotfiles-profile.sh # Quick profile
|
||||
# dotfiles-profile.sh --detailed # Detailed zprof output
|
||||
# dotfiles-profile.sh --benchmark # Multiple runs with hyperfine
|
||||
# dotfiles-profile.sh --compare # Compare with minimal shell
|
||||
# ============================================================================
|
||||
|
||||
set -e
|
||||
|
||||
# Source bootstrap
|
||||
source "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null || {
|
||||
DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' DF_RED=$'\033[0;31m'
|
||||
DF_CYAN=$'\033[0;36m' DF_BLUE=$'\033[0;34m' DF_NC=$'\033[0m'
|
||||
DF_DIM=$'\033[2m'
|
||||
df_print_header() { echo "=== $1 ==="; }
|
||||
df_print_success() { echo -e "${DF_GREEN}✓${DF_NC} $1"; }
|
||||
df_print_warning() { echo -e "${DF_YELLOW}⚠${DF_NC} $1"; }
|
||||
df_print_step() { echo -e "${DF_BLUE}==>${DF_NC} $1"; }
|
||||
df_print_section() { echo -e "${DF_CYAN}$1:${DF_NC}"; }
|
||||
df_print_indent() { echo " $1"; }
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Configuration
|
||||
# ============================================================================
|
||||
|
||||
PROFILE_RUNS=5
|
||||
SLOW_THRESHOLD_MS=200
|
||||
VERY_SLOW_THRESHOLD_MS=500
|
||||
|
||||
# ============================================================================
|
||||
# Profiling Functions
|
||||
# ============================================================================
|
||||
|
||||
# Quick timing measurement
|
||||
quick_profile() {
|
||||
df_print_section "Quick Startup Timing"
|
||||
echo ""
|
||||
|
||||
local times=()
|
||||
for i in $(seq 1 $PROFILE_RUNS); do
|
||||
local start=$(date +%s%3N)
|
||||
zsh -i -c 'exit' 2>/dev/null
|
||||
local end=$(date +%s%3N)
|
||||
local duration=$((end - start))
|
||||
times+=($duration)
|
||||
printf " Run %d: ${DF_CYAN}%dms${DF_NC}\n" "$i" "$duration"
|
||||
done
|
||||
|
||||
# Calculate average
|
||||
local sum=0
|
||||
for t in "${times[@]}"; do
|
||||
sum=$((sum + t))
|
||||
done
|
||||
local avg=$((sum / PROFILE_RUNS))
|
||||
|
||||
echo ""
|
||||
df_print_section "Results"
|
||||
|
||||
if (( avg < SLOW_THRESHOLD_MS )); then
|
||||
df_print_indent "Average: ${DF_GREEN}${avg}ms${DF_NC} (excellent)"
|
||||
elif (( avg < VERY_SLOW_THRESHOLD_MS )); then
|
||||
df_print_indent "Average: ${DF_YELLOW}${avg}ms${DF_NC} (acceptable)"
|
||||
else
|
||||
df_print_indent "Average: ${DF_RED}${avg}ms${DF_NC} (slow - optimization recommended)"
|
||||
fi
|
||||
}
|
||||
|
||||
# Detailed zprof analysis
|
||||
detailed_profile() {
|
||||
df_print_section "Detailed Function Profiling (zprof)"
|
||||
echo ""
|
||||
|
||||
# Create temporary profile script
|
||||
local tmp_zshrc=$(mktemp)
|
||||
cat > "$tmp_zshrc" << 'EOF'
|
||||
zmodload zsh/zprof
|
||||
source ~/.zshrc
|
||||
zprof
|
||||
EOF
|
||||
|
||||
ZDOTDIR=$(dirname "$tmp_zshrc") zsh -i -c "source $tmp_zshrc; exit" 2>/dev/null | head -40
|
||||
|
||||
rm -f "$tmp_zshrc"
|
||||
|
||||
echo ""
|
||||
df_print_info "Top 40 functions shown. Run with ZDOTDIR override for full output."
|
||||
}
|
||||
|
||||
# Benchmark with hyperfine
|
||||
benchmark_profile() {
|
||||
if ! command -v hyperfine &>/dev/null; then
|
||||
df_print_warning "hyperfine not installed"
|
||||
df_print_info "Install: sudo pacman -S hyperfine"
|
||||
df_print_info "Falling back to quick profile..."
|
||||
echo ""
|
||||
quick_profile
|
||||
return
|
||||
fi
|
||||
|
||||
df_print_section "Benchmark (hyperfine)"
|
||||
echo ""
|
||||
|
||||
hyperfine --warmup 3 --min-runs 10 \
|
||||
--export-markdown /tmp/zsh-bench.md \
|
||||
'zsh -i -c exit' \
|
||||
2>&1
|
||||
|
||||
echo ""
|
||||
df_print_success "Results saved to /tmp/zsh-bench.md"
|
||||
}
|
||||
|
||||
# Compare with minimal shell
|
||||
compare_profile() {
|
||||
df_print_section "Comparison: Full vs Minimal Shell"
|
||||
echo ""
|
||||
|
||||
df_print_step "Full shell (with dotfiles):"
|
||||
local full_start=$(date +%s%3N)
|
||||
zsh -i -c 'exit' 2>/dev/null
|
||||
local full_end=$(date +%s%3N)
|
||||
local full_time=$((full_end - full_start))
|
||||
df_print_indent "${full_time}ms"
|
||||
|
||||
df_print_step "Minimal shell (no rc files):"
|
||||
local min_start=$(date +%s%3N)
|
||||
zsh --no-rcs -i -c 'exit' 2>/dev/null
|
||||
local min_end=$(date +%s%3N)
|
||||
local min_time=$((min_end - min_start))
|
||||
df_print_indent "${min_time}ms"
|
||||
|
||||
local overhead=$((full_time - min_time))
|
||||
local overhead_pct=$((overhead * 100 / (min_time + 1)))
|
||||
|
||||
echo ""
|
||||
df_print_section "Analysis"
|
||||
df_print_indent "Shell baseline: ${min_time}ms"
|
||||
df_print_indent "Dotfiles overhead: ${overhead}ms (+${overhead_pct}%)"
|
||||
|
||||
if (( overhead > VERY_SLOW_THRESHOLD_MS )); then
|
||||
echo ""
|
||||
df_print_warning "High overhead detected. Consider:"
|
||||
df_print_indent "• Lazy-loading heavy plugins (nvm, kubectl, etc.)"
|
||||
df_print_indent "• Compiling zsh files: dfcompile"
|
||||
df_print_indent "• Reducing oh-my-zsh plugins"
|
||||
df_print_indent "• Using zsh-defer for non-critical loads"
|
||||
fi
|
||||
}
|
||||
|
||||
# Identify slow components
|
||||
analyze_components() {
|
||||
df_print_section "Component Analysis"
|
||||
echo ""
|
||||
|
||||
local components=(
|
||||
"oh-my-zsh:source \$ZSH/oh-my-zsh.sh"
|
||||
"autosuggestions:source */zsh-autosuggestions.zsh"
|
||||
"syntax-highlight:source */zsh-syntax-highlighting.zsh"
|
||||
"fzf:source */fzf/*.zsh"
|
||||
"nvm:source \$NVM_DIR/nvm.sh"
|
||||
"dotfiles-funcs:source */functions/*.zsh"
|
||||
)
|
||||
|
||||
for comp in "${components[@]}"; do
|
||||
local name="${comp%%:*}"
|
||||
local pattern="${comp#*:}"
|
||||
|
||||
# Time loading this component
|
||||
local start=$(date +%s%3N)
|
||||
zsh -i -c "
|
||||
# Disable the component by commenting it out temporarily
|
||||
# This is a simplified check
|
||||
" 2>/dev/null
|
||||
local end=$(date +%s%3N)
|
||||
|
||||
printf " %-20s: checking...\n" "$name"
|
||||
done
|
||||
|
||||
echo ""
|
||||
df_print_info "For detailed component timing, use: --detailed"
|
||||
}
|
||||
|
||||
# Show optimization tips
|
||||
show_tips() {
|
||||
df_print_section "Optimization Tips"
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
1. COMPILE ZSH FILES
|
||||
Run: dfcompile
|
||||
Compiles .zsh files to .zwc bytecode for faster parsing.
|
||||
|
||||
2. LAZY-LOAD HEAVY TOOLS
|
||||
nvm, pyenv, rbenv, kubectl - only load when first used.
|
||||
Example in .zshrc:
|
||||
kubectl() { unfunction kubectl; source <(command kubectl completion zsh); kubectl "$@"; }
|
||||
|
||||
3. REDUCE OH-MY-ZSH PLUGINS
|
||||
Each plugin adds startup time. Only enable what you use.
|
||||
Heavy plugins: nvm, kubectl, docker-compose, thefuck
|
||||
|
||||
4. USE ZSH-DEFER
|
||||
Defer non-critical loading until after first prompt:
|
||||
zsh-defer source ~/.dotfiles/zsh/functions/heavy-stuff.zsh
|
||||
|
||||
5. PROFILE REGULARLY
|
||||
Run this script after changes to track impact.
|
||||
|
||||
6. CHECK FOR SLOW COMPLETIONS
|
||||
Completion initialization can be slow:
|
||||
autoload -Uz compinit
|
||||
if [[ -n ~/.zcompdump(#qN.mh+24) ]]; then
|
||||
compinit
|
||||
else
|
||||
compinit -C # Skip security check (faster)
|
||||
fi
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Help
|
||||
# ============================================================================
|
||||
|
||||
show_help() {
|
||||
cat << 'EOF'
|
||||
Dotfiles Startup Profiler
|
||||
|
||||
Usage: dotfiles-profile.sh [OPTIONS]
|
||||
|
||||
Options:
|
||||
(none) Quick profile (5 runs, average time)
|
||||
--detailed Detailed zprof function-level profiling
|
||||
--benchmark Benchmark with hyperfine (if installed)
|
||||
--compare Compare full shell vs minimal shell
|
||||
--tips Show optimization tips
|
||||
--all Run all profiling methods
|
||||
--help Show this help
|
||||
|
||||
Thresholds:
|
||||
< 200ms Excellent (green)
|
||||
200-500ms Acceptable (yellow)
|
||||
> 500ms Slow (red) - optimization recommended
|
||||
|
||||
Examples:
|
||||
dotfiles-profile.sh # Quick timing
|
||||
dotfiles-profile.sh --detailed # See which functions are slow
|
||||
dotfiles-profile.sh --compare # See dotfiles overhead
|
||||
dotfiles-profile.sh --all # Full analysis
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Main
|
||||
# ============================================================================
|
||||
|
||||
main() {
|
||||
df_print_header "dotfiles-profile"
|
||||
|
||||
case "${1:-}" in
|
||||
--detailed|-d)
|
||||
detailed_profile
|
||||
;;
|
||||
--benchmark|-b)
|
||||
benchmark_profile
|
||||
;;
|
||||
--compare|-c)
|
||||
compare_profile
|
||||
;;
|
||||
--tips|-t)
|
||||
show_tips
|
||||
;;
|
||||
--all|-a)
|
||||
quick_profile
|
||||
echo ""
|
||||
compare_profile
|
||||
echo ""
|
||||
detailed_profile
|
||||
echo ""
|
||||
show_tips
|
||||
;;
|
||||
--help|-h)
|
||||
show_help
|
||||
;;
|
||||
*)
|
||||
quick_profile
|
||||
echo ""
|
||||
df_print_info "For more analysis: $0 --all"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
480
bin/dotfiles-tour.sh
Executable file
480
bin/dotfiles-tour.sh
Executable file
@@ -0,0 +1,480 @@
|
||||
#!/usr/bin/env bash
|
||||
# ============================================================================
|
||||
# Dotfiles First-Run Experience & Tour
|
||||
# ============================================================================
|
||||
# Provides a guided introduction for new users and after updates.
|
||||
#
|
||||
# Usage:
|
||||
# dotfiles-tour.sh # Interactive tour
|
||||
# dotfiles-tour.sh --quick # Quick feature overview
|
||||
# dotfiles-tour.sh --changelog # Show recent changes
|
||||
# ============================================================================
|
||||
|
||||
set -e
|
||||
|
||||
# Source bootstrap
|
||||
source "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null || {
|
||||
DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' DF_RED=$'\033[0;31m'
|
||||
DF_CYAN=$'\033[0;36m' DF_BLUE=$'\033[0;34m' DF_MAGENTA=$'\033[0;35m'
|
||||
DF_NC=$'\033[0m' DF_DIM=$'\033[2m' DF_BOLD=$'\033[1m'
|
||||
DOTFILES_HOME="${DOTFILES_HOME:-$HOME/.dotfiles}"
|
||||
DOTFILES_VERSION="${DOTFILES_VERSION:-1.0.0}"
|
||||
df_print_header() { echo "=== $1 ==="; }
|
||||
df_print_section() { echo -e "${DF_CYAN}$1:${DF_NC}"; }
|
||||
df_print_indent() { echo " $1"; }
|
||||
df_print_success() { echo -e "${DF_GREEN}✓${DF_NC} $1"; }
|
||||
df_print_info() { echo -e "${DF_CYAN}ℹ${DF_NC} $1"; }
|
||||
}
|
||||
|
||||
DOTFILES_DIR="${DOTFILES_HOME:-$HOME/.dotfiles}"
|
||||
FIRST_RUN_FILE="$DOTFILES_DIR/.initialized"
|
||||
LAST_VERSION_FILE="$DOTFILES_DIR/.last-version"
|
||||
|
||||
# ============================================================================
|
||||
# Welcome Screen
|
||||
# ============================================================================
|
||||
|
||||
show_welcome() {
|
||||
clear
|
||||
cat << 'EOF'
|
||||
╔═══════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ █████╗ ██████╗ ██╗ ███████╗███████╗ ║
|
||||
║ ██╔══██╗██╔══██╗██║ ██╔════╝██╔════╝ ║
|
||||
║ ███████║██║ ██║██║ █████╗ █████╗ ║
|
||||
║ ██╔══██║██║ ██║██║ ██╔══╝ ██╔══╝ ║
|
||||
║ ██║ ██║██████╔╝███████╗███████╗███████╗ ║
|
||||
║ ╚═╝ ╚═╝╚═════╝ ╚══════╝╚══════╝╚══════╝ ║
|
||||
║ ║
|
||||
║ D O T F I L E S ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════════╝
|
||||
EOF
|
||||
echo ""
|
||||
echo -e " ${DF_DIM}Version: ${DOTFILES_VERSION}${DF_NC}"
|
||||
echo ""
|
||||
echo -e " Welcome to ADLee's Dotfiles!"
|
||||
echo -e " ${DF_DIM}A productive development environment for Arch/CachyOS${DF_NC}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Tour Pages
|
||||
# ============================================================================
|
||||
|
||||
tour_navigation() {
|
||||
clear
|
||||
df_print_header "Navigation & Shortcuts"
|
||||
echo ""
|
||||
|
||||
df_print_section "Directory Navigation"
|
||||
echo ""
|
||||
echo -e " ${DF_CYAN}..${DF_NC} Go up one directory"
|
||||
echo -e " ${DF_CYAN}...${DF_NC} Go up two directories"
|
||||
echo -e " ${DF_CYAN}~${DF_NC} Go to home"
|
||||
echo -e " ${DF_CYAN}c.${DF_NC} Go to dotfiles directory"
|
||||
echo ""
|
||||
|
||||
df_print_section "Bookmarks"
|
||||
echo ""
|
||||
echo -e " ${DF_CYAN}bookmark add work ~/projects/work${DF_NC}"
|
||||
echo -e " ${DF_CYAN}j work${DF_NC} Jump to bookmark"
|
||||
echo -e " ${DF_CYAN}bm list${DF_NC} List all bookmarks"
|
||||
echo ""
|
||||
|
||||
df_print_section "Command Palette (Ctrl+Space)"
|
||||
echo ""
|
||||
echo -e " Fuzzy search through:"
|
||||
echo -e " • Aliases and functions"
|
||||
echo -e " • Command history"
|
||||
echo -e " • Bookmarks"
|
||||
echo -e " • Quick actions"
|
||||
}
|
||||
|
||||
tour_dotfiles_management() {
|
||||
clear
|
||||
df_print_header "Dotfiles Management"
|
||||
echo ""
|
||||
|
||||
df_print_section "Quick Commands"
|
||||
echo ""
|
||||
echo -e " ${DF_GREEN}dfd${DF_NC} Health check (doctor)"
|
||||
echo -e " ${DF_GREEN}dffix${DF_NC} Auto-fix issues"
|
||||
echo -e " ${DF_GREEN}dfu${DF_NC} Update dotfiles"
|
||||
echo -e " ${DF_GREEN}dfs${DF_NC} Sync status"
|
||||
echo -e " ${DF_GREEN}dfpush${DF_NC} Push changes"
|
||||
echo -e " ${DF_GREEN}dfpull${DF_NC} Pull changes"
|
||||
echo ""
|
||||
|
||||
df_print_section "Quick Edit"
|
||||
echo ""
|
||||
echo -e " ${DF_CYAN}v.zshrc${DF_NC} Edit ~/.zshrc"
|
||||
echo -e " ${DF_CYAN}v.conf${DF_NC} Edit dotfiles.conf"
|
||||
echo -e " ${DF_CYAN}v.alias${DF_NC} Edit aliases"
|
||||
echo -e " ${DF_CYAN}reload${DF_NC} Reload shell config"
|
||||
echo ""
|
||||
|
||||
df_print_section "Machine-Specific Config"
|
||||
echo ""
|
||||
echo -e " ${DF_CYAN}machine-info${DF_NC} Show current machine detection"
|
||||
echo -e " ${DF_CYAN}machine-create${DF_NC} Create config for this machine"
|
||||
}
|
||||
|
||||
tour_git_helpers() {
|
||||
clear
|
||||
df_print_header "Git & Development"
|
||||
echo ""
|
||||
|
||||
df_print_section "Git Shortcuts"
|
||||
echo ""
|
||||
echo -e " ${DF_GREEN}g${DF_NC} = git"
|
||||
echo -e " ${DF_GREEN}gs${DF_NC} = git status"
|
||||
echo -e " ${DF_GREEN}ga${DF_NC} = git add"
|
||||
echo -e " ${DF_GREEN}gc${DF_NC} = git commit"
|
||||
echo -e " ${DF_GREEN}gp${DF_NC} = git push"
|
||||
echo -e " ${DF_GREEN}gl${DF_NC} = git pull"
|
||||
echo -e " ${DF_GREEN}gd${DF_NC} = git diff"
|
||||
echo -e " ${DF_GREEN}glog${DF_NC} = pretty log graph"
|
||||
echo ""
|
||||
|
||||
df_print_section "Project Templates"
|
||||
echo ""
|
||||
echo -e " ${DF_CYAN}py-new myproject${DF_NC} Basic Python"
|
||||
echo -e " ${DF_CYAN}py-flask myapp${DF_NC} Flask web app"
|
||||
echo -e " ${DF_CYAN}py-fastapi myapi${DF_NC} FastAPI REST"
|
||||
echo -e " ${DF_CYAN}py-cli mytool${DF_NC} CLI with Click"
|
||||
echo -e " ${DF_CYAN}py-data analysis${DF_NC} Data science"
|
||||
echo ""
|
||||
|
||||
df_print_section "Project Environments"
|
||||
echo ""
|
||||
echo -e " Auto-loads ${DF_CYAN}.dotfiles-local${DF_NC} when entering directories"
|
||||
echo -e " Auto-activates Python virtualenvs"
|
||||
echo -e " Auto-switches Node versions via .nvmrc"
|
||||
}
|
||||
|
||||
tour_tmux_workspaces() {
|
||||
clear
|
||||
df_print_header "Tmux Workspaces"
|
||||
echo ""
|
||||
|
||||
df_print_section "Quick Commands"
|
||||
echo ""
|
||||
echo -e " ${DF_GREEN}tw myproject${DF_NC} Create/attach workspace"
|
||||
echo -e " ${DF_GREEN}tw myproject dev${DF_NC} Create with 'dev' template"
|
||||
echo -e " ${DF_GREEN}twl${DF_NC} List workspaces"
|
||||
echo -e " ${DF_GREEN}twf${DF_NC} Fuzzy search workspaces"
|
||||
echo -e " ${DF_GREEN}tws mytemplate${DF_NC} Save current layout"
|
||||
echo ""
|
||||
|
||||
df_print_section "Built-in Templates"
|
||||
echo ""
|
||||
echo -e " ${DF_CYAN}dev${DF_NC} Editor + terminal + logs"
|
||||
echo -e " ${DF_CYAN}ops${DF_NC} 4-pane monitoring grid"
|
||||
echo -e " ${DF_CYAN}review${DF_NC} Side-by-side comparison"
|
||||
echo -e " ${DF_CYAN}debug${DF_NC} Main (70%) + helper (30%)"
|
||||
echo -e " ${DF_CYAN}ssh-multi${DF_NC} 4 panes for servers"
|
||||
echo ""
|
||||
|
||||
df_print_section "Tmuxinator (if installed)"
|
||||
echo ""
|
||||
echo -e " ${DF_CYAN}txi myproject${DF_NC} Start tmuxinator project"
|
||||
echo -e " ${DF_CYAN}txi-new myproj dev${DF_NC} Create from template"
|
||||
}
|
||||
|
||||
tour_system_tools() {
|
||||
clear
|
||||
df_print_header "System Administration"
|
||||
echo ""
|
||||
|
||||
df_print_section "Systemd Helpers"
|
||||
echo ""
|
||||
echo -e " ${DF_GREEN}sc${DF_NC} sudo systemctl"
|
||||
echo -e " ${DF_GREEN}scr${DF_NC} svc Restart + status"
|
||||
echo -e " ${DF_GREEN}sce${DF_NC} svc Enable + start"
|
||||
echo -e " ${DF_GREEN}scd${DF_NC} svc Disable + stop"
|
||||
echo -e " ${DF_GREEN}sclog${DF_NC} svc Follow logs"
|
||||
echo -e " ${DF_GREEN}scf${DF_NC} Interactive (fzf)"
|
||||
echo -e " ${DF_GREEN}sc-failed${DF_NC} Show failed services"
|
||||
echo ""
|
||||
|
||||
df_print_section "Btrfs & Snapshots (if using btrfs)"
|
||||
echo ""
|
||||
echo -e " ${DF_CYAN}btrfs-health${DF_NC} Quick filesystem check"
|
||||
echo -e " ${DF_CYAN}snap 'desc'${DF_NC} Create snapshot"
|
||||
echo -e " ${DF_CYAN}snapls${DF_NC} List snapshots"
|
||||
echo -e " ${DF_CYAN}sys-update${DF_NC} Update with pre/post snapshot"
|
||||
echo ""
|
||||
|
||||
df_print_section "SSH Manager"
|
||||
echo ""
|
||||
echo -e " ${DF_CYAN}ssh-save myserver user@host${DF_NC}"
|
||||
echo -e " ${DF_CYAN}sshc myserver${DF_NC} Connect (auto-tmux)"
|
||||
echo -e " ${DF_CYAN}sshf${DF_NC} Fuzzy search servers"
|
||||
}
|
||||
|
||||
tour_productivity() {
|
||||
clear
|
||||
df_print_header "Productivity Features"
|
||||
echo ""
|
||||
|
||||
df_print_section "Smart Suggestions"
|
||||
echo ""
|
||||
echo -e " Auto-corrects common typos: ${DF_DIM}gti → git${DF_NC}"
|
||||
echo -e " Suggests packages for missing commands"
|
||||
echo -e " Use ${DF_CYAN}fuck${DF_NC} to re-run corrected command"
|
||||
echo ""
|
||||
|
||||
df_print_section "Long-Running Command Notifications"
|
||||
echo ""
|
||||
echo -e " Desktop notifications when commands take > 60s"
|
||||
echo -e " ${DF_CYAN}notify-toggle${DF_NC} Enable/disable"
|
||||
echo -e " ${DF_CYAN}notify-status${DF_NC} Check configuration"
|
||||
echo ""
|
||||
|
||||
df_print_section "Password Manager (LastPass)"
|
||||
echo ""
|
||||
echo -e " ${DF_CYAN}pw search${DF_NC} Search and copy password"
|
||||
echo -e " ${DF_CYAN}pwf${DF_NC} Fuzzy search (fzf)"
|
||||
echo -e " ${DF_CYAN}pw gen 24${DF_NC} Generate 24-char password"
|
||||
echo ""
|
||||
|
||||
df_print_section "Secrets Vault"
|
||||
echo ""
|
||||
echo -e " ${DF_CYAN}vault init${DF_NC} Initialize encrypted vault"
|
||||
echo -e " ${DF_CYAN}vault set KEY${DF_NC} Store a secret"
|
||||
echo -e " ${DF_CYAN}vault get KEY${DF_NC} Retrieve a secret"
|
||||
}
|
||||
|
||||
tour_complete() {
|
||||
clear
|
||||
df_print_header "Tour Complete!"
|
||||
echo ""
|
||||
|
||||
df_print_success "You're ready to go!"
|
||||
echo ""
|
||||
|
||||
df_print_section "Quick Reference"
|
||||
echo ""
|
||||
echo -e " ${DF_CYAN}dfd${DF_NC} Run health check"
|
||||
echo -e " ${DF_CYAN}Ctrl+Space${DF_NC} Command palette"
|
||||
echo -e " ${DF_CYAN}dotfiles-cli help${DF_NC} Full command list"
|
||||
echo ""
|
||||
|
||||
df_print_section "Documentation"
|
||||
echo ""
|
||||
echo -e " ${DF_CYAN}~/.dotfiles/README.md${DF_NC}"
|
||||
echo -e " ${DF_CYAN}~/.dotfiles/docs/REFERENCE.md${DF_NC}"
|
||||
echo ""
|
||||
|
||||
df_print_section "Getting Help"
|
||||
echo ""
|
||||
echo -e " Most commands support ${DF_CYAN}--help${DF_NC}"
|
||||
echo -e " Check ${DF_CYAN}~/.dotfiles/INSTALL.md${DF_NC} for troubleshooting"
|
||||
echo ""
|
||||
|
||||
# Mark first run complete
|
||||
touch "$FIRST_RUN_FILE"
|
||||
echo "$DOTFILES_VERSION" > "$LAST_VERSION_FILE"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Interactive Tour
|
||||
# ============================================================================
|
||||
|
||||
run_interactive_tour() {
|
||||
local pages=(
|
||||
"show_welcome:Welcome"
|
||||
"tour_navigation:Navigation & Shortcuts"
|
||||
"tour_dotfiles_management:Dotfiles Management"
|
||||
"tour_git_helpers:Git & Development"
|
||||
"tour_tmux_workspaces:Tmux Workspaces"
|
||||
"tour_system_tools:System Administration"
|
||||
"tour_productivity:Productivity Features"
|
||||
"tour_complete:Complete"
|
||||
)
|
||||
|
||||
local current=0
|
||||
local total=${#pages[@]}
|
||||
|
||||
while true; do
|
||||
local page_info="${pages[$current]}"
|
||||
local func="${page_info%%:*}"
|
||||
local title="${page_info#*:}"
|
||||
|
||||
# Show current page
|
||||
$func
|
||||
|
||||
# Navigation footer
|
||||
echo ""
|
||||
echo -e "${DF_DIM}─────────────────────────────────────────────────────────────${DF_NC}"
|
||||
echo -e " Page $((current + 1)) of $total: ${DF_CYAN}$title${DF_NC}"
|
||||
echo ""
|
||||
|
||||
if (( current == total - 1 )); then
|
||||
echo -e " Press ${DF_GREEN}Enter${DF_NC} to finish, ${DF_CYAN}p${DF_NC} for previous, ${DF_RED}q${DF_NC} to quit"
|
||||
else
|
||||
echo -e " Press ${DF_GREEN}Enter${DF_NC} for next, ${DF_CYAN}p${DF_NC} for previous, ${DF_RED}q${DF_NC} to quit"
|
||||
fi
|
||||
|
||||
read -rsn1 key
|
||||
|
||||
case "$key" in
|
||||
q|Q)
|
||||
echo ""
|
||||
echo "Tour cancelled. Run 'dotfiles-tour.sh' anytime to continue."
|
||||
exit 0
|
||||
;;
|
||||
p|P)
|
||||
(( current > 0 )) && ((current--))
|
||||
;;
|
||||
*)
|
||||
if (( current == total - 1 )); then
|
||||
echo ""
|
||||
echo -e "${DF_GREEN}Enjoy your new shell!${DF_NC}"
|
||||
exit 0
|
||||
fi
|
||||
((current++))
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Quick Overview
|
||||
# ============================================================================
|
||||
|
||||
show_quick_overview() {
|
||||
df_print_header "Quick Feature Overview"
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
╭──────────────────────────────────────────────────────────────╮
|
||||
│ NAVIGATION │
|
||||
│ Ctrl+Space Command palette j <bookmark> Jump │
|
||||
│ .. Up directory c. Dotfiles dir │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ DOTFILES │
|
||||
│ dfd Health check dfu Update │
|
||||
│ dfpush Push changes reload Reload shell │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ GIT │
|
||||
│ gs Status glog Pretty log │
|
||||
│ ga/gc/gp Add/commit/push gd Diff │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ TMUX │
|
||||
│ tw <name> Create workspace twl List │
|
||||
│ twf Fuzzy search tws Save layout │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ SYSTEM │
|
||||
│ sc systemctl scf Interactive │
|
||||
│ scr <svc> Restart service sc-failed Show failed │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ PYTHON │
|
||||
│ py-new Basic project py-flask Flask app │
|
||||
│ py-fastapi REST API venv Activate env │
|
||||
╰──────────────────────────────────────────────────────────────╯
|
||||
EOF
|
||||
|
||||
echo ""
|
||||
df_print_info "Run 'dotfiles-tour.sh' for full interactive tour"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Changelog
|
||||
# ============================================================================
|
||||
|
||||
show_changelog() {
|
||||
df_print_header "Recent Changes"
|
||||
echo ""
|
||||
|
||||
cd "$DOTFILES_DIR"
|
||||
|
||||
local last_version=""
|
||||
[[ -f "$LAST_VERSION_FILE" ]] && last_version=$(cat "$LAST_VERSION_FILE")
|
||||
|
||||
if [[ -n "$last_version" && "$last_version" != "$DOTFILES_VERSION" ]]; then
|
||||
echo -e "Updated from ${DF_YELLOW}$last_version${DF_NC} to ${DF_GREEN}$DOTFILES_VERSION${DF_NC}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
df_print_section "Recent Commits"
|
||||
echo ""
|
||||
|
||||
if [[ -d .git ]]; then
|
||||
git log --oneline -15 2>/dev/null | while read -r line; do
|
||||
echo -e " ${DF_DIM}•${DF_NC} $line"
|
||||
done
|
||||
else
|
||||
echo " (git history not available)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Update version tracking
|
||||
echo "$DOTFILES_VERSION" > "$LAST_VERSION_FILE"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# First Run Check
|
||||
# ============================================================================
|
||||
|
||||
check_first_run() {
|
||||
if [[ ! -f "$FIRST_RUN_FILE" ]]; then
|
||||
echo ""
|
||||
echo -e "${DF_CYAN}Welcome!${DF_NC} This appears to be your first time using these dotfiles."
|
||||
echo -e "Run ${DF_GREEN}dotfiles-tour.sh${DF_NC} for a quick introduction."
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Help
|
||||
# ============================================================================
|
||||
|
||||
show_help() {
|
||||
cat << 'EOF'
|
||||
Dotfiles Tour & First-Run Experience
|
||||
|
||||
Usage: dotfiles-tour.sh [OPTIONS]
|
||||
|
||||
Options:
|
||||
(none) Interactive tour
|
||||
--quick, -q Quick feature overview
|
||||
--changelog Show recent changes
|
||||
--check Check if first run (for .zshrc)
|
||||
--help Show this help
|
||||
|
||||
The tour introduces all major features of the dotfiles system.
|
||||
Run it anytime to refresh your memory!
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Main
|
||||
# ============================================================================
|
||||
|
||||
main() {
|
||||
case "${1:-}" in
|
||||
--quick|-q)
|
||||
df_print_header "dotfiles-tour"
|
||||
show_quick_overview
|
||||
;;
|
||||
--changelog|-c)
|
||||
df_print_header "dotfiles-tour"
|
||||
show_changelog
|
||||
;;
|
||||
--check)
|
||||
check_first_run
|
||||
;;
|
||||
--help|-h)
|
||||
show_help
|
||||
;;
|
||||
*)
|
||||
run_interactive_tour
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
58
machines/default.zsh
Normal file
58
machines/default.zsh
Normal file
@@ -0,0 +1,58 @@
|
||||
# ============================================================================
|
||||
# Default Machine Configuration
|
||||
# ============================================================================
|
||||
# This file is loaded on ALL machines before hostname-specific configs.
|
||||
# Use it for settings that should be shared across all your machines.
|
||||
#
|
||||
# Load order:
|
||||
# 1. dotfiles.conf (base config)
|
||||
# 2. machines/default.zsh (this file)
|
||||
# 3. machines/type-<type>.zsh (laptop, desktop, server, virtual)
|
||||
# 4. machines/<hostname>.zsh (machine-specific)
|
||||
# 5. ~/.zshrc.local (local overrides, not synced)
|
||||
# ============================================================================
|
||||
|
||||
# ============================================================================
|
||||
# Shared Settings
|
||||
# ============================================================================
|
||||
|
||||
# Uncomment and modify settings you want on all machines
|
||||
|
||||
# --- Display ---
|
||||
# DF_WIDTH="74"
|
||||
|
||||
# --- Features ---
|
||||
# ENABLE_SMART_SUGGESTIONS="true"
|
||||
# ENABLE_COMMAND_PALETTE="true"
|
||||
|
||||
# --- Notification Settings ---
|
||||
# DF_NOTIFY_ENABLED="true"
|
||||
# DF_NOTIFY_THRESHOLD="60"
|
||||
|
||||
# --- Project Environment ---
|
||||
# DF_PROJECT_ENV_ENABLED="true"
|
||||
# DF_PROJECT_AUTO_VENV="true"
|
||||
|
||||
# ============================================================================
|
||||
# Shared Aliases
|
||||
# ============================================================================
|
||||
|
||||
# Add aliases that should exist on all machines
|
||||
# alias mycompany='cd ~/work/mycompany'
|
||||
|
||||
# ============================================================================
|
||||
# Shared Environment
|
||||
# ============================================================================
|
||||
|
||||
# Environment variables for all machines
|
||||
# export EDITOR="nvim"
|
||||
# export BROWSER="firefox"
|
||||
|
||||
# ============================================================================
|
||||
# Shared Functions
|
||||
# ============================================================================
|
||||
|
||||
# Functions available on all machines
|
||||
# myfunction() {
|
||||
# echo "This works everywhere"
|
||||
# }
|
||||
31
machines/type-laptop.zsh
Normal file
31
machines/type-laptop.zsh
Normal file
@@ -0,0 +1,31 @@
|
||||
# ============================================================================
|
||||
# Laptop Machine Type Configuration
|
||||
# ============================================================================
|
||||
# Loaded on machines detected as laptops (has battery).
|
||||
# ============================================================================
|
||||
|
||||
# --- Power-aware settings ---
|
||||
# Reduce resource usage on battery
|
||||
|
||||
# Shorter MOTD on laptops (faster)
|
||||
# MOTD_STYLE="mini"
|
||||
|
||||
# --- Battery monitoring alias ---
|
||||
alias battery='cat /sys/class/power_supply/BAT0/capacity 2>/dev/null && echo "%" || echo "No battery"'
|
||||
alias power='cat /sys/class/power_supply/BAT0/status 2>/dev/null || echo "Unknown"'
|
||||
|
||||
# --- Brightness control (if available) ---
|
||||
if command -v brightnessctl &>/dev/null; then
|
||||
alias bright='brightnessctl set'
|
||||
alias brightness='brightnessctl get'
|
||||
fi
|
||||
|
||||
# --- WiFi helpers ---
|
||||
if command -v nmcli &>/dev/null; then
|
||||
alias wifi='nmcli device wifi list'
|
||||
alias wifi-connect='nmcli device wifi connect'
|
||||
fi
|
||||
|
||||
# --- Suspend/hibernate helpers ---
|
||||
alias suspend='systemctl suspend'
|
||||
alias hibernate='systemctl hibernate'
|
||||
36
machines/type-server.zsh
Normal file
36
machines/type-server.zsh
Normal file
@@ -0,0 +1,36 @@
|
||||
# ============================================================================
|
||||
# Server Machine Type Configuration
|
||||
# ============================================================================
|
||||
# Loaded on machines detected as servers.
|
||||
# ============================================================================
|
||||
|
||||
# --- Minimal MOTD (servers don't need fancy displays) ---
|
||||
MOTD_STYLE="mini"
|
||||
|
||||
# --- Disable notifications (no desktop on servers) ---
|
||||
DF_NOTIFY_ENABLED="false"
|
||||
|
||||
# --- Server monitoring aliases ---
|
||||
alias ports='ss -tulpn'
|
||||
alias listening='ss -tulpn | grep LISTEN'
|
||||
alias connections='ss -tan | grep ESTAB | wc -l'
|
||||
|
||||
# --- Log watching ---
|
||||
alias syslog='sudo tail -f /var/log/syslog 2>/dev/null || sudo journalctl -f'
|
||||
alias authlog='sudo tail -f /var/log/auth.log 2>/dev/null || sudo journalctl -f -u sshd'
|
||||
|
||||
# --- Docker shortcuts (servers often run containers) ---
|
||||
if command -v docker &>/dev/null; then
|
||||
alias dstats='docker stats --no-stream'
|
||||
alias dclean='docker system prune -af'
|
||||
alias dlogs='docker logs -f'
|
||||
fi
|
||||
|
||||
# --- Quick system checks ---
|
||||
alias diskspace='df -h | grep -v tmpfs | grep -v loop'
|
||||
alias meminfo='free -h'
|
||||
alias cpuinfo='lscpu | grep -E "Model name|Socket|Core|Thread"'
|
||||
|
||||
# --- Security ---
|
||||
alias failed-logins='sudo journalctl -u sshd | grep -i "failed\|invalid"'
|
||||
alias active-users='who'
|
||||
241
tests/run-tests.zsh
Executable file
241
tests/run-tests.zsh
Executable file
@@ -0,0 +1,241 @@
|
||||
#!/usr/bin/env zsh
|
||||
# ============================================================================
|
||||
# Dotfiles Test Framework
|
||||
# ============================================================================
|
||||
# Simple unit testing for shell functions and scripts.
|
||||
#
|
||||
# Usage:
|
||||
# ./tests/run-tests.zsh # Run all tests
|
||||
# ./tests/run-tests.zsh test_utils # Run specific test file
|
||||
# ============================================================================
|
||||
|
||||
set -e
|
||||
|
||||
# ============================================================================
|
||||
# Configuration
|
||||
# ============================================================================
|
||||
|
||||
SCRIPT_DIR="${0:A:h}"
|
||||
DOTFILES_DIR="${SCRIPT_DIR:h}"
|
||||
TESTS_DIR="$SCRIPT_DIR"
|
||||
|
||||
# Colors
|
||||
RED=$'\033[0;31m'
|
||||
GREEN=$'\033[0;32m'
|
||||
YELLOW=$'\033[1;33m'
|
||||
CYAN=$'\033[0;36m'
|
||||
NC=$'\033[0m'
|
||||
|
||||
# Counters
|
||||
TESTS_RUN=0
|
||||
TESTS_PASSED=0
|
||||
TESTS_FAILED=0
|
||||
CURRENT_TEST=""
|
||||
|
||||
# ============================================================================
|
||||
# Test Framework Functions
|
||||
# ============================================================================
|
||||
|
||||
# Start a test group
|
||||
describe() {
|
||||
echo ""
|
||||
echo -e "${CYAN}▶ $1${NC}"
|
||||
}
|
||||
|
||||
# Run a single test
|
||||
it() {
|
||||
CURRENT_TEST="$1"
|
||||
((TESTS_RUN++))
|
||||
}
|
||||
|
||||
# Assert equality
|
||||
assert_eq() {
|
||||
local actual="$1"
|
||||
local expected="$2"
|
||||
local message="${3:-Values should be equal}"
|
||||
|
||||
if [[ "$actual" == "$expected" ]]; then
|
||||
echo -e " ${GREEN}✓${NC} $CURRENT_TEST"
|
||||
((TESTS_PASSED++))
|
||||
else
|
||||
echo -e " ${RED}✗${NC} $CURRENT_TEST"
|
||||
echo -e " ${RED}Expected:${NC} $expected"
|
||||
echo -e " ${RED}Actual:${NC} $actual"
|
||||
((TESTS_FAILED++))
|
||||
fi
|
||||
}
|
||||
|
||||
# Assert not equal
|
||||
assert_ne() {
|
||||
local actual="$1"
|
||||
local not_expected="$2"
|
||||
|
||||
if [[ "$actual" != "$not_expected" ]]; then
|
||||
echo -e " ${GREEN}✓${NC} $CURRENT_TEST"
|
||||
((TESTS_PASSED++))
|
||||
else
|
||||
echo -e " ${RED}✗${NC} $CURRENT_TEST"
|
||||
echo -e " ${RED}Should not equal:${NC} $not_expected"
|
||||
((TESTS_FAILED++))
|
||||
fi
|
||||
}
|
||||
|
||||
# Assert command succeeds
|
||||
assert_success() {
|
||||
local cmd="$1"
|
||||
|
||||
if eval "$cmd" &>/dev/null; then
|
||||
echo -e " ${GREEN}✓${NC} $CURRENT_TEST"
|
||||
((TESTS_PASSED++))
|
||||
else
|
||||
echo -e " ${RED}✗${NC} $CURRENT_TEST"
|
||||
echo -e " ${RED}Command failed:${NC} $cmd"
|
||||
((TESTS_FAILED++))
|
||||
fi
|
||||
}
|
||||
|
||||
# Assert command fails
|
||||
assert_fail() {
|
||||
local cmd="$1"
|
||||
|
||||
if ! eval "$cmd" &>/dev/null; then
|
||||
echo -e " ${GREEN}✓${NC} $CURRENT_TEST"
|
||||
((TESTS_PASSED++))
|
||||
else
|
||||
echo -e " ${RED}✗${NC} $CURRENT_TEST"
|
||||
echo -e " ${RED}Expected failure but succeeded:${NC} $cmd"
|
||||
((TESTS_FAILED++))
|
||||
fi
|
||||
}
|
||||
|
||||
# Assert string contains
|
||||
assert_contains() {
|
||||
local haystack="$1"
|
||||
local needle="$2"
|
||||
|
||||
if [[ "$haystack" == *"$needle"* ]]; then
|
||||
echo -e " ${GREEN}✓${NC} $CURRENT_TEST"
|
||||
((TESTS_PASSED++))
|
||||
else
|
||||
echo -e " ${RED}✗${NC} $CURRENT_TEST"
|
||||
echo -e " ${RED}String should contain:${NC} $needle"
|
||||
((TESTS_FAILED++))
|
||||
fi
|
||||
}
|
||||
|
||||
# Assert file exists
|
||||
assert_file_exists() {
|
||||
local file="$1"
|
||||
|
||||
if [[ -f "$file" ]]; then
|
||||
echo -e " ${GREEN}✓${NC} $CURRENT_TEST"
|
||||
((TESTS_PASSED++))
|
||||
else
|
||||
echo -e " ${RED}✗${NC} $CURRENT_TEST"
|
||||
echo -e " ${RED}File not found:${NC} $file"
|
||||
((TESTS_FAILED++))
|
||||
fi
|
||||
}
|
||||
|
||||
# Assert directory exists
|
||||
assert_dir_exists() {
|
||||
local dir="$1"
|
||||
|
||||
if [[ -d "$dir" ]]; then
|
||||
echo -e " ${GREEN}✓${NC} $CURRENT_TEST"
|
||||
((TESTS_PASSED++))
|
||||
else
|
||||
echo -e " ${RED}✗${NC} $CURRENT_TEST"
|
||||
echo -e " ${RED}Directory not found:${NC} $dir"
|
||||
((TESTS_FAILED++))
|
||||
fi
|
||||
}
|
||||
|
||||
# Assert command exists
|
||||
assert_cmd_exists() {
|
||||
local cmd="$1"
|
||||
|
||||
if command -v "$cmd" &>/dev/null; then
|
||||
echo -e " ${GREEN}✓${NC} $CURRENT_TEST"
|
||||
((TESTS_PASSED++))
|
||||
else
|
||||
echo -e " ${RED}✗${NC} $CURRENT_TEST"
|
||||
echo -e " ${RED}Command not found:${NC} $cmd"
|
||||
((TESTS_FAILED++))
|
||||
fi
|
||||
}
|
||||
|
||||
# Skip a test
|
||||
skip() {
|
||||
local reason="${1:-No reason given}"
|
||||
echo -e " ${YELLOW}○${NC} $CURRENT_TEST (SKIPPED: $reason)"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Test Runner
|
||||
# ============================================================================
|
||||
|
||||
run_test_file() {
|
||||
local test_file="$1"
|
||||
source "$test_file"
|
||||
}
|
||||
|
||||
print_summary() {
|
||||
echo ""
|
||||
echo "─────────────────────────────────────────"
|
||||
echo -e "Tests: ${CYAN}$TESTS_RUN${NC}"
|
||||
echo -e "Passed: ${GREEN}$TESTS_PASSED${NC}"
|
||||
echo -e "Failed: ${RED}$TESTS_FAILED${NC}"
|
||||
echo "─────────────────────────────────────────"
|
||||
|
||||
if (( TESTS_FAILED > 0 )); then
|
||||
echo -e "${RED}FAILED${NC}"
|
||||
return 1
|
||||
else
|
||||
echo -e "${GREEN}PASSED${NC}"
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Main
|
||||
# ============================================================================
|
||||
|
||||
main() {
|
||||
echo "╔═══════════════════════════════════════╗"
|
||||
echo "║ Dotfiles Test Suite ║"
|
||||
echo "╚═══════════════════════════════════════╝"
|
||||
|
||||
# Source the libraries we're testing
|
||||
source "$DOTFILES_DIR/zsh/lib/bootstrap.zsh" 2>/dev/null || true
|
||||
|
||||
local specific_test="$1"
|
||||
|
||||
if [[ -n "$specific_test" ]]; then
|
||||
# Run specific test file
|
||||
if [[ -f "$TESTS_DIR/${specific_test}.zsh" ]]; then
|
||||
run_test_file "$TESTS_DIR/${specific_test}.zsh"
|
||||
elif [[ -f "$TESTS_DIR/test_${specific_test}.zsh" ]]; then
|
||||
run_test_file "$TESTS_DIR/test_${specific_test}.zsh"
|
||||
else
|
||||
echo "Test file not found: $specific_test"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
# Run all test files
|
||||
for test_file in "$TESTS_DIR"/test_*.zsh(N); do
|
||||
[[ -f "$test_file" ]] || continue
|
||||
echo ""
|
||||
echo -e "${YELLOW}Running: $(basename "$test_file")${NC}"
|
||||
run_test_file "$test_file"
|
||||
done
|
||||
fi
|
||||
|
||||
print_summary
|
||||
}
|
||||
|
||||
# Export functions for test files
|
||||
export -f describe it assert_eq assert_ne assert_success assert_fail
|
||||
export -f assert_contains assert_file_exists assert_dir_exists assert_cmd_exists skip
|
||||
|
||||
main "$@"
|
||||
94
tests/test_config.zsh
Executable file
94
tests/test_config.zsh
Executable file
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env zsh
|
||||
# ============================================================================
|
||||
# Tests for zsh/lib/config.zsh
|
||||
# ============================================================================
|
||||
|
||||
# Source config if not already loaded
|
||||
source "${DOTFILES_DIR:-$HOME/.dotfiles}/zsh/lib/config.zsh" 2>/dev/null
|
||||
|
||||
# ============================================================================
|
||||
# Tests
|
||||
# ============================================================================
|
||||
|
||||
describe "Core configuration variables"
|
||||
|
||||
it "should have DOTFILES_VERSION defined"
|
||||
assert_ne "${DOTFILES_VERSION:-}" ""
|
||||
|
||||
it "should have DOTFILES_DIR defined"
|
||||
assert_ne "${DOTFILES_DIR:-}" ""
|
||||
|
||||
it "should have DOTFILES_BRANCH defined"
|
||||
assert_ne "${DOTFILES_BRANCH:-}" ""
|
||||
|
||||
# ============================================================================
|
||||
|
||||
describe "Display configuration"
|
||||
|
||||
it "should have DF_WIDTH defined"
|
||||
assert_ne "${DF_WIDTH:-}" ""
|
||||
|
||||
it "should have DF_WIDTH as a number"
|
||||
[[ "${DF_WIDTH:-66}" =~ ^[0-9]+$ ]] && assert_success "true" || assert_fail "DF_WIDTH not a number"
|
||||
|
||||
it "should have MOTD_STYLE defined"
|
||||
assert_ne "${MOTD_STYLE:-}" ""
|
||||
|
||||
it "should have ENABLE_MOTD defined"
|
||||
assert_ne "${ENABLE_MOTD:-}" ""
|
||||
|
||||
# ============================================================================
|
||||
|
||||
describe "Theme configuration"
|
||||
|
||||
it "should have ZSH_THEME_NAME defined"
|
||||
assert_ne "${ZSH_THEME_NAME:-}" ""
|
||||
|
||||
it "should have THEME_TIMER_THRESHOLD defined"
|
||||
assert_ne "${THEME_TIMER_THRESHOLD:-}" ""
|
||||
|
||||
# ============================================================================
|
||||
|
||||
describe "Feature toggles"
|
||||
|
||||
it "should have ENABLE_SMART_SUGGESTIONS defined"
|
||||
assert_ne "${ENABLE_SMART_SUGGESTIONS:-}" ""
|
||||
|
||||
it "should have ENABLE_COMMAND_PALETTE defined"
|
||||
assert_ne "${ENABLE_COMMAND_PALETTE:-}" ""
|
||||
|
||||
it "should have ENABLE_VAULT defined"
|
||||
assert_ne "${ENABLE_VAULT:-}" ""
|
||||
|
||||
# ============================================================================
|
||||
|
||||
describe "Path configuration"
|
||||
|
||||
it "should have valid DOTFILES_DIR path"
|
||||
assert_dir_exists "${DOTFILES_DIR:-$HOME/.dotfiles}"
|
||||
|
||||
it "should have dotfiles.conf in DOTFILES_DIR"
|
||||
if [[ -d "${DOTFILES_DIR:-$HOME/.dotfiles}" ]]; then
|
||||
assert_file_exists "${DOTFILES_DIR:-$HOME/.dotfiles}/dotfiles.conf"
|
||||
else
|
||||
skip "DOTFILES_DIR not found"
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
|
||||
describe "df_config helper function"
|
||||
|
||||
it "should have df_config function defined"
|
||||
if typeset -f df_config &>/dev/null; then
|
||||
assert_success "true"
|
||||
else
|
||||
skip "df_config not defined in this version"
|
||||
fi
|
||||
|
||||
it "should return default for undefined variable"
|
||||
if typeset -f df_config &>/dev/null; then
|
||||
local result=$(df_config "UNDEFINED_VAR_12345" "default_value")
|
||||
assert_eq "$result" "default_value"
|
||||
else
|
||||
skip "df_config not defined"
|
||||
fi
|
||||
111
tests/test_utils.zsh
Executable file
111
tests/test_utils.zsh
Executable file
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env zsh
|
||||
# ============================================================================
|
||||
# Tests for zsh/lib/utils.zsh
|
||||
# ============================================================================
|
||||
|
||||
# Source utils if not already loaded
|
||||
source "${DOTFILES_DIR:-$HOME/.dotfiles}/zsh/lib/utils.zsh" 2>/dev/null
|
||||
|
||||
# ============================================================================
|
||||
# Tests
|
||||
# ============================================================================
|
||||
|
||||
describe "df_cmd_exists"
|
||||
|
||||
it "should return true for existing command (ls)"
|
||||
assert_success "df_cmd_exists ls"
|
||||
|
||||
it "should return false for non-existent command"
|
||||
assert_fail "df_cmd_exists this_command_does_not_exist_12345"
|
||||
|
||||
it "should work with common tools"
|
||||
assert_success "df_cmd_exists git"
|
||||
|
||||
# ============================================================================
|
||||
|
||||
describe "df_print functions"
|
||||
|
||||
it "should have df_print_success defined"
|
||||
assert_success "typeset -f df_print_success"
|
||||
|
||||
it "should have df_print_error defined"
|
||||
assert_success "typeset -f df_print_error"
|
||||
|
||||
it "should have df_print_warning defined"
|
||||
assert_success "typeset -f df_print_warning"
|
||||
|
||||
it "should have df_print_step defined"
|
||||
assert_success "typeset -f df_print_step"
|
||||
|
||||
# ============================================================================
|
||||
|
||||
describe "df_in_git_repo"
|
||||
|
||||
it "should detect git repository in dotfiles dir"
|
||||
(
|
||||
cd "${DOTFILES_DIR:-$HOME/.dotfiles}"
|
||||
if [[ -d .git ]]; then
|
||||
assert_success "df_in_git_repo"
|
||||
else
|
||||
skip "Not a git repo"
|
||||
fi
|
||||
)
|
||||
|
||||
it "should return false in /tmp"
|
||||
(
|
||||
cd /tmp
|
||||
assert_fail "df_in_git_repo"
|
||||
)
|
||||
|
||||
# ============================================================================
|
||||
|
||||
describe "df_ensure_dir"
|
||||
|
||||
it "should create directory if it doesn't exist"
|
||||
local test_dir="/tmp/dotfiles_test_$$"
|
||||
df_ensure_dir "$test_dir"
|
||||
assert_dir_exists "$test_dir"
|
||||
rmdir "$test_dir" 2>/dev/null
|
||||
|
||||
it "should not fail if directory exists"
|
||||
df_ensure_dir "/tmp"
|
||||
assert_success "true"
|
||||
|
||||
# ============================================================================
|
||||
|
||||
describe "_df_hline"
|
||||
|
||||
it "should create a line of specified width"
|
||||
local line=$(_df_hline "=" 10)
|
||||
assert_eq "${#line}" "10"
|
||||
|
||||
it "should use specified character"
|
||||
local line=$(_df_hline "-" 5)
|
||||
assert_eq "$line" "-----"
|
||||
|
||||
# ============================================================================
|
||||
|
||||
describe "Color variables"
|
||||
|
||||
it "should have DF_GREEN defined"
|
||||
assert_ne "$DF_GREEN" ""
|
||||
|
||||
it "should have DF_RED defined"
|
||||
assert_ne "$DF_RED" ""
|
||||
|
||||
it "should have DF_NC (reset) defined"
|
||||
assert_ne "$DF_NC" ""
|
||||
|
||||
it "should have DF_CYAN defined"
|
||||
assert_ne "$DF_CYAN" ""
|
||||
|
||||
# ============================================================================
|
||||
|
||||
describe "Configuration variables"
|
||||
|
||||
it "should have DOTFILES_DIR defined"
|
||||
assert_ne "${DOTFILES_DIR:-}" ""
|
||||
|
||||
it "should have DF_WIDTH defined with reasonable value"
|
||||
local width="${DF_WIDTH:-66}"
|
||||
(( width >= 40 && width <= 120 )) && assert_success "true" || assert_fail "DF_WIDTH out of range"
|
||||
206
zsh/aliases-extended.zsh
Normal file
206
zsh/aliases-extended.zsh
Normal file
@@ -0,0 +1,206 @@
|
||||
# ============================================================================
|
||||
# Dotfiles Command Aliases - Extended Version
|
||||
# ============================================================================
|
||||
# Includes all original aliases plus new improvement aliases.
|
||||
# ============================================================================
|
||||
|
||||
# Dotfiles directory
|
||||
_df_dir="${DOTFILES_DIR:-$HOME/.dotfiles}"
|
||||
_df_bin="$_df_dir/bin"
|
||||
|
||||
# Helper to run dotfiles scripts
|
||||
_df_run() {
|
||||
local script="$1"
|
||||
shift
|
||||
if [[ -x "$_df_bin/$script" ]]; then
|
||||
"$_df_bin/$script" "$@"
|
||||
elif command -v "$script" &>/dev/null; then
|
||||
"$script" "$@"
|
||||
else
|
||||
echo "Error: $script not found" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Quality of Life Aliases
|
||||
# ============================================================================
|
||||
|
||||
alias hist="history"
|
||||
alias cls="clear"
|
||||
alias q="exit"
|
||||
|
||||
# ============================================================================
|
||||
# Core Dotfiles Commands
|
||||
# ============================================================================
|
||||
|
||||
alias dfdir='cd $HOME/.dotfiles'
|
||||
alias c.='cd $HOME/.dotfiles'
|
||||
|
||||
# Doctor - health check
|
||||
dfd() { _df_run dotfiles-doctor.sh "$@"; }
|
||||
doctor() { _df_run dotfiles-doctor.sh "$@"; }
|
||||
dffix() { _df_run dotfiles-doctor.sh --fix "$@"; }
|
||||
|
||||
# Sync
|
||||
dfs() { _df_run dotfiles-sync.sh "$@"; }
|
||||
dfpush() { _df_run dotfiles-sync.sh push "${1:-Dotfiles update $(date '+%Y-%m-%d %H:%M')}"; }
|
||||
dfpull() { _df_run dotfiles-sync.sh pull "$@"; }
|
||||
dfstatus() { _df_run dotfiles-sync.sh status "$@"; }
|
||||
|
||||
# Update
|
||||
dfu() { _df_run dotfiles-update.sh "$@"; }
|
||||
dfupdate() { _df_run dotfiles-update.sh "$@"; }
|
||||
|
||||
# Version
|
||||
dfv() { _df_run dotfiles-version.sh "$@"; }
|
||||
dfversion() { _df_run dotfiles-version.sh "$@"; }
|
||||
|
||||
# Stats / Analytics
|
||||
dfstats() { _df_run dotfiles-stats.sh "$@"; }
|
||||
dfanalytics() { _df_run dotfiles-analytics.sh "$@"; }
|
||||
|
||||
# Vault
|
||||
vault() { _df_run dotfiles-vault.sh "$@"; }
|
||||
vls() { _df_run dotfiles-vault.sh list "$@"; }
|
||||
vget() { _df_run dotfiles-vault.sh get "$@"; }
|
||||
vset() { _df_run dotfiles-vault.sh set "$@"; }
|
||||
|
||||
# Compile
|
||||
dfcompile() { _df_run dotfiles-compile.sh "$@"; }
|
||||
|
||||
# ============================================================================
|
||||
# NEW: Profile & Performance
|
||||
# ============================================================================
|
||||
|
||||
dfprofile() { _df_run dotfiles-profile.sh "$@"; }
|
||||
alias profile='dfprofile'
|
||||
alias startup-time='dfprofile --quick'
|
||||
|
||||
# ============================================================================
|
||||
# NEW: Diff & Audit
|
||||
# ============================================================================
|
||||
|
||||
dfdiff() { _df_run dotfiles-diff.sh "$@"; }
|
||||
dfaudit() { _df_run dotfiles-diff.sh --audit "$@"; }
|
||||
alias audit='dfaudit'
|
||||
|
||||
# ============================================================================
|
||||
# NEW: Tour & First-Run
|
||||
# ============================================================================
|
||||
|
||||
dftour() { _df_run dotfiles-tour.sh "$@"; }
|
||||
alias tour='dftour'
|
||||
alias quickref='dftour --quick'
|
||||
|
||||
# ============================================================================
|
||||
# Quick Edit Aliases
|
||||
# ============================================================================
|
||||
|
||||
alias v.zshrc='${EDITOR:-vim} ~/.zshrc'
|
||||
alias v.conf='${EDITOR:-vim} ~/.dotfiles/dotfiles.conf'
|
||||
alias v.edit='cd ~/.dotfiles && ${EDITOR:-vim} .'
|
||||
alias v.alias='${EDITOR:-vim} ~/.dotfiles/zsh/aliases.zsh'
|
||||
alias v.motd='${EDITOR:-vim} ~/.dotfiles/zsh/functions/motd.zsh'
|
||||
alias v.theme='${EDITOR:-vim} ~/.dotfiles/zsh/themes/adlee.zsh-theme'
|
||||
|
||||
# NEW: Edit machine config
|
||||
alias v.machine='${EDITOR:-vim} ~/.dotfiles/machines/${DF_HOSTNAME:-$(hostname -s)}.zsh'
|
||||
|
||||
# ============================================================================
|
||||
# Reload Aliases
|
||||
# ============================================================================
|
||||
|
||||
alias reload='source ~/.zshrc'
|
||||
alias rl='source ~/.zshrc'
|
||||
|
||||
# ============================================================================
|
||||
# Dotfiles CLI
|
||||
# ============================================================================
|
||||
|
||||
dotfiles-cli() {
|
||||
case "${1:-help}" in
|
||||
doctor|doc|d) shift; _df_run dotfiles-doctor.sh "$@" ;;
|
||||
sync|s) shift; _df_run dotfiles-sync.sh "$@" ;;
|
||||
update|up|u) shift; _df_run dotfiles-update.sh "$@" ;;
|
||||
version|ver|v) shift; _df_run dotfiles-version.sh "$@" ;;
|
||||
stats|st) shift; _df_run dotfiles-stats.sh "$@" ;;
|
||||
analytics|an) shift; _df_run dotfiles-analytics.sh "$@" ;;
|
||||
vault|vlt) shift; _df_run dotfiles-vault.sh "$@" ;;
|
||||
compile|comp) shift; _df_run dotfiles-compile.sh "$@" ;;
|
||||
profile|prof) shift; _df_run dotfiles-profile.sh "$@" ;;
|
||||
diff|df) shift; _df_run dotfiles-diff.sh "$@" ;;
|
||||
audit|aud) shift; _df_run dotfiles-diff.sh --audit "$@" ;;
|
||||
tour|t) shift; _df_run dotfiles-tour.sh "$@" ;;
|
||||
test) shift; zsh ~/.dotfiles/tests/run-tests.zsh "$@" ;;
|
||||
edit|e) cd ~/.dotfiles && ${EDITOR:-vim} . ;;
|
||||
cd) cd ~/.dotfiles ;;
|
||||
help|--help|-h|*)
|
||||
cat << 'EOF'
|
||||
Dotfiles CLI - Extended
|
||||
|
||||
Usage: dotfiles-cli <command> [args]
|
||||
|
||||
Core Commands:
|
||||
doctor, d Health check (--fix to repair)
|
||||
sync, s Sync dotfiles across machines
|
||||
update, u Pull latest and reinstall
|
||||
version, v Show version info
|
||||
stats, st Basic shell analytics
|
||||
vault, vlt Secrets management
|
||||
compile Compile zsh files for speed
|
||||
|
||||
New Commands:
|
||||
analytics, an Enhanced shell analytics
|
||||
profile, prof Startup time profiling
|
||||
diff, df Show changes and compare
|
||||
audit, aud Security audit
|
||||
tour, t Interactive tour / help
|
||||
test Run test suite
|
||||
|
||||
Navigation:
|
||||
edit, e Open dotfiles in editor
|
||||
cd Change to dotfiles directory
|
||||
|
||||
Aliases: dfd, dffix, dfs, dfpush, dfpull, dfu, dfv, dfstats, vault
|
||||
dfprofile, dfdiff, dfaudit, dftour
|
||||
|
||||
EOF
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
alias dfc='dotfiles-cli'
|
||||
|
||||
# ============================================================================
|
||||
# System Utilities
|
||||
# ============================================================================
|
||||
|
||||
# Use glow for markdown
|
||||
alias glow='glow -p'
|
||||
less() {
|
||||
if command -v glow &>/dev/null && [[ $# -eq 1 && "$1" == *.md ]]; then
|
||||
glow -p "$1"
|
||||
else
|
||||
command less "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
# Arch system upgrade with snapper
|
||||
sys-update() {
|
||||
local update_date=$(date +"%Y-%m-%d %H:%M")
|
||||
if command -v snapper &>/dev/null; then
|
||||
sudo snapper -c root create --description "System Update ${update_date}" --command "sudo pacman -Syu"
|
||||
else
|
||||
sudo pacman -Syu
|
||||
fi
|
||||
# Update package count for prompt
|
||||
command -v checkupdates &>/dev/null && export UPDATE_PKG_COUNT=$(checkupdates 2>/dev/null | wc -l)
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Testing
|
||||
# ============================================================================
|
||||
|
||||
alias dftest='zsh ~/.dotfiles/tests/run-tests.zsh'
|
||||
alias test-dotfiles='dftest'
|
||||
500
zsh/functions/fzf-extras.zsh
Normal file
500
zsh/functions/fzf-extras.zsh
Normal file
@@ -0,0 +1,500 @@
|
||||
# ============================================================================
|
||||
# FZF-Powered Utilities
|
||||
# ============================================================================
|
||||
# Additional fuzzy finders for various system exploration tasks.
|
||||
#
|
||||
# Features:
|
||||
# - envf: Environment variable browser
|
||||
# - pathf: PATH explorer
|
||||
# - procf: Process manager
|
||||
# - aliasf: Alias browser
|
||||
# - funcf: Function browser
|
||||
# - histf: Enhanced history search
|
||||
# ============================================================================
|
||||
|
||||
# Prevent double-sourcing
|
||||
[[ -n "$_DF_FZF_EXTRAS_LOADED" ]] && return 0
|
||||
typeset -g _DF_FZF_EXTRAS_LOADED=1
|
||||
|
||||
# Source utils
|
||||
source "${0:A:h}/../lib/utils.zsh" 2>/dev/null || \
|
||||
source "$HOME/.dotfiles/zsh/lib/utils.zsh" 2>/dev/null
|
||||
|
||||
# ============================================================================
|
||||
# Check FZF
|
||||
# ============================================================================
|
||||
|
||||
_fzf_check() {
|
||||
if ! command -v fzf &>/dev/null; then
|
||||
df_print_error "fzf not installed"
|
||||
df_print_info "Install: sudo pacman -S fzf"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# Common fzf options
|
||||
_fzf_common_opts() {
|
||||
echo "--height=60% --layout=reverse --border=rounded --info=inline"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Environment Variable Browser
|
||||
# ============================================================================
|
||||
|
||||
envf() {
|
||||
_fzf_check || return 1
|
||||
|
||||
local selected=$(env | sort | fzf $(_fzf_common_opts) \
|
||||
--prompt="Env > " \
|
||||
--preview='echo {} | cut -d= -f1 | xargs -I{} bash -c "echo -e \"Variable: {}\n\nValue:\n\"; printenv {}"' \
|
||||
--preview-window=right:50%:wrap \
|
||||
--header="Enter: copy value | Ctrl-E: edit | Ctrl-U: unset")
|
||||
|
||||
[[ -z "$selected" ]] && return
|
||||
|
||||
local var_name="${selected%%=*}"
|
||||
local var_value="${selected#*=}"
|
||||
|
||||
echo ""
|
||||
echo -e "${DF_CYAN}$var_name${DF_NC}=$var_value"
|
||||
echo ""
|
||||
|
||||
# Copy to clipboard if available
|
||||
if command -v wl-copy &>/dev/null; then
|
||||
echo -n "$var_value" | wl-copy
|
||||
df_print_success "Value copied to clipboard"
|
||||
elif command -v xclip &>/dev/null; then
|
||||
echo -n "$var_value" | xclip -selection clipboard
|
||||
df_print_success "Value copied to clipboard"
|
||||
fi
|
||||
}
|
||||
|
||||
# Set/edit environment variable interactively
|
||||
env-set() {
|
||||
local var_name="$1"
|
||||
|
||||
if [[ -z "$var_name" ]]; then
|
||||
_fzf_check || return 1
|
||||
var_name=$(env | cut -d= -f1 | sort | fzf $(_fzf_common_opts) \
|
||||
--prompt="Select var to edit > " \
|
||||
--header="Select existing variable or type new name")
|
||||
[[ -z "$var_name" ]] && return
|
||||
fi
|
||||
|
||||
local current_value="${(P)var_name}"
|
||||
|
||||
echo "Variable: $var_name"
|
||||
echo "Current: ${current_value:-(not set)}"
|
||||
echo ""
|
||||
read -r "new_value?New value: "
|
||||
|
||||
if [[ -n "$new_value" ]]; then
|
||||
export "$var_name"="$new_value"
|
||||
df_print_success "Set $var_name=$new_value"
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# PATH Explorer
|
||||
# ============================================================================
|
||||
|
||||
pathf() {
|
||||
_fzf_check || return 1
|
||||
|
||||
local selected=$(echo "$PATH" | tr ':' '\n' | nl -ba | \
|
||||
fzf $(_fzf_common_opts) \
|
||||
--prompt="PATH > " \
|
||||
--preview='dir=$(echo {} | awk "{print \$2}");
|
||||
if [[ -d "$dir" ]]; then
|
||||
echo "Directory: $dir"
|
||||
echo ""
|
||||
echo "Executables:"
|
||||
ls -1 "$dir" 2>/dev/null | head -30
|
||||
count=$(ls -1 "$dir" 2>/dev/null | wc -l)
|
||||
[[ $count -gt 30 ]] && echo "... and $((count-30)) more"
|
||||
else
|
||||
echo "Directory not found: $dir"
|
||||
fi' \
|
||||
--preview-window=right:50% \
|
||||
--header="PATH entries (in order)")
|
||||
|
||||
[[ -z "$selected" ]] && return
|
||||
|
||||
local dir=$(echo "$selected" | awk '{print $2}')
|
||||
|
||||
echo ""
|
||||
df_print_section "Directory: $dir"
|
||||
|
||||
if [[ -d "$dir" ]]; then
|
||||
ls -la "$dir" | head -20
|
||||
else
|
||||
df_print_warning "Directory does not exist"
|
||||
fi
|
||||
}
|
||||
|
||||
# Add to PATH interactively
|
||||
path-add() {
|
||||
local dir="${1:-$PWD}"
|
||||
|
||||
if [[ ! -d "$dir" ]]; then
|
||||
df_print_error "Not a directory: $dir"
|
||||
return 1
|
||||
fi
|
||||
|
||||
dir=$(realpath "$dir")
|
||||
|
||||
if [[ ":$PATH:" == *":$dir:"* ]]; then
|
||||
df_print_warning "Already in PATH: $dir"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "Add to PATH: $dir"
|
||||
echo ""
|
||||
echo "1) Prepend (higher priority)"
|
||||
echo "2) Append (lower priority)"
|
||||
echo "3) Cancel"
|
||||
echo ""
|
||||
|
||||
read -k1 "choice?Choice [1]: "
|
||||
echo ""
|
||||
|
||||
case "${choice:-1}" in
|
||||
1)
|
||||
export PATH="$dir:$PATH"
|
||||
df_print_success "Prepended to PATH"
|
||||
;;
|
||||
2)
|
||||
export PATH="$PATH:$dir"
|
||||
df_print_success "Appended to PATH"
|
||||
;;
|
||||
*)
|
||||
echo "Cancelled"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Process Manager
|
||||
# ============================================================================
|
||||
|
||||
procf() {
|
||||
_fzf_check || return 1
|
||||
|
||||
local selected=$(ps aux --sort=-%mem | \
|
||||
fzf $(_fzf_common_opts) \
|
||||
--prompt="Process > " \
|
||||
--header-lines=1 \
|
||||
--preview='pid=$(echo {} | awk "{print \$2}");
|
||||
echo "=== Process Details ==="
|
||||
ps -p $pid -o pid,ppid,user,%cpu,%mem,stat,start,time,cmd 2>/dev/null
|
||||
echo ""
|
||||
echo "=== Open Files (first 10) ==="
|
||||
sudo lsof -p $pid 2>/dev/null | head -10 || echo "(requires sudo)"
|
||||
echo ""
|
||||
echo "=== Environment (first 10) ==="
|
||||
sudo cat /proc/$pid/environ 2>/dev/null | tr "\0" "\n" | head -10 || echo "(requires sudo)"' \
|
||||
--preview-window=right:50%:wrap \
|
||||
--header="Process list | Enter: details | Ctrl-K: kill")
|
||||
|
||||
[[ -z "$selected" ]] && return
|
||||
|
||||
local pid=$(echo "$selected" | awk '{print $2}')
|
||||
local cmd=$(echo "$selected" | awk '{for(i=11;i<=NF;i++) printf $i" "; print ""}')
|
||||
|
||||
echo ""
|
||||
df_print_section "Selected Process"
|
||||
echo " PID: $pid"
|
||||
echo " CMD: $cmd"
|
||||
echo ""
|
||||
echo "Actions:"
|
||||
echo " 1) Show details"
|
||||
echo " 2) Send SIGTERM (graceful)"
|
||||
echo " 3) Send SIGKILL (force)"
|
||||
echo " 4) Send SIGHUP (reload)"
|
||||
echo " 5) Cancel"
|
||||
echo ""
|
||||
|
||||
read -k1 "action?Action [1]: "
|
||||
echo ""
|
||||
|
||||
case "${action:-1}" in
|
||||
1)
|
||||
ps -p "$pid" -f
|
||||
;;
|
||||
2)
|
||||
df_print_step "Sending SIGTERM to $pid..."
|
||||
kill -TERM "$pid" 2>/dev/null && df_print_success "Signal sent" || df_print_error "Failed (try sudo)"
|
||||
;;
|
||||
3)
|
||||
df_print_step "Sending SIGKILL to $pid..."
|
||||
kill -KILL "$pid" 2>/dev/null && df_print_success "Signal sent" || df_print_error "Failed (try sudo)"
|
||||
;;
|
||||
4)
|
||||
df_print_step "Sending SIGHUP to $pid..."
|
||||
kill -HUP "$pid" 2>/dev/null && df_print_success "Signal sent" || df_print_error "Failed (try sudo)"
|
||||
;;
|
||||
*)
|
||||
echo "Cancelled"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Quick kill by name
|
||||
killf() {
|
||||
_fzf_check || return 1
|
||||
|
||||
local selected=$(ps aux | grep -v "grep" | \
|
||||
fzf $(_fzf_common_opts) \
|
||||
--prompt="Kill > " \
|
||||
--header-lines=1 \
|
||||
--multi \
|
||||
--header="Select process(es) to kill (Tab to select multiple)")
|
||||
|
||||
[[ -z "$selected" ]] && return
|
||||
|
||||
echo "$selected" | while read -r line; do
|
||||
local pid=$(echo "$line" | awk '{print $2}')
|
||||
local cmd=$(echo "$line" | awk '{for(i=11;i<=NF;i++) printf $i" "; print ""}')
|
||||
|
||||
df_print_step "Killing PID $pid ($cmd)"
|
||||
kill "$pid" 2>/dev/null && df_print_success "Killed" || df_print_error "Failed"
|
||||
done
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Alias Browser
|
||||
# ============================================================================
|
||||
|
||||
aliasf() {
|
||||
_fzf_check || return 1
|
||||
|
||||
local selected=$(alias | sed "s/^alias //" | sort | \
|
||||
fzf $(_fzf_common_opts) \
|
||||
--prompt="Alias > " \
|
||||
--preview='name=$(echo {} | cut -d= -f1);
|
||||
cmd=$(echo {} | cut -d= -f2- | sed "s/^'\''//;s/'\''$//");
|
||||
echo "Alias: $name"
|
||||
echo ""
|
||||
echo "Expands to:"
|
||||
echo "$cmd"
|
||||
echo ""
|
||||
echo "Type: $(type $name 2>/dev/null || echo "alias")"' \
|
||||
--preview-window=right:50%:wrap \
|
||||
--header="Enter: insert alias | Ctrl-E: edit definition")
|
||||
|
||||
[[ -z "$selected" ]] && return
|
||||
|
||||
local alias_name="${selected%%=*}"
|
||||
print -z "$alias_name "
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Function Browser
|
||||
# ============================================================================
|
||||
|
||||
funcf() {
|
||||
_fzf_check || return 1
|
||||
|
||||
local selected=$(print -l ${(ok)functions} | grep -v "^_" | sort | \
|
||||
fzf $(_fzf_common_opts) \
|
||||
--prompt="Function > " \
|
||||
--preview='whence -f {}' \
|
||||
--preview-window=right:60%:wrap \
|
||||
--header="Shell functions | Enter: insert | Ctrl-V: view source")
|
||||
|
||||
[[ -z "$selected" ]] && return
|
||||
|
||||
print -z "$selected "
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Enhanced History Search
|
||||
# ============================================================================
|
||||
|
||||
histf() {
|
||||
_fzf_check || return 1
|
||||
|
||||
local selected=$(fc -ln 1 | tac | awk '!seen[$0]++' | \
|
||||
fzf $(_fzf_common_opts) \
|
||||
--prompt="History > " \
|
||||
--no-sort \
|
||||
--header="Command history (newest first) | Enter: execute | Ctrl-E: edit")
|
||||
|
||||
[[ -z "$selected" ]] && return
|
||||
|
||||
print -z "$selected"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# File Finder (enhanced)
|
||||
# ============================================================================
|
||||
|
||||
ff() {
|
||||
_fzf_check || return 1
|
||||
|
||||
local search_dir="${1:-.}"
|
||||
local query="${2:-}"
|
||||
|
||||
local cmd="find $search_dir -type f 2>/dev/null"
|
||||
|
||||
# Use fd if available (faster)
|
||||
if command -v fd &>/dev/null; then
|
||||
cmd="fd --type f . $search_dir"
|
||||
fi
|
||||
|
||||
local selected=$(eval "$cmd" | \
|
||||
fzf $(_fzf_common_opts) \
|
||||
--query="$query" \
|
||||
--prompt="File > " \
|
||||
--preview='
|
||||
file={}
|
||||
if file "$file" | grep -q "text"; then
|
||||
bat --style=numbers --color=always "$file" 2>/dev/null || cat "$file"
|
||||
else
|
||||
file "$file"
|
||||
echo ""
|
||||
ls -lh "$file"
|
||||
fi' \
|
||||
--preview-window=right:60% \
|
||||
--header="Files | Enter: open | Ctrl-E: edit | Ctrl-Y: copy path")
|
||||
|
||||
[[ -z "$selected" ]] && return
|
||||
|
||||
echo "$selected"
|
||||
}
|
||||
|
||||
# Open file with appropriate application
|
||||
ffo() {
|
||||
local file=$(ff "$@")
|
||||
[[ -z "$file" ]] && return
|
||||
|
||||
if [[ -f "$file" ]]; then
|
||||
if file "$file" | grep -q "text"; then
|
||||
${EDITOR:-vim} "$file"
|
||||
else
|
||||
xdg-open "$file" 2>/dev/null || open "$file" 2>/dev/null
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Directory Finder
|
||||
# ============================================================================
|
||||
|
||||
fdir() {
|
||||
_fzf_check || return 1
|
||||
|
||||
local search_dir="${1:-.}"
|
||||
|
||||
local cmd="find $search_dir -type d 2>/dev/null"
|
||||
|
||||
if command -v fd &>/dev/null; then
|
||||
cmd="fd --type d . $search_dir"
|
||||
fi
|
||||
|
||||
local selected=$(eval "$cmd" | \
|
||||
fzf $(_fzf_common_opts) \
|
||||
--prompt="Directory > " \
|
||||
--preview='ls -la {} | head -30' \
|
||||
--preview-window=right:50% \
|
||||
--header="Directories | Enter: cd")
|
||||
|
||||
[[ -z "$selected" ]] && return
|
||||
|
||||
cd "$selected"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Git Helpers
|
||||
# ============================================================================
|
||||
|
||||
# Git branch switcher
|
||||
gbf() {
|
||||
_fzf_check || return 1
|
||||
|
||||
if ! git rev-parse --git-dir &>/dev/null; then
|
||||
df_print_error "Not a git repository"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local selected=$(git branch -a --color=always | grep -v '/HEAD\s' | \
|
||||
fzf $(_fzf_common_opts) \
|
||||
--ansi \
|
||||
--prompt="Branch > " \
|
||||
--preview='git log --oneline --graph --color=always $(echo {} | sed "s/.* //" | sed "s#remotes/##") -- | head -20' \
|
||||
--header="Git branches | Enter: checkout")
|
||||
|
||||
[[ -z "$selected" ]] && return
|
||||
|
||||
local branch=$(echo "$selected" | sed "s/.* //" | sed "s#remotes/[^/]*/##")
|
||||
git checkout "$branch"
|
||||
}
|
||||
|
||||
# Git commit browser
|
||||
glogf() {
|
||||
_fzf_check || return 1
|
||||
|
||||
if ! git rev-parse --git-dir &>/dev/null; then
|
||||
df_print_error "Not a git repository"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local selected=$(git log --oneline --color=always | \
|
||||
fzf $(_fzf_common_opts) \
|
||||
--ansi \
|
||||
--prompt="Commit > " \
|
||||
--preview='git show --color=always $(echo {} | cut -d" " -f1)' \
|
||||
--preview-window=right:60% \
|
||||
--header="Git commits | Enter: show | Ctrl-D: diff")
|
||||
|
||||
[[ -z "$selected" ]] && return
|
||||
|
||||
local commit=$(echo "$selected" | cut -d" " -f1)
|
||||
git show "$commit"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Help
|
||||
# ============================================================================
|
||||
|
||||
fzf-help() {
|
||||
df_print_func_name "FZF Utilities"
|
||||
cat << 'EOF'
|
||||
|
||||
Environment:
|
||||
envf Browse environment variables
|
||||
env-set [VAR] Set/edit environment variable
|
||||
|
||||
Path:
|
||||
pathf Explore PATH directories
|
||||
path-add [DIR] Add directory to PATH
|
||||
|
||||
Process:
|
||||
procf Browse and manage processes
|
||||
killf Fuzzy kill processes
|
||||
|
||||
Shell:
|
||||
aliasf Browse aliases
|
||||
funcf Browse functions
|
||||
histf Search command history
|
||||
|
||||
Files:
|
||||
ff [DIR] Find files
|
||||
ffo [DIR] Find and open file
|
||||
fdir [DIR] Find and cd to directory
|
||||
|
||||
Git:
|
||||
gbf Branch switcher
|
||||
glogf Commit browser
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Aliases
|
||||
# ============================================================================
|
||||
|
||||
alias envbrowse='envf'
|
||||
alias pathbrowse='pathf'
|
||||
alias proc='procf'
|
||||
293
zsh/functions/notifications.zsh
Normal file
293
zsh/functions/notifications.zsh
Normal file
@@ -0,0 +1,293 @@
|
||||
# ============================================================================
|
||||
# Long-Running Command Notifications
|
||||
# ============================================================================
|
||||
# Sends notifications when long-running commands complete.
|
||||
# Integrates with the existing timer in the adlee theme.
|
||||
#
|
||||
# Features:
|
||||
# - Desktop notifications (notify-send/libnotify)
|
||||
# - Terminal bell fallback
|
||||
# - Sound notification (optional)
|
||||
# - Configurable thresholds
|
||||
# - Smart filtering (no notifications for editors, etc.)
|
||||
# ============================================================================
|
||||
|
||||
# Prevent double-sourcing
|
||||
[[ -n "$_DF_NOTIFY_LOADED" ]] && return 0
|
||||
typeset -g _DF_NOTIFY_LOADED=1
|
||||
|
||||
# ============================================================================
|
||||
# Configuration
|
||||
# ============================================================================
|
||||
|
||||
# Minimum duration (seconds) before notification is sent
|
||||
typeset -g DF_NOTIFY_THRESHOLD="${DF_NOTIFY_THRESHOLD:-60}"
|
||||
|
||||
# Enable/disable notifications
|
||||
typeset -g DF_NOTIFY_ENABLED="${DF_NOTIFY_ENABLED:-true}"
|
||||
|
||||
# Notification methods (space-separated): desktop bell sound
|
||||
typeset -g DF_NOTIFY_METHODS="${DF_NOTIFY_METHODS:-desktop bell}"
|
||||
|
||||
# Sound file for audio notification (optional)
|
||||
typeset -g DF_NOTIFY_SOUND="${DF_NOTIFY_SOUND:-/usr/share/sounds/freedesktop/stereo/complete.oga}"
|
||||
|
||||
# Commands to ignore (editors, pagers, interactive tools)
|
||||
typeset -g DF_NOTIFY_IGNORE_CMDS="${DF_NOTIFY_IGNORE_CMDS:-vim nvim nano vi less more man htop top btop watch ssh tmux}"
|
||||
|
||||
# Only notify on failure (exit code != 0)
|
||||
typeset -g DF_NOTIFY_ONLY_FAILURES="${DF_NOTIFY_ONLY_FAILURES:-false}"
|
||||
|
||||
# ============================================================================
|
||||
# Internal State
|
||||
# ============================================================================
|
||||
|
||||
typeset -g _df_notify_cmd=""
|
||||
typeset -g _df_notify_start=0
|
||||
|
||||
# ============================================================================
|
||||
# Notification Functions
|
||||
# ============================================================================
|
||||
|
||||
# Check if command should be ignored
|
||||
_df_notify_should_ignore() {
|
||||
local cmd="$1"
|
||||
local first_word="${cmd%% *}"
|
||||
|
||||
# Check against ignore list
|
||||
for ignore in ${(s: :)DF_NOTIFY_IGNORE_CMDS}; do
|
||||
[[ "$first_word" == "$ignore" ]] && return 0
|
||||
done
|
||||
|
||||
# Ignore backgrounded commands
|
||||
[[ "$cmd" == *'&'* ]] && return 0
|
||||
|
||||
# Ignore commands run with nohup
|
||||
[[ "$cmd" == nohup* ]] && return 0
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Send desktop notification
|
||||
_df_notify_desktop() {
|
||||
local title="$1"
|
||||
local body="$2"
|
||||
local urgency="${3:-normal}"
|
||||
local icon="${4:-terminal}"
|
||||
|
||||
if command -v notify-send &>/dev/null; then
|
||||
notify-send --urgency="$urgency" --icon="$icon" --app-name="Terminal" "$title" "$body" 2>/dev/null
|
||||
return 0
|
||||
fi
|
||||
|
||||
# macOS fallback
|
||||
if command -v osascript &>/dev/null; then
|
||||
osascript -e "display notification \"$body\" with title \"$title\"" 2>/dev/null
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Send terminal bell
|
||||
_df_notify_bell() {
|
||||
printf '\a'
|
||||
}
|
||||
|
||||
# Play sound notification
|
||||
_df_notify_sound() {
|
||||
local sound_file="$1"
|
||||
|
||||
if [[ -f "$sound_file" ]]; then
|
||||
if command -v paplay &>/dev/null; then
|
||||
paplay "$sound_file" &>/dev/null &
|
||||
elif command -v aplay &>/dev/null; then
|
||||
aplay -q "$sound_file" &>/dev/null &
|
||||
elif command -v afplay &>/dev/null; then
|
||||
afplay "$sound_file" &>/dev/null &
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Format duration for display
|
||||
_df_notify_format_duration() {
|
||||
local secs=$1
|
||||
|
||||
if (( secs >= 3600 )); then
|
||||
printf "%dh %dm %ds" $((secs/3600)) $((secs%3600/60)) $((secs%60))
|
||||
elif (( secs >= 60 )); then
|
||||
printf "%dm %ds" $((secs/60)) $((secs%60))
|
||||
else
|
||||
printf "%ds" $secs
|
||||
fi
|
||||
}
|
||||
|
||||
# Main notification function
|
||||
_df_notify_send() {
|
||||
local cmd="$1"
|
||||
local exit_code="$2"
|
||||
local duration="$3"
|
||||
|
||||
# Skip if disabled
|
||||
[[ "$DF_NOTIFY_ENABLED" != "true" ]] && return
|
||||
|
||||
# Skip if below threshold
|
||||
(( duration < DF_NOTIFY_THRESHOLD )) && return
|
||||
|
||||
# Skip ignored commands
|
||||
_df_notify_should_ignore "$cmd" && return
|
||||
|
||||
# Skip if only failures and this succeeded
|
||||
[[ "$DF_NOTIFY_ONLY_FAILURES" == "true" && $exit_code -eq 0 ]] && return
|
||||
|
||||
# Build notification content
|
||||
local title icon urgency
|
||||
local duration_str=$(_df_notify_format_duration "$duration")
|
||||
local cmd_short="${cmd:0:50}"
|
||||
[[ ${#cmd} -gt 50 ]] && cmd_short="${cmd_short}..."
|
||||
|
||||
if (( exit_code == 0 )); then
|
||||
title="✓ Command Complete"
|
||||
icon="dialog-information"
|
||||
urgency="normal"
|
||||
else
|
||||
title="✗ Command Failed (exit $exit_code)"
|
||||
icon="dialog-error"
|
||||
urgency="critical"
|
||||
fi
|
||||
|
||||
local body="$cmd_short\nDuration: $duration_str"
|
||||
|
||||
# Send notifications based on configured methods
|
||||
for method in ${(s: :)DF_NOTIFY_METHODS}; do
|
||||
case "$method" in
|
||||
desktop)
|
||||
_df_notify_desktop "$title" "$body" "$urgency" "$icon"
|
||||
;;
|
||||
bell)
|
||||
_df_notify_bell
|
||||
;;
|
||||
sound)
|
||||
[[ -n "$DF_NOTIFY_SOUND" ]] && _df_notify_sound "$DF_NOTIFY_SOUND"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Hook Functions
|
||||
# ============================================================================
|
||||
|
||||
# Called before command execution
|
||||
_df_notify_preexec() {
|
||||
_df_notify_cmd="$1"
|
||||
_df_notify_start=$SECONDS
|
||||
}
|
||||
|
||||
# Called after command completion
|
||||
_df_notify_precmd() {
|
||||
local exit_code=$?
|
||||
|
||||
# Skip if no command was tracked
|
||||
[[ -z "$_df_notify_cmd" ]] && return
|
||||
[[ $_df_notify_start -eq 0 ]] && return
|
||||
|
||||
local duration=$((SECONDS - _df_notify_start))
|
||||
|
||||
# Send notification
|
||||
_df_notify_send "$_df_notify_cmd" "$exit_code" "$duration"
|
||||
|
||||
# Reset state
|
||||
_df_notify_cmd=""
|
||||
_df_notify_start=0
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# User Commands
|
||||
# ============================================================================
|
||||
|
||||
# Toggle notifications
|
||||
df_notify_toggle() {
|
||||
if [[ "$DF_NOTIFY_ENABLED" == "true" ]]; then
|
||||
DF_NOTIFY_ENABLED="false"
|
||||
echo "Notifications: OFF"
|
||||
else
|
||||
DF_NOTIFY_ENABLED="true"
|
||||
echo "Notifications: ON"
|
||||
fi
|
||||
}
|
||||
|
||||
# Set notification threshold
|
||||
df_notify_threshold() {
|
||||
if [[ -z "$1" ]]; then
|
||||
echo "Current threshold: ${DF_NOTIFY_THRESHOLD}s"
|
||||
echo "Usage: df_notify_threshold <seconds>"
|
||||
else
|
||||
DF_NOTIFY_THRESHOLD="$1"
|
||||
echo "Threshold set to: ${DF_NOTIFY_THRESHOLD}s"
|
||||
fi
|
||||
}
|
||||
|
||||
# Test notification
|
||||
df_notify_test() {
|
||||
echo "Sending test notification..."
|
||||
_df_notify_desktop "Test Notification" "This is a test notification from dotfiles" "normal" "terminal"
|
||||
_df_notify_bell
|
||||
echo "Done. Did you see/hear it?"
|
||||
}
|
||||
|
||||
# Show notification status
|
||||
df_notify_status() {
|
||||
source "${DOTFILES_DIR:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null
|
||||
|
||||
df_print_func_name "Notification Status"
|
||||
echo ""
|
||||
df_print_section "Configuration"
|
||||
df_print_indent "Enabled: $DF_NOTIFY_ENABLED"
|
||||
df_print_indent "Threshold: ${DF_NOTIFY_THRESHOLD}s"
|
||||
df_print_indent "Methods: $DF_NOTIFY_METHODS"
|
||||
df_print_indent "Only fail: $DF_NOTIFY_ONLY_FAILURES"
|
||||
|
||||
echo ""
|
||||
df_print_section "Capabilities"
|
||||
|
||||
if command -v notify-send &>/dev/null; then
|
||||
df_print_indent "Desktop: ✓ (notify-send)"
|
||||
elif command -v osascript &>/dev/null; then
|
||||
df_print_indent "Desktop: ✓ (osascript/macOS)"
|
||||
else
|
||||
df_print_indent "Desktop: ✗ (install libnotify)"
|
||||
fi
|
||||
|
||||
df_print_indent "Bell: ✓ (always available)"
|
||||
|
||||
if [[ -n "$DF_NOTIFY_SOUND" && -f "$DF_NOTIFY_SOUND" ]]; then
|
||||
df_print_indent "Sound: ✓ ($DF_NOTIFY_SOUND)"
|
||||
else
|
||||
df_print_indent "Sound: ✗ (no sound file configured)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
df_print_section "Ignored Commands"
|
||||
df_print_indent "$DF_NOTIFY_IGNORE_CMDS"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Aliases
|
||||
# ============================================================================
|
||||
|
||||
alias notify-toggle='df_notify_toggle'
|
||||
alias notify-test='df_notify_test'
|
||||
alias notify-status='df_notify_status'
|
||||
|
||||
# ============================================================================
|
||||
# Initialize Hooks
|
||||
# ============================================================================
|
||||
|
||||
# Only set up hooks if not already done (avoid duplicates)
|
||||
if [[ -z "$_DF_NOTIFY_HOOKS_SET" ]]; then
|
||||
autoload -Uz add-zsh-hook
|
||||
add-zsh-hook preexec _df_notify_preexec
|
||||
add-zsh-hook precmd _df_notify_precmd
|
||||
typeset -g _DF_NOTIFY_HOOKS_SET=1
|
||||
fi
|
||||
423
zsh/functions/project-env.zsh
Normal file
423
zsh/functions/project-env.zsh
Normal file
@@ -0,0 +1,423 @@
|
||||
# ============================================================================
|
||||
# Project-Local Environment Manager
|
||||
# ============================================================================
|
||||
# Automatically activates project-specific settings when entering directories.
|
||||
# Similar to direnv but integrated with dotfiles.
|
||||
#
|
||||
# Features:
|
||||
# - Auto-load .dotfiles-local or .envrc files
|
||||
# - Virtual environment auto-activation
|
||||
# - Node version switching (via nvm)
|
||||
# - Custom environment variables per project
|
||||
# - Security: prompts before loading untrusted files
|
||||
# ============================================================================
|
||||
|
||||
# Prevent double-sourcing
|
||||
[[ -n "$_DF_PROJECT_ENV_LOADED" ]] && return 0
|
||||
typeset -g _DF_PROJECT_ENV_LOADED=1
|
||||
|
||||
# ============================================================================
|
||||
# Configuration
|
||||
# ============================================================================
|
||||
|
||||
# Enable/disable auto-loading
|
||||
typeset -g DF_PROJECT_ENV_ENABLED="${DF_PROJECT_ENV_ENABLED:-true}"
|
||||
|
||||
# Files to look for (in order of priority)
|
||||
typeset -g DF_PROJECT_ENV_FILES="${DF_PROJECT_ENV_FILES:-.dotfiles-local .envrc .env.local}"
|
||||
|
||||
# Trusted directories (auto-allow without prompt)
|
||||
typeset -g DF_PROJECT_ENV_TRUSTED_DIRS="${DF_PROJECT_ENV_TRUSTED_DIRS:-$HOME/projects $HOME/work $HOME/.dotfiles}"
|
||||
|
||||
# Store allowed files
|
||||
typeset -g DF_PROJECT_ENV_ALLOWED_FILE="${XDG_DATA_HOME:-$HOME/.local/share}/dotfiles/allowed-envs"
|
||||
|
||||
# Auto-activate Python virtualenvs
|
||||
typeset -g DF_PROJECT_AUTO_VENV="${DF_PROJECT_AUTO_VENV:-true}"
|
||||
|
||||
# Auto-switch Node versions via .nvmrc
|
||||
typeset -g DF_PROJECT_AUTO_NVM="${DF_PROJECT_AUTO_NVM:-true}"
|
||||
|
||||
# ============================================================================
|
||||
# Internal State
|
||||
# ============================================================================
|
||||
|
||||
typeset -g _df_project_current_env=""
|
||||
typeset -g _df_project_original_path="$PATH"
|
||||
typeset -gA _df_project_original_vars=()
|
||||
|
||||
# ============================================================================
|
||||
# Helper Functions
|
||||
# ============================================================================
|
||||
|
||||
# Check if a path is in trusted directories
|
||||
_df_project_is_trusted() {
|
||||
local dir="$1"
|
||||
|
||||
for trusted in ${(s: :)DF_PROJECT_ENV_TRUSTED_DIRS}; do
|
||||
[[ "$dir" == "$trusted"* ]] && return 0
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Check if file is explicitly allowed
|
||||
_df_project_is_allowed() {
|
||||
local file="$1"
|
||||
local file_hash=$(sha256sum "$file" 2>/dev/null | cut -d' ' -f1)
|
||||
|
||||
[[ ! -f "$DF_PROJECT_ENV_ALLOWED_FILE" ]] && return 1
|
||||
|
||||
grep -q "^${file}:${file_hash}$" "$DF_PROJECT_ENV_ALLOWED_FILE" 2>/dev/null
|
||||
}
|
||||
|
||||
# Add file to allowed list
|
||||
_df_project_allow_file() {
|
||||
local file="$1"
|
||||
local file_hash=$(sha256sum "$file" 2>/dev/null | cut -d' ' -f1)
|
||||
|
||||
mkdir -p "$(dirname "$DF_PROJECT_ENV_ALLOWED_FILE")"
|
||||
|
||||
# Remove old entry if exists
|
||||
if [[ -f "$DF_PROJECT_ENV_ALLOWED_FILE" ]]; then
|
||||
grep -v "^${file}:" "$DF_PROJECT_ENV_ALLOWED_FILE" > "${DF_PROJECT_ENV_ALLOWED_FILE}.tmp" 2>/dev/null || true
|
||||
mv "${DF_PROJECT_ENV_ALLOWED_FILE}.tmp" "$DF_PROJECT_ENV_ALLOWED_FILE"
|
||||
fi
|
||||
|
||||
echo "${file}:${file_hash}" >> "$DF_PROJECT_ENV_ALLOWED_FILE"
|
||||
}
|
||||
|
||||
# Save current environment variable
|
||||
_df_project_save_var() {
|
||||
local var="$1"
|
||||
if [[ -z "${_df_project_original_vars[$var]+x}" ]]; then
|
||||
_df_project_original_vars[$var]="${(P)var}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Restore saved environment variable
|
||||
_df_project_restore_var() {
|
||||
local var="$1"
|
||||
if [[ -n "${_df_project_original_vars[$var]+x}" ]]; then
|
||||
export "$var"="${_df_project_original_vars[$var]}"
|
||||
unset "_df_project_original_vars[$var]"
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Environment Loading
|
||||
# ============================================================================
|
||||
|
||||
# Load a project environment file
|
||||
_df_project_load_env() {
|
||||
local env_file="$1"
|
||||
|
||||
[[ ! -f "$env_file" ]] && return 1
|
||||
|
||||
# Security check
|
||||
if ! _df_project_is_trusted "$(dirname "$env_file")" && ! _df_project_is_allowed "$env_file"; then
|
||||
echo ""
|
||||
echo -e "${DF_YELLOW}⚠${DF_NC} Found project env: $env_file"
|
||||
echo -e "${DF_DIM}$(head -5 "$env_file")${DF_NC}"
|
||||
echo ""
|
||||
|
||||
if read -q "?Allow loading this file? [y/N] "; then
|
||||
echo ""
|
||||
_df_project_allow_file "$env_file"
|
||||
else
|
||||
echo ""
|
||||
echo "Skipped. To allow later: project-env allow $env_file"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Save current PATH
|
||||
_df_project_save_var "PATH"
|
||||
|
||||
# Source the file
|
||||
_df_project_current_env="$env_file"
|
||||
source "$env_file"
|
||||
|
||||
# Visual indicator
|
||||
local project_name=$(basename "$(dirname "$env_file")")
|
||||
echo -e "${DF_GREEN}●${DF_NC} Project: ${DF_CYAN}${project_name}${DF_NC}"
|
||||
}
|
||||
|
||||
# Unload current project environment
|
||||
_df_project_unload_env() {
|
||||
[[ -z "$_df_project_current_env" ]] && return
|
||||
|
||||
# Restore PATH
|
||||
_df_project_restore_var "PATH"
|
||||
|
||||
# Deactivate virtualenv if active
|
||||
[[ -n "$VIRTUAL_ENV" ]] && deactivate 2>/dev/null
|
||||
|
||||
local project_name=$(basename "$(dirname "$_df_project_current_env")")
|
||||
echo -e "${DF_DIM}○ Left: ${project_name}${DF_NC}"
|
||||
|
||||
_df_project_current_env=""
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Auto-Detection
|
||||
# ============================================================================
|
||||
|
||||
# Auto-activate Python virtualenv
|
||||
_df_project_auto_venv() {
|
||||
[[ "$DF_PROJECT_AUTO_VENV" != "true" ]] && return
|
||||
|
||||
local venv_dirs=("venv" ".venv" "env" ".env")
|
||||
|
||||
for dir in "${venv_dirs[@]}"; do
|
||||
if [[ -f "$dir/bin/activate" ]]; then
|
||||
source "$dir/bin/activate"
|
||||
echo -e "${DF_GREEN}●${DF_NC} Virtualenv: ${DF_CYAN}${dir}${DF_NC}"
|
||||
return
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Auto-switch Node version via .nvmrc
|
||||
_df_project_auto_nvm() {
|
||||
[[ "$DF_PROJECT_AUTO_NVM" != "true" ]] && return
|
||||
[[ ! -f ".nvmrc" ]] && return
|
||||
|
||||
# Check if nvm is available
|
||||
if command -v nvm &>/dev/null || [[ -s "$NVM_DIR/nvm.sh" ]]; then
|
||||
# Load nvm if not loaded
|
||||
[[ -z "$(command -v nvm)" && -s "$NVM_DIR/nvm.sh" ]] && source "$NVM_DIR/nvm.sh"
|
||||
|
||||
local nvmrc_version=$(cat .nvmrc)
|
||||
local current_version=$(node --version 2>/dev/null || echo "none")
|
||||
|
||||
if [[ "$current_version" != "$nvmrc_version"* ]]; then
|
||||
echo -e "${DF_GREEN}●${DF_NC} Node: ${DF_CYAN}${nvmrc_version}${DF_NC}"
|
||||
nvm use 2>/dev/null
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Directory Change Hook
|
||||
# ============================================================================
|
||||
|
||||
_df_project_chpwd_hook() {
|
||||
[[ "$DF_PROJECT_ENV_ENABLED" != "true" ]] && return
|
||||
|
||||
local current_dir="$PWD"
|
||||
|
||||
# Check if we left a project directory
|
||||
if [[ -n "$_df_project_current_env" ]]; then
|
||||
local env_dir=$(dirname "$_df_project_current_env")
|
||||
if [[ "$current_dir" != "$env_dir"* ]]; then
|
||||
_df_project_unload_env
|
||||
fi
|
||||
fi
|
||||
|
||||
# Look for project env files
|
||||
for env_file in ${(s: :)DF_PROJECT_ENV_FILES}; do
|
||||
if [[ -f "$current_dir/$env_file" ]]; then
|
||||
_df_project_load_env "$current_dir/$env_file"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Auto-activate virtualenv
|
||||
_df_project_auto_venv
|
||||
|
||||
# Auto-switch Node version
|
||||
_df_project_auto_nvm
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# User Commands
|
||||
# ============================================================================
|
||||
|
||||
# Main project-env command
|
||||
project-env() {
|
||||
local cmd="${1:-status}"
|
||||
shift 2>/dev/null || true
|
||||
|
||||
case "$cmd" in
|
||||
status|s)
|
||||
source "${DOTFILES_DIR:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null
|
||||
|
||||
df_print_func_name "Project Environment Status"
|
||||
echo ""
|
||||
df_print_section "Configuration"
|
||||
df_print_indent "Enabled: $DF_PROJECT_ENV_ENABLED"
|
||||
df_print_indent "Auto venv: $DF_PROJECT_AUTO_VENV"
|
||||
df_print_indent "Auto nvm: $DF_PROJECT_AUTO_NVM"
|
||||
df_print_indent "Env files: $DF_PROJECT_ENV_FILES"
|
||||
|
||||
echo ""
|
||||
df_print_section "Current State"
|
||||
if [[ -n "$_df_project_current_env" ]]; then
|
||||
df_print_indent "Active env: $_df_project_current_env"
|
||||
else
|
||||
df_print_indent "Active env: (none)"
|
||||
fi
|
||||
|
||||
if [[ -n "$VIRTUAL_ENV" ]]; then
|
||||
df_print_indent "Virtualenv: $VIRTUAL_ENV"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
df_print_section "Trusted Directories"
|
||||
for dir in ${(s: :)DF_PROJECT_ENV_TRUSTED_DIRS}; do
|
||||
df_print_indent "● $dir"
|
||||
done
|
||||
;;
|
||||
|
||||
allow|a)
|
||||
local file="${1:-$PWD/.dotfiles-local}"
|
||||
if [[ -f "$file" ]]; then
|
||||
_df_project_allow_file "$file"
|
||||
echo "Allowed: $file"
|
||||
else
|
||||
echo "File not found: $file"
|
||||
fi
|
||||
;;
|
||||
|
||||
deny|d)
|
||||
local file="${1:-$PWD/.dotfiles-local}"
|
||||
if [[ -f "$DF_PROJECT_ENV_ALLOWED_FILE" ]]; then
|
||||
grep -v "^${file}:" "$DF_PROJECT_ENV_ALLOWED_FILE" > "${DF_PROJECT_ENV_ALLOWED_FILE}.tmp" 2>/dev/null || true
|
||||
mv "${DF_PROJECT_ENV_ALLOWED_FILE}.tmp" "$DF_PROJECT_ENV_ALLOWED_FILE"
|
||||
echo "Denied: $file"
|
||||
fi
|
||||
;;
|
||||
|
||||
list|l)
|
||||
echo "Allowed environment files:"
|
||||
if [[ -f "$DF_PROJECT_ENV_ALLOWED_FILE" ]]; then
|
||||
cat "$DF_PROJECT_ENV_ALLOWED_FILE" | cut -d: -f1 | while read -r file; do
|
||||
if [[ -f "$file" ]]; then
|
||||
echo -e " ${DF_GREEN}✓${DF_NC} $file"
|
||||
else
|
||||
echo -e " ${DF_RED}✗${DF_NC} $file (missing)"
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo " (none)"
|
||||
fi
|
||||
;;
|
||||
|
||||
create|c)
|
||||
local file="${1:-.dotfiles-local}"
|
||||
if [[ -f "$file" ]]; then
|
||||
echo "File already exists: $file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
cat > "$file" << 'EOF'
|
||||
# ============================================================================
|
||||
# Project-Local Environment
|
||||
# ============================================================================
|
||||
# This file is automatically loaded when entering this directory.
|
||||
# Add project-specific settings below.
|
||||
# ============================================================================
|
||||
|
||||
# --- Environment Variables ---
|
||||
# export PROJECT_NAME="myproject"
|
||||
# export DATABASE_URL="postgresql://localhost/mydb"
|
||||
|
||||
# --- Path Additions ---
|
||||
# export PATH="$PWD/bin:$PATH"
|
||||
|
||||
# --- Virtual Environment ---
|
||||
# [[ -f venv/bin/activate ]] && source venv/bin/activate
|
||||
|
||||
# --- Custom Aliases ---
|
||||
# alias build='./scripts/build.sh'
|
||||
# alias test='pytest'
|
||||
|
||||
# --- Startup Message ---
|
||||
# echo "Welcome to $(basename $PWD)!"
|
||||
EOF
|
||||
|
||||
echo "Created: $file"
|
||||
echo "Edit with: \${EDITOR:-vim} $file"
|
||||
;;
|
||||
|
||||
edit|e)
|
||||
local file=""
|
||||
for env_file in ${(s: :)DF_PROJECT_ENV_FILES}; do
|
||||
[[ -f "$env_file" ]] && { file="$env_file"; break; }
|
||||
done
|
||||
|
||||
if [[ -n "$file" ]]; then
|
||||
${EDITOR:-vim} "$file"
|
||||
else
|
||||
echo "No project env file found. Create one: project-env create"
|
||||
fi
|
||||
;;
|
||||
|
||||
reload|r)
|
||||
_df_project_chpwd_hook
|
||||
;;
|
||||
|
||||
off)
|
||||
DF_PROJECT_ENV_ENABLED="false"
|
||||
_df_project_unload_env
|
||||
echo "Project environments disabled"
|
||||
;;
|
||||
|
||||
on)
|
||||
DF_PROJECT_ENV_ENABLED="true"
|
||||
_df_project_chpwd_hook
|
||||
echo "Project environments enabled"
|
||||
;;
|
||||
|
||||
help|--help|-h)
|
||||
cat << 'EOF'
|
||||
Project Environment Manager
|
||||
|
||||
Usage: project-env <command> [args]
|
||||
|
||||
Commands:
|
||||
status, s Show current status
|
||||
allow <file> Trust a project env file
|
||||
deny <file> Remove trust for a file
|
||||
list, l List allowed files
|
||||
create [file] Create a new project env file
|
||||
edit, e Edit current project's env file
|
||||
reload, r Reload current directory's env
|
||||
on/off Enable/disable auto-loading
|
||||
|
||||
Files checked (in order): .dotfiles-local, .envrc, .env.local
|
||||
|
||||
Examples:
|
||||
project-env create # Create .dotfiles-local
|
||||
project-env allow # Trust current dir's env file
|
||||
project-env off # Disable auto-loading
|
||||
EOF
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Unknown command: $cmd"
|
||||
echo "Use 'project-env help' for usage"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Aliases
|
||||
# ============================================================================
|
||||
|
||||
alias penv='project-env'
|
||||
alias penv-create='project-env create'
|
||||
alias penv-edit='project-env edit'
|
||||
|
||||
# ============================================================================
|
||||
# Initialize Hook
|
||||
# ============================================================================
|
||||
|
||||
if [[ "$DF_PROJECT_ENV_ENABLED" == "true" ]]; then
|
||||
autoload -Uz add-zsh-hook
|
||||
add-zsh-hook chpwd _df_project_chpwd_hook
|
||||
|
||||
# Run on initial shell load
|
||||
_df_project_chpwd_hook
|
||||
fi
|
||||
230
zsh/lib/machines.zsh
Normal file
230
zsh/lib/machines.zsh
Normal file
@@ -0,0 +1,230 @@
|
||||
# ============================================================================
|
||||
# Machine-Specific Configuration Loader
|
||||
# ============================================================================
|
||||
# Automatically loads configuration based on hostname, allowing different
|
||||
# settings per machine while keeping a single dotfiles repository.
|
||||
#
|
||||
# Configuration hierarchy (later files override earlier):
|
||||
# 1. dotfiles.conf (base config)
|
||||
# 2. machines/default.zsh (shared overrides)
|
||||
# 3. machines/<hostname>.zsh (machine-specific)
|
||||
# 4. ~/.zshrc.local (local user overrides)
|
||||
#
|
||||
# Usage:
|
||||
# Create ~/.dotfiles/machines/<hostname>.zsh for machine-specific settings
|
||||
# Use `df_machine_info` to see current machine detection
|
||||
# ============================================================================
|
||||
|
||||
# Prevent double-sourcing
|
||||
[[ -n "$_DF_MACHINES_LOADED" ]] && return 0
|
||||
typeset -g _DF_MACHINES_LOADED=1
|
||||
|
||||
# ============================================================================
|
||||
# Machine Detection
|
||||
# ============================================================================
|
||||
|
||||
typeset -g DF_HOSTNAME="${HOST:-${HOSTNAME:-$(hostname -s 2>/dev/null)}}"
|
||||
typeset -g DF_HOSTNAME_FULL="$(hostname -f 2>/dev/null || echo "$DF_HOSTNAME")"
|
||||
typeset -g DF_MACHINE_TYPE="unknown"
|
||||
typeset -g DF_MACHINE_CONFIG=""
|
||||
|
||||
# Detect machine type based on hostname patterns or hardware
|
||||
_df_detect_machine_type() {
|
||||
local hostname="$DF_HOSTNAME"
|
||||
|
||||
# Check for common naming patterns
|
||||
case "$hostname" in
|
||||
*laptop*|*book*|*portable*|*mobile*)
|
||||
DF_MACHINE_TYPE="laptop"
|
||||
;;
|
||||
*server*|*srv*|*node*|*host*)
|
||||
DF_MACHINE_TYPE="server"
|
||||
;;
|
||||
*desktop*|*workstation*|*ws*|*pc*)
|
||||
DF_MACHINE_TYPE="desktop"
|
||||
;;
|
||||
*vm*|*virtual*|*container*)
|
||||
DF_MACHINE_TYPE="virtual"
|
||||
;;
|
||||
*)
|
||||
# Try to detect from hardware
|
||||
if [[ -d /sys/class/power_supply/BAT0 ]]; then
|
||||
DF_MACHINE_TYPE="laptop"
|
||||
elif [[ -f /proc/cpuinfo ]] && grep -qi "hypervisor\|vmware\|virtualbox\|kvm\|xen" /proc/cpuinfo 2>/dev/null; then
|
||||
DF_MACHINE_TYPE="virtual"
|
||||
elif systemd-detect-virt &>/dev/null && [[ "$(systemd-detect-virt)" != "none" ]]; then
|
||||
DF_MACHINE_TYPE="virtual"
|
||||
else
|
||||
DF_MACHINE_TYPE="desktop"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Configuration Loading
|
||||
# ============================================================================
|
||||
|
||||
_df_load_machine_config() {
|
||||
local machines_dir="${DOTFILES_DIR:-$HOME/.dotfiles}/machines"
|
||||
local loaded=()
|
||||
|
||||
# Create machines directory if it doesn't exist
|
||||
[[ ! -d "$machines_dir" ]] && mkdir -p "$machines_dir"
|
||||
|
||||
# 1. Load default machine config (shared across all machines)
|
||||
if [[ -f "$machines_dir/default.zsh" ]]; then
|
||||
source "$machines_dir/default.zsh"
|
||||
loaded+=("default")
|
||||
fi
|
||||
|
||||
# 2. Load machine-type specific config
|
||||
if [[ -f "$machines_dir/type-${DF_MACHINE_TYPE}.zsh" ]]; then
|
||||
source "$machines_dir/type-${DF_MACHINE_TYPE}.zsh"
|
||||
loaded+=("type-${DF_MACHINE_TYPE}")
|
||||
fi
|
||||
|
||||
# 3. Load hostname-specific config (highest priority)
|
||||
if [[ -f "$machines_dir/${DF_HOSTNAME}.zsh" ]]; then
|
||||
source "$machines_dir/${DF_HOSTNAME}.zsh"
|
||||
loaded+=("$DF_HOSTNAME")
|
||||
DF_MACHINE_CONFIG="$machines_dir/${DF_HOSTNAME}.zsh"
|
||||
fi
|
||||
|
||||
# Store what was loaded for debugging
|
||||
typeset -g DF_MACHINE_CONFIGS_LOADED="${(j:, :)loaded}"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Machine Information Commands
|
||||
# ============================================================================
|
||||
|
||||
# Display machine detection info
|
||||
df_machine_info() {
|
||||
source "${DOTFILES_DIR:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null
|
||||
|
||||
df_print_func_name "Machine Configuration"
|
||||
echo ""
|
||||
df_print_section "Detection"
|
||||
df_print_indent "Hostname: $DF_HOSTNAME"
|
||||
df_print_indent "Full hostname: $DF_HOSTNAME_FULL"
|
||||
df_print_indent "Machine type: $DF_MACHINE_TYPE"
|
||||
echo ""
|
||||
df_print_section "Loaded Configs"
|
||||
if [[ -n "$DF_MACHINE_CONFIGS_LOADED" ]]; then
|
||||
df_print_indent "$DF_MACHINE_CONFIGS_LOADED"
|
||||
else
|
||||
df_print_indent "(none)"
|
||||
fi
|
||||
echo ""
|
||||
df_print_section "Config File"
|
||||
if [[ -n "$DF_MACHINE_CONFIG" ]]; then
|
||||
df_print_indent "$DF_MACHINE_CONFIG"
|
||||
else
|
||||
df_print_indent "No machine-specific config found"
|
||||
df_print_info "Create: ${DOTFILES_DIR:-$HOME/.dotfiles}/machines/${DF_HOSTNAME}.zsh"
|
||||
fi
|
||||
}
|
||||
|
||||
# Create a new machine config from template
|
||||
df_machine_create() {
|
||||
local hostname="${1:-$DF_HOSTNAME}"
|
||||
local machines_dir="${DOTFILES_DIR:-$HOME/.dotfiles}/machines"
|
||||
local config_file="$machines_dir/${hostname}.zsh"
|
||||
|
||||
source "${DOTFILES_DIR:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null
|
||||
|
||||
if [[ -f "$config_file" ]]; then
|
||||
df_print_warning "Config already exists: $config_file"
|
||||
df_confirm "Edit existing config?" && ${EDITOR:-vim} "$config_file"
|
||||
return
|
||||
fi
|
||||
|
||||
df_print_step "Creating machine config: $hostname"
|
||||
|
||||
cat > "$config_file" << EOF
|
||||
# ============================================================================
|
||||
# Machine Configuration: ${hostname}
|
||||
# ============================================================================
|
||||
# This file is automatically loaded when the hostname matches.
|
||||
# Created: $(date '+%Y-%m-%d %H:%M')
|
||||
#
|
||||
# Available variables to override:
|
||||
# DF_WIDTH, MOTD_STYLE, ENABLE_MOTD, ZSH_THEME_NAME
|
||||
# Any variable from dotfiles.conf
|
||||
# ============================================================================
|
||||
|
||||
# --- Display Settings ---
|
||||
# DF_WIDTH="80" # Wider terminal?
|
||||
# MOTD_STYLE="mini" # mini, compact, full, none
|
||||
|
||||
# --- Machine-specific paths ---
|
||||
# export PATH="\$HOME/custom-tools:\$PATH"
|
||||
|
||||
# --- Machine-specific aliases ---
|
||||
# alias proj='cd ~/projects/work'
|
||||
|
||||
# --- Machine-specific environment ---
|
||||
# export JAVA_HOME="/usr/lib/jvm/java-17"
|
||||
|
||||
# --- Conditional features ---
|
||||
# Example: Disable heavy features on slow machines
|
||||
# ENABLE_SMART_SUGGESTIONS="false"
|
||||
|
||||
# --- SSH agent (if needed on this machine) ---
|
||||
# if [[ -z "\$SSH_AUTH_SOCK" ]]; then
|
||||
# eval "\$(ssh-agent -s)" &>/dev/null
|
||||
# ssh-add ~/.ssh/id_ed25519 2>/dev/null
|
||||
# fi
|
||||
|
||||
# --- Custom startup commands ---
|
||||
# echo "Welcome to ${hostname}!"
|
||||
EOF
|
||||
|
||||
df_print_success "Created: $config_file"
|
||||
df_print_info "Edit with: ${EDITOR:-vim} $config_file"
|
||||
|
||||
df_confirm "Edit now?" && ${EDITOR:-vim} "$config_file"
|
||||
}
|
||||
|
||||
# List all machine configs
|
||||
df_machine_list() {
|
||||
local machines_dir="${DOTFILES_DIR:-$HOME/.dotfiles}/machines"
|
||||
|
||||
source "${DOTFILES_DIR:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null
|
||||
|
||||
df_print_func_name "Machine Configurations"
|
||||
echo ""
|
||||
|
||||
if [[ ! -d "$machines_dir" ]] || [[ -z "$(ls -A "$machines_dir" 2>/dev/null)" ]]; then
|
||||
df_print_info "No machine configs found"
|
||||
df_print_info "Create one: df_machine_create <hostname>"
|
||||
return
|
||||
fi
|
||||
|
||||
for config in "$machines_dir"/*.zsh(N); do
|
||||
[[ -f "$config" ]] || continue
|
||||
local name=$(basename "$config" .zsh)
|
||||
local marker=""
|
||||
[[ "$name" == "$DF_HOSTNAME" ]] && marker=" ${DF_GREEN}(current)${DF_NC}"
|
||||
[[ "$name" == "default" ]] && marker=" ${DF_CYAN}(shared)${DF_NC}"
|
||||
[[ "$name" == type-* ]] && marker=" ${DF_YELLOW}(type)${DF_NC}"
|
||||
df_print_indent "● ${name}${marker}"
|
||||
done
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Aliases
|
||||
# ============================================================================
|
||||
|
||||
alias machines='df_machine_list'
|
||||
alias machine-info='df_machine_info'
|
||||
alias machine-create='df_machine_create'
|
||||
alias machine-edit='${EDITOR:-vim} "${DOTFILES_DIR:-$HOME/.dotfiles}/machines/${DF_HOSTNAME}.zsh"'
|
||||
|
||||
# ============================================================================
|
||||
# Initialize
|
||||
# ============================================================================
|
||||
|
||||
_df_detect_machine_type
|
||||
_df_load_machine_config
|
||||
301
zsh/lib/plugins.zsh
Normal file
301
zsh/lib/plugins.zsh
Normal file
@@ -0,0 +1,301 @@
|
||||
# ============================================================================
|
||||
# Dotfiles Plugin Manager
|
||||
# ============================================================================
|
||||
# A thin wrapper for managing zsh plugins without heavy frameworks.
|
||||
#
|
||||
# Features:
|
||||
# - Simple git-based plugin installation
|
||||
# - Automatic updates
|
||||
# - Lazy loading support
|
||||
# - Oh-My-Zsh plugin compatibility
|
||||
# ============================================================================
|
||||
|
||||
# Prevent double-sourcing
|
||||
[[ -n "$_DF_PLUGINS_LOADED" ]] && return 0
|
||||
typeset -g _DF_PLUGINS_LOADED=1
|
||||
|
||||
# ============================================================================
|
||||
# Configuration
|
||||
# ============================================================================
|
||||
|
||||
typeset -g DF_PLUGIN_DIR="${DF_PLUGIN_DIR:-$HOME/.dotfiles/zsh/plugins}"
|
||||
typeset -g DF_PLUGIN_REPOS_FILE="${DF_PLUGIN_DIR}/.repos"
|
||||
|
||||
# Track loaded plugins
|
||||
typeset -ga DF_LOADED_PLUGINS=()
|
||||
|
||||
# ============================================================================
|
||||
# Core Functions
|
||||
# ============================================================================
|
||||
|
||||
# Install a plugin from GitHub
|
||||
# Usage: df_plugin "zsh-users/zsh-autosuggestions" [branch]
|
||||
df_plugin() {
|
||||
local repo="$1"
|
||||
local branch="${2:-master}"
|
||||
local name="${repo##*/}"
|
||||
local dir="$DF_PLUGIN_DIR/$name"
|
||||
|
||||
# Ensure plugin directory exists
|
||||
[[ ! -d "$DF_PLUGIN_DIR" ]] && mkdir -p "$DF_PLUGIN_DIR"
|
||||
|
||||
# Clone if not exists
|
||||
if [[ ! -d "$dir" ]]; then
|
||||
echo "Installing plugin: $name..."
|
||||
git clone --depth 1 --branch "$branch" "https://github.com/$repo.git" "$dir" 2>/dev/null
|
||||
if [[ $? -eq 0 ]]; then
|
||||
echo "✓ Installed: $name"
|
||||
# Track the repo
|
||||
echo "$repo|$branch" >> "$DF_PLUGIN_REPOS_FILE"
|
||||
else
|
||||
echo "✗ Failed to install: $name"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Source the plugin
|
||||
df_plugin_load "$name"
|
||||
}
|
||||
|
||||
# Load a plugin by name
|
||||
df_plugin_load() {
|
||||
local name="$1"
|
||||
local dir="$DF_PLUGIN_DIR/$name"
|
||||
|
||||
# Check if already loaded
|
||||
[[ " ${DF_LOADED_PLUGINS[*]} " =~ " $name " ]] && return 0
|
||||
|
||||
if [[ -d "$dir" ]]; then
|
||||
# Try common plugin file names
|
||||
local plugin_files=(
|
||||
"$dir/$name.plugin.zsh"
|
||||
"$dir/$name.zsh"
|
||||
"$dir/init.zsh"
|
||||
"$dir/$name.sh"
|
||||
)
|
||||
|
||||
for file in "${plugin_files[@]}"; do
|
||||
if [[ -f "$file" ]]; then
|
||||
source "$file"
|
||||
DF_LOADED_PLUGINS+=("$name")
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Warning: Could not find plugin entry point for $name"
|
||||
return 1
|
||||
else
|
||||
echo "Plugin not found: $name"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Lazy load a plugin (load on first use of command)
|
||||
# Usage: df_plugin_lazy "plugin-name" "command1" "command2"
|
||||
df_plugin_lazy() {
|
||||
local plugin="$1"
|
||||
shift
|
||||
local commands=("$@")
|
||||
|
||||
for cmd in "${commands[@]}"; do
|
||||
eval "
|
||||
$cmd() {
|
||||
unfunction $cmd 2>/dev/null
|
||||
df_plugin_load '$plugin'
|
||||
$cmd \"\$@\"
|
||||
}
|
||||
"
|
||||
done
|
||||
}
|
||||
|
||||
# Update all plugins
|
||||
df_plugin_update() {
|
||||
source "${DOTFILES_DIR:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null
|
||||
|
||||
df_print_func_name "Plugin Update"
|
||||
echo ""
|
||||
|
||||
for dir in "$DF_PLUGIN_DIR"/*/; do
|
||||
[[ -d "$dir/.git" ]] || continue
|
||||
|
||||
local name=$(basename "$dir")
|
||||
df_print_step "Updating: $name"
|
||||
|
||||
(
|
||||
cd "$dir"
|
||||
git pull --quiet 2>/dev/null && \
|
||||
df_print_success "$name updated" || \
|
||||
df_print_warning "$name: update failed"
|
||||
)
|
||||
done
|
||||
}
|
||||
|
||||
# List installed plugins
|
||||
df_plugin_list() {
|
||||
source "${DOTFILES_DIR:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null
|
||||
|
||||
df_print_func_name "Installed Plugins"
|
||||
echo ""
|
||||
|
||||
if [[ ! -d "$DF_PLUGIN_DIR" ]] || [[ -z "$(ls -A "$DF_PLUGIN_DIR" 2>/dev/null)" ]]; then
|
||||
df_print_info "No plugins installed"
|
||||
return
|
||||
fi
|
||||
|
||||
for dir in "$DF_PLUGIN_DIR"/*/; do
|
||||
[[ -d "$dir" ]] || continue
|
||||
|
||||
local name=$(basename "$dir")
|
||||
local loaded=""
|
||||
[[ " ${DF_LOADED_PLUGINS[*]} " =~ " $name " ]] && loaded=" ${DF_GREEN}(loaded)${DF_NC}"
|
||||
|
||||
# Get repo info if available
|
||||
local repo_info=""
|
||||
if [[ -d "$dir/.git" ]]; then
|
||||
local remote=$(cd "$dir" && git remote get-url origin 2>/dev/null)
|
||||
repo_info=" ${DF_DIM}${remote##*github.com/}${DF_NC}"
|
||||
fi
|
||||
|
||||
df_print_indent "● ${name}${loaded}${repo_info}"
|
||||
done
|
||||
|
||||
echo ""
|
||||
df_print_section "Loaded Plugins"
|
||||
if [[ ${#DF_LOADED_PLUGINS[@]} -gt 0 ]]; then
|
||||
df_print_indent "${DF_LOADED_PLUGINS[*]}"
|
||||
else
|
||||
df_print_indent "(none)"
|
||||
fi
|
||||
}
|
||||
|
||||
# Remove a plugin
|
||||
df_plugin_remove() {
|
||||
local name="$1"
|
||||
local dir="$DF_PLUGIN_DIR/$name"
|
||||
|
||||
[[ -z "$name" ]] && { echo "Usage: df_plugin_remove <name>"; return 1; }
|
||||
|
||||
if [[ ! -d "$dir" ]]; then
|
||||
echo "Plugin not found: $name"
|
||||
return 1
|
||||
fi
|
||||
|
||||
source "${DOTFILES_DIR:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null
|
||||
|
||||
df_confirm "Remove plugin '$name'?" || return 1
|
||||
|
||||
rm -rf "$dir"
|
||||
|
||||
# Remove from repos file
|
||||
if [[ -f "$DF_PLUGIN_REPOS_FILE" ]]; then
|
||||
grep -v "/$name|" "$DF_PLUGIN_REPOS_FILE" > "${DF_PLUGIN_REPOS_FILE}.tmp" 2>/dev/null || true
|
||||
mv "${DF_PLUGIN_REPOS_FILE}.tmp" "$DF_PLUGIN_REPOS_FILE"
|
||||
fi
|
||||
|
||||
df_print_success "Removed: $name"
|
||||
df_print_info "Restart shell to fully unload"
|
||||
}
|
||||
|
||||
# Install recommended plugins
|
||||
df_plugin_recommended() {
|
||||
source "${DOTFILES_DIR:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null
|
||||
|
||||
df_print_func_name "Recommended Plugins"
|
||||
echo ""
|
||||
|
||||
local plugins=(
|
||||
"zsh-users/zsh-autosuggestions:Fish-like autosuggestions"
|
||||
"zsh-users/zsh-syntax-highlighting:Syntax highlighting"
|
||||
"zsh-users/zsh-completions:Additional completions"
|
||||
"romkatv/zsh-defer:Deferred loading for faster startup"
|
||||
"Aloxaf/fzf-tab:FZF-powered tab completion"
|
||||
)
|
||||
|
||||
for plugin_info in "${plugins[@]}"; do
|
||||
local repo="${plugin_info%%:*}"
|
||||
local desc="${plugin_info#*:}"
|
||||
local name="${repo##*/}"
|
||||
local status=""
|
||||
|
||||
if [[ -d "$DF_PLUGIN_DIR/$name" ]]; then
|
||||
status="${DF_GREEN}(installed)${DF_NC}"
|
||||
else
|
||||
status="${DF_DIM}(not installed)${DF_NC}"
|
||||
fi
|
||||
|
||||
echo -e " ${DF_CYAN}$name${DF_NC} $status"
|
||||
echo -e " ${DF_DIM}$desc${DF_NC}"
|
||||
echo -e " ${DF_DIM}Install: df_plugin $repo${DF_NC}"
|
||||
echo ""
|
||||
done
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Command Interface
|
||||
# ============================================================================
|
||||
|
||||
# Main plugin command
|
||||
plugin() {
|
||||
local cmd="${1:-list}"
|
||||
shift 2>/dev/null || true
|
||||
|
||||
case "$cmd" in
|
||||
install|add|i)
|
||||
[[ -z "$1" ]] && { echo "Usage: plugin install <github-user/repo>"; return 1; }
|
||||
df_plugin "$@"
|
||||
;;
|
||||
load|l)
|
||||
df_plugin_load "$@"
|
||||
;;
|
||||
lazy)
|
||||
df_plugin_lazy "$@"
|
||||
;;
|
||||
update|up|u)
|
||||
df_plugin_update
|
||||
;;
|
||||
list|ls)
|
||||
df_plugin_list
|
||||
;;
|
||||
remove|rm|r)
|
||||
df_plugin_remove "$@"
|
||||
;;
|
||||
recommended|rec)
|
||||
df_plugin_recommended
|
||||
;;
|
||||
help|--help|-h)
|
||||
cat << 'EOF'
|
||||
Dotfiles Plugin Manager
|
||||
|
||||
Usage: plugin <command> [args]
|
||||
|
||||
Commands:
|
||||
install <repo> Install plugin from GitHub (e.g., zsh-users/zsh-autosuggestions)
|
||||
load <name> Load an installed plugin
|
||||
lazy <n> <cmds> Lazy-load plugin on command use
|
||||
update Update all plugins
|
||||
list List installed plugins
|
||||
remove <name> Remove a plugin
|
||||
recommended Show recommended plugins
|
||||
|
||||
Examples:
|
||||
plugin install zsh-users/zsh-autosuggestions
|
||||
plugin lazy zsh-nvm nvm node npm
|
||||
plugin update
|
||||
plugin remove zsh-autosuggestions
|
||||
|
||||
EOF
|
||||
;;
|
||||
*)
|
||||
echo "Unknown command: $cmd"
|
||||
echo "Use 'plugin help' for usage"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Initialize
|
||||
# ============================================================================
|
||||
|
||||
# Ensure plugin directory exists
|
||||
[[ ! -d "$DF_PLUGIN_DIR" ]] && mkdir -p "$DF_PLUGIN_DIR"
|
||||
Reference in New Issue
Block a user