Skip to main content

Per-folder GitHub CLI workspace manager

Project description

GH Workspace Manager (ghw)

Per-folder GitHub CLI auth for multi-account workflows.

Why This Exists

You work with multiple GitHub accounts:

  • personal - Your personal account
  • work - Your work/employer account
  • client - Client projects

Example uses: personal + work + freelance clients, or personal + open source + consulting.

GitHub CLI stores auth globally in ~/.config/gh/. Every gh auth login overwrites the previous one. You're constantly switching and losing track.

Solution: Each folder uses mise to set GH_CONFIG_DIR, and ghw validates before running gh. You cd into a project, gh automatically uses the right account.

Install

# You already have mise installed and in shell

# Install ghw
cd ~/src/gh-workspace-manager
uv tool install -e .

Verify:

ghw --help

Setup

1. Create Workspaces

One per GitHub account you want to use:

ghw workspace init personal
ghw workspace init work
ghw workspace list

Configs live under ~/.config/gh-workspaces/<workspace>/.

2. Add .mise.toml to Each Project

Lazy way (with init-here):

cd ~/src/my-project

# If git remote exists, auto-detect from URL:
git remote add origin git@github.com:personal/myrepo.git
ghw init-here --auto

# Or specify workspace explicitly:
ghw init-here --workspace personal

Manual way:

Create ~/src/PROJECT/.mise.toml:

[env]
# Absolute path required — mise does not expand ~ or $HOME in env values.
# macOS: /Users/<you>/.config/gh-workspaces/personal
# Linux: /home/<you>/.config/gh-workspaces/personal
GH_CONFIG_DIR = "/home/<you>/.config/gh-workspaces/personal"

Replace personal with work or client as needed. Tip: ghw init-here --workspace personal emits the correct absolute path for your OS automatically.

3. Configure mise Auto-Trust

Add to your shell rc (~/.bashrc or ~/.zshrc) so all .mise.toml files under ~/src are trusted without manual mise trust:

# Must be exported BEFORE `mise activate` so activation picks it up.
export MISE_TRUSTED_CONFIG_PATHS="$HOME/src"
eval "$(mise activate bash)"   # or: eval "$(mise activate zsh)"

4. Add gh Wrapper Function

Add to the same shell rc:

gh() {
    if [[ -n "$GH_CONFIG_DIR" ]]; then
        echo "[ghw] Using gh via ghw wrapper (GH_CONFIG_DIR: $GH_CONFIG_DIR)"
        ghw gh "$@"
    else
        command gh "$@"
    fi
}

Reload your shell:

exec $SHELL

5. Authenticate Each Workspace

# In a project using personal workspace
cd ~/src/some-project
gh auth login    # Authenticates personal

# In a project using work workspace
cd ~/src/other-project
gh auth login    # Authenticates work (different from personal)

Folder names don't matter - the .mise.toml in each folder determines the workspace.

Daily Workflow

# In a project using personal workspace
cd ~/src/myapp
gh auth status
# [ghw] Using gh via ghw wrapper (GH_CONFIG_DIR: <your-home>/.config/gh-workspaces/personal)
# github.com
# ✓ Logged in to github.com account your-username (keyring)

# Switch to project using work workspace
cd ~/src/client-portal
gh auth status
# [ghw] Using gh via ghw wrapper (GH_CONFIG_DIR: <your-home>/.config/gh-workspaces/work)
# github.com
# ✓ Logged in to github.com account work-account (keyring)

# Outside any project
cd ~
gh auth status
# (no [ghw] message - regular gh)

Note: Folder names like myapp or client-portal are arbitrary. What matters is the .mise.toml inside each folder.

Safety: Strict Validation

The protection mechanism: If GH_CONFIG_DIR is set but workspace missing, ghw errors hard.

cd ~/src/myapp
export GH_CONFIG_DIR=<your-home>/.config/gh-workspaces/missing
gh auth status
# [ghw] Using gh via ghw wrapper (GH_CONFIG_DIR: <your-home>/.config/gh-workspaces/missing)
# ╭─────────────────────────────────── Error ────────────────────────────────────╮
# │ Workspace config missing at <your-home>/.config/gh-workspaces/missing. Run:  │
# │ ghw workspace init missing                                                   │
# ╰──────────────────────────────────────────────────────────────────────────────╯

No fallback. No silent default. You always know what's happening.

