Install
Install script (recommended)
Detects your OS and architecture and puts the latest release binary in a directory on your $PATH. Supports linux/amd64, darwin/amd64 and darwin/arm64. Read the script first.
$ curl -fsSL https://raw.githubusercontent.com/mabd-dev/reposcan/main/install.sh | sh
Two optional environment variables:
| Variable | Default | Effect |
|---|---|---|
VERSION | latest | Install a specific version. |
ALIAS | reposcan | Name of the installed binary. |
$ curl -fsSL https://raw.githubusercontent.com/mabd-dev/reposcan/main/install.sh | VERSION=1.4.1 ALIAS=rs sh
Release binaries
Download an archive for your platform from GitHub releases and put the binary on your $PATH.
go install
$ go install github.com/mabd-dev/reposcan@latest
This puts the binary in $GOPATH/bin (usually ~/go/bin). The install script uses a different directory, so if you switch methods, remove the old binary first with rm "$(which reposcan)"; otherwise whichever comes first on your $PATH wins.
From source
$ git clone https://github.com/mabd-dev/reposcan.git
$ cd reposcan
$ go build -o reposcan .
Usage
# scan your home directory (the default root)
$ reposcan
# several roots
$ reposcan -r ~/Code -r ~/work
# only repos with commits that are not on the remote
$ reposcan -f unpushed
# a JSON report for scripts
$ reposcan -o json
Settings load in three layers, each overriding the one before: built-in defaults, then ~/.config/reposcan/config.toml if it exists, then CLI flags.
CLI flags
| Flag | config.toml | Default | What it does |
|---|---|---|---|
-r, --root PATH | roots | $HOME | Directory to scan. Repeat for more roots. |
-d, --dirIgnore GLOB | dirIgnore | see config | Glob to skip while walking. Repeatable; doublestar patterns such as **/node_modules/** work. Any -d replaces the configured list for that run. |
-f, --filter TYPE | only | dirty | Which repos to report: dirty, uncommitted, unpushed, unpulled, stash or all. |
-o, --output TYPE | output.type | interactive | interactive opens the TUI, json prints a report, none prints nothing. |
--json-output-path DIR | output.jsonPath | unset | Also write a timestamped JSON report into DIR, creating it if missing. |
-w, --max-workers N | maxWorkers | 8 | How many repositories are checked concurrently. |
--debug | debug | false | Write a log file to ~/.config/reposcan/logs/. |
--no-telemetry | no-telemetry | false | Turn off anonymous usage data. See telemetry. |
Filters
dirty: uncommitted files, commits ahead, or commits behind. Stash-only repos count too whencountStashAsDirty = true.uncommitted: repos with uncommitted files.unpushed: repos with commits ahead of their upstream.unpulled: repos with commits behind their upstream.stash: repos with stash entries, regardless ofcountStashAsDirty.all: every repository found.
config.toml
reposcan reads ~/.config/reposcan/config.toml. Every key is optional; anything you leave out keeps its default. A complete example with the defaults spelled out:
version = 1
debug = false
no-telemetry = false
# directories to search for repositories (default: your home directory)
roots = ["~/Code", "~/work"]
# all | dirty | uncommitted | unpushed | unpulled | stash
only = "dirty"
# treat repos whose only local state is a stash as dirty (config only, no flag)
countStashAsDirty = false
maxWorkers = 8
# globs skipped during the walk; setting this replaces the built-in list
dirIgnore = [
"**/node_modules/**",
"**/vendor/**",
"**/.cache/**",
]
[output]
type = "interactive" # interactive | json | none
jsonPath = "" # directory for timestamped JSON reports
[tui]
showVCS = true # VCS column in the table (next release)
Default ignore list
Out of the box reposcan skips dependency folders (node_modules, vendor, .venv, venv, .m2, .gradle, .cargo, target and similar), build output (build, dist, .next, .nuxt), caches (.cache, .local, .pytest_cache), editor folders (.idea, .vscode), and system directories such as /proc, /sys, /tmp, /System, /Library and ~/Library. The full list is in sample/config.toml.
JSON output
-o json prints one report to stdout; --json-output-path writes the same report to a timestamped file. A trimmed example:
{
"version": 1,
"repoStates": [
{
"id": "4aa059a05b26cd1a",
"path": "/home/you/Code/api",
"repo": "api",
"vcsType": "git",
"branch": "main",
"uncommitedFiles": [" M server.go"],
"remoteStatus": [
{ "remote": "origin", "ahead": 2, "behind": 0 }
],
"stashes": []
}
],
"totalScannedRepos": 142,
"generatedAt": "2026-10-07T21:26:03.802613+03:00",
"warnings": []
}
| Field | Meaning |
|---|---|
version | Report format version, currently 1. |
repoStates[] | One entry per repository that matched the filter. |
id | Stable identifier derived from the path. |
path, repo | Absolute path and directory name. |
vcsType | git or jj. Added in the next release. |
branch | Current branch (for jj, the current bookmark or change). |
uncommitedFiles[] | Porcelain-style status lines. The key is spelled this way in the output. |
remoteStatus[] | remote, ahead, behind. -1 means there is no upstream to compare with. jj entries also carry outgoingCommits. |
stashes[] | Stash entries. |
totalScannedRepos | Every repository found, before filtering. |
generatedAt | RFC 3339 timestamp. |
warnings[] | Non-fatal problems hit during the scan. |
Git worktrees
Besides ordinary .git directories, reposcan recognises the .git file that linked worktrees and submodules use (a file containing a gitdir: pointer), and reports each worktree as its own row. Linked worktrees share their main checkout's stash list, so a stash shows up on each of them.
jj (Jujutsu) next release
jj support is merged but not in v1.4.1; build from source to use it now. reposcan detects .jj directories in the same walk as Git and collects read-only state: name, current bookmark or change, uncommitted files, outgoing commits for tracked bookmarks, and incoming counts from already-fetched remote bookmarks.
Current limits:
- The TUI's fetch, push and pull keybindings do nothing for jj repos.
- Incoming (unpulled) counts depend on tracked bookmarks and on remote state you have already fetched.
- Remote status is folded into a single entry.
- JSON reports include outgoing commits but not incoming commit details.
- The details pane shows the shared status fields with little jj-specific metadata.
Updating
Re-run the install script; it always installs the latest release. From the next release on, reposcan update checks GitHub and replaces the binary in place. If you installed under a custom name, pass it with reposcan update --alias rs.
Telemetry
reposcan sends anonymous usage data to Mixpanel: OS, CPU architecture, tool version, whether it runs in CI, and the flags used (filter, output format, repo count). No usernames, tokens or file paths. You see a one-time notice on first run. Turn it off with --no-telemetry or no-telemetry = true in config.toml.