gz-shellforge 0.3.2
Forge your shell configuration from modular pieces with dependency resolution
License: MIT
Requires Python: >=3.12

# gzh-cli-shellforge

> Forge your shell configuration from modular pieces with dependency resolution

A build tool that transforms modular shell scripts into a unified `.zshrc`/`.bashrc` with automatic dependency ordering and OS-specific filtering.

## What it does


- Reads shell modules from your config directory
- Resolves dependencies automatically
- Filters by OS (Mac/Linux)
- Generates optimized `.zshrc`/`.bashrc`
- Validates configuration integrity
- **Automatic backup and version control** (new in v0.2.0)

## Problem

Managing shell configurations across multiple machines and operating systems is challenging:
- Manual concatenation is error-prone
- Dependency ordering is hard to maintain
- OS-specific logic scattered everywhere
- No validation before deployment

## Solution

Shellforge uses a declarative manifest to define your shell modules and their dependencies, then builds the final configuration file with correct ordering and OS filtering.

## Installation

```bash
# With uv (recommended)
uv pip install shellforge

# Or with pip
pip install shellforge
```

## Quick Start

### Option A: Auto-generate from existing shellrc (Recommended)

If you have an existing shell configuration in `init.d/`, `rc_pre.d/`, `rc_post.d/` structure:

```bash
# 1. Auto-generate manifest from existing config
shellforge init -c ~/devenv/config/shellrc -o manifest.yaml

# 2. Review and edit the generated manifest
vim manifest.yaml

# 3. Validate
shellforge validate -c ~/devenv/config/shellrc -m manifest.yaml

# 4. Build
shellforge build -c ~/devenv/config/shellrc -m manifest.yaml --auto-output
```

### Option B: Manual manifest creation

```yaml
# manifest.yaml
modules:
  - name: brew-path
    file: init.d/brew.sh
    requires: []
    os: [Mac]
    description: Homebrew PATH initialization

  - name: conda
    file: rc_pre.d/conda.sh
    requires: [brew-path]
    os: [Mac, Linux]
    description: Conda environment
```

Then build:

```bash
shellforge build \
  --config-dir ~/devenv/config/shellrc \
  --manifest manifest.yaml \
  --output ~/.zshrc
```

## New in v0.2.0: Deployment with Backup

Deploy with automatic backup and version control:

```bash
shellforge build \
  -c shellforge-modules/modules \
  -m shellforge-modules/manifest.yaml \
  --auto-output \
  --deploy
```

This will:

1. Build to `tmp/shellforge-build/` first
2. Create timestamped snapshot in `~/.backup/shellforge/snapshots/`
3. Commit to git in `~/.backup/shellforge/`
4. Deploy to actual location (e.g., `~/.zshrc`)

**Dual Protection:**

- Git history for full version control
- Timestamped snapshots for quick rollback

### Restore from Backup

Rollback to a previous version:

```bash
# List available snapshots
shellforge restore -t ~/.zshrc --list

# Restore interactively (shows recent snapshots)
shellforge restore -t ~/.zshrc

# Restore specific snapshot
shellforge restore -t ~/.zshrc -s ~/.backup/shellforge/snapshots/zshrc/2025-11-23_10-29-15
```

### Cleanup Old Snapshots

Manage snapshot retention:

```bash
# Keep only last 10 snapshots
shellforge clean-snapshots -t ~/.zshrc --keep-count 10

# Keep snapshots from last 30 days
shellforge clean-snapshots -t ~/.zshrc --keep-days 30

# Preview what would be deleted
shellforge clean-snapshots -t ~/.zshrc --keep-count 10 --dry-run
```

See [Deployment Guide](docs/deployment-guide.md) for details.

## Self-Documenting CLI (v0.3.0+)

Shellforge provides contextual guidance to help you navigate workflows
without constantly consulting documentation:

### Quick Start Guide

Run `shellforge --help` to see common workflows organized by use case:

- **First-time setup**: Migrate existing config or start from scratch
- **Daily workflow**: Validate → Build → Diff → Deploy cycle
- **Recovery**: Restore from backups

### Smart Statistics

Commands show helpful statistics after execution:

```bash
$ shellforge validate -c modules -m manifest.yaml

✓ Validation passed

📊 Validation Statistics:
  • Total modules: 27
  • Mac modules: 27
  • Linux modules: 22
  • Total dependencies: 18

💡 Next steps:
  Preview:  shellforge build -c modules -m manifest.yaml --dry-run
  Compare:  shellforge diff -c modules -m manifest.yaml --auto-detect-existing
```

