Modern git worktree manager with color coding and automation
Project description
⛩️ Portal - Git Worktree Manager
Portal is a modern CLI tool for managing Git worktrees with automatic color coding and editor integration. Each worktree gets a deterministic color that syncs across your terminal, IDE, and CLI output, making it easy to identify which worktree you're working in.
Features
Color Coding System
- Deterministic colors: Each worktree gets a consistent color based on its name
- Cross-tool sync: Colors appear in iTerm tabs, VS Code/Cursor themes, and CLI output
- Visual identification: Quickly identify which worktree you're working in
Automation & Hooks
- 8 hook types:
command,copy,template,mkdir,symlink,env_update,script,git - Project-level overrides: Use
.portalfiles for project-specific automation - Conditional execution: Run hooks based on file existence, platform, environment
- Security-hardened: Built-in protection against injection attacks and path traversal
- Variable substitution: Dynamic values like
{{worktree_name}},{{color_hex}},autoports
Developer Tools
- Interactive menu: Arrow-key navigation with color-highlighted worktree entries
- iTerm integration: Open worktrees in new tabs with colored tab indicators
- Claude integration: Start AI-assisted coding sessions in worktree context
- Shell completions: Support for Bash and Zsh
Technical Design
- Type-safe: Fully type-checked with mypy strict mode
- Async operations: Built with Python's async/await for better performance
- Event-driven: Extensible architecture using event bus pattern
- Cross-platform: Works on macOS and Linux (Windows via WSL)
Installation
Prerequisites
- Python 3.12 or higher
- Git 2.5+ (for worktree support)
- iTerm2 (optional, for terminal integration on macOS)
- VS Code or Cursor (optional, for editor integration)
Quick Install
# With uv (recommended)
uv tool install git-portal[cli]
# With pipx
pipx install git-portal[cli]
# With Homebrew (macOS)
brew tap aureliensibiril/portal
brew install portal
From Source
git clone https://github.com/aureliensibiril/portal.git
cd portal
uv venv --python 3.12
source .venv/bin/activate
uv pip install -e ".[cli]"
Shell Integration
Install shell completions and functions:
# Install shell completions and portal cd function
portal shell install
This adds tab completion for Portal commands and the pw alias for quick worktree switching.
Quick Start
Create Your First Worktree
# Interactive mode - shows numbered list with color indicators
portal list
# Create a new worktree
portal new feature/awesome-feature
# Create from specific base branch
portal new hotfix/urgent-fix --base release-v2.0
Example Workflow
# 1. Open the interactive menu (arrow-key navigation with true-color entries)
$ portal
⛩️ Portal - Git Worktree Manager
↑/↓ Navigate • Enter: Select • N: New worktree • Q: Quit
▶ main (base) # highlighted, colored per worktree
feature/auth
hotfix/bug
# Pressing Enter on a worktree opens an action submenu:
# ↵ Open terminal in new tab
# 📝 Open in Cursor
# 🤖 Open with Claude
# 🎨 Reset IDE colors
# 🗑️ Delete worktree
# 2. Create new worktree with hooks
$ portal new feature/payments
✅ Created worktree: feature/payments
Path: ../portal_worktrees/feature_payments
Color: Purple (#9C27B0)
✅ Hook: Copy environment file
✅ Hook: Install dependencies
✅ Opened in Cursor with Purple theme
# 3. Switch between worktrees
$ portal switch feature/auth
✅ Switched to feature/auth
# 4. Open in iTerm with tab color
$ portal terminal feature/payments
✅ Opened feature/payments in new iTerm tab
# 5. Start AI coding session
$ portal claude feature/payments
✅ Opened feature/payments with Claude
Command Reference
Core Worktree Management
| Command | Description | Example |
|---|---|---|
portal |
Open interactive menu | portal |
portal list |
Interactive worktree list with colors | portal list --format table |
portal new <branch> |
Create new worktree | portal new feature/auth --base develop |
portal delete [worktree] |
Delete a worktree | portal delete feature/old --force |
portal switch [worktree] |
Switch to a worktree | portal switch main --open |
portal info [worktree] |
Show worktree information | portal info feature/auth |
portal branches |
List available branches | portal branches --fetch |
Options detail:
| Command | Option | Description |
|---|---|---|
portal new |
-b/--base <branch> |
Base branch for the new worktree |
portal new |
-t/--template <name> |
Template to use |
portal new |
--fetch |
Fetch remote branches before creating |
portal new |
--no-hooks |
Skip hook execution |
portal new |
--no-open |
Don't open in editor after creation |
portal delete |
-f/--force |
Force deletion |
portal delete |
--with-branch |
Also delete the associated branch |
portal list |
--format [interactive|simple|table|json] |
Output format (default: interactive) |
portal list |
--no-interactive |
Disable interactive mode |
portal switch |
--open |
Open in editor after switching |
Editor Integration
| Command | Description | Example |
|---|---|---|
portal cursor [worktree] |
Open in Cursor IDE with color theme | portal cursor feature/auth |
portal vscode [worktree] |
Open in VS Code with color theme | portal vscode main |
Terminal Integration
| Command | Description | Example |
|---|---|---|
portal terminal [worktree] |
Open iTerm tab with color | portal terminal feature/ui |
portal claude [worktree] |
Open iTerm with Claude AI | portal claude hotfix/bug |
Hook Management
| Command | Description | Example |
|---|---|---|
portal hooks list |
Show hook configuration guidance | portal hooks list |
portal hooks run <stage> |
Run hooks for a specific stage | portal hooks run post_create --dry-run |
Configuration
| Command | Description | Example |
|---|---|---|
portal config show |
Display current configuration with sources | portal config show |
portal config show --json |
Show configuration as JSON | portal config show --json |
portal config show --global |
Show only global configuration | portal config show --global |
portal config edit |
Edit configuration file | portal config edit --global |
portal config set |
Set a configuration value | portal config set editor.default vscode --global |
Integration Management
| Command | Description | Example |
|---|---|---|
portal integrations list |
List available integrations | portal integrations list |
portal integrations status |
Show status of all integrations | portal integrations status |
portal integrations test |
Test all available integrations | portal integrations test |
Utility Commands
| Command | Description | Example |
|---|---|---|
portal shell install |
Install shell completions | portal shell install |
portal --version |
Show Portal version | portal --version |
Configuration
Portal uses YAML configuration files with sensible defaults. You can inspect the current configuration and see exactly what values Portal will use with the config show command.
Viewing Configuration
The portal config show command displays the merged configuration from all sources:
# Show current configuration with resolved paths and sources
portal config show
# Output as JSON for scripting
portal config show --json
# Show only global configuration
portal config show --global
This command shows:
- Repository Context: Current Git repository, project name, and branch
- Configuration Values: All settings with resolved paths showing exactly where worktrees will be created
- Configuration Sources: Which config files are loaded (project
.portal.yml, global~/.portal/config.yml, and defaults)
Example Configuration (~/.portal/config.yml)
# Portal Configuration
version: "1.0"
base_dir: "../{project}_worktrees" # Where worktrees are created
# Color settings
colors:
enabled: true
sync_iterm: true # Sync colors to iTerm
sync_cursor: true # Sync colors to Cursor
sync_claude: true # Generate Claude context
high_contrast: false # Use high-contrast palette
# Editor settings
editor:
default: "cursor" # Default editor (cursor/vscode/vim)
auto_open: true # Auto-open on creation
# Shell integration
shell:
completions_enabled: true # Enable tab completion
cd_function: true # Install 'portal cd' function
prompt_integration: false # Show worktree in prompt
# Global hooks (can be overridden per-project)
hooks:
post_create:
- type: mkdir
config:
paths: ["logs", "tmp"]
- type: command
config:
command: "echo 'Worktree created'"
# Branch pattern mappings
branch_patterns:
"feature/*": "feature"
"hotfix/*": "hotfix"
"release/*": "release"
"bugfix/*": "bugfix"
Hook System
Portal's powerful hook system automates worktree setup, teardown, and environment configuration. Hooks execute at specific lifecycle stages and support multiple operation types.
Hook Types
Portal supports 8 different hook types for comprehensive automation:
| Hook Type | Purpose | Configuration |
|---|---|---|
command |
Execute shell commands | command: "npm install" |
copy |
Copy files/directories | from: ".env.example", to: ".env" |
template |
Process Jinja2 templates | template: "config.j2", output: "config.json" |
mkdir |
Create directories | paths: ["logs", "tmp", "cache"] |
symlink |
Create symbolic links | source: "../shared", target: "public" |
env_update |
Update environment files | file: ".env", updates: {...} |
script |
Run custom scripts | script: "setup.sh" |
git |
Git operations | operation: "fetch" |
Hook Stages
Hooks execute at these lifecycle stages:
pre_create: Before worktree creationpost_create: After worktree creationpre_delete: Before worktree deletionpost_delete: After worktree deletionpre_switch: Before switching worktreespost_switch: After switching worktreespre_list: Before listing worktreespost_list: After listing worktrees
Note: The
portal hooks runcommand supports manually runningpost_create,pre_delete,pre_switch, andpost_switch. The other stages are triggered automatically by their respective operations.
Configuration Hierarchy
Portal merges configuration from multiple sources (in order of priority):
.portal.yml- Project configuration (highest priority)~/.portal/config.yml- Global configuration- Default configuration (lowest priority)
Each level merges with the previous, allowing you to override specific values without redefining everything.
Basic Hook Configuration
Global Configuration (~/.portal/config.yml):
version: "1.0"
base_dir: "../{project}_worktrees"
hooks:
post_create:
- type: command
config:
command: "echo 'Setting up worktree...'"
- type: mkdir
config:
paths: ["logs", "tmp"]
Project Configuration (.portal.yml in project root):
# These hooks merge with (and override) global hooks
hooks:
post_create:
# Copy files from project root to new worktree
- type: copy
config:
from: ".env.local.example"
to: ".env"
name: "Setup local environment"
# Install dependencies conditionally
- type: command
config:
command: "npm install --frozen-lockfile"
condition: "file_exists:package.json"
name: "Install Node.js dependencies"
# Auto-configure environment with dynamic values
- type: env_update
config:
file: ".env"
updates:
DATABASE_NAME: "{{project}}_{{worktree_name}}_dev"
API_PORT: "auto" # Automatically finds available port
REDIS_PREFIX: "{{worktree_name}}:"
name: "Configure development environment"
pre_delete:
# Cleanup before worktree deletion
- type: command
config:
command: "docker-compose down -v"
condition: "file_exists:docker-compose.yml"
on_error: "warn"
name: "Stop Docker services"
Advanced Hook Features
Variable Substitution
All hooks support {{variable}} substitution:
hooks:
post_create:
- type: env_update
config:
file: ".env"
updates:
DB_NAME: "{{project}}_{{worktree_name}}_dev"
WORKTREE_PATH: "{{worktree_path}}"
ASSIGNED_COLOR: "{{color_hex}}"
Available Variables:
{{project}}- Project name (derived from worktree parent directory){{worktree_name}}- Worktree name{{worktree_path}}- Full worktree path{{worktree}}- Alias for{{worktree_path}}{{branch}}- Git branch name{{color_hex}}- Assigned color hex code (e.g.,#3F51B5){{home}}- User home directory{{user}}- Current username
Conditional Execution
Execute hooks only when conditions are met:
hooks:
post_create:
- type: command
config:
command: "npm install"
condition: "file_exists:package.json"
- type: command
config:
command: "pip install -r requirements.txt"
condition: "file_exists:requirements.txt"
- type: command
config:
command: "brew services start postgresql"
condition: "platform:darwin"
Available Conditions:
file_exists:filename- File exists in worktreefile_not_exists:filename- File doesn't existdir_exists:dirname- Directory existsenv:VAR_NAME- Environment variable is setenv:VAR_NAME=value- Environment variable equals valueplatform:darwin- Platform check (darwin/linux/win32)command_success:command- Command executes successfully
Negation: Prefix any condition with ! to negate it (e.g., !file_exists:package.json runs the hook only when package.json does not exist).
Error Handling
Control what happens when hooks fail:
hooks:
post_create:
- type: command
config:
command: "critical-setup"
on_error: "fail" # Stop execution on failure
- type: command
config:
command: "optional-setup"
on_error: "warn" # Continue with warning (default)
- type: command
config:
command: "nice-to-have"
on_error: "ignore" # Continue silently
Common Hook Examples
Node.js Project Setup
# .portal.yml file
hooks:
post_create:
- type: copy
config:
from: ".env.example"
to: ".env"
- type: command
config:
command: "npm install"
condition: "file_exists:package.json"
- type: env_update
config:
file: ".env"
updates:
NODE_ENV: "development"
PORT: "auto"
Rust Project Setup
# .portal.yml file
hooks:
post_create:
- type: env_update
config:
file: ".env"
updates:
RUST_LOG: "{{project}}={{worktree_name}}=debug"
DATABASE_URL: "postgres://localhost/{{project}}_{{worktree_name}}"
name: "Configure Rust environment"
- type: command
config:
command: "cargo build --workspace"
condition: "file_exists:Cargo.toml"
name: "Build workspace"
- type: command
config:
command: "cargo sqlx database setup"
condition: "file_exists:migrations"
on_error: "warn"
name: "Setup database"
Full-Stack Development
# .portal.yml file
hooks:
post_create:
# Setup environment files
- type: template
config:
template: "docker-compose.template.yml"
output: "docker-compose.yml"
variables:
db_port: "{{color_hex}}"
# Start services
- type: command
config:
command: "docker-compose up -d postgres redis"
# Install dependencies
- type: command
config:
command: "npm run setup:dev"
pre_delete:
# Cleanup services
- type: command
config:
command: "docker-compose down -v"
on_error: "warn"
Security Features
Portal's hook system includes built-in security protections:
- Command injection prevention: Dangerous shell operators blocked by default
- Path traversal protection: Prevents
../attacks in file operations - Input validation: All hook configurations validated
- Secure defaults: Safe error handling and timeout protection
Shell Command Security
# ❌ This is blocked for security:
- type: command
config:
command: "echo test; rm -rf /"
# ✅ Explicit bypass when needed:
- type: command
config:
command: "echo test && echo done"
allow_shell: true # Explicitly allow shell operators
Project-Level Configuration
Use .portal.yml file in your project root for project-specific configurations:
# Project structure
my-project/
├── .git/
├── .portal.yml # Project configuration
└── src/
The .portal.yml file merges with and overrides global settings for project-specific automation.
Tips & Tricks
Productivity Aliases
After running portal shell install, you get:
pw <worktree>- Quick switch to a worktree and cd into it
Additional aliases you can add to your shell config:
# Quick worktree commands
alias pnew="portal new"
alias plist="portal list"
alias pdel="portal delete"
# Open in editor
alias pcursor="portal cursor"
alias pvscode="portal vscode"
Example Hook Scripts
Auto-install dependencies:
hooks:
post_create:
- type: command
name: "Install deps"
config:
command: |
if [ -f package.json ]; then npm install
elif [ -f requirements.txt ]; then pip install -r requirements.txt
elif [ -f Gemfile ]; then bundle install
fi
allow_shell: true
Rust project with workspace setup:
hooks:
post_create:
- type: command
name: "Build workspace"
config:
command: "cargo build --workspace"
- type: command
name: "Setup git hooks"
config:
command: "cargo husky install"
Troubleshooting
Worktrees Created in Wrong Location
Use portal config show to verify where worktrees will be created:
# Check current configuration and resolved paths
portal config show
# Look for this section in the output:
# Worktree Settings:
# Base Directory Template: ../{project}_worktrees
# Resolved Base Directory: ../myproject_worktrees
# Actual Worktree Path: /Users/you/code/myproject_worktrees
If the path is incorrect, check your .portal.yml file:
# Default: Creates worktrees as siblings to main repo (recommended)
base_dir: "../{project}_worktrees"
# This would create worktrees inside the main repo (not recommended)
base_dir: "{project}_worktrees"
# This would create worktrees two levels up from the main repo
base_dir: "../../{project}_worktrees"
The base_dir path is relative to your main repository. Using ../ creates a sibling folder, which keeps worktrees organized and separate from your main codebase.
Configuration Not Loading
Verify which configuration files are being loaded:
portal config show
# Check the "Configuration Sources" section to see which files are found
Debugging Configuration Issues
# View merged configuration as JSON for detailed inspection
portal config show --json | jq '.'
# Check if running from correct Git repository
git rev-parse --show-toplevel
License
MIT License - see LICENSE file for details.
⛩️ Portal - Modern Git Worktree Manager
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file git_portal-0.1.1.tar.gz.
File metadata
- Download URL: git_portal-0.1.1.tar.gz
- Upload date:
- Size: 88.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
020cd42e86965ccf852fbc27abaa1cde3b3b781f79d5b5caadc7fbefb9caf4b8
|
|
| MD5 |
640026e93fc7d67df8a28d568773da5e
|
|
| BLAKE2b-256 |
9ec8c114bc277ffe21db3b8423368d1ec9019fcf459b600fcdd9dab851c8c64d
|
Provenance
The following attestation bundles were made for git_portal-0.1.1.tar.gz:
Publisher:
publish.yml on aureliensibiril/portal
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
git_portal-0.1.1.tar.gz -
Subject digest:
020cd42e86965ccf852fbc27abaa1cde3b3b781f79d5b5caadc7fbefb9caf4b8 - Sigstore transparency entry: 973250883
- Sigstore integration time:
-
Permalink:
aureliensibiril/portal@0f173f9fa51d6ca788181dd588eab9c414d627d6 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/aureliensibiril
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0f173f9fa51d6ca788181dd588eab9c414d627d6 -
Trigger Event:
release
-
Statement type:
File details
Details for the file git_portal-0.1.1-py3-none-any.whl.
File metadata
- Download URL: git_portal-0.1.1-py3-none-any.whl
- Upload date:
- Size: 121.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ee02ba9cd5b2b8bc147e9b6a40782b2edf707c07c81a391bf611679cec083cce
|
|
| MD5 |
9f2518ebd5711611e028246b6dbcc2eb
|
|
| BLAKE2b-256 |
b6f7db6b3f4ebcfaf187bb0b46f682fa04974cd8168ec6e5449c22af86f501bb
|
Provenance
The following attestation bundles were made for git_portal-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on aureliensibiril/portal
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
git_portal-0.1.1-py3-none-any.whl -
Subject digest:
ee02ba9cd5b2b8bc147e9b6a40782b2edf707c07c81a391bf611679cec083cce - Sigstore transparency entry: 973250886
- Sigstore integration time:
-
Permalink:
aureliensibiril/portal@0f173f9fa51d6ca788181dd588eab9c414d627d6 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/aureliensibiril
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0f173f9fa51d6ca788181dd588eab9c414d627d6 -
Trigger Event:
release
-
Statement type: