SkillAgentSearch skills...

Projj

Manage git repositories with directory conventions.

Install / Use

/learn @popomore/Projj
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Projj

Manage git repositories with directory conventions — clone once, find instantly.

Crates.io CI License: MIT

The Problem

Git repos pile up. You clone them into ~/code, ~/projects, ~/work, or wherever feels right at the moment. Six months later:

~/code/projj
~/projects/old-projj
~/misc/projj-backup
~/work/projj-fork

Which one is current? Where did you put that internal GitLab repo? You find / -name .git and wait.

The Solution

Projj gives every repo a predictable home based on its URL — just like GOPATH did for Go:

$BASE/
├── github.com/
│   └── popomore/
│       └── projj/
└── gitlab.com/
    └── company/
        └── internal-tool/
  • One repo, one location — no duplicates, no guessing
  • Instant lookup — fuzzy find with fzf, jump with p projj
  • Hooks — auto-configure git user, register with zoxide, run custom scripts on clone
  • Multi-host — GitHub, GitLab, Gitee, self-hosted — all organized the same way
  • Zero overhead — no daemon, no cache, no database, just your filesystem

Install

# Cargo
cargo install projj

# Homebrew (after first release)
brew install popomore/tap/projj

Quick Start

projj init                              # one-time setup
projj add popomore/projj               # clone → ~/projj/github.com/popomore/projj
projj add git@gitlab.com:team/app.git   # clone → ~/projj/gitlab.com/team/app
p projj                                 # jump to repo instantly (shell function)
projj run repo-status --all             # batch operations across all repos

Shell Integration

Add to ~/.zshrc (or ~/.bashrc, ~/.config/fish/config.fish):

eval "$(projj shell-setup zsh)"    # zsh
eval "$(projj shell-setup bash)"   # bash
projj shell-setup fish | source    # fish

This sets up:

  • Tab completions for all commands
  • projj run completes task names from [tasks] config and ~/.projj/tasks/
  • p() function for quick navigation
p projj       # jump to projj
p egg         # multiple matches → fzf selection
p             # browse all repos with fzf

Commands

projj init

Initialize configuration. Creates ~/.projj/config.toml, installs built-in tasks to ~/.projj/tasks/, and shows a summary of your setup (base directories, repos found, hooks, tasks).

projj add <repo>

Clone a repo into the conventional directory structure.

projj add popomore/projj                                  # short form
projj add git@github.com:popomore/projj.git               # SSH
projj add https://github.com/popomore/projj               # HTTPS
projj add ssh://git@git.gitlab.cn:2224/web/cms.git         # SSH with port
projj add ./local/repo                                     # move local repo

Runs post_add hooks after cloning. Skips hooks if repo already exists.

projj find [keyword]

Find a repo by keyword (case-insensitive). Outputs the path to stdout.

  • Single match — prints path directly
  • Multiple matches — opens fzf for fuzzy selection with colored group tags (base/domain) and git URL
  • No keyword — lists all repos for selection
  • No fzf — falls back to numbered list

projj remove <keyword>

Remove a repo. Searches the same way as find, then requires typing owner/repo to confirm. Runs pre_remove / post_remove hooks.

projj run <task> [--all] [--match PATTERN] [-- ARGS...]

Run a task in the current directory, or all repos with --all.

projj run "npm install"                             # raw command
projj run update --all                              # named task in all repos
projj run "git status" --all --match "SeeleAI"      # filter repos by regex
projj run repo-status -- --detail                   # pass args to task after --

projj list [--raw]

List all repositories with grouped display, colored by base directory and domain.

projj list              # pretty: grouped by base/host, colored, with git URL
projj list --raw        # plain paths, one per line (for piping)

Configuration

~/.projj/config.toml

base = ["/Users/x/projj", "/Users/x/work"]
platform = "github.com"

[tasks]
update = "git fetch && git pull origin -p"
clean = "rm -rf node_modules dist target"
status = "git status --short"

[[hooks]]
event = "post_add"
tasks = ["zoxide"]

[[hooks]]
event = "post_add"
matcher = "github\\.com"
tasks = ["zoxide", "git-config-user"]
env = { GIT_USER_NAME = "popomore", GIT_USER_EMAIL = "me@example.com" }

[[hooks]]
event = "post_add"
matcher = "gitlab\\.com"
tasks = ["zoxide", "git-config-user"]
env = { GIT_USER_NAME = "Other Name", GIT_USER_EMAIL = "other@corp.com" }