### Next Steps Suggestions

Each command suggests what to do next based on the operation result:

- After `validate`: Suggests preview, compare, or deploy
- After `build`: Suggests compare or deploy (context-aware)
- After `diff`: Suggests deploy or backup strategies

### Verbose Mode for Details

Use `-v` or `--verbose` flag for detailed information:

```bash
# See dependency resolution details
shellforge validate -c modules -m manifest.yaml -v

# See module load order during build
shellforge build -c modules -m manifest.yaml -o output.zsh -v

# See detailed change analysis
shellforge diff -c modules -m manifest.yaml --auto-detect-existing -v
```

### Error-Specific Help

Errors include specific troubleshooting steps:

```bash
$ shellforge validate -c wrong -m manifest.yaml

✗ Validation failed:
  • Circular dependency detected: asdf-core -> nvm -> asdf-core

  💡 Fix circular dependency:
     Edit manifest.yaml and review the 'requires' fields
     Remove one dependency from the circular chain
```

Common error scenarios covered:

- **Circular dependencies**: Shows which dependencies to review
- **Missing files**: Guides path verification
- **YAML syntax errors**: Links to example manifests
- **Permission errors**: Provides chmod examples

## Usage

### Init

Generate manifest from existing shell configuration:

```bash
# Auto-generate manifest from existing shellrc directory
shellforge init -c config/shellrc -o manifest.yaml

# Preview without writing file
shellforge init -c config/shellrc --dry-run

# Custom output location
shellforge init -c ~/dotfiles/shellrc -o ~/dotfiles/manifest.yaml
```

**What it does:**

- Scans `init.d/`, `rc_pre.d/`, `rc_post.d/` directories
- Auto-detects module dependencies:
  - `$MACHINE` usage → depends on `os-detection`
  - `brew` commands → depends on `brew-path`
  - Module-specific (e.g., `asdf-flutter` → `asdf-core`)
- Detects OS support from `case $MACHINE` statements
- Extracts descriptions from comments or generates defaults
- Generates YAML manifest with helpful category comments

**Example output:**

```yaml
# Shellforge Manifest
# Auto-generated from existing shell configuration

modules:
  # System Initialization (init.d)
  - name: os-detection
    file: init.d/00_check_os.sh
    requires: []
    os: [Mac, Linux]
    description: Detect operating system and set MACHINE variable

  # Pre-RC Configuration (rc_pre.d)
  - name: conda
    file: rc_pre.d/conda.sh
    requires: [os-detection, brew-path]
    os: [Mac, Linux]
    description: Conda environment initialization
```

### Build

Generate shell configuration file:

```bash
# Auto-detect OS and use recommended output file
shellforge build -c config/shellrc -m manifest.yaml --auto-output

# Specify OS, shell, and session type
shellforge build -c config/shellrc -m manifest.yaml \
  --os macos --shell zsh --session interactive \
  --auto-output

# Manual output file
shellforge build -c config/shellrc -m manifest.yaml -o ~/.zshrc

# Build for different session types
# Login shell (.zprofile for zsh, .bash_profile for bash)
shellforge build -c config/shellrc -m manifest.yaml \
  --session login --auto-output

# Interactive shell (.zshrc for zsh, .bashrc for bash)
shellforge build -c config/shellrc -m manifest.yaml \
  --session interactive --auto-output

# Always sourced (.zshenv for zsh)
shellforge build -c config/shellrc -m manifest.yaml \
  --shell zsh --session always --auto-output

# Fish shell (always use --session always for fish)
shellforge build -c config/shellrc -m manifest.yaml \
  --shell fish --session always --auto-output

# Dry run (preview output)
shellforge build -c config/shellrc -m manifest.yaml --auto-output --dry-run

# Verbose output
shellforge build -c config/shellrc -m manifest.yaml --auto-output -v
```

### Template

Generate new modules from common patterns:

