{"id":13504316,"url":"https://github.com/popomore/projj","last_synced_at":"2026-04-06T10:00:57.484Z","repository":{"id":16881041,"uuid":"80803957","full_name":"popomore/projj","owner":"popomore","description":"Manage repository easily.","archived":false,"fork":false,"pushed_at":"2026-04-06T01:14:03.000Z","size":257,"stargazers_count":279,"open_issues_count":20,"forks_count":42,"subscribers_count":5,"default_branch":"master","last_synced_at":"2026-04-06T01:17:24.709Z","etag":null,"topics":["hooks","project-management","repository","repository-management"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/popomore.png","metadata":{"files":{"readme":"README.md","changelog":"History.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-02-03T06:38:03.000Z","updated_at":"2026-04-05T13:25:29.000Z","dependencies_parsed_at":"2024-06-19T20:02:50.730Z","dependency_job_id":"f0ded282-aff6-4f7d-a0cc-e7482341830c","html_url":"https://github.com/popomore/projj","commit_stats":{"total_commits":52,"total_committers":5,"mean_commits":10.4,"dds":"0.15384615384615385","last_synced_commit":"b2ef85c491429ba16d876b0eb74295597f9ffd7d"},"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/popomore/projj","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/popomore%2Fprojj","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/popomore%2Fprojj/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/popomore%2Fprojj/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/popomore%2Fprojj/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/popomore","download_url":"https://codeload.github.com/popomore/projj/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/popomore%2Fprojj/sbom","scorecard":{"id":741113,"data":{"date":"2025-08-11","repo":{"name":"github.com/popomore/projj","commit":"b2ef85c491429ba16d876b0eb74295597f9ffd7d"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.8,"checks":[{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":2,"reason":"Found 8/30 approved changesets -- score normalized to 2","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 18 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-22T17:29:55.349Z","repository_id":16881041,"created_at":"2025-08-22T17:29:55.350Z","updated_at":"2025-08-22T17:29:55.350Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31467985,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-06T08:36:52.050Z","status":"ssl_error","status_checked_at":"2026-04-06T08:36:51.267Z","response_time":112,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hooks","project-management","repository","repository-management"],"created_at":"2024-08-01T00:00:29.322Z","updated_at":"2026-04-06T10:00:57.477Z","avatar_url":"https://github.com/popomore.png","language":"JavaScript","readme":"# Projj\n\nManage git repositories with directory conventions — clone once, find instantly.\n\n[![Crates.io](https://img.shields.io/crates/v/projj.svg)](https://crates.io/crates/projj)\n[![CI](https://github.com/popomore/projj/actions/workflows/ci.yml/badge.svg)](https://github.com/popomore/projj/actions)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n\n## The Problem\n\nGit repos pile up. You clone them into `~/code`, `~/projects`, `~/work`, or wherever feels right at the moment. Six months later:\n\n```\n~/code/projj\n~/projects/old-projj\n~/misc/projj-backup\n~/work/projj-fork\n```\n\nWhich one is current? Where did you put that internal GitLab repo? You `find / -name .git` and wait.\n\n## The Solution\n\nProjj gives every repo a predictable home based on its URL — just like `GOPATH` did for Go:\n\n```text\n$BASE/\n├── github.com/\n│   └── popomore/\n│       └── projj/\n└── gitlab.com/\n    └── company/\n        └── internal-tool/\n```\n\n- **One repo, one location** — no duplicates, no guessing\n- **Instant lookup** — fuzzy find with fzf, jump with `p projj`\n- **Hooks** — auto-configure git user, register with zoxide, run custom scripts on clone\n- **Multi-host** — GitHub, GitLab, Gitee, self-hosted — all organized the same way\n- **Zero overhead** — no daemon, no cache, no database, just your filesystem\n\n## Install\n\n```bash\n# Cargo\ncargo install projj\n\n# Homebrew (after first release)\nbrew install popomore/tap/projj\n```\n\n## Quick Start\n\n```bash\nprojj init                              # one-time setup\nprojj add popomore/projj               # clone → ~/projj/github.com/popomore/projj\nprojj add git@gitlab.com:team/app.git   # clone → ~/projj/gitlab.com/team/app\np projj                                 # jump to repo instantly (shell function)\nprojj run repo-status --all             # batch operations across all repos\n```\n\n### Shell Integration\n\nAdd to `~/.zshrc` (or `~/.bashrc`, `~/.config/fish/config.fish`):\n\n```bash\neval \"$(projj shell-setup zsh)\"    # zsh\neval \"$(projj shell-setup bash)\"   # bash\nprojj shell-setup fish | source    # fish\n```\n\nThis sets up:\n\n- Tab completions for all commands\n- `projj run` completes task names from `[tasks]` config and `~/.projj/tasks/`\n- `p()` function for quick navigation\n\n```bash\np projj       # jump to projj\np egg         # multiple matches → fzf selection\np             # browse all repos with fzf\n```\n\n## Commands\n\n### projj init\n\nInitialize 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).\n\n### projj add \\\u003crepo\\\u003e\n\nClone a repo into the conventional directory structure.\n\n```bash\nprojj add popomore/projj                                  # short form\nprojj add git@github.com:popomore/projj.git               # SSH\nprojj add https://github.com/popomore/projj               # HTTPS\nprojj add ssh://git@git.gitlab.cn:2224/web/cms.git         # SSH with port\nprojj add ./local/repo                                     # move local repo\n```\n\nRuns `post_add` hooks after cloning. Skips hooks if repo already exists.\n\n### projj find [keyword]\n\nFind a repo by keyword (case-insensitive). Outputs the path to stdout.\n\n- **Single match** — prints path directly\n- **Multiple matches** — opens fzf for fuzzy selection with colored group tags (base/domain) and git URL\n- **No keyword** — lists all repos for selection\n- **No fzf** — falls back to numbered list\n\n### projj remove \\\u003ckeyword\\\u003e\n\nRemove a repo. Searches the same way as `find`, then requires typing `owner/repo` to confirm. Runs `pre_remove` / `post_remove` hooks.\n\n### projj run \\\u003ctask\\\u003e [--all] [--match PATTERN] [-- ARGS...]\n\nRun a task in the current directory, or all repos with `--all`.\n\n```bash\nprojj run \"npm install\"                             # raw command\nprojj run update --all                              # named task in all repos\nprojj run \"git status\" --all --match \"SeeleAI\"      # filter repos by regex\nprojj run repo-status -- --detail                   # pass args to task after --\n```\n\n### projj list [--raw]\n\nList all repositories with grouped display, colored by base directory and domain.\n\n```bash\nprojj list              # pretty: grouped by base/host, colored, with git URL\nprojj list --raw        # plain paths, one per line (for piping)\n```\n\n## Configuration\n\n`~/.projj/config.toml`\n\n```toml\nbase = [\"/Users/x/projj\", \"/Users/x/work\"]\nplatform = \"github.com\"\n\n[tasks]\nupdate = \"git fetch \u0026\u0026 git pull origin -p\"\nclean = \"rm -rf node_modules dist target\"\nstatus = \"git status --short\"\n\n[[hooks]]\nevent = \"post_add\"\ntasks = [\"zoxide\"]\n\n[[hooks]]\nevent = \"post_add\"\nmatcher = \"github\\\\.com\"\ntasks = [\"zoxide\", \"git-config-user\"]\nenv = { GIT_USER_NAME = \"popomore\", GIT_USER_EMAIL = \"me@example.com\" }\n\n[[hooks]]\nevent = \"post_add\"\nmatcher = \"gitlab\\\\.com\"\ntasks = [\"zoxide\", \"git-config-user\"]\nenv = { GIT_USER_NAME = \"Other Name\", GIT_USER_EMAIL = \"other@corp.com\" }\n```\n\n| Field | Description | Default |\n|-------|-------------|---------|\n| `base` | Root directory (string or array) | `~/projj` |\n| `platform` | Default host for short form `owner/repo` | `github.com` |\n| `tasks` | Named tasks (see [Tasks](#tasks)) | `{}` |\n| `hooks` | Event-driven hooks (see [Hooks](#hooks)) | `[]` |\n\n## Tasks\n\nTasks are reusable commands that can be run manually via `projj run` or triggered by hooks.\n\n### Defining Tasks\n\n**Inline** — one-liners in `[tasks]` table:\n\n```toml\n[tasks]\nupdate = \"git fetch \u0026\u0026 git pull origin -p\"\nclean = \"rm -rf node_modules dist target\"\n```\n\n**Script files** — executables in `~/.projj/tasks/`:\n\n```bash\ncat \u003e ~/.projj/tasks/notify \u003c\u003c 'EOF'\n#!/bin/bash\necho \"Added $PROJJ_REPO_OWNER/$PROJJ_REPO_NAME\"\nEOF\nchmod +x ~/.projj/tasks/notify\n```\n\n### Running Tasks\n\n```bash\nprojj run update --all                    # inline task\nprojj run notify --all                    # task file\nprojj run repo-status -- --detail         # pass arguments after --\nprojj run \"git log -5\"                    # raw command (not a named task)\n```\n\nResolution order: `[tasks]` table → `~/.projj/tasks/` file → raw shell command.\n\n### Task Context\n\nWhen tasks are executed via hooks, they receive repo context via environment variables:\n\n```text\nPROJJ_EVENT        — event name (e.g. post_add)\nPROJJ_REPO_PATH    — full path to repo\nPROJJ_REPO_HOST    — e.g. github.com\nPROJJ_REPO_OWNER   — e.g. popomore\nPROJJ_REPO_NAME    — e.g. projj\nPROJJ_REPO_URL     — e.g. git@github.com:popomore/projj.git\n```\n\nThese are system-provided variables. Hooks can also pass custom variables via the `env` field (see [Hooks](#hooks)).\n\nJSON is also sent via stdin for richer parsing. When run manually via `projj run`, these variables are not set.\n\n### Built-in Tasks\n\nInstalled to `~/.projj/tasks/` on `projj init`.\n\n#### zoxide\n\nRegisters the repo path with [zoxide](https://github.com/ajeetdsouza/zoxide) so `z` can jump to it. Silently skips if zoxide is not installed.\n\n```toml\n[[hooks]]\nevent = \"post_add\"\ntasks = [\"zoxide\"]\n```\n\n#### git-config-user\n\nSets `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).\n\n```toml\n[[hooks]]\nevent = \"post_add\"\nmatcher = \"github\\\\.com\"\ntasks = [\"git-config-user\"]\nenv = { GIT_USER_NAME = \"popomore\", GIT_USER_EMAIL = \"me@example.com\" }\n\n[[hooks]]\nevent = \"post_add\"\nmatcher = \"gitlab\\\\.com\"\ntasks = [\"git-config-user\"]\nenv = { GIT_USER_NAME = \"Other Name\", GIT_USER_EMAIL = \"other@corp.com\" }\n```\n\n| Env var | Description |\n|---------|-------------|\n| `GIT_USER_NAME` | Value for `git config user.name` |\n| `GIT_USER_EMAIL` | Value for `git config user.email` |\n\nBoth are optional. Skips if not set.\n\n#### repo-status\n\nShows disk usage, git status, and ignored files for a repo.\n\n```bash\nprojj run repo-status                   # current repo\nprojj run repo-status --all             # all repos (quick summary)\nprojj run repo-status -- --detail       # include ignored files breakdown\n```\n\nOutput example:\n\n```text\n📦 1.1G total | 🗃️  .git 2.0M | ✓ clean\n📦 1.1G total | 🗃️  .git 2.0M | ✓ clean | 🚫 15437 ignored: target(1.1G, 99%)\n```\n\nColors by size: green (\u003c100M), yellow (100M–1G), red (\u003e1G). Respects `NO_COLOR`.\n\n## Hooks\n\nHooks trigger tasks automatically at repo lifecycle events. They are the glue between events and tasks.\n\n### Events\n\n| Event | When | cwd |\n|-------|------|-----|\n| `pre_add` | Before clone/move | Target directory |\n| `post_add` | After clone/move | Repo directory |\n| `pre_remove` | Before deletion | Repo directory |\n| `post_remove` | After deletion | Parent directory |\n\n### Configuration\n\n```toml\n[[hooks]]\nevent = \"post_add\"                                    # required: event name\nmatcher = \"github\\\\.com\"                              # optional: regex on host/owner/repo\ntasks = [\"zoxide\", \"git-config-user\"]                 # required: tasks to run in order\nenv = { GIT_USER_NAME = \"popomore\" }                       # optional: custom env vars for tasks\n```\n\n| Field | Required | Description |\n|-------|----------|-------------|\n| `event` | Yes | Event name |\n| `matcher` | No | Regex against `host/owner/repo`. Omit to match all |\n| `tasks` | Yes | List of task names or commands, executed in order. Stops on first failure |\n| `env` | No | Custom environment variables passed to tasks (user-defined, not `PROJJ_*`) |\n\nEach entry in `tasks` is resolved the same way as `projj run` (task table → task file → raw command).\n\n### Matcher\n\nThe `matcher` field is a regex matched against `host/owner/repo`. Omit to match all repos.\n\n| Matcher | Matches |\n|---------|---------|\n| *(omitted)* or `*` | All repos |\n| `github\\\\.com` | All GitHub repos |\n| `github\\\\.com/SeeleAI` | All repos under SeeleAI org |\n| `github\\\\.com/popomore/projj` | Exact repo |\n| `gitlab\\\\.com\\|gitee\\\\.com` | GitLab or Gitee repos |\n\nNote: `.` in regex matches any character. Use `\\\\.` to match a literal dot.\n\n## Environment Variables\n\n| Variable | Description |\n|----------|-------------|\n| `NO_COLOR` | Disable all colored output ([no-color.org](https://no-color.org/)) |\n| `PROJJ_HOME` | Override home directory for config location |\n\n## External Tools\n\nOptional integrations. Projj works fine without them.\n\n| Tool | Integration | Without it |\n|------|------------|------------|\n| [fzf](https://github.com/junegunn/fzf) | Fuzzy search in `find` / `remove` | Numbered list |\n| [zoxide](https://github.com/ajeetdsouza/zoxide) | `post_add` hook registers paths | No auto-registration |\n\n```bash\n# macOS\nbrew install fzf zoxide\n```\n\n## License\n\n[MIT](LICENSE)\n","funding_links":[],"categories":["JavaScript","工程","Uncategorized","Mac 软件"],"sub_categories":["redux 扩展","macros","Uncategorized","命令行"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpopomore%2Fprojj","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpopomore%2Fprojj","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpopomore%2Fprojj/lists"}