@hasna/repos
Local repo intelligence platform for AI agents. Track all repos on your machine, search commits, PRs, branches across every repository. CLI + MCP server + Web dashboard.
Install
bun install -g @hasna/reposQuick Start
# Scan all repos under ~/Workspace
repos scan
# List all tracked repos
repos repos
# Search across everything
repos search "authentication"
# Show stats
repos stats
# Start the dashboard
repos-serve # http://localhost:19450CLI Commands
| Command | Description |
|---|---|
repos scan |
Discover and index all git repos |
repos repos |
List repositories |
repos repo <name> |
Get repo details |
repos commits |
List commits |
repos branches |
List branches |
repos tags |
List tags |
repos prs |
List pull requests |
repos search <query> |
Unified search across all entities |
repos stats |
Global statistics |
repos activity |
Recent commit activity |
repos contributors |
Top contributors |
repos stale |
Stale repos with no recent commits |
repos heatmap |
Commit activity heatmap |
repos sync-github |
Sync PRs from GitHub |
repos gh-info <name> |
Fetch GitHub metadata |
repos gh-catalog |
Enumerate/cache GitHub repository catalog JSON for OpenLoops |
repos package health [path] |
Check package scripts, bins, lockfiles, and release metadata |
repos package drift [path] |
Compare package.json against bun.lock |
repos package resolve-bin [name] |
Resolve package bins from package.json, node_modules, or PATH |
repos ports scan [path] |
Scan listening ports and match package script port hints |
repos triage branches [path] |
Summarize branch, dirty, stale, merged, ahead/behind state |
repos triage prs [path] |
Summarize GitHub PR state through gh |
repos docs drift [path] |
Check README coverage for package and agent ops commands |
repos release health [path] |
Combine package, drift, docs, and branch release checks |
repos no-cloud inventory [path] |
Scan repos for legacy cloud package references and route-safe remediation metadata |
repos ops pr-queue |
Emit PR merge task seeds, optional bounded GitHub sync, reports, and todos upserts |
repos ops global-cli-smoke |
Check global CLIs, emit task seeds for failures, reports, and todos upserts |
repos ops package-hygiene |
Check Bun/npm Hasna package hygiene |
Legacy list/search/status commands support --json for machine-readable output.
Agent-loop ops commands emit compact JSON by default and bound returned lists with
--limit. Each supports --pretty for readable JSON, --todo <id> for a dry-run
todos comment preview, and --todo-apply to write that compact result back to a
task. Mutating todos integration is opt-in.
Loop producer commands use a stricter contract for deterministic OpenLoops jobs:
they emit task_suggestions, can write a private JSON report with --report-dir,
and can upsert a bounded number of deduped todos tasks with --upsert-tasks.
This lets loops follow the pattern: check expectation, write compact evidence,
upsert one task per unmet expectation, then let task-created headless workflows
claim the task. They should not dispatch prompts into tmux panes.
Examples:
repos ops pr-queue \
--sync-orgs hasna,hasnaxyz,hasnatools,hasnastudio,hasnaai,hasnaeducation,hasnafamily \
--sync-max-repos 80 \
--state open \
--limit 100 \
--report-dir ~/.hasna/loops/reports/repo-pr-sync-producer \
--upsert-tasks \
--todos-project ~/.hasna/loops \
--task-list repo-pr-merge-queue \
--max-task-actions 50 \
--json
repos ops global-cli-smoke \
--report-dir ~/.hasna/loops/evidence/global-cli-smoke-native \
--upsert-tasks \
--todos-project ~/.hasna/loops \
--task-list global-cli-smoke \
--max-task-actions 20 \
--json--sync-orgs requires --sync-max-repos; GitHub sync errors make the command
exit non-zero by default so loop health cannot silently run on stale metadata.
Use --allow-sync-errors only for exploratory reads where stale cached PR data
is acceptable.
MCP Server
repos-mcp34 tools available for AI agents:
list_repos,get_repo,search_reposlist_commits,search_commitslist_branches,list_tagslist_prs,search_prslist_remotessearch(unified)scan_reposget_stats,get_repo_statssync_github_prs,sync_all_github_prs,fetch_repo_metadatagraph_build,graph_query,graph_related,graph_path,graph_deps,graph_statspackage_health,package_drift,package_resolve_binports_scan,triage_branches,triage_prsdocs_drift,release_healthregister_agent,heartbeat,list_agents
REST API
repos-serve # Default port: 19450| Endpoint | Method | Description |
|---|---|---|
/api/repos |
GET | List repos |
/api/repos/:id |
GET | Get repo + stats |
/api/search/repos |
GET | Search repos |
/api/commits |
GET | List commits |
/api/search/commits |
GET | Search commits |
/api/branches |
GET | List branches |
/api/tags |
GET | List tags |
/api/prs |
GET | List PRs |
/api/search |
GET | Unified search |
/api/stats |
GET | Global stats |
/api/scan |
POST | Trigger scan |
SDK
import { scanRepos, searchAll, listRepos, getGlobalStats } from "@hasna/repos";
const result = await scanRepos(["/home/user/code"]);
const repos = listRepos({ org: "myorg" });
const results = searchAll("authentication");OpenLoops GitHub Catalog
OpenLoops should use the GitHub catalog contract instead of scraping CLI text:
# Refresh at most one GitHub API page, then return the first 100 matching records.
repos gh-catalog --sync --max-pages 1 --json --limit 100
# Continue a partial sync later without loading all repos in one run.
repos gh-catalog --sync --resume --max-pages 1 --json
# Enumerate cached records only, filtered for sequential multi-repo loop setup.
repos gh-catalog --json --org hasna --language TypeScript --tags open-loops --limit 25 --offset 0SDK entry points:
import {
enumerateGithubRepoCatalog,
iterateGithubRepoCatalog,
syncGithubRepoCatalog,
} from "@hasna/repos";
const cache = syncGithubRepoCatalog({ maxPages: 1, resume: true });
const page = enumerateGithubRepoCatalog({
limit: 25,
offset: 0,
filter: { org: "hasna", packageScope: "@hasna", tags: ["open-loops"] },
});
for (const repo of iterateGithubRepoCatalog({ filter: { language: "TypeScript" } })) {
// Run one repository loop at a time.
}The JSON envelope uses schema open-repos.github-catalog.v1 and includes source.cacheSyncedAt, source.staleAt, source.completed, source.nextCursor, page.nextOffset, GitHub rate-limit metadata, discovered accounts/orgs, and repository records. Each record includes owner/account, org, repo name/full name, default branch, visibility, archived/disabled/fork flags, topics, description, safe HTTPS/SSH clone URLs, pushed/updated timestamps, primary language, package hints, local path and branch/dirty/ahead/behind status when matched, and loop tags.
The catalog is cacheable and resumable. By default repos gh-catalog reads the cache and does not call GitHub; add --sync when OpenLoops intentionally wants to refresh data. The cache path defaults to ~/.hasna/repos/github-catalog.json and can be overridden with HASNA_REPOS_GITHUB_CACHE_PATH or --cache.
HTTP mode
Run a shared Streamable HTTP MCP server (stateless, 127.0.0.1 only):
repos-mcp --http # default port 8830
MCP_HTTP=1 repos-mcp # via env
repos-mcp --http --port 8830- Health:
GET http://127.0.0.1:8830/health - MCP:
http://127.0.0.1:8830/mcp - Stdio remains the default when
--http/MCP_HTTP=1are not set. repos-servealso mounts/healthand/mcpon its HTTP port.
Data Storage
SQLite database at ~/.hasna/repos/repos.db with WAL mode and FTS5 full-text search.
License
Apache-2.0