From c4c3f9ca3b47fd4119950ee229b717f88fa22e4d Mon Sep 17 00:00:00 2001 From: "Aaron D. Lee" Date: Tue, 16 Dec 2025 20:37:11 -0500 Subject: [PATCH] Auto-sync from catchthesethighs --- CHANGELOG_UPDATE.md | 170 ++++ CHANGELOG_v1.2.0.md | 328 +++++++ SSH_TMUX_INTEGRATION.md | 479 +++++++++ zsh/aliases.zsh | 5 +- zsh/functions/python-templates.zsh | 1446 ++++++++++++++++++++++++++++ zsh/functions/ssh-manager.zsh | 462 +++++++++ zsh/functions/tmux-workspaces.zsh | 575 +++++++++++ 7 files changed, 3463 insertions(+), 2 deletions(-) create mode 100644 CHANGELOG_UPDATE.md create mode 100644 CHANGELOG_v1.2.0.md create mode 100644 SSH_TMUX_INTEGRATION.md create mode 100644 zsh/functions/python-templates.zsh create mode 100644 zsh/functions/ssh-manager.zsh create mode 100644 zsh/functions/tmux-workspaces.zsh diff --git a/CHANGELOG_UPDATE.md b/CHANGELOG_UPDATE.md new file mode 100644 index 0000000..c7fcd1d --- /dev/null +++ b/CHANGELOG_UPDATE.md @@ -0,0 +1,170 @@ +# Changelog Update for v1.2.0 + +## [1.2.0] - 2025-12-16 + +### Added + +#### Python Project Templates +- **`py-new`** - Create basic Python project with venv, tests, and structure +- **`py-django`** - Django web application template with best practices +- **`py-flask`** - Flask web application with blueprints and templates +- **`py-fastapi`** - FastAPI REST API with automatic docs +- **`py-data`** - Data science project with Jupyter, pandas, and proper data directory structure +- **`py-cli`** - Command-line tool template using Click framework + +#### Python Template Features +- Automatic virtual environment creation +- Poetry support (configurable via `PY_TEMPLATE_USE_POETRY`) +- Pre-configured .gitignore for Python projects +- README with setup instructions +- Requirements.txt with common dependencies +- Project structure following best practices +- Optional git initialization +- Quick aliases: `pynew`, `pydjango`, `pyflask`, `pyfast`, `pydata`, `pycli` +- `venv` function to quickly activate virtual environments + +### Changed + +#### Alias System Cleanup +- **Removed `stats` alias** - Forces explicit `dfstats` usage to avoid conflicts +- Updated help text in `dotfiles-cli` to reflect removal +- Clarified that `stats` removal is intentional in documentation + +### Configuration + +New Python template settings in `dotfiles.conf`: + +```bash +# Python Project Templates +PY_TEMPLATE_BASE_DIR="$HOME/projects" # Where to create projects +PY_TEMPLATE_PYTHON="python3" # Python executable +PY_TEMPLATE_VENV_NAME="venv" # Virtual environment name +PY_TEMPLATE_USE_POETRY="false" # Use Poetry instead of venv +PY_TEMPLATE_GIT_INIT="true" # Auto-initialize git repos +``` + +### Usage Examples + +#### Basic Python Project +```bash +py-new myproject +cd myproject +source venv/bin/activate +# Start coding! +``` + +#### Django Project +```bash +py-django myblog +cd myblog +source venv/bin/activate +python manage.py runserver +# Visit: http://localhost:8000 +``` + +#### FastAPI Project +```bash +py-fastapi myapi +cd myapi +source venv/bin/activate +python run.py +# Docs at: http://localhost:8000/docs +``` + +#### Data Science Project +```bash +py-data analysis +cd analysis +source venv/bin/activate +jupyter notebook +``` + +#### CLI Tool +```bash +py-cli mytool +cd mytool +pip install -e . +mytool --help +``` + +--- + +## Breaking Changes + +- **`stats` alias removed** - Use `dfstats` instead + - Reason: Potential conflicts with other tools/scripts + - Migration: Replace `stats` with `dfstats` in any scripts or muscle memory + +--- + +## File Changes + +### New Files +- `zsh/functions/python-templates.zsh` - Python project template functions + +### Modified Files +- `zsh/aliases.zsh` - Removed `stats` alias, added cleanup notes +- `dotfiles.conf` - New Python template configuration section (optional) +- `.zshrc` - Sources `python-templates.zsh` (needs manual addition) + +### To Enable Python Templates + +Add to your `.zshrc`: + +```bash +# Python project templates +[[ -f "$_dotfiles_dir/zsh/functions/python-templates.zsh" ]] && \ + source "$_dotfiles_dir/zsh/functions/python-templates.zsh" +``` + +Or add to deferred loading section: + +```bash +_deferred_load() { + # ... existing code ... + + # Python templates + [[ -f "$_dotfiles_dir/zsh/functions/python-templates.zsh" ]] && \ + source "$_dotfiles_dir/zsh/functions/python-templates.zsh" +} +``` + +--- + +## Documentation Updates Needed + +### README.md +- Add Python Templates section +- Update aliases table (remove `stats`, add `dfstats`) +- Add examples of template usage + +### SETUP_GUIDE.md +- Add Python project templates section +- Document template configuration options + +### New Documentation +- Consider creating `docs/PYTHON_TEMPLATES.md` with detailed examples + +--- + +## Testing Checklist + +- [ ] Test each template type creates correct structure +- [ ] Verify virtual environment creation works +- [ ] Test with both venv and Poetry modes +- [ ] Confirm git initialization works +- [ ] Check that `stats` alias is truly removed +- [ ] Verify `dfstats` still works correctly +- [ ] Test on fresh installation + +--- + +## Future Enhancements (v1.3.0) + +- Add `py-test` template for testing frameworks +- Add `py-package` for PyPI package development +- Add `py-ml` for machine learning projects (with more ML tools) +- Add interactive template customization wizard +- Support for different Python versions (pyenv integration) +- Add GitHub Actions workflow templates +- Add Docker support for projects diff --git a/CHANGELOG_v1.2.0.md b/CHANGELOG_v1.2.0.md new file mode 100644 index 0000000..5c3e8cd --- /dev/null +++ b/CHANGELOG_v1.2.0.md @@ -0,0 +1,328 @@ +# Changelog - Version 1.2.0 + +## [1.2.0] - 2025-12-16 + +### Added + +#### Python Project Templates +- **`py-new`** - Basic Python project with venv, tests, and proper structure +- **`py-django`** - Django web application template with best practices +- **`py-flask`** - Flask web application with blueprints and templates +- **`py-fastapi`** - FastAPI REST API with automatic documentation +- **`py-data`** - Data science project with Jupyter, pandas, and structured data directories +- **`py-cli`** - Command-line tool template using Click framework + +**Features:** +- Automatic virtual environment creation +- Poetry support (configurable via `PY_TEMPLATE_USE_POETRY`) +- Pre-configured .gitignore for Python projects +- README with setup instructions +- Requirements.txt with common dependencies +- Project structure following best practices +- Optional git initialization +- Quick aliases: `pynew`, `pydjango`, `pyflask`, `pyfast`, `pydata`, `pycli` +- `venv` function to quickly activate virtual environments + +#### SSH Session Manager with Tmux Integration +- **Save SSH connection profiles** with aliases for quick access +- **Automatic tmux session attachment** on remote hosts +- **Auto-create named sessions** per server connection +- **Fuzzy search connections** with fzf integration +- **Dotfiles sync** to remote servers +- **Quick reconnect** to last used connection + +**Commands:** +- `ssh-save ` - Save connection profile +- `ssh-connect ` - Connect with auto-tmux attach +- `ssh-list` - List all saved profiles +- `sshf` - Fuzzy search and connect +- `ssh-reconnect` - Quick reconnect to last/specific connection +- `ssh-sync-dotfiles ` - Deploy dotfiles to remote + +**Aliases:** +- `sshl`, `sshs`, `sshc`, `sshd`, `sshr`, `sshsync` + +#### Tmux Workspace Manager +- **Pre-configured workspace templates** for different workflows +- **Quick workspace creation** from templates +- **Session management** with persistence across disconnects +- **Custom template creation** by saving current layouts +- **Fuzzy search workspaces** with fzf +- **Pane synchronization toggle** for multi-server commands + +**Templates:** +- `dev` - 3 panes: vim (50%), terminal (25%), logs (25%) +- `ops` - 4 panes in grid for monitoring +- `ssh-multi` - 4 panes for multi-server management +- `debug` - 2 panes: main (70%), helper (30%) +- `full` - Single full-screen pane +- `review` - Side-by-side comparison + +**Commands:** +- `tw ` - Quick attach or create workspace +- `tw-create [template]` - Create from template +- `tw-list` - List all workspaces +- `tw-save ` - Save current layout as template +- `tw-sync` - Toggle pane synchronization +- `twf` - Fuzzy search workspaces + +**Aliases:** +- `twl`, `twc`, `twa`, `twd`, `tws`, `twt`, `twe`, `twf` + +### Changed + +#### Alias System Cleanup +- **Removed `stats` alias** - Forces explicit `dfstats` usage to avoid conflicts with other tools +- Updated help text in `dotfiles-cli` to reflect removal +- Added clarifying comments in aliases.zsh + +### Configuration + +#### New Python Template Settings (dotfiles.conf) +```bash +# Python Project Templates +PY_TEMPLATE_BASE_DIR="$HOME/projects" # Where to create projects +PY_TEMPLATE_PYTHON="python3" # Python executable +PY_TEMPLATE_VENV_NAME="venv" # Virtual environment name +PY_TEMPLATE_USE_POETRY="false" # Use Poetry instead of venv +PY_TEMPLATE_GIT_INIT="true" # Auto-initialize git repos +``` + +#### New SSH Manager Settings (dotfiles.conf) +```bash +# SSH Session Manager +SSH_AUTO_TMUX="true" # Auto-attach to tmux on connect +SSH_TMUX_SESSION_PREFIX="ssh" # Tmux session prefix +SSH_SYNC_DOTFILES="ask" # ask, true, or false +``` + +#### New Tmux Workspace Settings (dotfiles.conf) +```bash +# Tmux Workspace Manager +TW_SESSION_PREFIX="work" # Session name prefix +TW_DEFAULT_TEMPLATE="dev" # Default template +``` + +--- + +## Breaking Changes + +- **`stats` alias removed** - Use `dfstats` instead + - **Reason:** Potential conflicts with other tools/scripts + - **Migration:** Replace `stats` with `dfstats` in any scripts or muscle memory + +--- + +## File Structure + +### New Files +``` +zsh/functions/ +├── python-templates.zsh # Python project templates +├── ssh-manager.zsh # SSH session manager +└── tmux-workspaces.zsh # Tmux workspace manager + +docs/ +└── SSH_TMUX_INTEGRATION.md # Complete integration guide + +.ssh-profiles # SSH connection profiles (generated) +.tmux-templates/ # Tmux workspace templates (generated) +├── dev.tmux +├── ops.tmux +├── ssh-multi.tmux +├── debug.tmux +├── full.tmux +└── review.tmux +``` + +### Modified Files +- `zsh/aliases.zsh` - Removed `stats` alias +- `dotfiles.conf` - New configuration sections (optional) + +--- + +## Integration Instructions + +### 1. Add to .zshrc + +Add to the deferred loading section in `.zshrc`: + +```bash +_deferred_load() { + # ... existing code ... + + # Python project templates + [[ -f "$_dotfiles_dir/zsh/functions/python-templates.zsh" ]] && \ + source "$_dotfiles_dir/zsh/functions/python-templates.zsh" + + # SSH Session Manager + [[ -f "$_dotfiles_dir/zsh/functions/ssh-manager.zsh" ]] && \ + source "$_dotfiles_dir/zsh/functions/ssh-manager.zsh" + + # Tmux Workspace Manager + [[ -f "$_dotfiles_dir/zsh/functions/tmux-workspaces.zsh" ]] && \ + source "$_dotfiles_dir/zsh/functions/tmux-workspaces.zsh" +} +``` + +### 2. Reload Shell + +```bash +source ~/.zshrc +# or +exec zsh +``` + +--- + +## Usage Examples + +### Python Templates + +**Basic project:** +```bash +py-new myproject +cd myproject +source venv/bin/activate +``` + +**Django:** +```bash +py-django myblog +cd myblog +source venv/bin/activate +python manage.py runserver +``` + +**Data Science:** +```bash +py-data analysis +cd analysis +source venv/bin/activate +jupyter notebook +``` + +### SSH + Tmux Workflow + +**Save and connect:** +```bash +# Save connection +ssh-save prod user@prod.example.com 22 ~/.ssh/prod_key + +# Connect (auto-attaches to tmux) +ssh-connect prod +``` + +**Multi-server monitoring:** +```bash +# Create workspace +tw-create monitoring ssh-multi + +# In each pane, connect to different server +# Enable sync to run commands on all +tw-sync +``` + +### Tmux Workspaces + +**Quick project setup:** +```bash +# One command creates workspace with dev template +tw myproject + +# Panes ready: +# 1. Vim/editor +# 2. Terminal +# 3. Logs +``` + +**Custom workflow:** +```bash +# Create with specific template +tw-create backend ops + +# Save current layout for reuse +tw-save my-custom-template +``` + +--- + +## Testing Checklist + +- [ ] Python templates create correct structure +- [ ] Virtual environments activate properly +- [ ] SSH profiles save and load correctly +- [ ] SSH auto-tmux attachment works on remote +- [ ] Tmux templates create expected layouts +- [ ] Workspace persistence across sessions +- [ ] Fuzzy search works (requires fzf) +- [ ] `stats` alias is removed +- [ ] `dfstats` still works correctly +- [ ] All new aliases function properly + +--- + +## Documentation Updates + +### Created +- `docs/SSH_TMUX_INTEGRATION.md` - Complete guide for SSH and Tmux features + +### Update Needed +- `README.md` - Add Python Templates, SSH Manager, and Tmux Workspaces sections +- `README.md` - Update aliases table (remove `stats`) +- `SETUP_GUIDE.md` - Add integration instructions +- `SETUP_GUIDE.md` - Document configuration options + +--- + +## Future Enhancements (v1.3.0) + +### Python Templates +- Add `py-test` template for testing frameworks +- Add `py-package` for PyPI package development +- Add `py-ml` for ML projects with more ML tools +- Interactive template customization wizard +- Pyenv integration for version management +- GitHub Actions workflow templates +- Docker support for projects + +### SSH & Tmux +- SSH connection health monitoring +- Auto-reconnect on network drop +- Tmux session backup/restore +- Remote tmux session discovery +- Multi-hop SSH connections +- SSH tunnel management +- Tmux plugin recommendations + +--- + +## Known Issues + +None reported yet. + +--- + +## Credits + +- SSH Manager: Inspired by SSH config management tools +- Tmux Workspaces: Inspired by tmuxinator and teamocil +- Python Templates: Best practices from Python community + +--- + +## Upgrade Notes + +This is a **minor version** update with new features. No breaking changes except the intentional removal of the `stats` alias. + +**Recommended upgrade path:** +1. Pull latest dotfiles +2. Review new configuration options in `dotfiles.conf` +3. Add integration code to `.zshrc` (see above) +4. Reload shell +5. Test new features + +**Optional:** +- Customize Python template settings +- Set up SSH profiles for your servers +- Create custom tmux templates diff --git a/SSH_TMUX_INTEGRATION.md b/SSH_TMUX_INTEGRATION.md new file mode 100644 index 0000000..d3834cc --- /dev/null +++ b/SSH_TMUX_INTEGRATION.md @@ -0,0 +1,479 @@ +# SSH & Tmux Integration Guide + +Complete guide for integrating the new SSH Session Manager and Tmux Workspace Manager into your dotfiles. + +## Quick Start + +### 1. Add to .zshrc + +Add to the deferred loading section in `.zshrc`: + +```bash +_deferred_load() { + # ... existing code ... + + # SSH Session Manager + [[ -f "$_dotfiles_dir/zsh/functions/ssh-manager.zsh" ]] && \ + source "$_dotfiles_dir/zsh/functions/ssh-manager.zsh" + + # Tmux Workspace Manager + [[ -f "$_dotfiles_dir/zsh/functions/tmux-workspaces.zsh" ]] && \ + source "$_dotfiles_dir/zsh/functions/tmux-workspaces.zsh" +} +``` + +### 2. Reload Shell + +```bash +source ~/.zshrc +# or +exec zsh +``` + +--- + +## SSH Session Manager + +### Basic Usage + +**Save a connection:** +```bash +ssh-save prod user@prod.example.com +ssh-save dev user@dev.example.com 2222 ~/.ssh/dev_key +``` + +**Connect with auto-tmux:** +```bash +ssh-connect prod +# Automatically attaches to or creates tmux session "ssh-prod" +``` + +**List all profiles:** +```bash +ssh-list +``` + +**Fuzzy search and connect:** +```bash +sshf +# Requires fzf +``` + +### Advanced Features + +**With port forwarding:** +```bash +ssh-save vpn user@vpn.com 22 '' '-D 9090' 'VPN with SOCKS proxy' +``` + +**Edit existing profile:** +```bash +ssh-edit prod +``` + +**Quick reconnect:** +```bash +ssh-reconnect # Reconnects to last connection +ssh-reconnect prod # Reconnect to specific profile +``` + +**Sync dotfiles to remote:** +```bash +ssh-sync-dotfiles prod +# Syncs ~/.dotfiles to remote host +``` + +### Configuration + +Add to `dotfiles.conf`: + +```bash +# SSH Session Manager +SSH_AUTO_TMUX="true" # Auto-attach to tmux on connect +SSH_TMUX_SESSION_PREFIX="ssh" # Tmux session prefix +SSH_SYNC_DOTFILES="ask" # ask, true, or false +``` + +### Aliases + +```bash +sshl # ssh-list +sshs # ssh-save +sshc # ssh-connect +sshd # ssh-delete +sshr # ssh-reconnect +sshsync # ssh-sync-dotfiles +sshf # Fuzzy search +``` + +--- + +## Tmux Workspace Manager + +### Basic Usage + +**Create a workspace:** +```bash +tw-create myproject # Uses default 'dev' template +tw-create backend ops # Uses 'ops' template +``` + +**Quick attach (or create if not exists):** +```bash +tw myproject +``` + +**List workspaces:** +```bash +tw-list +# or +tw +``` + +**Delete workspace:** +```bash +tw-delete myproject +``` + +### Available Templates + +**dev** - Development (3 panes) +- Vim/editor (50% left) +- Terminal (25% top-right) +- Logs (25% bottom-right) + +**ops** - Operations (4 panes in grid) +- Perfect for monitoring multiple things + +**ssh-multi** - Multi-server (4 panes) +- Manage multiple SSH connections +- Optional pane synchronization + +**debug** - Debugging (2 panes) +- Main pane (70%) +- Helper pane (30%) + +**full** - Single pane +- Just one full-screen pane + +**review** - Code review (2 equal panes) +- Side-by-side comparison + +### Working with Templates + +**List available templates:** +```bash +tw-templates +``` + +**Edit a template:** +```bash +tw-template-edit dev +``` + +**Save current layout as template:** +```bash +# Inside tmux, arrange your panes how you want +tw-save my-custom-layout +``` + +### Advanced Features + +**Fuzzy search workspaces:** +```bash +twf +# Requires fzf +``` + +**Rename workspace:** +```bash +tw-rename old-name new-name +``` + +**Toggle pane synchronization:** +```bash +tw-sync +# Sends same input to all panes - great for multi-server commands +``` + +### Configuration + +Add to `dotfiles.conf`: + +```bash +# Tmux Workspace Manager +TW_SESSION_PREFIX="work" # Session name prefix +TW_DEFAULT_TEMPLATE="dev" # Default template +``` + +### Aliases + +```bash +tw # Quick attach/create +twl # tw-list +twc # tw-create +twa # tw-attach +twd # tw-delete +tws # tw-save +twt # tw-templates +twe # tw-template-edit +twf # Fuzzy search +``` + +--- + +## Integration Examples + +### Combined Workflow + +**1. Create a workspace for remote work:** +```bash +# Save SSH connection +ssh-save backend-prod user@backend.prod.com 22 ~/.ssh/prod_key + +# Create local workspace to track what you're doing +tw-create backend-work dev + +# Connect to remote with auto-tmux +ssh-connect backend-prod +# Now on remote server in tmux session "ssh-backend-prod" +``` + +**2. Multi-server monitoring:** +```bash +# Create workspace for ops +tw-create monitoring ops + +# In each pane, connect to different server: +# Pane 1: ssh-connect server1 +# Pane 2: ssh-connect server2 +# Pane 3: ssh-connect server3 +# Pane 4: local monitoring + +# Enable synchronization for commands across all +tw-sync +``` + +**3. Development workflow:** +```bash +# Morning routine - one command: +tw myproject + +# If workspace exists: attaches +# If not: creates with dev template + +# Inside workspace: +# - Pane 1: vim +# - Pane 2: run dev server +# - Pane 3: tail -f logs/development.log +``` + +### Custom Template Example + +Create a template for your specific workflow: + +**File:** `~/.dotfiles/.tmux-templates/webdev.tmux` +```tmux +# Web development workspace +# Vim (left) + Dev server (top-right) + Browser sync (bottom-right) + +split-window -h -p 50 +split-window -v -p 50 + +# Auto-start commands +send-keys -t 0 'vim' C-m +send-keys -t 1 'npm run dev' C-m +send-keys -t 2 'npm run watch' C-m + +select-pane -t 0 +``` + +Usage: +```bash +tw-create my-webapp webdev +``` + +--- + +## Tips & Tricks + +### SSH Manager + +**1. Auto-sync dotfiles on first connect:** +```bash +ssh-save newserver user@new.com +ssh-sync-dotfiles newserver +ssh-connect newserver +``` + +**2. Use descriptive names:** +```bash +ssh-save aws-prod-db "user@prod-db.amazonaws.com" 22 ~/.ssh/aws-prod.pem "" "Production Database" +``` + +**3. Port forwarding shorthand:** +```bash +# Local port 8080 → Remote port 80 +ssh-save webapp "user@server.com" 22 "" "-L 8080:localhost:80" +``` + +### Tmux Workspaces + +**1. Project-specific setup:** +Create `.tmux-project` in project root with workspace commands: +```bash +#!/bin/bash +tw-create ${PWD##*/} dev +tw ${PWD##*/} +``` + +**2. Quick workspace switching:** +Add to your `.zshrc`: +```bash +# Switch to workspace by number +alias tw1='tw project1' +alias tw2='tw project2' +alias tw3='tw project3' +``` + +**3. Persistent sessions:** +Workspaces survive reboots if you use `tmux-resurrect` or `tmux-continuum` plugins. + +**4. Multi-pane commands:** +```bash +# Send command to all panes +tw-sync # Enable sync +echo "Running on all panes" # Typed in all +tw-sync # Disable sync +``` + +--- + +## Tmux Configuration Enhancements + +Add to `~/.tmux.conf` for better integration: + +```tmux +# Better pane navigation (vim-style) +bind h select-pane -L +bind j select-pane -D +bind k select-pane -U +bind l select-pane -R + +# Quick pane resizing +bind -r H resize-pane -L 5 +bind -r J resize-pane -D 5 +bind -r K resize-pane -U 5 +bind -r L resize-pane -R 5 + +# Split panes using | and - +bind | split-window -h +bind - split-window -v + +# Reload config +bind r source-file ~/.tmux.conf \; display "Reloaded!" + +# Enable mouse support +set -g mouse on + +# Status bar +set -g status-position bottom +set -g status-style 'bg=colour234 fg=colour137' +set -g status-left '#[fg=colour233,bg=colour245,bold] #S ' +set -g status-right '#[fg=colour233,bg=colour245,bold] %d/%m %H:%M ' + +# Pane borders +set -g pane-border-style 'fg=colour238' +set -g pane-active-border-style 'fg=colour51' +``` + +--- + +## Troubleshooting + +### SSH Issues + +**Connection fails:** +```bash +# Test connection directly +ssh -v user@host + +# Check profile +ssh-list +ssh-edit myprofile +``` + +**Tmux not attaching on remote:** +```bash +# Check if tmux is installed on remote +ssh user@host 'which tmux' + +# Disable auto-tmux for specific connection +SSH_AUTO_TMUX=false ssh-connect myprofile +``` + +### Tmux Issues + +**Workspace not found:** +```bash +# List all tmux sessions +tmux ls + +# Check session prefix +echo $TW_SESSION_PREFIX +``` + +**Template not working:** +```bash +# Validate template syntax +cat ~/.dotfiles/.tmux-templates/dev.tmux + +# Recreate default templates +rm ~/.dotfiles/.tmux-templates/* +source ~/.zshrc # Will regenerate +``` + +**Panes not splitting correctly:** +```bash +# Check tmux version +tmux -V + +# Update tmux if < 3.0 +# Some split options may not work on older versions +``` + +--- + +## Migration from Existing Setup + +### If you already use SSH config: + +Convert `~/.ssh/config` entries to profiles: + +```bash +# Old ~/.ssh/config: +# Host prod +# HostName prod.example.com +# User ubuntu +# Port 22 +# IdentityFile ~/.ssh/prod.pem + +# New: +ssh-save prod ubuntu@prod.example.com 22 ~/.ssh/prod.pem +``` + +### If you already use tmux: + +Existing sessions aren't affected. The workspace manager only manages sessions with the `work-` prefix (configurable). + +--- + +## Next Steps + +1. Save your most-used SSH connections +2. Create workspaces for your projects +3. Customize templates for your workflow +4. Set up project-specific workspace scripts +5. Add fuzzy search shortcuts to your workflow + +Enjoy your enhanced terminal productivity! diff --git a/zsh/aliases.zsh b/zsh/aliases.zsh index 6ebcaab..87c169c 100644 --- a/zsh/aliases.zsh +++ b/zsh/aliases.zsh @@ -50,9 +50,8 @@ dfupdate() { _df_run dotfiles-update.sh "$@"; } dfv() { _df_run dotfiles-version.sh "$@"; } dfversion() { _df_run dotfiles-version.sh "$@"; } -# Stats - shell analytics +# Stats - shell analytics (removed short 'stats' alias to force explicit usage) dfstats() { _df_run dotfiles-stats.sh "$@"; } -stats() { _df_run dotfiles-stats.sh "$@"; } tophist() { _df_run dotfiles-stats.sh --top "$@"; } suggest() { _df_run dotfiles-stats.sh --suggest "$@"; } @@ -108,6 +107,8 @@ dotfiles-cli() { echo echo "Aliases:" echo " dfd, dffix, dfs, dfpush, dfpull, dfu, dfv, dfstats, vault" + echo + echo "Note: 'stats' alias removed - use 'dfstats' instead" ;; esac } diff --git a/zsh/functions/python-templates.zsh b/zsh/functions/python-templates.zsh new file mode 100644 index 0000000..cc5cee1 --- /dev/null +++ b/zsh/functions/python-templates.zsh @@ -0,0 +1,1446 @@ +# ============================================================================ +# Python Project Template Functions +# ============================================================================ +# Quick project scaffolding with virtual environments +# +# Usage: +# py-new # Create new Python project +# py-django # Create Django project +# py-flask # Create Flask project +# py-fastapi # Create FastAPI project +# py-data # Create data science project +# py-cli # Create CLI tool project +# +# Add to .zshrc: +# source ~/.dotfiles/zsh/functions/python-templates.zsh +# ============================================================================ + +# ============================================================================ +# Configuration +# ============================================================================ + +typeset -g PY_TEMPLATE_BASE_DIR="${PY_TEMPLATE_BASE_DIR:-$HOME/projects}" +typeset -g PY_TEMPLATE_PYTHON="${PY_TEMPLATE_PYTHON:-python3}" +typeset -g PY_TEMPLATE_VENV_NAME="${PY_TEMPLATE_VENV_NAME:-venv}" +typeset -g PY_TEMPLATE_USE_POETRY="${PY_TEMPLATE_USE_POETRY:-false}" +typeset -g PY_TEMPLATE_GIT_INIT="${PY_TEMPLATE_GIT_INIT:-true}" + +# Colors +typeset -g PY_GREEN=$'\033[0;32m' +typeset -g PY_BLUE=$'\033[0;34m' +typeset -g PY_YELLOW=$'\033[1;33m' +typeset -g PY_CYAN=$'\033[0;36m' +typeset -g PY_NC=$'\033[0m' + +# ============================================================================ +# Helper Functions +# ============================================================================ + +_py_print_step() { + echo -e "${PY_BLUE}==>${PY_NC} $1" +} + +_py_print_success() { + echo -e "${PY_GREEN}✓${PY_NC} $1" +} + +_py_print_info() { + echo -e "${PY_CYAN}ℹ${PY_NC} $1" +} + +_py_check_project_name() { + local name="$1" + if [[ -z "$name" ]]; then + echo -e "${PY_YELLOW}⚠${PY_NC} Project name required" + return 1 + fi + if [[ -d "$name" ]]; then + echo -e "${PY_YELLOW}⚠${PY_NC} Directory '$name' already exists" + return 1 + fi + return 0 +} + +_py_create_venv() { + local project_dir="$1" + + _py_print_step "Creating virtual environment" + + if [[ "$PY_TEMPLATE_USE_POETRY" == "true" ]] && command -v poetry &>/dev/null; then + cd "$project_dir" + poetry init --no-interaction + poetry env use "$PY_TEMPLATE_PYTHON" + _py_print_success "Poetry environment created" + else + "$PY_TEMPLATE_PYTHON" -m venv "$project_dir/$PY_TEMPLATE_VENV_NAME" + _py_print_success "Virtual environment created: $PY_TEMPLATE_VENV_NAME" + fi +} + +_py_create_gitignore() { + local project_dir="$1" + + cat > "$project_dir/.gitignore" << 'EOF' +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual Environment +venv/ +env/ +ENV/ +.venv + +# IDEs +.vscode/ +.idea/ +*.swp +*.swo +*~ +.DS_Store + +# Testing +.pytest_cache/ +.coverage +htmlcov/ +.tox/ +.hypothesis/ + +# Environment variables +.env +.env.local +.env.*.local + +# Logs +*.log +logs/ + +# Database +*.db +*.sqlite +*.sqlite3 + +# Distribution / packaging +.Python +build/ +dist/ +*.egg-info/ + +# Poetry +poetry.lock + +# Jupyter Notebook +.ipynb_checkpoints +*.ipynb + +# MyPy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Type checking +.pyre/ +.pytype/ +EOF + + _py_print_success "Created .gitignore" +} + +_py_create_readme() { + local project_name="$1" + local project_dir="$2" + local description="$3" + + cat > "$project_dir/README.md" << EOF +# $project_name + +$description + +## Setup + +### Using venv + +\`\`\`bash +# Create virtual environment +python3 -m venv venv + +# Activate virtual environment +source venv/bin/activate # Linux/Mac +# or +venv\\Scripts\\activate # Windows + +# Install dependencies +pip install -r requirements.txt +\`\`\` + +### Using Poetry (alternative) + +\`\`\`bash +poetry install +poetry shell +\`\`\` + +## Development + +\`\`\`bash +# Activate virtual environment +source venv/bin/activate + +# Run the application +python main.py +\`\`\` + +## Testing + +\`\`\`bash +# Install dev dependencies +pip install pytest pytest-cov + +# Run tests +pytest + +# Run tests with coverage +pytest --cov=src tests/ +\`\`\` + +## Project Structure + +\`\`\` +$project_name/ +├── src/ # Source code +├── tests/ # Test files +├── docs/ # Documentation +├── .gitignore +├── README.md +├── requirements.txt +└── setup.py +\`\`\` + +## License + +MIT +EOF + + _py_print_success "Created README.md" +} + +_py_init_git() { + local project_dir="$1" + + if [[ "$PY_TEMPLATE_GIT_INIT" == "true" ]]; then + cd "$project_dir" + git init + git add . + git commit -m "Initial commit: project scaffolding" + _py_print_success "Git repository initialized" + fi +} + +_py_show_next_steps() { + local project_name="$1" + local has_venv="$2" + + echo + echo -e "${PY_CYAN}Next steps:${PY_NC}" + echo + echo " cd $project_name" + + if [[ "$has_venv" == "true" ]]; then + if [[ "$PY_TEMPLATE_USE_POETRY" == "true" ]]; then + echo " poetry shell" + else + echo " source $PY_TEMPLATE_VENV_NAME/bin/activate" + fi + fi + + echo " # Start coding!" + echo +} + +# ============================================================================ +# Base Python Project Template +# ============================================================================ + +py-new() { + local project_name="$1" + local project_type="${2:-basic}" + + _py_check_project_name "$project_name" || return 1 + + echo -e "${PY_BLUE}╔════════════════════════════════════════════════════════════╗${PY_NC}" + echo -e "${PY_BLUE}║${PY_NC} Creating Python Project: $project_name" + echo -e "${PY_BLUE}╚════════════════════════════════════════════════════════════╝${PY_NC}" + echo + + # Create project structure + _py_print_step "Creating project structure" + mkdir -p "$project_name"/{src,tests,docs} + + # Create __init__.py files + touch "$project_name/src/__init__.py" + touch "$project_name/tests/__init__.py" + + # Create main.py + cat > "$project_name/src/main.py" << 'EOF' +#!/usr/bin/env python3 +""" +Main module for the application. +""" + +def main(): + """Main entry point.""" + print("Hello from Python!") + +if __name__ == "__main__": + main() +EOF + + # Create basic test + cat > "$project_name/tests/test_main.py" << 'EOF' +"""Tests for main module.""" +import pytest +from src.main import main + +def test_main(): + """Test main function runs without error.""" + main() +EOF + + # Create requirements.txt + cat > "$project_name/requirements.txt" << 'EOF' +# Production dependencies + +# Development dependencies (uncomment as needed) +# pytest>=7.0.0 +# pytest-cov>=4.0.0 +# black>=23.0.0 +# flake8>=6.0.0 +# mypy>=1.0.0 +# pylint>=2.17.0 +EOF + + # Create setup.py + cat > "$project_name/setup.py" << EOF +from setuptools import setup, find_packages + +setup( + name="$project_name", + version="0.1.0", + packages=find_packages(), + install_requires=[], + python_requires=">=3.8", +) +EOF + + _py_print_success "Project structure created" + + # Create virtual environment + _py_create_venv "$project_name" + + # Create .gitignore + _py_create_gitignore "$project_name" + + # Create README + _py_create_readme "$project_name" "$project_name" "A Python project" + + # Initialize git + _py_init_git "$project_name" + + echo + _py_print_success "Project '$project_name' created successfully!" + _py_show_next_steps "$project_name" "true" +} + +# ============================================================================ +# Django Project Template +# ============================================================================ + +py-django() { + local project_name="$1" + + _py_check_project_name "$project_name" || return 1 + + echo -e "${PY_BLUE}╔════════════════════════════════════════════════════════════╗${PY_NC}" + echo -e "${PY_BLUE}║${PY_NC} Creating Django Project: $project_name" + echo -e "${PY_BLUE}╚════════════════════════════════════════════════════════════╝${PY_NC}" + echo + + # Create project directory + mkdir -p "$project_name" + + # Create virtual environment first + _py_create_venv "$project_name" + + # Install Django + _py_print_step "Installing Django" + cd "$project_name" + + if [[ "$PY_TEMPLATE_USE_POETRY" == "true" ]]; then + poetry add django + else + "$PY_TEMPLATE_VENV_NAME/bin/pip" install django + fi + + # Create Django project + _py_print_step "Creating Django project structure" + + if [[ "$PY_TEMPLATE_USE_POETRY" == "true" ]]; then + poetry run django-admin startproject config . + else + "$PY_TEMPLATE_VENV_NAME/bin/django-admin" startproject config . + fi + + # Create requirements.txt + cat > "requirements.txt" << 'EOF' +Django>=4.2.0 +python-decouple>=3.8 +psycopg2-binary>=2.9.0 # PostgreSQL adapter +django-environ>=0.11.0 + +# Development +django-debug-toolbar>=4.2.0 +django-extensions>=3.2.0 +EOF + + # Create .env.example + cat > ".env.example" << 'EOF' +SECRET_KEY=your-secret-key-here +DEBUG=True +ALLOWED_HOSTS=localhost,127.0.0.1 + +# Database +DATABASE_URL=sqlite:///db.sqlite3 +# DATABASE_URL=postgresql://user:password@localhost:5432/dbname +EOF + + # Create README + cat > "README.md" << EOF +# $project_name + +A Django web application. + +## Setup + +\`\`\`bash +# Create and activate virtual environment +python3 -m venv venv +source venv/bin/activate + +# Install dependencies +pip install -r requirements.txt + +# Copy environment file +cp .env.example .env +# Edit .env with your settings + +# Run migrations +python manage.py migrate + +# Create superuser +python manage.py createsuperuser + +# Run development server +python manage.py runserver +\`\`\` + +## Development + +\`\`\`bash +# Create a new app +python manage.py startapp myapp + +# Make migrations +python manage.py makemigrations + +# Run tests +python manage.py test + +# Collect static files +python manage.py collectstatic +\`\`\` + +## Project Structure + +\`\`\` +$project_name/ +├── config/ # Django settings +├── apps/ # Django apps +├── static/ # Static files +├── templates/ # HTML templates +├── media/ # Uploaded files +├── manage.py +└── requirements.txt +\`\`\` +EOF + + # Create directories + mkdir -p apps static templates media + + _py_create_gitignore "." + _py_init_git "." + + cd .. + + echo + _py_print_success "Django project '$project_name' created!" + _py_print_info "Don't forget to set SECRET_KEY in .env" + _py_show_next_steps "$project_name" "true" +} + +# ============================================================================ +# Flask Project Template +# ============================================================================ + +py-flask() { + local project_name="$1" + + _py_check_project_name "$project_name" || return 1 + + echo -e "${PY_BLUE}╔════════════════════════════════════════════════════════════╗${PY_NC}" + echo -e "${PY_BLUE}║${PY_NC} Creating Flask Project: $project_name" + echo -e "${PY_BLUE}╚════════════════════════════════════════════════════════════╝${PY_NC}" + echo + + # Create project structure + mkdir -p "$project_name"/{app/{templates,static/{css,js,img}},tests} + + # Create virtual environment + _py_create_venv "$project_name" + + cd "$project_name" + + # Install Flask + _py_print_step "Installing Flask" + if [[ "$PY_TEMPLATE_USE_POETRY" == "true" ]]; then + poetry add flask + else + "$PY_TEMPLATE_VENV_NAME/bin/pip" install flask + fi + + # Create app/__init__.py + cat > "app/__init__.py" << 'EOF' +"""Flask application factory.""" +from flask import Flask + +def create_app(config=None): + """Create and configure the Flask application.""" + app = Flask(__name__) + + if config: + app.config.from_object(config) + + # Register blueprints + from app.routes import main + app.register_blueprint(main) + + return app +EOF + + # Create app/routes.py + cat > "app/routes.py" << 'EOF' +"""Application routes.""" +from flask import Blueprint, render_template + +main = Blueprint('main', __name__) + +@main.route('/') +def index(): + """Home page.""" + return render_template('index.html') + +@main.route('/api/health') +def health(): + """Health check endpoint.""" + return {'status': 'healthy'} +EOF + + # Create app.py + cat > "app.py" << 'EOF' +#!/usr/bin/env python3 +"""Flask application entry point.""" +from app import create_app + +app = create_app() + +if __name__ == '__main__': + app.run(debug=True, host='0.0.0.0', port=5000) +EOF + chmod +x app.py + + # Create base template + cat > "app/templates/base.html" << 'EOF' + + + + + + {% block title %}Flask App{% endblock %} + + + +
+ {% block content %}{% endblock %} +
+ + + +EOF + + # Create index template + cat > "app/templates/index.html" << 'EOF' +{% extends "base.html" %} + +{% block title %}Home{% endblock %} + +{% block content %} +

