On v1.1.0-beta.1 every job passed and the release is fully signed (verified locally: 289 files, 0 failures, attestation OK), but `verify-signatures` looked for `gclo-cli-win-x64-…zip` while the dev edition ships `gclo-dev-cli-win-x64-…zip`. The job now uses the app id for the CLI name too. RELEASING.md corrected and notes that both editions share Credential Manager entries. Closes #116. Tested: actionlint clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: James Maes <james@kof22.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Clone an entire GitHub organization. Keep it up to date. In parallel.
| Tier | Product (see the KofTwentyTwo standards scope) |
| Standards | Conforms to KofTwentyTwo standards main@855cf44 (pre-1.0 draft); conformance tracked in #62 |
| Latest release | Releases |
gclo (Git Clone Large Organizations) mirrors every repository of a GitHub organization or user account into a local folder in one pass: it clones the repositories that are missing and fast-forwards the ones that already exist, with live per-repo progress and bounded parallelism. It ships as a Windows 11 desktop app (WinUI 3) and a scriptable CLI built on the same engine.
Download & install
All builds are published on the GitHub Releases page.
Desktop app (GUI) — download and run gclo-stable-Setup.exe. It installs per-user (no admin rights needed) and updates itself in-app via Help > Check for updates. A portable build (gclo-stable-Portable.zip) is also attached to each release; portable builds do not self-update.
CLI — download gclo-cli-win-x64-<version>.zip and unzip it anywhere. It is a self-contained single gclo.exe — no .NET runtime required.
Plain version tags (v1.2.3) are stable releases; pre-release tags (v1.2.3-beta.1) publish to the dev channel, and an installed app only ever updates within its own channel.
Upgrading from a beta? Channels don't cross: a dev/beta install never auto-updates to a stable release (its Check for updates only looks at the dev channel). To move to stable, install
gclo-stable-Setup.exeonce — it takes over the existing install and self-updates within the stable channel from then on.
Package managers (CLI):
# Scoop: this repository is its own bucket
scoop bucket add gclo https://github.com/KofTwentyTwo/gclo
scoop install gclo
# Chocolatey (once the package has cleared moderation; see #7)
choco install gclo
winget support for the desktop app is tracked in #7. Every official channel (GitHub Releases, the in-app updater, Scoop, Chocolatey, winget) is served over HTTPS.
Verifying a release
Every release asset is listed in SHA256SUMS, carries a build-provenance attestation, and ships with a CycloneDX SBOM (gclo-<version>.cdx.json for the app, gclo-cli-<version>.cdx.json for the CLI). Releases are built by the KofTwentyTwo standards' shared release workflow, so the provenance names that workflow as the builder:
# 1. Integrity: the file matches the published checksum
(Get-FileHash .\gclo-stable-Setup.exe -Algorithm SHA256).Hash # compare with the line in SHA256SUMS
sha256sum --check --ignore-missing SHA256SUMS # Linux / macOS
# 2. Origin: built from this repository's tagged commit by the shared release workflow
gh attestation verify .\gclo-stable-Setup.exe --repo KofTwentyTwo/gclo --signer-repo KofTwentyTwo/standards
# 3. Dependencies: the SBOM is attested too
gh attestation verify .\gclo-cli-win-x64-<version>.zip --repo KofTwentyTwo/gclo --signer-repo KofTwentyTwo/standards --predicate-type https://cyclonedx.org/bom
# 4. Publisher: Setup.exe and every gclo executable are Authenticode-signed (Azure
# Artifact Signing, SHA-256, RFC 3161 timestamp) by KofTwentyTwo
signtool verify /pa /all /v .\gclo-stable-Setup.exe
Get-AuthenticodeSignature .\gclo-stable-Setup.exe | Format-List Status, SignerCertificate, TimeStamperCertificate
Windows checks step 4 for you: a signed installer carries the publisher name in the SmartScreen and UAC prompts instead of "Unknown publisher".
Getting a token
gclo authenticates to GitHub with a personal access token — it is used for both the API (listing repositories) and the git transport (cloning and fetching). Create one under GitHub Settings > Developer settings > Personal access tokens.
| Token type | What you need |
|---|---|
| Classic PAT | repo scope (read access to the org's repositories); optionally read:org so the organization dropdown / gclo orgs can list your orgs |
| Fine-grained PAT | Repository read access for the organization |
Token storage. In Quick Sync, the token is held in memory for the session only and never written anywhere. A saved account's token and the optional default token (Settings) are kept in the Windows Credential Manager (per-user, DPAPI-protected) — never in settings.json, accounts.json, or any log. See SECURITY.md.
Using the GUI
On launch, gclo shows a brief branded splash, then the Quick Sync connect card:
-
Paste your token first. After a moment, the Organization or account dropdown fills with everything the token can sync — your personal account is listed first, then your organizations alphabetically. If the token cannot list organizations (a fine-grained PAT, or a classic PAT without
read:org), the status line says so; the dropdown is editable, so just type the name. -
Pick the organization or account from the dropdown.
-
Choose a target folder — type a path or click Browse.... Tick Create a
<org>subfolder to keep several organizations under one root; the live preview line underneath always shows exactly where repositories will land (e.g.C:\src\acme\my-repo). -
Set parallelism if you like (1–64, default 8): how many git operations run at once.
-
Press Load repos. The table fills with every repository of the organization — name, status, default branch, and an Archived marker — all selected. Click a column header to sort by it (click again to flip); use the funnel on any column to filter by name, status, branch, or archived state.
-
Uncheck anything you don't want. The header checkbox selects or clears every row at once.
-
Press Sync selected. Each row moves through its states live — Queued, Cloning (with a per-row progress bar and transfer percentage), Pulling, Done, Failed, or Canceled — while the overall progress bar and completed/total counter track the run, which ends in a summary (
Finished: 3 cloned, 41 updated, 1 failed, 0 canceled of 45.). -
Cancel at any time: in-flight repos stop cleanly, unstarted repos are marked Canceled, and the summary still appears. After a run, Retry failed re-runs exactly the failed rows, and Open folder opens the target folder in Explorer.
When something goes wrong: failures are isolated per repository — one broken repo never stops the rest, and a failed row shows its error message inline. Failed clones are cleaned up, and a clone whose checkout never finished (a crash, a kill, a cleanup that could not delete the folder) is flagged inside its .git, so a partial working tree is never mistaken for an up-to-date repo on the next run — gclo completes the checkout instead. Repositories with Windows-impossible paths are the deliberate exception: gclo checks every path in the incoming tree against Windows file-system rules before checkout — invalid characters, reserved device names like CON, trailing spaces or dots, names that differ only by case — and when it finds any, the repo is marked Failed with a per-path list of reasons, but the fully fetched .git is kept (nothing was ever checked out). Recovery happens in place, with no re-download: click the row's Resolve… link to rename each offending path (safe names are pre-suggested) or skip it, and gclo materializes the working tree right there; the CLI does the same automatically with --sanitize-paths. When WSL is installed with git in its default distribution, the same dialog offers Clone in WSL instead: the repository is cloned unchanged into ~/gclo/<org>/<repo> inside WSL (reachable from Explorer as \wsl.localhost\<distro>\home\...), the token travels only through the environment, and the Windows copy is left as it is. Long paths beyond the classic 260-character limit are handled automatically (core.longpaths).
Settings (File > Settings…) let you set a default GitHub token (stored in the Windows Credential Manager), the default target folder (with a folder picker), the default parallelism, and the theme (System, Light, or Dark); an Advanced section toggles the startup splash and its duration.
Activity log: View > Activity log… shows the tail of today's log and links to the logs folder. Logs live under %LOCALAPPDATA%\KofTwentyTwo\gclo\logs, one file per day; they record run parameters and per-repo failures and never contain your token (see SECURITY.md).
Updates: Help > Check for updates… downloads and applies the latest release for your channel. Self-update is only available in installed builds (not the portable zip or a local debug build).
Saved accounts
Quick Sync is ad-hoc — nothing is saved. For organizations you sync regularly, Add account stores a reusable profile: a name, the organization, target folder, and parallelism, with the token kept in the Windows Credential Manager (never in a file). Each account gets its own entry in the navigation pane and its own workspace; Sync all runs every saved account in turn, and the pane shows each account's live status.
Using the CLI
The CLI drives the same engine from scripts and scheduled jobs. There is deliberately no --token <value> option — command-line arguments leak to other processes — so the token comes from the GITHUB_TOKEN environment variable by default, or from --token-env, --token-file, or --token-stdin.
PowerShell:
# List everything the token can sync (your account first, then orgs)
$env:GITHUB_TOKEN = (Get-Secret -Name GitHubPat -AsPlainText)
gclo orgs
# Mirror an organization, 16 git operations at a time
gclo sync --org contoso --target C:\src\contoso --parallel 16
# Preview, then sync a subset (globs on the repository name; archived ones skipped)
gclo repos --org contoso --include "platform-*" --skip-archived
gclo sync --org contoso --target C:\src\contoso --include "platform-*" --skip-archived --dry-run
# Save the connection as an account (token in Windows Credential Manager) and reuse it
gh auth token | gclo accounts add --name work --org contoso --target C:\src\contoso --token-stdin
gclo sync --account work
# Pipe the token from a secret store — it never touches a command line or disk
gh auth token | gclo sync --org contoso --target C:\src\contoso --token-stdin
# Machine-readable result
$result = gclo sync --org contoso --target C:\src\contoso --json | ConvertFrom-Json
if ($result.failed -gt 0) { $result.failures | ForEach-Object { "$($_.repo): $($_.error)" } }
bash:
# List orgs, then mirror one
export GITHUB_TOKEN="$(pass show github/pat)"
gclo orgs
gclo sync --org contoso --target ~/src/contoso
# Pipe the token from a secret store
op read "op://Private/GitHub PAT/token" | gclo sync --org contoso --target ~/src/contoso --token-stdin
# Machine-readable result with jq
gclo sync --org contoso --target ~/src/contoso --json |
jq -r '.failures[] | "\(.repo): \(.error)"'
--org accepts an organization or a user account login. --json suppresses progress output and prints a single JSON summary line; combined with the exit code it makes gclo easy to wire into cron/scheduled tasks:
| Exit code | Meaning |
|---|---|
| 0 | Everything succeeded. |
| 1 | The run completed, but some repositories failed or the run was canceled (Ctrl+C). |
| 2 | Fatal: bad arguments, missing/empty/rejected token, or organization not found. |
Full reference — all options, output streams, Ctrl+C behavior, and more examples: docs/CLI.md.
How it works
The engine lists every repository through the GitHub REST API (Octokit, paginated 100 per page), then runs the set through Parallel.ForEachAsync bounded by the parallelism setting. Git operations use LibGit2Sharp: missing repos are cloned with object-transfer progress; existing repos get a fetch from origin followed by a fast-forward-only merge — if a local branch has diverged from its upstream, the repo is reported Failed rather than auto-merged, so gclo never manufactures merge commits. Archived repositories are included and sync normally. Every repo ends in exactly one terminal state — Done, Failed (with the error), or Canceled — and per-repo exceptions are contained so the rest of the run continues. The PAT is the only credential; on the git side it is sent as HTTPS basic credentials with username x-access-token.
Build from source
Requirements: Windows 11 (or Windows 10 1809+), the .NET 10 SDK, and — for the desktop app — Visual Studio 2026 with the Windows App SDK / WinUI workload.
git clone https://github.com/KofTwentyTwo/gclo.git
cd gclo
# Desktop app (packaged build)
dotnet build gclo.slnx -p:Platform=x64
# Desktop app, unpackaged — a plain exe under gclo\bin\x64\Debug\, no MSIX deployment
dotnet build gclo.slnx -p:Platform=x64 -p:WindowsPackageType=None
# CLI only (plain net10.0 — also builds on Linux/macOS)
dotnet run --project gclo.Cli -- sync --org contoso --target C:\src\contoso
# Tests
dotnet test
To F5-debug the packaged app in Visual Studio, open gclo.slnx, set gclo as the startup project, select the x64 platform, and make sure Windows Developer Mode is enabled (Settings > System > For developers) — packaged deployment requires it.
Project layout
| Project | Description |
|---|---|
gclo |
WinUI 3 packaged desktop app — XAML, dialogs, pickers, and update plumbing only; no business logic |
gclo.Cli |
Cross-platform command-line head over the engine (docs/CLI.md) |
gclo.Engine |
Core sync engine: OrgSyncEngine, Octokit-based listers, LibGit2Sharp git client, Windows path validation |
gclo.ViewModels |
UI-framework-free view models and settings persistence, shared presentation logic |
gclo.Engine.Tests |
xunit tests: engine (with fakes), real-git integration suite, and view-model tests |
gclo.Cli.Tests |
xunit tests for the CLI, driving injectable command cores over fakes |
gclo.UiTests |
FlaUI/UIA end-to-end tests that drive the real gclo.exe |
Line coverage is held at 100% for gclo.Engine, gclo.ViewModels, and gclo (the CLI), enforced in CI.
Support
Only the latest release line receives fixes: the latest MINOR of the latest MAJOR gets all fixes, and when a new MAJOR ships the previous MAJOR's last MINOR gets security fixes for 6 months. Details, and how to report a vulnerability privately, are in SECURITY.md.
Contributing
Contributions are welcome. Branch from main (<type>/<description>), commit with Conventional Commit messages signed and signed off (git commit -S -s), and open a pull request against main; it is squash-merged once every required check is green. See CONTRIBUTING.md for prerequisites, build and test commands, the checks that gate a merge, and the code style.
License
gclo is licensed under the MIT License. Copyright (c) 2026 James Maes.
gclo builds on the following open-source projects:
| Dependency | License |
|---|---|
| Octokit | MIT |
| LibGit2Sharp | MIT |
| libgit2 (native library used by LibGit2Sharp) | GPLv2 with linking exception |
| CommunityToolkit.Mvvm | MIT |
| Windows App SDK | MIT |
| Velopack (self-update) | MIT |






