A standalone Go CLI that groups GitHub/GitLab repositories into named collections and controls who can access them — at both the collection level (membership) and the repo level (which groups or individuals can reach which repos).
Why I built gitcollect: read the full story on RecallRun →The single rule everything else in gitcollect follows.
gitcollect's local YAML manifest is a declaration of intent — it describes who should have access. The GitHub/GitLab platform is the enforcement point — it's where access is actually granted or revoked.
These two must never diverge. Every access mutation drives the platform API to completion before the local YAML is written. If the API call fails, the YAML does not change. There is no in-between state, and no shadow permission system.
The same rule decides what happens when gitcollect cannot reach the platform to confirm a grant. Before a read, it checks that the collaborator grant is really in place — purely so a missing one reads as “access has not synced yet” instead of a bare git authentication failure. That check is a courtesy, not the boundary. When it cannot be answered — a spent rate limit, a token scope, a refused endpoint — gitcollect says so and continues, because the platform enforces the operation regardless of what gitcollect believes. Treating an unanswerable check as a denial would add no safety and would lock out precisely the read-only members the tool exists to serve. A definite “not a collaborator” is still a refusal; the rules in your manifest always apply either way.
Identity is based on immutable platform user IDs, not usernames. Renaming a GitHub or GitLab account never breaks collection ownership or membership — gitcollect resolves and caches the platform's permanent numeric ID alongside the login string, and all access decisions compare by ID. The cached login is used only for display, API path-building, and audit log entries — never for security decisions. Collections automatically update a stale cached login the next time a write command is run.
| Concept | What it means |
| Collection | A named group of repos with shared membership/visibility rules. Stored as one YAML file under
~/.gitcollect/collections/.
|
| Namespace | The GitHub/GitLab username or org under which the collection's repos actually live — used for all API
path-building (GET /repos/{namespace}/{repo}). Defaults to the collection owner's login. Set
--namespace acme-corp on init for org repos. Distinct from the collection owner, who is
always the authenticated user.
|
| Member | A person who belongs to a collection. On a private collection, non-members can't even discover it exists. |
| Group | A named subset of members, used to restrict which repos a collection's members can reach. |
| Repo access | Per-repo rule: open to all members, restricted to one or more groups, restricted to specific users, or a union of both. |
| Audit log | Every mutation — success or failure — is appended to
~/.gitcollect/audit/<collection>.log as newline-delimited JSON.
|
Windows: ~/.gitcollect/ maps to
%USERPROFILE%\.gitcollect\
GitHub vs GitLab: On GitHub, adding a collaborator sends an invite email that must be accepted before access is active. On GitLab, API-granted project membership is immediate — there is no invite step. gitcollect warns you when a GitHub repo is in the pending-invite state.
GitLab users: access grants take effect immediately —
there is no collaborator invite to accept. Use
--host gitlab.com or your self-hosted domain on all
commands. GitLab personal access tokens use the format
glpat-xxxx.... Namespaces on GitLab can be nested
(e.g. acme/backend) — pass the full path as --namespace.
Four methods — pick the one that fits your setup. No daemon, no database: the binary is the whole tool.
Recommended for Go developers. Requires Go 1.26.4+
go install github.com/alby-tomy/gitcollect/v3@latest
This compiles gitcollect on your machine and places the binary in ~/go/bin/ (Linux/Mac) or
%USERPROFILE%\go\bin\ (Windows). If that directory is already on your PATH — which it is by
default after a standard Go installation — gitcollect is immediately available. To upgrade, run the same
command again with @latest, or let gitcollect do it:
gitcollect get-update re-runs the right command for however you installed it.
That command requires v3.2.0 or newer — on an older binary, upgrade with
@latest once and it is available from then on.
The /v3 suffix is required. Go resolves a module's major
version from its import path, so go install github.com/alby-tomy/gitcollect@latest — without
it — installs v1.0.0 and exits successfully, giving you a binary several versions behind with no warning.
Check what you got with gitcollect --version.
No Go required. Download from the GitHub Releases page.
# Latest tag is vX.Y.Z; the archive names drop the leading "v" TAG=$(curl -sL https://api.github.com/repos/alby-tomy/gitcollect/releases/latest | grep '"tag_name"' | cut -d'"' -f4) VERSION=${TAG#v} # Intel/AMD (amd64) curl -L "https://github.com/alby-tomy/gitcollect/releases/download/${TAG}/gitcollect_${VERSION}_linux_amd64.tar.gz" | tar xz sudo mv gitcollect /usr/local/bin/ # ARM64 (e.g. Raspberry Pi, AWS Graviton) curl -L "https://github.com/alby-tomy/gitcollect/releases/download/${TAG}/gitcollect_${VERSION}_linux_arm64.tar.gz" | tar xz sudo mv gitcollect /usr/local/bin/ # Verify gitcollect --version
# Latest tag is vX.Y.Z; the archive names drop the leading "v" TAG=$(curl -sL https://api.github.com/repos/alby-tomy/gitcollect/releases/latest | grep '"tag_name"' | cut -d'"' -f4) VERSION=${TAG#v} # Apple Silicon (M1/M2/M3 — arm64) curl -L "https://github.com/alby-tomy/gitcollect/releases/download/${TAG}/gitcollect_${VERSION}_darwin_arm64.tar.gz" | tar xz sudo mv gitcollect /usr/local/bin/ # Intel Mac (amd64) curl -L "https://github.com/alby-tomy/gitcollect/releases/download/${TAG}/gitcollect_${VERSION}_darwin_amd64.tar.gz" | tar xz sudo mv gitcollect /usr/local/bin/ # Verify gitcollect --version
If macOS blocks the binary with "cannot be opened because the developer cannot be verified",
run: xattr -d com.apple.quarantine /usr/local/bin/gitcollect
gitcollect_<version>_windows_amd64.zip from github.com/alby-tomy/gitcollect/releases/latestgitcollect.exegitcollect.exe to a permanent location, e.g.
C:\Users\YourName\bin\gitcollect.exe
C:\Users\YourName\bingitcollect --versionWindows support is provided as a best-effort build. If you encounter any Windows-specific issues, please open an issue.
# Requires Go 1.26.4+ and git git clone https://github.com/alby-tomy/gitcollect.git cd gitcollect go build -ldflags="-s -w -X main.version=dev" -o bin/gitcollect . # Move to somewhere on your PATH: sudo mv bin/gitcollect /usr/local/bin/ # Linux/Mac # or add bin/ to your PATH on Windows
brew install alby-tomy/tap/gitcollect
The Homebrew tap is not yet published. This will be available in a future release. Use one of the methods above in the meantime.
gitcollect version
# Expected output format:
gitcollect v3.3.0 linux/amd64
The version and platform strings are embedded at build time. gitcollect --version and
gitcollect -v print the same line.
If this reports v1.0.0, you installed without the /v3 suffix —
see the note under Method 1. Re-run the install command exactly as written there.
A binary built from a source checkout does not report dev: Go stamps a pseudo-version
such as v3.0.2-0.20260914175157-eb4982ebc6d1+dirty, where the trailing +dirty
marks uncommitted changes. Only a build with no module information at all reports dev.
The completion subcommand is built in (provided by the Cobra framework). Every command and
flag is completion-aware out of the box.
gitcollect completion bash >> ~/.bashrc source ~/.bashrc
gitcollect completion zsh >> ~/.zshrc source ~/.zshrc
Or for Oh My Zsh:
gitcollect completion zsh > ~/.oh-my-zsh/completions/_gitcollect
gitcollect completion fish > ~/.config/fish/completions/gitcollect.fish
gitcollect completion powershell >> $PROFILE . $PROFILE
Pre-built binaries for Linux, macOS and Windows. Each release also ships a
checksums.txt — verify before you run anything you downloaded.
Verifying a download. Each release's checksums.txt lists the
SHA-256 of every archive. On Linux or macOS,
sha256sum -c checksums.txt --ignore-missing; in PowerShell,
Get-FileHash .\gitcollect_<version>_windows_amd64.zip -Algorithm SHA256 and compare the
line for that file. gitcollect get-update does this check for you and refuses a download whose
checksum does not match.
The full two-person flow, command by command — every line below is the actual output gitcollect prints, not an illustration.
# the owner is always whoever ran "gitcollect auth" # for repos under an org, pass --namespace to set the repo namespace: # gitcollect init cybersecurity --namespace acme-corp gitcollect init cybersecurity gitcollect add cybersecurity pen-test-tools gitcollect add cybersecurity vuln-scanner
This writes ~/.gitcollect/collections/cybersecurity.yaml on
your machine only. add defaults every repo to
open-to-all-members until you run repo access --groups/--users to
restrict it.
gitcollect member add cybersecurity teammate-username
✓ Added teammate-username to cybersecurity Granted access: pen-test-tools, vuln-scanner
member add immediately calls the GitHub/GitLab API to add
teammate-username as a collaborator on every repo they can already reach (both
repos here, since they're open). GitHub then sends its own collaborator-invite
email — that's GitHub's behavior, not something gitcollect prints; the teammate
has to accept that invite before the access actually works.
add, member add, and group add all
accept more than one name in a single command — useful when standing up a
new collection or onboarding a whole team instead of running the command
once per item. Here's the same idea applied to a fresh collection,
research:
gitcollect add research repo-a repo-b repo-c
✓ Added repo-a to "research" (open to all 0 members) Run: gitcollect repo access research repo-a --groups <g1,g2> ✓ Added repo-b to "research" (open to all 0 members) Run: gitcollect repo access research repo-b --groups <g1,g2> ✓ Added repo-c to "research" (open to all 0 members) Run: gitcollect repo access research repo-c --groups <g1,g2>
gitcollect member add research alice bob charlie
--- alice --- ✓ Added alice to research Granted access: repo-a, repo-b, repo-c --- bob --- ✓ Added bob to research Granted access: repo-a, repo-b, repo-c --- charlie --- ✓ Added charlie to research Granted access: repo-a, repo-b, repo-c
Past two usernames, member add prints a ---
username --- header before each one's block so the per-user
"Added"/"Granted access" lines never run together — group add
and add skip the header since their per-item output is
already a single line each:
gitcollect group create research red-team gitcollect group add research red-team alice bob
✓ Created group red-team in research Run: gitcollect group add research red-team <username> ✓ Added alice to group red-team in research ✓ Added bob to group red-team in research
One bad name in the batch — a malformed username, a repo already in the collection, a sync failure — doesn't stop the rest. Every name is attempted independently; the header for each prints before that name is attempted, so you can see exactly which one was in progress when something failed. Failures are collected and reported together as the very last line, once the whole batch has run, and the command only exits non-zero if at least one name actually failed:
$ gitcollect member add research alice bad_name
--- alice ---
alice is already a member of "research"
--- bad_name ---
✗ member add: 1 of 2 failed: bad_name (invalid name: username "bad_name" must match ^[a-zA-Z0-9]([a-zA-Z0-9-]{0,37}[a-zA-Z0-9])?$)
alice above is left exactly as before:
already-a-member is a no-op, not a failure, even inside a batch where a
different name failed.
A collection is just one YAML file on your disk. Your teammate needs
an exact copy at ~/.gitcollect/collections/cybersecurity.yaml
on their machine before any of their commands will find it.
Three ways:
# Option A — publish to a shared git repo, teammate pulls it gitcollect publish --repo acme-corp/gitcollect-config # teammate: gitcollect pull-config --repo acme-corp/gitcollect-config
publish shallow-clones the target repo, copies collection YAMLs
into collections/, commits, and pushes.
pull-config does the reverse — fetches from the shared repo and drops
the files into ~/.gitcollect/collections/ automatically.
This is the recommended approach for teams — you only need to run
publish once after each import or config change.
# Option B — commit and send the file directly git add cybersecurity.yaml && git commit -m "share cybersecurity collection" && git push # teammate: git pull cp cybersecurity.yaml ~/.gitcollect/collections/ # Option C — just send them the file (chat, email, anything) # teammate places it at ~/.gitcollect/collections/cybersecurity.yaml
A gitcollect fetch command for sharing collections directly by URL is planned
for a future release. Until then, use Option A (publish/pull-config) for teams.
Hand-editing the YAML to add yourself as a member doesn't actually grant you anything — the platform is the enforcement point, not the file. See the security note in Step 6 below.
gitcollect auth gitcollect whoami
With their own personal access token — never yours.
gitcollect show cybersecurity
Collection: cybersecurity Host: github.com Owner: your-username Visibility: private Members: 1 Groups: 0 Repos: 2 MEMBER teammate-username REPO ACCESS RULE YOU pen-test-tools open to all members ✓ yes vuln-scanner open to all members ✓ yes
The YOU column is always about whoever ran the command — it's
not a generic property of the repo. If a repo were restricted to a group they
aren't in, that row would read ✗ no — no access — group red-team
required instead, and a footer below the table would list every repo
they can't reach with a pointer to inspect --user for the full
explanation.
gitcollect clone cybersecurity
✓ Access verified (teammate-username · no groups) 2 of 2 repos accessible [1/2] Cloning pen-test-tools... ✓ done (1.2s) [2/2] Cloning vuln-scanner... ✓ done (0.8s) ✓ Cloned 2 repo(s) in 2.0s
clone only ever fetches the repos show marked
✓ yes — the two columns are backed by the exact same access
decision, so there's no scenario where show says yes and
clone says no, or vice versa.
Say you later restrict exploit-db to a red-team
group your teammate isn't in. A plain clone just skips it and
tells them how to find out why:
1 repo(s) skipped (no access): exploit-db Run: gitcollect inspect cybersecurity --user teammate-username
Naming it explicitly with --pick is a hard error instead:
$ gitcollect clone cybersecurity --pick exploit-db ✗ clone: "exploit-db" is not an accessible repo in this collection
And the suggested inspect command spells out the exact reason:
REPO ACCESS REASON exploit-db ✗ no no access — group red-team required
Even a raw git clone of that repo's URL fails for them — GitHub
itself rejects it, because gitcollect's API call to add them as a collaborator
on that specific repo never happened.
Why this is safe even if someone edits the YAML by hand: a teammate can only actually clone a repo when two independent things are both true — they're listed as having access in the local manifest, and GitHub/GitLab itself has them as a real collaborator on that repo. gitcollect's mutation commands are the only thing that makes the second one true. Editing the YAML changes the first; it can never fake the second, because the platform — not the file — is the actual enforcement point.
gitcollect supports GitLab with the same commands. Key differences from GitHub are noted at each step.
gitcollect auth --host gitlab.com
GitLab personal access tokens use the format glpat-xxxx... (not ghp_xxx...).
For self-hosted GitLab: gitcollect auth --host gitlab.company.com
gitcollect init my-project --host gitlab.com
For GitLab groups use --namespace to set the repo namespace.
GitLab namespaces can be nested paths: --namespace acme/backend
gitcollect init my-project --namespace my-group --host gitlab.com
gitcollect member add my-project teammate-username
GitLab access is immediate — teammates can clone as soon as you run
member add. There is no email invite to accept (unlike GitHub).
gitcollect auth --host gitlab.company.com gitcollect init my-project --host gitlab.company.com
Pass your instance hostname to every command via --host,
or set it once during init — the collection stores the host
and uses it for all subsequent commands.
For teams that already exist on GitHub or GitLab, the import commands remove the manual setup burden entirely. This shows the complete admin → employee flow.
Read the org's existing team structure — teams, members, repos — and
create one collection per team. Uses the GitHub/GitLab Teams API, so
a token with read:org and repo scopes is required.
$ gitcollect import --from github --org acme-corp Pre-flight checks... ✓ Authenticated as alby-tomy (github.com) ✓ Token scopes: read:org, repo ✓ Organisation acme-corp found Importing collections (30): [1/30] payments-team 8 repos · 25 members · owner: payments-lead [2/30] mobile-team 12 repos · 18 members · owner: mobile-lead ... ✓ Imported 30 collections 312 unique members across all collections
Each team becomes a YAML file at
~/.gitcollect/collections/acme-corp-{team-slug}.yaml.
Use --dry-run to preview without writing anything.
Use --team payments-team to import a single team.
If a collection already exists, import will prompt:
[o] overwrite, [s] skip, or
[m] merge (add new repos/members, keep existing ones).
Pass --merge, --overwrite, or --skip-existing
for non-interactive runs.
Push all collection YAMLs to a regular GitHub repo so teammates can
fetch them without needing read:org themselves. The target
repo can be private — team members only need read access.
$ gitcollect publish --repo acme-corp/gitcollect-config Publishing 30 collections to acme-corp/gitcollect-config... ✓ acme-corp-payments-team.yaml ✓ acme-corp-mobile-team.yaml ✓ acme-corp-devops-team.yaml ... 27 more ✓ Published 30 collections to acme-corp/gitcollect-config (main) Commit: "update gitcollect collections" Share this with your team: gitcollect pull-config --repo acme-corp/gitcollect-config
One command that fetches the team's collection and optionally clones
all repos the new hire can access. Replaces the old
pull-config + clone two-step.
$ gitcollect join --org acme-corp --team payments-team --clone Setting up gitcollect for acme-corp/payments-team... ✓ Authenticated as new-hire (github.com) Fetching payments-team configuration... ✓ payments-team fetched (8 repos · 25 members) Written to ~/.gitcollect/collections/acme-corp-payments-team.yaml Cloning repos... [1/8] Cloning payments-api... ✓ done (2.1s) [2/8] Cloning payments-frontend... ✓ done (1.4s) [3/8] Cloning payments-db-migrations... ✓ done (0.8s) ... ✓ Joined acme-corp/payments-team 8 repos cloned to ./ Welcome to the team. Next steps: gitcollect show acme-corp-payments-team see your full repo access gitcollect pull acme-corp-payments-team pull updates any time
Pass --repo acme-corp/gitcollect-config to pull the config from
the shared repo instead of calling the GitHub Teams API directly.
This is the recommended path for employees — they don't need
read:org, only read access to the config repo.
After team membership or repo list changes on GitHub, re-fetch the current state and update the local collection. Shows exactly what changed before applying.
$ gitcollect sync-config payments-team Syncing acme-corp-payments-team from github.com/acme-corp... Changes detected: + new member: new-hire (id: 9988776) + new repo: payments-notifications - removed member: former-employee (no longer in GitHub team) Applying changes... ✓ acme-corp-payments-team synced Run: gitcollect sync acme-corp-payments-team to clone new repos and pull existing
Use --dry-run to see the diff without writing.
Use --all to sync every collection that has a namespace set.
After syncing, re-publish so teammates can pull the updated config:
$ gitcollect publish --repo acme-corp/gitcollect-config
Every command gitcollect ships, grouped the way the CLI itself groups them. Run
gitcollect <command> --help for the live version of any of this.
Global flag: --offline disables all network calls and limits
gitcollect to local-only operations. Available on every command — useful for inspecting local state without
touching the platform API.
gitcollect auth
Authenticate with GitHub or GitLab and store the access token. Prompts for a personal access
token (input hidden), verifies it against the platform API, then saves it to
~/.gitcollect/config (mode 0600). The token is reused by every later command
until the platform itself rejects it — there's no local expiry tracking, and no need to re-run this unless
the token actually stops working.
| --host | platform host to authenticate (default github.com; e.g.
gitlab.com or a self-hosted GitLab domain)
|
gitcollect whoami
Show the authenticated user for every host you've run auth on. If a stored
token has been rejected (expired/revoked), that host's row shows the error inline instead of hiding the
others' valid status, plus a hint to re-run auth.
| --check | make a live API call to verify each stored token is still valid — exits with code 1 if any token is rejected |
| --json | machine-readable output |
gitcollect init <name>
Create a new collection. Private by default — being the owner does not automatically make
you a "member"; add yourself explicitly if you want member-only views to include you. When
repos live under an organisation rather than your personal account, pass
--namespace <org-name> so gitcollect builds API paths correctly. The collection owner
(who administers it) is always the authenticated user regardless of namespace.
On an interactive terminal, init also offers to enable group admin
(organisation-tier) support — say yes to start with group admin wiring already applied, or run
gitcollect scale <name> organisation later.
| --host | platform host the collection's repos live on (default github.com) |
| --description | human-readable description |
| --public | create as public instead of the default, private |
| --namespace | GitHub/GitLab username or org whose repos this collection contains. Defaults to the
authenticated user's login. Set this when the repos live under an org (e.g.
--namespace acme-corp) rather than your personal account.
|
gitcollect delete <collection>
Delete a collection and revoke every member's access to every repo in it before removing the
manifest. Requires typing the collection's name to confirm. Use --dry-run first to preview
which members will lose access.
| --dry-run | show which members would have access revoked without deleting anything |
gitcollect list
List every collection you own or are a member of, public or private, reading only local manifests (no network calls). Narrow the results to one visibility instead of seeing both. Any collection whose YAML hasn't been updated in over 30 days gets a stale-file warning printed below the table, suggesting you ask the owner for a fresh copy if you're not the owner yourself.
| --private | show only private collections |
| --public | show only public collections |
| --json | machine-readable output |
gitcollect show <collection>
Show a summary of a collection: description, owner, visibility, and tables of its members,
groups, and repos. The repo table's YOU column is personal to whoever runs the command —
✓ yes or ✗ no — <reason> — so you immediately see which repos you can and
can't reach, before trying to clone. clone only ever clones the ones marked ✓
here. Any denied repo gets the exact fix command in a footer below the table (e.g.
gitcollect group add my-collection red-team alice). If you're the collection's
owner, the YOU column is replaced with WHO HAS ACCESS — every
member who can reach each repo — since YOU is trivially always ✓ for an owner.
Also prints the same >30-day stale-file warning as list.
| --json | machine-readable output — each repo also carries
you_can_access/you_reason/you_fix_cmd (the same fix command the
denied-repo footer shows, omitted when access is already granted) and who_has_access
(every member who can reach that repo, populated regardless of which table view you'd see in the table
output); the top level carries stale_days when the >30-day warning would fire
|
gitcollect visibility <collection> <public|private>
Change a collection's visibility. Switching to public prompts for confirmation, since it makes the collection's existence discoverable to non-members.
Visibility controls who the collection admits — it does not switch off a repo's own rules. A public collection opens its unrestricted repos to everyone, and leaves repos restricted to a group or to named users exactly as restricted as they were. Making a collection public is not a way to grant blanket access.
Changed in v3.1.0. Earlier versions short-circuited on visibility: making a
collection public granted every member access to every repo in it, including group-restricted
ones, and sync pushed those grants to the platform. If you have public collections with
restricted repos, run gitcollect diff after upgrading — some members will lose access they
should never have had.
gitcollect transfer <collection> <new-owner>
Transfer ownership of a collection to another member. The previous owner becomes a regular
member and retains access. Requires typing the new owner's username to confirm — this action cannot be
undone by the previous owner. If the new owner holds a group admin role in the collection, remove it first
with group admin remove.
gitcollect scale <collection> organisation|team
Switch a collection between TEAM and ORGANISATION tiers. organisation
enables group admin support, allowing the owner to delegate per-group membership management to specific
members via group admin add. team disables it — if group admins are
assigned, lists them and requires confirmation before revoking their rights. Idempotent: switching to the
current tier is a no-op.
gitcollect rename <collection> <new-name>
Rename a collection. Pure local operation — no platform API calls are made. Members, repos, and groups are preserved exactly as-is. The new name must not already exist.
gitcollect copy <collection> <new-name>
Copy a collection to a new name. The authenticated user becomes the owner of the copy. The new collection is always private regardless of the source's visibility. Platform collaborator access is not synced automatically — the copy starts with the same members and repos as the source, but live access is not re-granted until you run a write command against it.
gitcollect diff <collection>
Compare a local collection against GitHub/GitLab reality. Reports repos present in the
manifest but missing from the platform, repos on the platform not in the manifest, and members no longer
holding collaborator status. Read-only — no changes are made. Use gitcollect sync-config to
pull the platform's current state back into the local collection.
| --repos-only | only compare repos, skip member check |
| --members-only | only compare members, skip repo check |
| --json | machine-readable JSON output |
gitcollect move <source-collection> <repo>
<dest-collection>
gitcollect move <source-collection> <dest-collection>
--group <name>
Move a repo from one collection to another atomically. Grants access to members of the destination collection; revokes access from members of the source collection who are not also in the destination. Always shows the access diff before executing. Caller must own both collections.
Moving a whole module. --group <name> moves every repo
that group can reach, which is what handing a module from one team to another actually means — twenty
pricing repos in one operation rather than twenty moves that can be left half-done. Note the positional
arguments: with --group there is no repo name, so it is
move <source> <dest> --group <name>. Every repo is validated before
anything moves; if even one already exists in the destination, nothing is moved.
A move revokes real access for real people, so it asks you to type the
name back before it does anything — the module name for a --group move, the repo name
otherwise — the same confirmation GitHub asks for when deleting a repository. Use --yes
in scripts. --dry-run never prompts, because it changes nothing.
| --group | move every repo this group can reach, instead of one named repo |
| --dry-run | preview access changes without executing |
| --yes | skip the typed confirmation |
gitcollect add <collection> <repo> [repo...]
Add one or more repos to a collection, each open to all members by default. Supports
individual repo names, bulk name-pattern search (--pattern), or topic search
(--topic). Syncs collaborator access on the platform and saves the manifest after each repo,
so if a later repo in the batch fails, every repo before it is still kept. On an interactive terminal, if
a repo doesn't exist on the platform yet, add offers to create it — the new repo's visibility
is set by --new-repo-visibility.
| --pattern <glob> | add all repos whose name matches this pattern (supports * wildcard) —
uses the platform search API |
| --topic <name> | add all repos tagged with this GitHub topic |
| --org <name> | org to search in when using --pattern or --topic
(default: collection's namespace) |
| --limit <n> | max repos to add via search (default 50, max 100) |
| --new-repo-visibility | visibility for any repo created by this command — private (default) or
public
|
| --dry-run | show which repos would be added without adding them |
gitcollect remove <collection> <repo>
Remove a repo from a collection and revoke every member's collaborator access to it on the platform first. Requires typing the repo's name to confirm.
gitcollect repo access <collection> <repo>
Replace a repo's entire access rule: restrict to groups, restrict to specific users, or open it to all members. Groups and users are unioned (either satisfies access), and exactly one of the three flags below is required.
| --groups g1,g2 | restrict to these groups (comma-separated) |
| --users u1,u2 | restrict to these individual users (comma-separated) |
| --open | open the repo to all members (clears any restriction) |
gitcollect repo show <collection> <repo>
Show a repo's current access rule and a per-member table of who can reach it and why.
gitcollect member add <collection> <username> [username...]
Add one or more members to a collection, syncing each one's access across every repo they're
now entitled to. Reports which repos were granted and which were skipped (with the reason), plus a
suggested next command for restricted repos. On GitHub, if a newly granted repo leaves a user with a
pending, unaccepted collaborator invite rather than immediate access, warns about it with a link to accept
it — this state doesn't exist on GitLab, where membership added via the API is immediate. With more than
one username, each one's output is printed under its own --- username --- header; one
username failing (e.g. an invalid name) doesn't stop the rest — the command reports every failure together
at the end and exits non-zero only if at least one failed.
gitcollect member remove <collection> <username>
Remove a member from a collection and every group, revoking their collaborator access across
every repo first. Prompts for confirmation; removing yourself additionally requires
--confirm-self. Use --dry-run to preview which repos the member can currently
reach before committing to the removal.
| --confirm-self | required if the username being removed is your own |
| --dry-run | preview which repos access would be revoked from, without removing the member |
gitcollect member list <collection>
List a collection's members and which groups each one belongs to.
gitcollect group create <collection> <group>
Create a new, empty group within a collection.
gitcollect group delete <collection> <group>
Delete a group. Blocked, with the list of blocking repos, if any repo still restricts access to this group — clear those restrictions first.
gitcollect group add <collection> <group> <username> [username...]
Add one or more members to a group, syncing each one's repo access afterward. Guides you to
member add first for any username that isn't a collection member yet. One username failing
doesn't stop the rest — every failure is reported together at the end and the command exits non-zero only
if at least one failed. When organisation tier is enabled, a group admin of that specific group may also
run this command.
gitcollect group remove <collection> <group> <username>
Remove a member from a group and re-sync their repo access to match. When organisation tier is enabled, a group admin of that specific group may also run this command.
gitcollect group list <collection>
List every group in a collection with its member count and members.
gitcollect group show <collection> <group>
Show a group's members and which repos are restricted to it.
gitcollect group admin add <collection> <group> <username>
Grant a collection member group admin rights for a specific group. Owner-only. Requires
organisation tier to be enabled (gitcollect scale <collection> organisation). The
target must already be a collection member. Group admins can run group add and
group remove for their assigned group only.
gitcollect group admin remove <collection> <group> <username>
Revoke a member's group admin rights for a specific group. Owner-only, except a group admin may remove themselves.
gitcollect group admin list <collection>
List all group admin assignments in a collection. Shows a GROUP / ADMIN table. If organisation tier is not enabled, shows an info message instead.
gitcollect inspect <collection>
Show access decisions for the whole collection, one user, or one repo. With no flags: the
full member × repo access matrix. --user and --repo are mutually exclusive.
Every denied entry shown by --user or --repo is collected into a "To fix:"
footer listing the exact command to grant access.
| --user <name> | show the full access map for this user, repo by repo, with the reason for each decision |
| --repo <name> | show who can access this repo and why, member by member |
| --json | machine-readable output |
gitcollect audit <collection>
Show the access change log for a collection — every mutation ever attempted, including failures, newest first.
| --user <name> | filter to entries where this user is the actor or the target |
| --since <dur> | filter to entries within this duration — must be exactly one of 1h,
24h, 7d, 30d, 90d (strict allow-list, no other
value accepted)
|
| --json | machine-readable output |
Audit logs are stored locally at
~/.gitcollect/audit/<collection>.log and are not
automatically shared or backed up. Export them with
gitcollect audit <collection> --json for off-machine
storage. A shared audit log is planned for a future release.
gitcollect activity <collection> experimental
[experimental] — output format and flag names may change in a future release. Functionality is stable but the interface is not yet frozen.
Show code changes, not access changes: fetches the most recent commits on each accessible
repo's default branch live from GitHub/GitLab, records any genuinely new ones to
~/.gitcollect/activity/<collection>.log, and prints the combined history (this run's
fetch plus everything previously recorded) — repo, branch, author, SHA, message, and timestamp for every
commit, newest first.
| --repo <name> | only check this one repo instead of every accessible one |
| --since <dur> | only show commits within this duration — must be exactly one of 1h,
24h, 7d, 30d, 90d (strict allow-list, no other
value accepted)
|
| --limit <n> | max commits to fetch per repo this run (default 10) — bounds the live fetch only; older recorded commits are still shown from the log |
| --json | machine-readable output |
gitcollect clone <collection>
Clone every repo you can access in a collection. "Access" here means both the local YAML rule and a live platform check that you actually hold collaborator status — local rules alone are never trusted for cloning. Repos you can't reach are skipped and reported, not errored on. On GitHub, if a skipped repo turns out to be a pending, unaccepted collaborator invite rather than a genuine denial, warns about it separately with a link to accept it and a retry command.
| --pick "r1 r2" | clone only these repos, out of the ones you can access — value is
whitespace-separated; repeating --pick also works |
| --dry-run | preview exactly what would be cloned without doing it |
| --concurrency N | max repos to clone in parallel (default 4) |
| --dest <dir> | directory to clone repos into (default current directory) |
gitcollect pull <collection>
git pull inside every accessible repo that's already cloned locally. Repos that
exist in the collection but aren't cloned yet are reported, not treated as errors. Use
--prune to clean up local clones of repos that are no longer in the collection — never
deletes a repo with uncommitted changes.
| --dest <dir> | directory repos were cloned into (default current directory) |
| --prune | prompt to delete local clones of repos no longer in this collection |
| --dry-run | preview prune operations without executing (use with --prune) |
gitcollect status <collection>
git status inside every accessible repo that's already cloned, summarized as a
clean/changed table.
| --dest <dir> | directory repos were cloned into (default current directory) |
gitcollect sync <collection>
One command instead of two: for every accessible repo, clones it if it isn't present yet at
--dest, or runs git pull if it already is. Reports ✓ done (1.4s)
for a fresh clone, and either ✓ up to date or ✓ N new commit(s) for a pull.
Equivalent to running clone then pull back to back, but in a single pass and a
single access check.
| --dest <dir> | directory to clone into, or where repos were already cloned (default current directory) |
| --dry-run | preview what would be cloned/pulled without doing it |
| --concurrency N | max repos to sync in parallel (default 4) |
gitcollect import
Read a GitHub or GitLab org's team structure — teams, members, repos — and
create one local collection per team. Runs API calls concurrently (max 4 parallel)
and prints a progress line for each team. Maintainers of a team become the collection
owner by default; falls back to the authenticated caller when no maintainer is found.
Requires a token with read:org and repo (or public_repo)
scopes on GitHub. Files are written as ~/.gitcollect/collections/{org}-{team}.yaml.
| --from github|gitlab | platform to import from (required) |
| --org <org> | GitHub org or GitLab group to import (required) |
| --team <slug> | import only this team instead of all teams in the org |
| --dry-run | show what would be created without writing any files |
| --flatten | use the team slug as the collection name directly, ignoring parent-team nesting |
| --owner-from-maintainer | set the first maintainer as collection owner (default: true) |
| --namespace <ns> | set the collection namespace (default: the org name) |
| --merge | when a collection already exists locally, merge new repos/members into it |
| --overwrite | when a collection already exists locally, replace it with the imported version |
| --skip-existing | when a collection already exists locally, skip it without prompting |
gitcollect publish
Push local collection YAML files to a shared git repository so teammates
can fetch them with pull-config. Shallow-clones the target repo,
copies collection files into collections/ (or the directory set by
--path), commits, and pushes. Uses whatever git credentials are already
configured on the system. The target repo can be private — team members only need
read access to pull from it.
| --repo <org/repo> | GitHub/GitLab repository to publish to (required) |
| --collection <name> | publish only this one collection (default: all local collections) |
| --branch <name> | branch to push to (default main) |
| --path <dir> | subdirectory in the repo to copy files into (default collections/)
|
| --message <msg> | commit message (default update gitcollect collections) |
gitcollect pull-config
Fetch collection YAML files from a shared git repository (published with
gitcollect publish) and copy them into
~/.gitcollect/collections/. Skips existing local files unless
--overwrite is set. Does not require read:org scope —
only read access to the config repo.
| --repo <org/repo> | shared config repository to fetch from (required) |
| --collection <name> | fetch only this collection (default: all YAML files in the remote path) |
| --branch <name> | branch to fetch from (default main) |
| --path <dir> | subdirectory in the repo to look for files (default collections/) |
| --overwrite | overwrite existing local collections (default: skip them) |
gitcollect join
New-hire onboarding in one command: fetch the collection for your team,
then optionally clone all repos you have access to. Two fetch paths:
(1) --repo set — shallow-clones the shared config repo and copies
the team's YAML (does not need read:org);
(2) --repo not set — calls import directly against the
GitHub/GitLab API (requires read:org).
| --org <org> | GitHub org or GitLab group (required) |
| --team <slug> | team slug to join (required) |
| --repo <org/repo> | shared config repo to fetch the collection from; if omitted, imports directly from the platform API |
| --clone | clone all accessible repos immediately after joining (default: false) |
| --dest <dir> | directory to clone repos into (default: current directory) |
| --from github|gitlab | platform (default github) |
gitcollect sync-config
Re-fetch the current team state from GitHub or GitLab, compare with the
local collection, show what changed (new members, removed members, new repos, removed
repos), and update the local YAML. The collection must have a namespace
set (done automatically by import; set manually with
gitcollect init --namespace <org>). The team slug is derived from
the collection name by stripping the {org}- prefix. Use after the org's
team membership or repo list has changed on the platform.
| --all | sync every local collection that has a namespace set (default when no collection name is given) |
| --from github|gitlab | platform to sync from (default: the collection's stored host) |
| --org <org> | org to sync from (default: the collection's stored namespace) |
| --dry-run | show the diff without writing any changes to disk |
gitcollect scan (--org <org> | --user <login>) [--from github|gitlab] [--group-by token|prefix|flat] [--interactive] [--dry-run] [--apply] [--no-archived] [--verify]
Read every repository in an org or group — or, with --user, in a personal
account — and bucket them into collections by the words in their names. Prints a preview by default;
--apply writes the collection files and never overwrites an existing one,
so re-running it cannot lose members, groups or access rules. --verify reports what the
current token could actually see — repositories it cannot reach are absent from the count entirely.
Grouping strategies. token (the default) uses the most
widely shared meaningful word wherever it appears in the name, so china-pricing,
eu-pricing and us-pricing all land in one pricing collection.
A word must be shared by at least two repos before it can name a group, and generic terms
(service, api, core, …) are ignored so cart-service
and search-service are not collapsed into a meaningless service bucket.
prefix uses only the first hyphenated segment — right for a deliberate namespace prefix
like payments-gateway, but it splits china-pricing and eu-pricing
apart. flat puts everything in one collection.
Scanning a personal account. An org and a user are different endpoints,
so --user is required for a personal account — --org against a username simply
fails. Passing your own login includes your private repos; naming someone else returns only what your
token can see.
Grouping by name is a guess. Pass --interactive to confirm
each repo before anything is written: press Enter to accept the suggested category, d to
drop the repo, or type a different category name to file it there instead. Useful when one account
holds unrelated projects — an e-commerce set and a CRM, say — and you want to check each repo really
belongs to the project it was matched to.
gitcollect pr <collection> [--author <login>] [--json]
Open pull requests (GitHub) or merge requests (GitLab) across every repo you can reach in
the collection, newest first. --author matches case-insensitively. Repos the platform
refuses are named rather than counted as zero.
gitcollect health <collection> [--dest <dir>]
One dashboard for a whole collection: which repos are cloned, which have uncommitted
changes, which are behind their remote, and how many pull requests are open across all of them. Repos
whose PR listing fails show ? rather than 0.
gitcollect version [--json]
Print the build version and platform (GOOS/GOARCH). gitcollect -v
and gitcollect --version print the identical line; --json adds the Go runtime
version. A binary from go install reports the module version it was built from.
gitcollect get-update --check reports whether you are behind without changing
anything, and gitcollect get-update upgrades in place — a release binary is replaced
only after its published SHA-256 checksum verifies. Requires v3.2.0 or newer.
gitcollect archive <collection>
Hide a collection from list, sync --all and
status --all without deleting anything — the YAML, its repos and every access rule stay
exactly as they were. Pass --include-archived to those commands to see it again, or
gitcollect unarchive to restore it. Owner only.
gitcollect completion <bash|zsh|fish|powershell>
Generate a shell autocompletion script. Provided automatically by the underlying CLI framework (Cobra) — every command and flag above is completion-aware out of the box.