```bash
# List available templates
shellforge template list

# Generate PATH module
shellforge template generate path my-custom-bin \
  -f path_dir=/opt/myapp/bin \
  -f description="My custom application binaries"

# Generate environment variable
shellforge template generate env EDITOR \
  -f var_name=EDITOR \
  -f var_value=vim \
  -f description="Set default editor"

# Generate aliases
shellforge template generate alias git-shortcuts \
  -f description="Git command shortcuts" \
  -f aliases="alias gs='git status'
alias gp='git push'
alias gl='git pull'"

# Generate tool initialization with dependencies
shellforge template generate tool-init nvm \
  -f tool_name=nvm \
  -f tool_command=nvm \
  -f init_command='eval "$(nvm init -)"' \
  -f description="Node Version Manager" \
  -r brew-path

# Interactive mode (prompts for all fields)
shellforge template generate env MY_VAR -i

# Generate in specific directory
shellforge template generate path custom-bin \
  -c ~/dotfiles/shellrc \
  -f path_dir=/usr/local/mybin \
  -f description="Custom binaries"
```

**Available templates:**

- `path` - Add directory to PATH (init.d/)
- `env` - Set environment variable (rc_pre.d/)
- `alias` - Define shell aliases (rc_post.d/)
- `conditional-source` - Source file if it exists (rc_pre.d/)
- `tool-init` - Initialize development tool (rc_pre.d/)
- `os-specific` - OS-specific configuration (rc_pre.d/)

### Info

Show shell configuration file information:

```bash
# macOS zsh interactive shell
shellforge info --os macos --shell zsh --session interactive

# Ubuntu bash login shell
shellforge info --os ubuntu --shell bash --session login

# Arch zsh (always sourced)
shellforge info --os arch --shell zsh --session always

# Fish shell (all platforms, session type is always 'always')
shellforge info --os macos --shell fish --session always
shellforge info --os ubuntu --shell fish --session always
```

### Validate

Check manifest and module files:

```bash
shellforge validate -c config/shellrc -m manifest.yaml
```

### Migrate

Convert traditional RC file to shellforge structure:

```bash
# Migrate existing .zshrc to shellforge modules
shellforge migrate -s ~/.zshrc -t config/shellrc

# Preview migration without writing files
shellforge migrate -s ~/.zshrc -t config/shellrc --dry-run

# Migrate without backup
shellforge migrate -s ~/.zshrc -t config/shellrc --no-backup

# Migrate without auto-generating manifest
shellforge migrate -s ~/.zshrc -t config/shellrc --no-manifest
```

**What it does:**

- Analyzes your existing RC file and identifies logical sections
- Automatically categorizes sections into init.d/, rc_pre.d/, or rc_post.d/
- Creates modular shell scripts for each section
- Creates backup of original file (default)
- Auto-generates manifest.yaml (default)

**Example workflow:**

```bash
# 1. Migrate existing .zshrc
shellforge migrate -s ~/.zshrc -t ~/dotfiles/shellrc

# 2. Review migrated modules
ls ~/dotfiles/shellrc/*/

# 3. Validate
shellforge validate -c ~/dotfiles/shellrc -m ~/dotfiles/shellrc/manifest.yaml

# 4. Build and test
shellforge build -c ~/dotfiles/shellrc -m ~/dotfiles/shellrc/manifest.yaml --auto-output --dry-run
```

### Diff

Compare generated configuration with existing RC file:

```bash
# Auto-detect existing RC file based on OS/shell
shellforge diff -c config/shellrc -m manifest.yaml --auto-detect-existing

# Compare with specific file
shellforge diff -c config/shellrc -m manifest.yaml -e ~/.zshrc

# Show detailed unified diff
shellforge diff -c config/shellrc -m manifest.yaml \
  --auto-detect-existing --format unified

# Context diff format
shellforge diff -c config/shellrc -m manifest.yaml \
  --auto-detect-existing --format context

# Summary only (default)
shellforge diff -c config/shellrc -m manifest.yaml \
  --auto-detect-existing --format summary
```

**Output formats:**

- `summary`: Statistics about differences (default)
- `unified`: Standard unified diff format
- `context`: Context diff format

### List Modules

Show modules in load order:

```bash
# Current OS
shellforge list-modules -c config/shellrc -m manifest.yaml

# Specific OS
shellforge list-modules -c config/shellrc -m manifest.yaml --os Linux
```

## Manifest Format

```yaml
modules:
  - name: module-name          # Unique identifier
    file: path/to/module.sh    # Relative to config-dir
    requires: [dep1, dep2]     # Dependencies (optional)
    os: [Mac, Linux]           # Supported OS (optional)
    description: What it does  # Description (optional)
```

### Example Manifest

See [examples/manifest.yaml](examples/manifest.yaml) for a complete example.

## Project Structure