Commands

# Quick setup (lazy way)
ghw init-here --workspace personal    # Create .mise.toml for current project
# OR if git remote is set:
git remote add origin git@github.com:personal/repo.git
ghw init-here --auto            # Auto-detect from git remote

# Workspace management
ghw workspace list              # Show: client, personal, work, test
ghw workspace init new-client   # Create new workspace

# Run gh with validation
ghw gh auth status              # Explicit version
gh auth status                  # Via alias (same thing)

# Diagnostics
ghw doctor                      # Check setup and diagnose issues
ghw --version                   # Show version

How It Works

cd ~/src/myapp
↓
mise reads .mise.toml (contains GH_CONFIG_DIR=.../personal)
↓
exports GH_CONFIG_DIR=<your-home>/.config/gh-workspaces/personal
↓
gh auth status (alias)
↓
sees GH_CONFIG_DIR, calls ghw gh auth status
↓
ghw validates workspace exists
↓
runs the real gh (e.g. /opt/homebrew/bin/gh on macOS, /usr/bin/gh on Linux)

The folder name (myapp) is irrelevant. The .mise.toml inside determines the workspace.

File Locations

~/.config/gh-workspaces/          # Your workspaces
├── personal/
├── work/
└── ...

~/.bashrc or ~/.zshrc             # Your gh() wrapper function lives here

~/src/*/                          # Your projects with .mise.toml

Troubleshooting

Quick Diagnosis

Run ghw doctor to check your entire setup:

ghw doctor

This checks:

  • ghw is installed correctly
  • mise is installed and in shell
  • gh is installed
  • gh alias is loaded
  • Workspaces exist
  • GH_CONFIG_DIR detection

"Workspace config missing"

ghw workspace init <name>

"ghw command not found"

cd ~/src/gh-workspace-manager
uv tool install -e .

Alias not working

# Check if wrapper function loaded
type gh

# Should show "gh is a function", not the raw binary path
# (e.g. /opt/homebrew/bin/gh on macOS, /usr/bin/gh on Linux)

# Reload if needed
exec $SHELL

mise not setting GH_CONFIG_DIR

# Check mise is in shell
echo $SHELL
mise --version

# Check .mise.toml exists in project folder
cat ~/src/myapp/.mise.toml

# Force reload
cd . && cd ~/src/myapp
env | grep GH

Further Reading

Development

cd ~/src/gh-workspace-manager

# Run tests
uv run --group dev pytest tests/ -v

# Reinstall after changes
uv tool install -e .

# Test locally
ghw workspace list

Key Principles

  1. Per-folder only: .mise.toml in exact folder only, no inheritance
  2. Strict validation: Error if workspace missing, never fallback
  3. Reminder message: Always know when using ghw vs regular gh
  4. Cross-platform: pathlib, works on macOS/Linux/Windows

Questions

Why not just use GH_CONFIG_DIR manually? Easy to forget, easy to mess up. ghw adds validation and reminder.

Why mise instead of direnv? Faster (<1ms vs 20-50ms), cleaner config, unified with your other tools.

What if I delete a workspace folder? ghw will error immediately and tell you to recreate it.

Can I use this outside my src folder? Yes, anywhere. Just add .mise.toml with the workspace you want.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ghw-1.0.0.tar.gz (30.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ghw-1.0.0-py3-none-any.whl (11.3 kB view details)

Uploaded Python 3

File details

Details for the file ghw-1.0.0.tar.gz.

File metadata

  • Download URL: ghw-1.0.0.tar.gz
  • Upload date:
  • Size: 30.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for ghw-1.0.0.tar.gz
Algorithm Hash digest
SHA256 c27582953247e682b28736f223d395210555c408432cb4030e61bd6629b8e704
MD5 7b429d441cff4e9c2114cbc3f5c443fc
BLAKE2b-256 05a6610bd5584eb54007c87bdd79acbe1e3222c9d48bc7d5f136a79dcef4ced0

See more details on using hashes here.

File details

Details for the file ghw-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: ghw-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 11.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for ghw-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 784fa811467426fdec7e2920fc2a1c2c0e6424b35d34241c0ed4c702cdcebc89
MD5 0394141d3cde0f35dcbeeec56eb1c1da
BLAKE2b-256 b78e6ca72e740e18989e318bb15e3cc57c204aa1bf5f795ba73adc33a1a26c93

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page