Welcome to Flask!

+

Your app is running.

+{% endblock %} +EOF + + # Create basic CSS + cat > "app/static/css/style.css" << 'EOF' +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + line-height: 1.6; + padding: 2rem; +} + +main { + max-width: 1200px; + margin: 0 auto; +} +EOF + + # Create basic JS + cat > "app/static/js/main.js" << 'EOF' +console.log('Flask app loaded'); +EOF + + # Create requirements.txt + cat > "requirements.txt" << 'EOF' +Flask>=3.0.0 +python-decouple>=3.8 + +# Development +flask-debugtoolbar>=0.14.0 +EOF + + # Create .env.example + cat > ".env.example" << 'EOF' +FLASK_APP=app.py +FLASK_ENV=development +SECRET_KEY=your-secret-key-here +EOF + + # Create README + cat > "README.md" << EOF +# $project_name + +A Flask web application. + +## Setup + +\`\`\`bash +# Create and activate virtual environment +python3 -m venv venv +source venv/bin/activate + +# Install dependencies +pip install -r requirements.txt + +# Copy environment file +cp .env.example .env + +# Run the application +python app.py +\`\`\` + +Visit: http://localhost:5000 + +## Development + +\`\`\`bash +# Run with auto-reload +export FLASK_ENV=development +python app.py + +# Run tests +pytest +\`\`\` + +## Project Structure + +\`\`\` +$project_name/ +├── app/ +│ ├── __init__.py +│ ├── routes.py +│ ├── templates/ +│ └── static/ +├── tests/ +├── app.py +└── requirements.txt +\`\`\` +EOF + + _py_create_gitignore "." + _py_init_git "." + + cd .. + + echo + _py_print_success "Flask project '$project_name' created!" + _py_show_next_steps "$project_name" "true" +} + +# ============================================================================ +# FastAPI Project Template +# ============================================================================ + +py-fastapi() { + local project_name="$1" + + _py_check_project_name "$project_name" || return 1 + + echo -e "${PY_BLUE}╔════════════════════════════════════════════════════════════╗${PY_NC}" + echo -e "${PY_BLUE}║${PY_NC} Creating FastAPI Project: $project_name" + echo -e "${PY_BLUE}╚════════════════════════════════════════════════════════════╝${PY_NC}" + echo + + # Create project structure + mkdir -p "$project_name"/{app/{api,models,schemas,services},tests} + + # Create virtual environment + _py_create_venv "$project_name" + + cd "$project_name" + + # Install FastAPI + _py_print_step "Installing FastAPI and dependencies" + if [[ "$PY_TEMPLATE_USE_POETRY" == "true" ]]; then + poetry add fastapi uvicorn pydantic + else + "$PY_TEMPLATE_VENV_NAME/bin/pip" install fastapi uvicorn[standard] pydantic + fi + + # Create app/__init__.py + touch "app/__init__.py" + + # Create main.py + cat > "app/main.py" << 'EOF' +"""FastAPI application.""" +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from app.api import router + +app = FastAPI( + title="My API", + description="FastAPI application", + version="0.1.0" +) + +# CORS +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Include routers +app.include_router(router, prefix="/api") + +@app.get("/") +def root(): + """Root endpoint.""" + return {"message": "Welcome to FastAPI"} + +@app.get("/health") +def health(): + """Health check.""" + return {"status": "healthy"} +EOF + + # Create api/__init__.py + cat > "app/api/__init__.py" << 'EOF' +"""API routes.""" +from fastapi import APIRouter + +router = APIRouter() + +@router.get("/items") +def list_items(): + """List all items.""" + return {"items": []} + +@router.get("/items/{item_id}") +def get_item(item_id: int): + """Get a specific item.""" + return {"id": item_id, "name": f"Item {item_id}"} +EOF + + # Create schemas/__init__.py + cat > "app/schemas/__init__.py" << 'EOF' +"""Pydantic schemas.""" +from pydantic import BaseModel + +class ItemBase(BaseModel): + """Base item schema.""" + name: str + description: str | None = None + +class ItemCreate(ItemBase): + """Schema for creating items.""" + pass + +class Item(ItemBase): + """Full item schema.""" + id: int + + class Config: + from_attributes = True +EOF + + # Create models, services __init__.py + touch "app/models/__init__.py" + touch "app/services/__init__.py" + + # Create requirements.txt + cat > "requirements.txt" << 'EOF' +fastapi>=0.104.0 +uvicorn[standard]>=0.24.0 +pydantic>=2.5.0 +python-decouple>=3.8 + +# Optional +# sqlalchemy>=2.0.0 +# alembic>=1.12.0 +# python-jose[cryptography]>=3.3.0 +# passlib[bcrypt]>=1.7.4 + +# Development +pytest>=7.4.0 +httpx>=0.25.0 +EOF + + # Create run.py + cat > "run.py" << 'EOF' +#!/usr/bin/env python3 +"""Run the FastAPI application.""" +import uvicorn + +if __name__ == "__main__": + uvicorn.run( + "app.main:app", + host="0.0.0.0", + port=8000, + reload=True + ) +EOF + chmod +x run.py + + # Create .env.example + cat > ".env.example" << 'EOF' +API_KEY=your-api-key +DATABASE_URL=sqlite:///./app.db +SECRET_KEY=your-secret-key +EOF + + # Create README + cat > "README.md" << EOF +# $project_name + +A FastAPI application. + +## Setup + +\`\`\`bash +# Create and activate virtual environment +python3 -m venv venv +source venv/bin/activate + +# Install dependencies +pip install -r requirements.txt + +# Copy environment file +cp .env.example .env + +# Run the application +python run.py +\`\`\` + +Visit: +- API: http://localhost:8000 +- Docs: http://localhost:8000/docs +- ReDoc: http://localhost:8000/redoc + +## Development + +\`\`\`bash +# Run with auto-reload +uvicorn app.main:app --reload + +# Run tests +pytest +\`\`\` + +## Project Structure + +\`\`\` +$project_name/ +├── app/ +│ ├── api/ # API routes +│ ├── models/ # Database models +│ ├── schemas/ # Pydantic schemas +│ ├── services/ # Business logic +│ └── main.py +├── tests/ +├── run.py +└── requirements.txt +\`\`\` +EOF + + _py_create_gitignore "." + _py_init_git "." + + cd .. + + echo + _py_print_success "FastAPI project '$project_name' created!" + _py_print_info "Docs will be at: http://localhost:8000/docs" + _py_show_next_steps "$project_name" "true" +} + +# ============================================================================ +# Data Science Project Template +# ============================================================================ + +py-data() { + local project_name="$1" + + _py_check_project_name "$project_name" || return 1 + + echo -e "${PY_BLUE}╔════════════════════════════════════════════════════════════╗${PY_NC}" + echo -e "${PY_BLUE}║${PY_NC} Creating Data Science Project: $project_name" + echo -e "${PY_BLUE}╚════════════════════════════════════════════════════════════╝${PY_NC}" + echo + + # Create project structure + mkdir -p "$project_name"/{data/{raw,processed,external},notebooks,src,models,reports/{figures}} + + # Create virtual environment + _py_create_venv "$project_name" + + cd "$project_name" + + # Install data science packages + _py_print_step "Installing data science packages" + if [[ "$PY_TEMPLATE_USE_POETRY" == "true" ]]; then + poetry add pandas numpy matplotlib seaborn jupyter + else + "$PY_TEMPLATE_VENV_NAME/bin/pip" install pandas numpy matplotlib seaborn jupyter notebook + fi + + # Create requirements.txt + cat > "requirements.txt" << 'EOF' +# Core +pandas>=2.1.0 +numpy>=1.24.0 +matplotlib>=3.8.0 +seaborn>=0.13.0 + +# Machine Learning +scikit-learn>=1.3.0 +# tensorflow>=2.14.0 +# torch>=2.1.0 + +# Jupyter +jupyter>=1.0.0 +notebook>=7.0.0 +ipykernel>=6.25.0 + +# Utilities +python-decouple>=3.8 + +# Development +pytest>=7.4.0 +black>=23.9.0 +pylint>=2.17.0 +EOF + + # Create starter notebook + cat > "notebooks/01_exploration.ipynb" << 'EOF' +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Data Exploration\n", + "\n", + "Initial data exploration and analysis." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import seaborn as sns\n", + "\n", + "%matplotlib inline\n", + "sns.set_style('whitegrid')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Load data\n", + "# df = pd.read_csv('../data/raw/data.csv')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} +EOF + + # Create src/__init__.py + touch "src/__init__.py" + + # Create example processing script + cat > "src/process_data.py" << 'EOF' +#!/usr/bin/env python3 +"""Data processing utilities.""" +import pandas as pd + +def load_raw_data(filepath): + """Load raw data from file.""" + return pd.read_csv(filepath) + +def clean_data(df): + """Clean and preprocess data.""" + # Remove duplicates + df = df.drop_duplicates() + + # Handle missing values + df = df.dropna() + + return df + +def main(): + """Main processing pipeline.""" + # Load data + df = load_raw_data('data/raw/data.csv') + + # Clean data + df = clean_data(df) + + # Save processed data + df.to_csv('data/processed/data_clean.csv', index=False) + print(f"Processed {len(df)} rows") + +if __name__ == '__main__': + main() +EOF + + # Create README + cat > "README.md" << EOF +# $project_name + +A data science project. + +## Setup + +\`\`\`bash +# Create and activate virtual environment +python3 -m venv venv +source venv/bin/activate + +# Install dependencies +pip install -r requirements.txt + +# Install Jupyter kernel +python -m ipykernel install --user --name=$project_name +\`\`\` + +## Usage + +\`\`\`bash +# Start Jupyter Notebook +jupyter notebook + +# Run processing script +python src/process_data.py +\`\`\` + +## Project Structure + +\`\`\` +$project_name/ +├── data/ +│ ├── raw/ # Original, immutable data +│ ├── processed/ # Cleaned, processed data +│ └── external/ # External data sources +├── notebooks/ # Jupyter notebooks +├── src/ # Source code +├── models/ # Trained models +├── reports/ # Analysis reports +│ └── figures/ # Generated graphics +└── requirements.txt +\`\`\` + +## Data + +Place your raw data files in \`data/raw/\`. + +## Notebooks + +1. \`01_exploration.ipynb\` - Initial data exploration +2. Add more notebooks as needed + +## Guidelines + +- Keep raw data immutable +- Document your analysis in notebooks +- Extract reusable code to \`src/\` +- Save processed data to \`data/processed/\` +- Save figures to \`reports/figures/\` +EOF + + # Create data README + cat > "data/README.md" << 'EOF' +# Data Directory + +## Structure + +- `raw/` - Original, immutable data dump +- `processed/` - Cleaned and processed data +- `external/` - Data from third party sources + +## Guidelines + +- Never modify files in `raw/` +- Document data sources +- Include data dictionaries where applicable +EOF + + _py_create_gitignore "." + + # Update gitignore for data science + cat >> ".gitignore" << 'EOF' + +# Data Science specific +*.pkl +*.h5 +*.hdf5 +*.parquet + +# Data files (comment out if you want to track them) +data/raw/* +data/processed/* +!data/raw/.gitkeep +!data/processed/.gitkeep + +# Model files +models/*.pkl +models/*.h5 + +# Jupyter +.ipynb_checkpoints + +# Large files +*.csv +*.tsv +*.dat +EOF + + # Create .gitkeep files + touch data/raw/.gitkeep data/processed/.gitkeep data/external/.gitkeep + + _py_init_git "." + + cd .. + + echo + _py_print_success "Data science project '$project_name' created!" + _py_print_info "Start Jupyter: jupyter notebook" + _py_show_next_steps "$project_name" "true" +} + +# ============================================================================ +# CLI Tool Project Template +# ============================================================================ + +py-cli() { + local project_name="$1" + + _py_check_project_name "$project_name" || return 1 + + echo -e "${PY_BLUE}╔════════════════════════════════════════════════════════════╗${PY_NC}" + echo -e "${PY_BLUE}║${PY_NC} Creating CLI Tool Project: $project_name" + echo -e "${PY_BLUE}╚════════════════════════════════════════════════════════════╝${PY_NC}" + echo + + # Create project structure + mkdir -p "$project_name"/{src/$project_name,tests} + + # Create virtual environment + _py_create_venv "$project_name" + + cd "$project_name" + + # Install click + _py_print_step "Installing click for CLI" + if [[ "$PY_TEMPLATE_USE_POETRY" == "true" ]]; then + poetry add click + else + "$PY_TEMPLATE_VENV_NAME/bin/pip" install click + fi + + # Create package __init__.py + cat > "src/$project_name/__init__.py" << 'EOF' +"""CLI tool package.""" +__version__ = "0.1.0" +EOF + + # Create cli.py + cat > "src/$project_name/cli.py" << 'EOF' +#!/usr/bin/env python3 +"""Command-line interface.""" +import click + +@click.group() +@click.version_option() +def cli(): + """CLI tool - A command-line utility.""" + pass + +@cli.command() +@click.argument('name', default='World') +@click.option('--greeting', default='Hello', help='Greeting to use') +def greet(name, greeting): + """Greet someone.""" + click.echo(f"{greeting}, {name}!") + +@cli.command() +@click.option('--count', default=1, help='Number of times to repeat') +@click.argument('message') +def repeat(message, count): + """Repeat a message.""" + for _ in range(count): + click.echo(message) + +if __name__ == '__main__': + cli() +EOF + chmod +x "src/$project_name/cli.py" + + # Create __main__.py + cat > "src/$project_name/__main__.py" << EOF +"""Allow running as python -m $project_name""" +from $project_name.cli import cli + +if __name__ == '__main__': + cli() +EOF + + # Create setup.py + cat > "setup.py" << EOF +from setuptools import setup, find_packages + +with open("README.md", "r", encoding="utf-8") as fh: + long_description = fh.read() + +setup( + name="$project_name", + version="0.1.0", + author="Your Name", + author_email="you@example.com", + description="A command-line tool", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/yourusername/$project_name", + packages=find_packages(where="src"), + package_dir={"": "src"}, + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ], + python_requires=">=3.8", + install_requires=[ + "click>=8.0.0", + ], + entry_points={ + "console_scripts": [ + "$project_name=$project_name.cli:cli", + ], + }, +) +EOF + + # Create requirements.txt + cat > "requirements.txt" << 'EOF' +click>=8.1.0 + +# Development +pytest>=7.4.0 +black>=23.9.0 +EOF + + # Create README + cat > "README.md" << EOF +# $project_name + +A command-line tool built with Python and Click. + +## Installation + +\`\`\`bash +# Development installation +pip install -e . + +# Or install from source +pip install . +\`\`\` + +## Usage + +\`\`\`bash +# Show help +$project_name --help + +# Example commands +$project_name greet +$project_name greet Alice +$project_name greet --greeting "Hi" Bob +$project_name repeat "Hello" --count 3 +\`\`\` + +## Development + +\`\`\`bash +# Create virtual environment +python3 -m venv venv +source venv/bin/activate + +# Install in development mode +pip install -e . + +# Run tests +pytest + +# Format code +black src/ +\`\`\` + +## Project Structure + +\`\`\` +$project_name/ +├── src/ +│ └── $project_name/ +│ ├── __init__.py +│ ├── __main__.py +│ └── cli.py +├── tests/ +├── setup.py +└── requirements.txt +\`\`\` + +## Adding Commands + +Add new commands to \`src/$project_name/cli.py\`: + +\`\`\`python +@cli.command() +@click.argument('arg') +def mycommand(arg): + """Description of command.""" + click.echo(f"Running with: {arg}") +\`\`\` +EOF + + _py_create_gitignore "." + _py_init_git "." + + cd .. + + echo + _py_print_success "CLI tool project '$project_name' created!" + _py_print_info "Install with: pip install -e $project_name" + _py_show_next_steps "$project_name" "true" +} + +# ============================================================================ +# Aliases +# ============================================================================ + +alias pynew='py-new' +alias pydjango='py-django' +alias pyflask='py-flask' +alias pyfast='py-fastapi' +alias pydata='py-data' +alias pycli='py-cli' + +# Quick venv activation +venv() { + if [[ -d "venv" ]]; then + source venv/bin/activate + elif [[ -d ".venv" ]]; then + source .venv/bin/activate + elif [[ -d "env" ]]; then + source env/bin/activate + else + echo "No virtual environment found (venv, .venv, or env)" + return 1 + fi +} + +# ============================================================================ +# Initialization Message +# ============================================================================ + +# Uncomment to show on load: +# echo "Python templates loaded. Use: py-new, py-django, py-flask, py-fastapi, py-data, py-cli" diff --git a/zsh/functions/ssh-manager.zsh b/zsh/functions/ssh-manager.zsh new file mode 100644 index 0000000..4c3f536 --- /dev/null +++ b/zsh/functions/ssh-manager.zsh @@ -0,0 +1,462 @@ +# ============================================================================ +# SSH Session Manager with Tmux Integration +# ============================================================================ +# Manage SSH connections with automatic tmux session handling +# +# Usage: +# ssh-save # Save SSH connection +# ssh-connect # Connect and attach/create tmux session +# ssh-list # List all saved connections +# ssh-delete # Delete saved connection +# ssh-edit # Edit connection details +# sshf # Fuzzy search and connect +# +# Features: +# - Automatic tmux session attach/create on remote host +# - Named sessions per connection +# - Connection profiles with SSH options +# - Auto-reconnect support +# - Dotfiles sync to remote (optional) +# +# Add to .zshrc: +# source ~/.dotfiles/zsh/functions/ssh-manager.zsh +# ============================================================================ + +# ============================================================================ +# Configuration +# ============================================================================ + +typeset -g SSH_PROFILES_FILE="${SSH_PROFILES_FILE:-$HOME/.dotfiles/.ssh-profiles}" +typeset -g SSH_AUTO_TMUX="${SSH_AUTO_TMUX:-true}" +typeset -g SSH_TMUX_SESSION_PREFIX="${SSH_TMUX_SESSION_PREFIX:-ssh}" +typeset -g SSH_SYNC_DOTFILES="${SSH_SYNC_DOTFILES:-ask}" + +# Colors +typeset -g SSH_GREEN=$'\033[0;32m' +typeset -g SSH_BLUE=$'\033[0;34m' +typeset -g SSH_YELLOW=$'\033[1;33m' +typeset -g SSH_CYAN=$'\033[0;36m' +typeset -g SSH_RED=$'\033[0;31m' +typeset -g SSH_NC=$'\033[0m' + +# ============================================================================ +# Helper Functions +# ============================================================================ + +_ssh_print_step() { + echo -e "${SSH_BLUE}==>${SSH_NC} $1" +} + +_ssh_print_success() { + echo -e "${SSH_GREEN}✓${SSH_NC} $1" +} + +_ssh_print_error() { + echo -e "${SSH_RED}✗${SSH_NC} $1" +} + +_ssh_print_info() { + echo -e "${SSH_CYAN}ℹ${SSH_NC} $1" +} + +_ssh_init_profiles() { + if [[ ! -f "$SSH_PROFILES_FILE" ]]; then + mkdir -p "$(dirname "$SSH_PROFILES_FILE")" + cat > "$SSH_PROFILES_FILE" << 'EOF' +# SSH Connection Profiles +# Format: name|user@host|port|key_file|options|description +# +# Example: +# prod|user@prod.example.com|22|~/.ssh/prod_key|-L 8080:localhost:80|Production server +# dev|user@dev.example.com|2222||ForwardAgent=yes|Development server +EOF + _ssh_print_success "Created SSH profiles file: $SSH_PROFILES_FILE" + fi +} + +_ssh_parse_profile() { + local name="$1" + local line=$(grep "^${name}|" "$SSH_PROFILES_FILE" 2>/dev/null | head -1) + + if [[ -z "$line" ]]; then + return 1 + fi + + # Parse: name|connection|port|key|options|description + IFS='|' read -r profile_name connection port key_file ssh_opts description <<< "$line" + + echo "$connection|$port|$key_file|$ssh_opts|$description" +} + +# ============================================================================ +# SSH Profile Management +# ============================================================================ + +ssh-save() { + local name="$1" + local connection="$2" + local port="${3:-22}" + local key_file="${4:-}" + local options="${5:-}" + local description="${6:-}" + + _ssh_init_profiles + + if [[ -z "$name" || -z "$connection" ]]; then + echo "Usage: ssh-save [port] [key_file] [options] [description]" + echo + echo "Examples:" + echo " ssh-save prod user@prod.com" + echo " ssh-save dev user@dev.com 2222 ~/.ssh/dev_key" + echo " ssh-save vpn user@vpn.com 22 '' '-D 9090' 'VPN server'" + return 1 + fi + + # Check if profile exists + if grep -q "^${name}|" "$SSH_PROFILES_FILE" 2>/dev/null; then + echo -e "${SSH_YELLOW}⚠${SSH_NC} Profile '$name' already exists" + read -q "REPLY?Overwrite? [y/N]: " + echo + [[ ! "$REPLY" =~ ^[Yy]$ ]] && return 1 + + # Remove old entry + grep -v "^${name}|" "$SSH_PROFILES_FILE" > "${SSH_PROFILES_FILE}.tmp" + mv "${SSH_PROFILES_FILE}.tmp" "$SSH_PROFILES_FILE" + fi + + # Save new profile + echo "${name}|${connection}|${port}|${key_file}|${options}|${description}" >> "$SSH_PROFILES_FILE" + + _ssh_print_success "Saved SSH profile: $name" + echo " Connection: $connection" + [[ "$port" != "22" ]] && echo " Port: $port" + [[ -n "$key_file" ]] && echo " Key: $key_file" + [[ -n "$options" ]] && echo " Options: $options" + [[ -n "$description" ]] && echo " Description: $description" +} + +ssh-list() { + _ssh_init_profiles + + echo -e "${SSH_BLUE}╔════════════════════════════════════════════════════════════╗${SSH_NC}" + echo -e "${SSH_BLUE}║${SSH_NC} SSH Connection Profiles ${SSH_BLUE}║${SSH_NC}" + echo -e "${SSH_BLUE}╚════════════════════════════════════════════════════════════╝${SSH_NC}" + echo + + local has_profiles=false + while IFS='|' read -r name connection port key options description; do + # Skip comments and empty lines + [[ "$name" =~ ^# ]] && continue + [[ -z "$name" ]] && continue + + has_profiles=true + + echo -e "${SSH_GREEN}●${SSH_NC} ${SSH_CYAN}$name${SSH_NC}" + echo " Connection: $connection" + [[ "$port" != "22" && -n "$port" ]] && echo " Port: $port" + [[ -n "$key" ]] && echo " Key: $key" + [[ -n "$options" ]] && echo " Options: $options" + [[ -n "$description" ]] && echo " Description: $description" + echo + done < "$SSH_PROFILES_FILE" + + if [[ "$has_profiles" != true ]]; then + _ssh_print_info "No profiles saved yet" + echo + echo "Create a profile with:" + echo " ssh-save myserver user@example.com" + fi +} + +ssh-delete() { + local name="$1" + + if [[ -z "$name" ]]; then + echo "Usage: ssh-delete " + return 1 + fi + + _ssh_init_profiles + + if ! grep -q "^${name}|" "$SSH_PROFILES_FILE" 2>/dev/null; then + _ssh_print_error "Profile '$name' not found" + return 1 + fi + + # Remove profile + grep -v "^${name}|" "$SSH_PROFILES_FILE" > "${SSH_PROFILES_FILE}.tmp" + mv "${SSH_PROFILES_FILE}.tmp" "$SSH_PROFILES_FILE" + + _ssh_print_success "Deleted profile: $name" +} + +ssh-edit() { + local name="$1" + + if [[ -z "$name" ]]; then + # Edit entire file + ${EDITOR:-vim} "$SSH_PROFILES_FILE" + return + fi + + _ssh_init_profiles + + local profile_data=$(_ssh_parse_profile "$name") + if [[ -z "$profile_data" ]]; then + _ssh_print_error "Profile '$name' not found" + return 1 + fi + + IFS='|' read -r connection port key_file ssh_opts description <<< "$profile_data" + + echo -e "${SSH_CYAN}Editing profile: $name${SSH_NC}" + echo + + read "new_connection?Connection [$connection]: " + new_connection="${new_connection:-$connection}" + + read "new_port?Port [$port]: " + new_port="${new_port:-$port}" + + read "new_key?Key file [$key_file]: " + new_key="${new_key:-$key_file}" + + read "new_opts?SSH options [$ssh_opts]: " + new_opts="${new_opts:-$ssh_opts}" + + read "new_desc?Description [$description]: " + new_desc="${new_desc:-$description}" + + # Remove old and add new + grep -v "^${name}|" "$SSH_PROFILES_FILE" > "${SSH_PROFILES_FILE}.tmp" + echo "${name}|${new_connection}|${new_port}|${new_key}|${new_opts}|${new_desc}" >> "${SSH_PROFILES_FILE}.tmp" + mv "${SSH_PROFILES_FILE}.tmp" "$SSH_PROFILES_FILE" + + _ssh_print_success "Updated profile: $name" +} + +# ============================================================================ +# SSH Connection with Tmux Integration +# ============================================================================ + +ssh-connect() { + local name="$1" + local session_name="${2:-${SSH_TMUX_SESSION_PREFIX}-${name}}" + + if [[ -z "$name" ]]; then + echo "Usage: ssh-connect [tmux_session_name]" + echo + echo "Saved profiles:" + ssh-list + return 1 + fi + + _ssh_init_profiles + + # Parse profile + local profile_data=$(_ssh_parse_profile "$name") + if [[ -z "$profile_data" ]]; then + _ssh_print_error "Profile '$name' not found" + echo "Use 'ssh-save $name user@host' to create it" + return 1 + fi + + IFS='|' read -r connection port key_file ssh_opts description <<< "$profile_data" + + _ssh_print_step "Connecting to: $name" + [[ -n "$description" ]] && echo " $description" + + # Build SSH command + local ssh_cmd="ssh" + + # Add port + [[ -n "$port" && "$port" != "22" ]] && ssh_cmd="$ssh_cmd -p $port" + + # Add key file + [[ -n "$key_file" ]] && ssh_cmd="$ssh_cmd -i $key_file" + + # Add custom options + [[ -n "$ssh_opts" ]] && ssh_cmd="$ssh_cmd $ssh_opts" + + # Add connection + ssh_cmd="$ssh_cmd $connection" + + # Tmux integration + if [[ "$SSH_AUTO_TMUX" == "true" ]]; then + _ssh_print_info "Attaching to tmux session: $session_name" + + # SSH with tmux attach or create + local tmux_cmd="tmux attach-session -t $session_name 2>/dev/null || tmux new-session -s $session_name" + + # Execute + eval "$ssh_cmd -t '$tmux_cmd'" + else + # Direct SSH without tmux + eval "$ssh_cmd" + fi +} + +# ============================================================================ +# Fuzzy Search Integration (requires fzf) +# ============================================================================ + +sshf() { + if ! command -v fzf &>/dev/null; then + _ssh_print_error "fzf not installed" + echo "Install: git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf && ~/.fzf/install" + return 1 + fi + + _ssh_init_profiles + + # Build selection list + local profiles=() + while IFS='|' read -r name connection port key options description; do + [[ "$name" =~ ^# ]] && continue + [[ -z "$name" ]] && continue + + local display="$name → $connection" + [[ -n "$description" ]] && display="$display ($description)" + + profiles+=("$name|$display") + done < "$SSH_PROFILES_FILE" + + if [[ ${#profiles[@]} -eq 0 ]]; then + _ssh_print_info "No profiles saved" + return 1 + fi + + # Fuzzy select + local selection=$(printf '%s\n' "${profiles[@]}" | \ + fzf --height=50% \ + --layout=reverse \ + --border=rounded \ + --prompt='SSH > ' \ + --preview='echo {}' \ + --preview-window=hidden \ + --delimiter='|' \ + --with-nth=2) + + if [[ -n "$selection" ]]; then + local profile_name="${selection%%|*}" + ssh-connect "$profile_name" + fi +} + +# ============================================================================ +# Quick Reconnect +# ============================================================================ + +ssh-reconnect() { + local name="${1:-last}" + + if [[ "$name" == "last" ]]; then + # Get last connected profile from history + local last_profile=$(grep "ssh-connect" "$HISTFILE" 2>/dev/null | tail -1 | awk '{print $2}') + + if [[ -z "$last_profile" ]]; then + _ssh_print_error "No previous connection found" + return 1 + fi + + name="$last_profile" + fi + + _ssh_print_info "Reconnecting to: $name" + ssh-connect "$name" +} + +# ============================================================================ +# Dotfiles Sync to Remote +# ============================================================================ + +ssh-sync-dotfiles() { + local name="$1" + + if [[ -z "$name" ]]; then + echo "Usage: ssh-sync-dotfiles " + return 1 + fi + + local profile_data=$(_ssh_parse_profile "$name") + if [[ -z "$profile_data" ]]; then + _ssh_print_error "Profile '$name' not found" + return 1 + fi + + IFS='|' read -r connection port key_file ssh_opts description <<< "$profile_data" + + local dotfiles_dir="${DOTFILES_DIR:-$HOME/.dotfiles}" + + if [[ ! -d "$dotfiles_dir" ]]; then + _ssh_print_error "Dotfiles directory not found: $dotfiles_dir" + return 1 + fi + + _ssh_print_step "Syncing dotfiles to: $connection" + + # Build rsync command + local rsync_cmd="rsync -avz --exclude='.git' --exclude='*.local'" + + [[ -n "$port" && "$port" != "22" ]] && rsync_cmd="$rsync_cmd -e 'ssh -p $port'" + [[ -n "$key_file" ]] && rsync_cmd="$rsync_cmd -e 'ssh -i $key_file'" + + rsync_cmd="$rsync_cmd $dotfiles_dir/ $connection:.dotfiles/" + + _ssh_print_info "Running: $rsync_cmd" + + if eval "$rsync_cmd"; then + _ssh_print_success "Dotfiles synced successfully" + + # Optionally run install script on remote + read -q "REPLY?Run install script on remote? [y/N]: " + echo + if [[ "$REPLY" =~ ^[Yy]$ ]]; then + local ssh_cmd="ssh" + [[ -n "$port" && "$port" != "22" ]] && ssh_cmd="$ssh_cmd -p $port" + [[ -n "$key_file" ]] && ssh_cmd="$ssh_cmd -i $key_file" + + eval "$ssh_cmd $connection 'cd .dotfiles && ./install.sh --skip-deps'" + fi + else + _ssh_print_error "Failed to sync dotfiles" + return 1 + fi +} + +# ============================================================================ +# Aliases +# ============================================================================ + +alias sshl='ssh-list' +alias sshs='ssh-save' +alias sshc='ssh-connect' +alias sshd='ssh-delete' +alias sshr='ssh-reconnect' +alias sshsync='ssh-sync-dotfiles' + +# ============================================================================ +# Completion Helper +# ============================================================================ + +_ssh_manager_profiles() { + local profiles=() + while IFS='|' read -r name rest; do + [[ "$name" =~ ^# ]] && continue + [[ -z "$name" ]] && continue + profiles+=("$name") + done < "$SSH_PROFILES_FILE" 2>/dev/null + + echo "${profiles[@]}" +} + +# ZSH completion (if you want to add it) +# compdef '_arguments "1:profile:($(_ssh_manager_profiles))"' ssh-connect +# compdef '_arguments "1:profile:($(_ssh_manager_profiles))"' ssh-delete +# compdef '_arguments "1:profile:($(_ssh_manager_profiles))"' ssh-edit + +# ============================================================================ +# Initialization +# ============================================================================ + +_ssh_init_profiles diff --git a/zsh/functions/tmux-workspaces.zsh b/zsh/functions/tmux-workspaces.zsh new file mode 100644 index 0000000..9ccaf24 --- /dev/null +++ b/zsh/functions/tmux-workspaces.zsh @@ -0,0 +1,575 @@ +# ============================================================================ +# Tmux Workspace Manager - Project Templates & Layouts +# ============================================================================ +# Quick project workspace setup with pre-configured tmux layouts +# +# Usage: +# tw-create [template] # Create workspace from template +# tw-attach # Attach to workspace +# tw-list # List all workspaces +# tw-delete # Delete workspace +# tw-save # Save current layout as template +# tw # Quick attach (or create if not exists) +# +# Templates: +# dev - Vim (50%) + terminal (25%) + logs (25%) +# ops - 4 panes: htop, logs, shell, monitoring +# ssh-multi - 4 panes for managing multiple servers +# debug - 2 panes: main (70%) + helper (30%) +# full - Just one full pane +# +# Add to .zshrc: +# source ~/.dotfiles/zsh/functions/tmux-workspaces.zsh +# ============================================================================ + +# ============================================================================ +# Configuration +# ============================================================================ + +typeset -g TW_TEMPLATES_DIR="${TW_TEMPLATES_DIR:-$HOME/.dotfiles/.tmux-templates}" +typeset -g TW_SESSION_PREFIX="${TW_SESSION_PREFIX:-work}" +typeset -g TW_DEFAULT_TEMPLATE="${TW_DEFAULT_TEMPLATE:-dev}" + +# Colors +typeset -g TW_GREEN=$'\033[0;32m' +typeset -g TW_BLUE=$'\033[0;34m' +typeset -g TW_YELLOW=$'\033[1;33m' +typeset -g TW_CYAN=$'\033[0;36m' +typeset -g TW_RED=$'\033[0;31m' +typeset -g TW_NC=$'\033[0m' + +# ============================================================================ +# Helper Functions +# ============================================================================ + +_tw_print_step() { + echo -e "${TW_BLUE}==>${TW_NC} $1" +} + +_tw_print_success() { + echo -e "${TW_GREEN}✓${TW_NC} $1" +} + +_tw_print_error() { + echo -e "${TW_RED}✗${TW_NC} $1" +} + +_tw_print_info() { + echo -e "${TW_CYAN}ℹ${TW_NC} $1" +} + +_tw_check_tmux() { + if ! command -v tmux &>/dev/null; then + _tw_print_error "tmux not installed" + return 1 + fi + return 0 +} + +_tw_init_templates() { + mkdir -p "$TW_TEMPLATES_DIR" + + # Create default templates if they don't exist + if [[ ! -f "$TW_TEMPLATES_DIR/dev.tmux" ]]; then + _tw_create_default_templates + fi +} + +# ============================================================================ +# Default Template Definitions +# ============================================================================ + +_tw_create_default_templates() { + _tw_print_step "Creating default templates..." + + # Development template - vim + terminal + logs + cat > "$TW_TEMPLATES_DIR/dev.tmux" << 'EOF' +# Development workspace +# Usage: tw-create myproject dev + +# Split vertically (vim on left 50%, rest on right) +split-window -h -p 50 + +# Split right pane horizontally (terminal top, logs bottom) +split-window -v -p 50 + +# Select the first pane (vim) +select-pane -t 0 + +# Optional: Start vim in first pane +# send-keys -t 0 'vim' C-m + +# Optional: Set pane titles +# select-pane -t 0 -T "Editor" +# select-pane -t 1 -T "Terminal" +# select-pane -t 2 -T "Logs" +EOF + + # Operations template - 4 panes for monitoring + cat > "$TW_TEMPLATES_DIR/ops.tmux" << 'EOF' +# Operations workspace +# 4-pane layout for system monitoring + +# Create 2x2 grid +split-window -h -p 50 +split-window -v -p 50 +select-pane -t 0 +split-window -v -p 50 + +# Optional: Auto-start monitoring tools +# send-keys -t 0 'htop' C-m +# send-keys -t 1 'docker ps' C-m +# send-keys -t 2 '' C-m +# send-keys -t 3 'tail -f /var/log/syslog' C-m + +select-pane -t 0 +EOF + + # SSH multi-server template + cat > "$TW_TEMPLATES_DIR/ssh-multi.tmux" << 'EOF' +# Multi-server SSH workspace +# 4 panes for managing multiple servers + +# Create 2x2 grid +split-window -h -p 50 +split-window -v -p 50 +select-pane -t 0 +split-window -v -p 50 + +# Enable pane synchronization (optional - uncomment to enable) +# set-window-option synchronize-panes on + +select-pane -t 0 +EOF + + # Debug template - main + helper pane + cat > "$TW_TEMPLATES_DIR/debug.tmux" << 'EOF' +# Debug workspace +# Main pane (70%) + helper pane (30%) + +split-window -h -p 30 + +select-pane -t 0 +EOF + + # Full template - single pane + cat > "$TW_TEMPLATES_DIR/full.tmux" << 'EOF' +# Full workspace +# Single full-screen pane (default tmux behavior) +EOF + + # Code review template - side-by-side comparison + cat > "$TW_TEMPLATES_DIR/review.tmux" << 'EOF' +# Code Review workspace +# Two equal panes side-by-side for comparison + +split-window -h -p 50 + +select-pane -t 0 +EOF + + _tw_print_success "Created default templates in: $TW_TEMPLATES_DIR" +} + +# ============================================================================ +# Template Management +# ============================================================================ + +tw-templates() { + _tw_init_templates + + echo -e "${TW_BLUE}╔════════════════════════════════════════════════════════════╗${TW_NC}" + echo -e "${TW_BLUE}║${TW_NC} Available Tmux Templates ${TW_BLUE}║${TW_NC}" + echo -e "${TW_BLUE}╚════════════════════════════════════════════════════════════╝${TW_NC}" + echo + + for template in "$TW_TEMPLATES_DIR"/*.tmux; do + [[ ! -f "$template" ]] && continue + + local name=$(basename "$template" .tmux) + local description=$(grep "^#" "$template" | head -2 | tail -1 | sed 's/^# *//') + + echo -e "${TW_GREEN}●${TW_NC} ${TW_CYAN}$name${TW_NC}" + [[ -n "$description" ]] && echo " $description" + done + + echo + echo "Create workspace: ${TW_CYAN}tw-create myproject dev${TW_NC}" + echo "Quick attach: ${TW_CYAN}tw myproject${TW_NC}" +} + +tw-template-edit() { + local template_name="$1" + + if [[ -z "$template_name" ]]; then + echo "Usage: tw-template-edit " + echo + tw-templates + return 1 + fi + + _tw_init_templates + + local template_file="$TW_TEMPLATES_DIR/${template_name}.tmux" + + ${EDITOR:-vim} "$template_file" + + _tw_print_success "Template edited: $template_name" +} + +# ============================================================================ +# Workspace Management +# ============================================================================ + +tw-create() { + local workspace_name="$1" + local template="${2:-$TW_DEFAULT_TEMPLATE}" + + if [[ -z "$workspace_name" ]]; then + echo "Usage: tw-create [template]" + echo + tw-templates + return 1 + fi + + _tw_check_tmux || return 1 + _tw_init_templates + + local session_name="${TW_SESSION_PREFIX}-${workspace_name}" + + # Check if session already exists + if tmux has-session -t "$session_name" 2>/dev/null; then + _tw_print_error "Workspace '$workspace_name' already exists" + echo "Use: ${TW_CYAN}tw $workspace_name${TW_NC} to attach" + return 1 + fi + + # Check if template exists + local template_file="$TW_TEMPLATES_DIR/${template}.tmux" + if [[ ! -f "$template_file" ]]; then + _tw_print_error "Template '$template' not found" + tw-templates + return 1 + fi + + _tw_print_step "Creating workspace: $workspace_name (template: $template)" + + # Create new tmux session (detached) + tmux new-session -d -s "$session_name" + + # Apply template + _tw_print_step "Applying template: $template" + tmux source-file "$template_file" -t "$session_name" + + # Set working directory if we're in a git repo or specific directory + if git rev-parse --git-dir &>/dev/null 2>&1; then + local git_root=$(git rev-parse --show-toplevel) + _tw_print_info "Setting workspace directory to: $git_root" + tmux send-keys -t "$session_name:0" "cd $git_root" C-m + fi + + _tw_print_success "Workspace created: $workspace_name" + + # Attach if not already in tmux + if [[ -z "$TMUX" ]]; then + _tw_print_step "Attaching to workspace..." + tmux attach-session -t "$session_name" + else + _tw_print_info "Switch with: ${TW_CYAN}tmux switch-client -t $session_name${TW_NC}" + fi +} + +tw-attach() { + local workspace_name="$1" + + if [[ -z "$workspace_name" ]]; then + echo "Usage: tw-attach " + echo + tw-list + return 1 + fi + + _tw_check_tmux || return 1 + + local session_name="${TW_SESSION_PREFIX}-${workspace_name}" + + if ! tmux has-session -t "$session_name" 2>/dev/null; then + _tw_print_error "Workspace '$workspace_name' not found" + echo + echo "Create it with: ${TW_CYAN}tw-create $workspace_name${TW_NC}" + return 1 + fi + + # Attach or switch + if [[ -z "$TMUX" ]]; then + tmux attach-session -t "$session_name" + else + tmux switch-client -t "$session_name" + fi +} + +tw-list() { + _tw_check_tmux || return 1 + + echo -e "${TW_BLUE}╔════════════════════════════════════════════════════════════╗${TW_NC}" + echo -e "${TW_BLUE}║${TW_NC} Active Tmux Workspaces ${TW_BLUE}║${TW_NC}" + echo -e "${TW_BLUE}╚════════════════════════════════════════════════════════════╝${TW_NC}" + echo + + local has_workspaces=false + + # List all tmux sessions + tmux list-sessions 2>/dev/null | while IFS=: read -r session_full rest; do + # Only show sessions with our prefix + if [[ "$session_full" == ${TW_SESSION_PREFIX}-* ]]; then + has_workspaces=true + local workspace_name="${session_full#${TW_SESSION_PREFIX}-}" + local attached="" + + # Check if currently attached + if [[ -n "$TMUX" ]]; then + local current_session=$(tmux display-message -p '#S') + [[ "$current_session" == "$session_full" ]] && attached=" ${TW_GREEN}(current)${TW_NC}" + fi + + echo -e "${TW_GREEN}●${TW_NC} ${TW_CYAN}$workspace_name${TW_NC}$attached" + echo " Session: $session_full" + fi + done + + if [[ "$has_workspaces" != true ]]; then + _tw_print_info "No active workspaces" + echo + echo "Create one with: ${TW_CYAN}tw-create myproject${TW_NC}" + fi +} + +tw-delete() { + local workspace_name="$1" + + if [[ -z "$workspace_name" ]]; then + echo "Usage: tw-delete " + echo + tw-list + return 1 + fi + + _tw_check_tmux || return 1 + + local session_name="${TW_SESSION_PREFIX}-${workspace_name}" + + if ! tmux has-session -t "$session_name" 2>/dev/null; then + _tw_print_error "Workspace '$workspace_name' not found" + return 1 + fi + + # Kill session + tmux kill-session -t "$session_name" + + _tw_print_success "Deleted workspace: $workspace_name" +} + +# ============================================================================ +# Save Current Layout as Template +# ============================================================================ + +tw-save() { + local template_name="$1" + + if [[ -z "$template_name" ]]; then + echo "Usage: tw-save " + echo + echo "Saves the current tmux window layout as a reusable template" + return 1 + fi + + _tw_check_tmux || return 1 + + if [[ -z "$TMUX" ]]; then + _tw_print_error "Must be run from inside tmux" + return 1 + fi + + _tw_init_templates + + local template_file="$TW_TEMPLATES_DIR/${template_name}.tmux" + + if [[ -f "$template_file" ]]; then + read -q "REPLY?Template '$template_name' exists. Overwrite? [y/N]: " + echo + [[ ! "$REPLY" =~ ^[Yy]$ ]] && return 1 + fi + + _tw_print_step "Saving current layout as template: $template_name" + + # Get current window layout + local layout=$(tmux display-message -p '#{window_layout}') + local pane_count=$(tmux display-message -p '#{window_panes}') + + # Create template with layout commands + cat > "$template_file" << EOF +# Custom template: $template_name +# Saved: $(date) +# Panes: $pane_count + +# Note: This is a simplified layout recreation +# You may need to adjust split percentages and commands + +EOF + + # Generate split commands based on pane count + if (( pane_count == 2 )); then + echo "split-window -h -p 50" >> "$template_file" + elif (( pane_count == 3 )); then + cat >> "$template_file" << 'EOF' +split-window -h -p 50 +split-window -v -p 50 +EOF + elif (( pane_count == 4 )); then + cat >> "$template_file" << 'EOF' +split-window -h -p 50 +split-window -v -p 50 +select-pane -t 0 +split-window -v -p 50 +EOF + fi + + echo "" >> "$template_file" + echo "select-pane -t 0" >> "$template_file" + + _tw_print_success "Template saved: $template_name" + echo " File: $template_file" + echo " Edit: ${TW_CYAN}tw-template-edit $template_name${TW_NC}" +} + +# ============================================================================ +# Quick Workspace (attach or create) +# ============================================================================ + +tw() { + local workspace_name="$1" + local template="${2:-$TW_DEFAULT_TEMPLATE}" + + if [[ -z "$workspace_name" ]]; then + tw-list + return 0 + fi + + _tw_check_tmux || return 1 + + local session_name="${TW_SESSION_PREFIX}-${workspace_name}" + + # If session exists, attach. Otherwise create. + if tmux has-session -t "$session_name" 2>/dev/null; then + tw-attach "$workspace_name" + else + _tw_print_info "Workspace doesn't exist. Creating with template: $template" + tw-create "$workspace_name" "$template" + fi +} + +# ============================================================================ +# Fuzzy Search (requires fzf) +# ============================================================================ + +twf() { + if ! command -v fzf &>/dev/null; then + _tw_print_error "fzf not installed" + return 1 + fi + + _tw_check_tmux || return 1 + + # Get list of sessions + local sessions=() + tmux list-sessions 2>/dev/null | while IFS=: read -r session_full rest; do + if [[ "$session_full" == ${TW_SESSION_PREFIX}-* ]]; then + local workspace_name="${session_full#${TW_SESSION_PREFIX}-}" + sessions+=("$workspace_name") + fi + done + + if [[ ${#sessions[@]} -eq 0 ]]; then + _tw_print_info "No workspaces found" + return 1 + fi + + # Fuzzy select + local selection=$(printf '%s\n' "${sessions[@]}" | \ + fzf --height=40% \ + --layout=reverse \ + --border=rounded \ + --prompt='Workspace > ' \ + --preview='tmux list-windows -t work-{} 2>/dev/null || echo "No preview"') + + if [[ -n "$selection" ]]; then + tw-attach "$selection" + fi +} + +# ============================================================================ +# Pane Synchronization Toggle +# ============================================================================ + +tw-sync() { + if [[ -z "$TMUX" ]]; then + _tw_print_error "Must be run from inside tmux" + return 1 + fi + + local current=$(tmux show-window-option -v synchronize-panes 2>/dev/null) + + if [[ "$current" == "on" ]]; then + tmux set-window-option synchronize-panes off + _tw_print_info "Pane synchronization: ${TW_RED}OFF${TW_NC}" + else + tmux set-window-option synchronize-panes on + _tw_print_info "Pane synchronization: ${TW_GREEN}ON${TW_NC}" + fi +} + +# ============================================================================ +# Rename Workspace +# ============================================================================ + +tw-rename() { + local old_name="$1" + local new_name="$2" + + if [[ -z "$old_name" || -z "$new_name" ]]; then + echo "Usage: tw-rename " + return 1 + fi + + _tw_check_tmux || return 1 + + local old_session="${TW_SESSION_PREFIX}-${old_name}" + local new_session="${TW_SESSION_PREFIX}-${new_name}" + + if ! tmux has-session -t "$old_session" 2>/dev/null; then + _tw_print_error "Workspace '$old_name' not found" + return 1 + fi + + tmux rename-session -t "$old_session" "$new_session" + + _tw_print_success "Renamed: $old_name → $new_name" +} + +# ============================================================================ +# Aliases +# ============================================================================ + +alias twl='tw-list' +alias twc='tw-create' +alias twa='tw-attach' +alias twd='tw-delete' +alias tws='tw-save' +alias twt='tw-templates' +alias twe='tw-template-edit' + +# ============================================================================ +# Initialization +# ============================================================================ + +_tw_init_templates