```
your-dotfiles/
├── config/
│   └── shellrc/
│       ├── init.d/
│       │   ├── 00-os-detection.sh
│       │   └── 05-brew-path.sh
│       ├── rc_pre.d/
│       │   ├── conda.sh
│       │   └── nvm.sh
│       └── rc_post.d/
│           └── aliases.sh
└── manifest.yaml
```

## Features

### Shell Configuration Metadata

Shellforge includes comprehensive metadata about shell configuration files for different operating systems and session types.

**Supported Operating Systems:**
- macOS
- Ubuntu
- Debian
- Arch Linux
- Manjaro Linux

**Supported Shells:**
- **Zsh** (~69% market share) - Default on macOS
- **Bash** (~17% market share) - Most widely available
- **Fish** (~6% market share) - Modern, user-friendly shell

**Session Types:**
- `login`: Login shells (TTY, SSH, Terminal.app on macOS) - bash/zsh
- `interactive`: Interactive non-login shells (most terminal emulators) - bash/zsh
- `always`: Always sourced - zsh (via .zshenv) and fish (all invocations)
- `gui`: GUI desktop environment startup

**Note on Fish Shell:**
Fish does not distinguish between login/non-login or interactive/non-interactive shells.
All fish shells load the same configuration files, so use `--session always` for fish.

The metadata includes:
- Which files are loaded in which order
- System-wide vs user-specific files
- File alternatives (e.g., .bash_profile OR .bash_login OR .profile)
- Recommended build targets for each environment

Use `shellforge info` to explore the metadata for your environment.

### Automatic Dependency Resolution

Shellforge builds a dependency graph and uses topological sorting to ensure modules are loaded in the correct order.

```yaml
modules:
  - name: gcloud
    requires: [brew-path]  # Will be loaded after brew-path

  - name: brew-path
    requires: []           # Loaded first
```

### OS Filtering

Only include modules for the target OS:

```yaml
modules:
  - name: brew-path
    os: [Mac]            # Only on macOS

  - name: pacman-setup
    os: [Linux]          # Only on Linux

  - name: common-aliases
    os: [Mac, Linux]     # Both
```

### Circular Dependency Detection

Shellforge detects and reports circular dependencies:

```bash
$ shellforge validate -c config -m manifest.yaml
✗ Validation failed:
  - Circular dependency detected: 'module-a' -> 'module-b' -> 'module-a'
```

## Development

### Setup

```bash
# Clone repository
git clone git@github.com:Gizzahub/gzh-cli-shellforge.git
cd gzh-cli-shellforge

# Quick setup (creates venv and installs dependencies)
make dev-setup

# Or manual installation
uv pip install -e ".[dev]"
```

### Available Make Commands

```bash
make help              # Show all available commands

# Installation
make install           # Install in development mode
make install-system    # Install system-wide
make install-dev       # Install with dev dependencies

# Testing
make test              # Run all tests
make test-coverage     # Run tests with coverage report

# Code Quality
make lint              # Check code style (no changes)
make lint-fix          # Auto-fix lint issues
make lint-strict-fix   # Strict lint with auto-fix

# Build
make build             # Build package
make clean             # Clean build artifacts
```

### Run Tests

```bash
make test
# or directly
uv run pytest -v
```

### Lint

```bash
make lint-fix
# or directly
uv run ruff check . --fix
uv run ruff format .
```

## Integration with Chezmoi

You can use Shellforge with [Chezmoi](https://www.chezmoi.io/) for multi-machine dotfile management:

```bash
# Build config
shellforge build -c config/shellrc -m manifest.yaml -o ~/.zshrc

# Add to Chezmoi
chezmoi add ~/.zshrc

# Apply to other machines
chezmoi apply
```

## License

MIT

## Contributing

Contributions welcome! Please open an issue or PR.

## Documentation

- [Testing Guide](TESTING.md) - Manual testing guide with sample data
- [Deployment Guide](docs/deployment-guide.md) - Comprehensive guide for backup and deployment features
- [Examples](examples/) - Example configurations and manifests

## Related Projects

- [chezmoi](https://www.chezmoi.io/) - Dotfile manager
- [GNU Stow](https://www.gnu.org/software/stow/) - Symlink farm manager
- [dotbot](https://github.com/anishathalye/dotbot) - Bootstrap tool for dotfiles

## Part of gzh-cli Series

Other tools in the gzh-cli series:
- `gzh-cli-shellforge` - This project
- More coming soon...