| Field | Description | Default | |-------|-------------|---------| | base | Root directory (string or array) | ~/projj | | platform | Default host for short form owner/repo | github.com | | tasks | Named tasks (see Tasks) | {} | | hooks | Event-driven hooks (see Hooks) | [] |

Tasks

Tasks are reusable commands that can be run manually via projj run or triggered by hooks.

Defining Tasks

Inline — one-liners in [tasks] table:

[tasks]
update = "git fetch && git pull origin -p"
clean = "rm -rf node_modules dist target"

Script files — executables in ~/.projj/tasks/:

cat > ~/.projj/tasks/notify << 'EOF'
#!/bin/bash
echo "Added $PROJJ_REPO_OWNER/$PROJJ_REPO_NAME"
EOF
chmod +x ~/.projj/tasks/notify

Running Tasks

projj run update --all                    # inline task
projj run notify --all                    # task file
projj run repo-status -- --detail         # pass arguments after --
projj run "git log -5"                    # raw command (not a named task)

Resolution order: [tasks] table → ~/.projj/tasks/ file → raw shell command.

Task Context

When tasks are executed via hooks, they receive repo context via environment variables:

PROJJ_EVENT        — event name (e.g. post_add)
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

These are system-provided variables. Hooks can also pass custom variables via the env field (see Hooks).

JSON is also sent via stdin for richer parsing. When run manually via projj run, these variables are not set.

Built-in Tasks

Installed to ~/.projj/tasks/ on projj init.

zoxide

Registers the repo path with zoxide so z can jump to it. Silently skips if zoxide is not installed.

[[hooks]]
event = "post_add"
tasks = ["zoxide"]

git-config-user

Sets user.name and user.email for the repo. Reads from custom env vars set in the hook's env field (not system-provided PROJJ_* variables).

[[hooks]]
event = "post_add"
matcher = "github\\.com"
tasks = ["git-config-user"]
env = { GIT_USER_NAME = "popomore", GIT_USER_EMAIL = "me@example.com" }

[[hooks]]
event = "post_add"
matcher = "gitlab\\.com"
tasks = ["git-config-user"]
env = { GIT_USER_NAME = "Other Name", GIT_USER_EMAIL = "other@corp.com" }

| Env var | Description | |---------|-------------| | GIT_USER_NAME | Value for git config user.name | | GIT_USER_EMAIL | Value for git config user.email |

Both are optional. Skips if not set.

repo-status

Shows disk usage, git status, and ignored files for a repo.

projj run repo-status                   # current repo
projj run repo-status --all             # all repos (quick summary)
projj run repo-status -- --detail       # include ignored files breakdown

Output example:

📦 1.1G total | 🗃️  .git 2.0M | ✓ clean
📦 1.1G total | 🗃️  .git 2.0M | ✓ clean | 🚫 15437 ignored: target(1.1G, 99%)

Colors by size: green (<100M), yellow (100M–1G), red (>1G). Respects NO_COLOR.

Hooks

Hooks trigger tasks automatically at repo lifecycle events. They are the glue between events and tasks.

Events

| Event | When | cwd | |-------|------|-----| | pre_add | Before clone/move | Target directory | | post_add | After clone/move | Repo directory | | pre_remove | Before deletion | Repo directory | | post_remove | After deletion | Parent directory |

Configuration

[[hooks]]
event = "post_add"                                    # required: event name
matcher = "github\\.com"                              # optional: regex on host/owner/repo
tasks = ["zoxide", "git-config-user"]                 # required: tasks to run in order
env = { GIT_USER_NAME = "popomore" }                       # optional: custom env vars for tasks

| Field | Required | Description | |-------|----------|-------------| | event | Yes | Event name | | matcher | No | Regex against host/owner/repo. Omit to match all | | tasks | Yes | List of task names or commands, executed in order. Stops on first failure | | env | No | Custom environment variables passed to tasks (user-defined, not PROJJ_*) |

Each entry in tasks is resolved the same way as projj run (task table → task file → raw command).

Matcher

The matcher field is a regex matched against host/owner/repo. Omit to match all repos.

| Matcher | Matches | |---------|---------| | (omitted) or * | All repos | | github\\.com | All GitHub repos | | github\\.com/SeeleAI | All repos under SeeleAI org | | github\\.com/popomore/projj | Exact repo | | gitlab\\.com\|gitee\\.com | GitLab or Gitee repos |

Note: . in regex matches any character. Use \\. to match a literal dot.

Environment Variables

| Variable | Description | |----------|-------------| |

Related Skills

View on GitHub
GitHub Stars279
CategoryProject
Updated15h ago
Forks43

Languages

Rust

Security Score

100/100

Audited on Apr 8, 2026

No findings