Projj
Manage git repositories with directory conventions.
Why?
Every git repo gets a predictable home based on its URL:
$BASE/
├── github.com/
│ └── popomore/
│ └── projj/
└── gitlab.com/
└── popomore/
└── projj/
No more ~/code/misc/old-projj-backup. Clone once, find instantly.
Install
# Cargo
# Homebrew (after first release)
Quick Start
# Initialize config + install built-in hooks
# Clone a repo
# Find and jump to a repo
# List all repos
Shell Integration
Add to ~/.zshrc (or ~/.bashrc, ~/.config/fish/config.fish):
| # fish
Then:
Commands
projj init
Initialize configuration. Creates ~/.projj/config.toml and installs built-in hooks to ~/.projj/hooks/.
Also runs post_add hooks for all existing repos (e.g. syncs to zoxide).
projj add <repo>
Clone a repo into the conventional directory structure.
After cloning, runs post_add hooks (e.g. zoxide registration, git user config).
projj find [keyword]
Find a repo by keyword (case-insensitive). Outputs the path to stdout.
- Single match → prints path with group info
- Multiple matches → opens fzf for selection (falls back to built-in list without fzf)
- No keyword → lists all repos for selection
- Results show colored group tags (base/domain) and git URL
projj remove <keyword>
Remove a repo. Requires typing owner/repo to confirm. Runs pre_remove / post_remove hooks.
projj run <script> [--all] [--match PATTERN]
Run a script in the current directory, or all repos with --all.
Script name is resolved in order:
[scripts]table in config- Executable in
~/.projj/hooks/ - Raw shell command
projj list
List all repo paths, one per line. Pipe-friendly.
|
|
|
Configuration
~/.projj/config.toml
= ["/Users/x/projj", "/Users/x/work"]
= "github.com"
[]
= "rm -rf node_modules && rm -rf dist"
= "git fetch && git pull origin -p"
= "git status --short"
[[]]
= "post_add"
= "zoxide" # → ~/.projj/hooks/zoxide
[[]]
= "post_add"
= "github\\.com"
= "git_config_user" # → ~/.projj/hooks/git_config_user
= { = "popomore", = "me@example.com" }
[[]]
= "post_add"
= "gitlab\\.com"
= "git_config_user"
= { = "Other Name", = "other@example.com" }
Config Fields
| Field | Description | Default |
|---|---|---|
base |
Root directory (string or array) | ~/projj |
platform |
Default host for short form owner/repo |
github.com |
scripts |
Named scripts, reusable by hooks and projj run |
{} |
hooks |
Event-driven hook entries (see below) | [] |
Hook System
Hooks fire at repo lifecycle events:
| Event | When | cwd |
|---|---|---|
pre_add |
Before clone/move | Target directory (may not exist yet) |
post_add |
After clone/move | Repo directory |
pre_remove |
Before deletion | Repo directory |
post_remove |
After deletion | Parent directory |
Each hook entry:
| Field | Required | Description |
|---|---|---|
event |
Yes | Event name |
matcher |
No | Regex against host/owner/repo. Omit to match all |
command |
Yes | Script name or shell command |
env |
No | Extra environment variables |
Hooks receive context via environment variables:
PROJJ_EVENT — event name
PROJJ_REPO_PATH — full path to repo
PROJJ_REPO_HOST — e.g. github.com
PROJJ_REPO_OWNER — e.g. popomore
PROJJ_REPO_NAME — e.g. projj
PROJJ_REPO_URL — e.g. git@github.com:popomore/projj.git
And JSON via stdin for richer parsing.
Built-in Hooks
Installed to ~/.projj/hooks/ on projj init:
| Hook | Description |
|---|---|
zoxide |
Registers repo path with zoxide (if installed) |
git_config_user |
Sets user.name / user.email from PROJJ_GIT_NAME / PROJJ_GIT_EMAIL env |
Custom Hooks
Drop executable scripts into ~/.projj/hooks/:
Then reference by name in config:
[[]]
= "post_add"
= "notify"
External Tools
Projj integrates with these tools when available. None are required — projj works fine without them, just with a simpler experience.
| Tool | Integration | Without it |
|---|---|---|
| fzf | Fuzzy search, colored groups in find / remove |
Falls back to numbered list |
| zoxide | post_add hook registers paths for z navigation |
No auto-registration |
Install (optional):
# macOS
# Ubuntu/Debian
|