Search by

A multi-host bulk git repository manager built with Laravel Zero. Clone, pull, and list open issues across hundreds of repos on GitHub, GitLab, and Bitbucket from your terminal.

Package info

github.com/jeffersongoncalves/repos-cli

Type:project

pkg:composer/jeffersongoncalves/repos-cli

Statistics

Installs: 44

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

1.6.0 2026-10-10 01:03 UTC

This package is auto-updated.

Last update: 2026-10-10 01:04:10 UTC


README

repos-cli

repos-cli

Buy Me A Coffee

A multi-host bulk git repository manager. Clone, pull, and list open issues across hundreds of repos on GitHub, GitLab, and Bitbucket from one terminal — no more cd-ing into 600 folders one at a time.

Built with Laravel Zero and modeled on the other CLIs in this monorepo.

Tests Total Downloads License PHP 8.2+

Install

Global (recommended)

composer global require jeffersongoncalves/repos-cli

The binary repos will be on your PATH as long as Composer's global vendor/bin is in it.

From source

git clone https://github.com/jeffersongoncalves/repos-cli.git
cd repos-cli
composer install

Usage

Update

repos self-update

Authenticate

Clones use your local SSH key, same as any git clone. The saved token is only used for API calls (listing repos, listing issues) — one login per host you use.

GitHub

Device flow (recommended — nothing to create or manage):

repos auth:github:login --device

Opens a verification URL and a short code; approve it in the browser. The saved token refreshes itself automatically once it expires.

Personal access token instead:

  1. Create a classic token with the repo scope (covers repos, issues, and search)
  2. repos auth:github:login and paste it

GitLab

Device flow (recommended):

repos auth:gitlab:login --device

Personal access token instead:

  1. GitLab → Settings → Access Tokens → create one with the read_api scope
  2. repos auth:gitlab:login and paste it

Bitbucket

No device flow — Bitbucket doesn't support it. API token only:

  1. Bitbucket → Personal settings → Security → API tokens → Create API token with scopes
  2. Name it (e.g. repos-cli), select Bitbucket as the app, and grant these permissions — Write does not imply Read, each is separate:
Scope Permission Scope ID
User Read read:user:bitbucket
Repositories Read read:repository:bitbucket
Issues Read read:issue:bitbucket

User Read is required even though the CLI never reads user data directly — it's what the login check (GET /2.0/user) needs to verify the token.

  1. repos auth:bitbucket:login — prompts for your Atlassian account email (not your Bitbucket username) and the token
repos auth:show

# Confirm every saved credential still works (pings each host's API,
# never prints the token/app password)
repos auth:show --verify

Credentials are stored in ~/.repos-cli/config.json (mode 0600).

Multiple credentials per host

Every auth:*:login command takes --profile=<name> (default: default), so you can keep more than one credential for the same host — e.g. a personal GitHub device-flow login alongside a work PAT for an org with OAuth App restrictions:

repos auth:github:login --device                       # profile "default"
repos auth:github:login --profile=work                  # a second, separate token

repos clone acme --host=github --profile=work --path=~/code/acme
repos issues acme --host=github --profile=work

repos auth:show lists every profile per host. Commands that hit a host's API (clone for bulk owner/org clones, issues) accept --profile=; it defaults to default when omitted.

Clone

# Single repo (URL, or owner/repo with --host)
repos clone git@github.com:acme/widgets.git
repos clone acme/widgets --host=github

# Bulk-clone every repo of an owner/org. Already-cloned repos are pulled
# instead of re-cloned, so re-running this is how you "update everything".
repos clone acme --host=github --path=~/code/acme

Submodules are cloned recursively (git clone --recurse-submodules).

Pull

# git pull --all --recurse-submodules on every repo found one level under a folder
repos pull ~/code/acme

Issues

# Single repo
repos issues acme/widgets --host=github

# Every repo of an owner/org
# (GitHub: one search API call. GitLab/Bitbucket: looped per repo.)
repos issues acme --host=github

Audit (GitHub)

Hygiene report for every repo of an owner — run it from the folder that holds your clones:

cd ~/code/acme
repos audit acme --automerge-template=~/templates/dependabot-auto-merge.yml
Check Flags a repo when
clone there's no clone of it under --path (default: current directory)
website its website points to packagist.org
description the description is empty
branch the default branch isn't the highest N.x branch (multi-branch packages) or main
dependabot .github/dependabot.yml is missing
automerge dependabot-auto-merge.yml differs from --automerge-template (skipped without it)
ci a push-triggered workflow run of the default branch's HEAD commit failed
immutable release immutability is disabled (skipped silently when the token can't read the setting)
tests a PHP repo (composer.json at the root of the default branch) has no tests/ folder or no workflow whose file name contains test
dependabot-prs an open Dependabot pull request has failing checks
social a public repo still uses GitHub's generated social preview image (one GraphQL call per 100 repos)
catalog an entry of --catalog (a plugins.json) has no GitHub repo, an archived one, or no Packagist package; or the owner publishes a Packagist package the catalog doesn't list and whose repo isn't archived (skipped without it)
repos audit acme --only=ci,dependabot-prs   # just the red builds
repos audit acme --only=catalog --catalog=plugins.json --catalog-ignore=acme/website
repos audit acme --skip=clone --filter=filament
repos audit acme --exclude=legacy-app,old-site # leave known exceptions out
repos audit acme --json > audit.json        # pure JSON, no progress bar

The catalog is a JSON object of categories; every nested object with a title and a package (vendor/name) is an entry, and an optional repo gives the GitHub path when it differs from package. Entries under startkit, filament, laravel, laravelZero, cli and cakephp must be on Packagist.

Network errors and GitHub 5xx are retried; a repo that still can't be read is reported as an error finding and the audit moves on. Archived repos and forks are skipped unless you pass --include-archived / --include-forks. The command exits 1 when it finds anything, so it can gate a CI job.

Supported hosts

Host Auth Notes
GitHub Personal access token, or --device flow Bulk clone/issues via orgs/{org}/repos or users/{user}/repos; issues search via search/issues.
GitLab Personal access token, or --device flow Bulk clone/issues via groups/{ns}/projects or users/{ns}/projects.
Bitbucket Account email + API token Same auth shape as bb-cli. Issues skipped for repos with no issue tracker enabled. No device flow — Bitbucket doesn't support it.

Development

composer install
composer test       # Pest tests + Pint lint
composer lint        # Auto-fix style
composer phpstan      # Static analysis
composer build        # Build the PHAR into builds/repos

License

MIT © Jefferson Goncalves