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-0.1.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-0.1.0-py3-none-any.whl (11.3 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: ghw-0.1.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-0.1.0.tar.gz
Algorithm Hash digest
SHA256 e63d946edd3900a670a8a0ce8999dc505b965a2b2153c925d202d8734ec7a22a
MD5 912b498c6157da941eff5e1a5b5ee84f
BLAKE2b-256 3b108780d6fbb016fb8630582e30a0510410f0cd90cc0e45e68191f7850cfe90

See more details on using hashes here.

File details

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

File metadata

  • Download URL: ghw-0.1.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-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 20606efc4238c2e290647f95f3e8d9bc82e23009de3caf41b3bed259e7280b2f
MD5 d6a1a95ba68d296a46eba1a395c6366e
BLAKE2b-256 bfc8bea671a2dc0d630535a2f3d8f9b0ebe543e947a62851affa0ec7b9fc0f0f

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