gitcollect / docs

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 →
Go 1.26.4+ GitHub & GitLab No daemon, no DB HTTPS-only clone

Philosophy

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.

Installation

Four methods — pick the one that fits your setup. No daemon, no database: the binary is the whole tool.

Method 1 — go install

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.

Method 2 — Pre-built binary

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

  1. Download gitcollect_<version>_windows_amd64.zip from github.com/alby-tomy/gitcollect/releases/latest
  2. Extract the zip — you get gitcollect.exe
  3. Move gitcollect.exe to a permanent location, e.g. C:\Users\YourName\bin\gitcollect.exe
  4. Add that folder to your PATH:
    1. Open Start → search "Edit the system environment variables"
    2. Click "Environment Variables..."
    3. Under "User variables", select "Path" → Edit → New
    4. Add: C:\Users\YourName\bin
    5. Click OK on all dialogs
  5. Open a new terminal (the old one won't see the updated PATH) and run: gitcollect --version

Windows support is provided as a best-effort build. If you encounter any Windows-specific issues, please open an issue.

Method 3 — Build from source

# 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

Method 4 — Homebrew

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.

Verify the installation

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.

Shell completion

The completion subcommand is built in (provided by the Cobra framework). Every command and flag is completion-aware out of the box.

Bash (Linux/Mac)

gitcollect completion bash >> ~/.bashrc
source ~/.bashrc

Zsh (Mac default shell)

gitcollect completion zsh >> ~/.zshrc
source ~/.zshrc

Or for Oh My Zsh: gitcollect completion zsh > ~/.oh-my-zsh/completions/_gitcollect

Fish

gitcollect completion fish > ~/.config/fish/completions/gitcollect.fish

PowerShell (Windows)

gitcollect completion powershell >> $PROFILE
. $PROFILE

Every release

Pre-built binaries for Linux, macOS and Windows. Each release also ships a checksums.txt — verify before you run anything you downloaded.

Loading releases…

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.

Walkthrough: sharing a collection with a teammate

The full two-person flow, command by command — every line below is the actual output gitcollect prints, not an illustration.

1

Create the collection and add repos (you)

# 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.

2

Add your teammate as a member (you)

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.

Bulk operations: adding several repos, members, or group members at once

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.

3

Get the collection file to your teammate (you)

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.

4

Your teammate installs gitcollect and authenticates

gitcollect auth
gitcollect whoami

With their own personal access token — never yours.

5

Your teammate checks what they can actually reach

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.

6

Your teammate clones

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.

What happens with a repo they can't reach

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.

Walkthrough: GitLab collections

gitcollect supports GitLab with the same commands. Key differences from GitHub are noted at each step.

1

Authenticate with GitLab

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

2

Create a collection

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
3

No invite step needed

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).

4

Self-hosted GitLab

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.

Walkthrough: enterprise onboarding with import

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.

1

Admin: import the whole org (runs once)

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.

2

Admin: publish to a shared config repo (runs once, then after each update)

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
3

Employee: join the team (runs once per new hire)

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.

4

Ongoing: sync when the org changes

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

Command reference

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.

Authentication

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

Collection lifecycle

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.

Collection tools

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

Repo management

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.

Member management

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.

Group management

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.

Access inspection, audit & activity

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

Git operations

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)

Organisation import

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

Discovery & dashboards

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.

System

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.