Back to projects
Active Started Mar 2026

Agent Astronomer

Query your local Claude Code skill, agent, and plugin library from any conversation. Wraps the Agent Astronomer CLI as MCP tools.

Product overview Private Repo
Next.js 16 Node.js TypeScript MCP Swift/SwiftUI Git

Install

claude plugin marketplace add tyroneross/RossLabs-AI-Toolkit
claude plugin install agent-astronomer@rosslabs-ai-toolkit

The Problem

A growing plugin collection is also a growing memory problem. Which skills exist? Which duplicates drifted? Which agent fires on which trigger? The answer lives on disk but the model cannot see it, so users default to loading everything or none of it.

What I Built

Agent Astronomer turns your installed skills, agents, and plugins into a searchable catalog any conversation can query. No more “which skill did that again?”

Agent Astronomer library view — 202 skills grouped by plugin (build-loop shown), each with description, source, type, and last-used status, plus filter and search ::border

Impact

The plugin does not bundle the scanner. All catalog reads go through the CLI, so plugin and web UI stay consistent. Node 22+ runs the TypeScript directly via --experimental-strip-types, with no build step and zero npm dependencies on the MCP side.

Architecture

Agent Astronomer is a local-first, single-user, no-cloud system with three surfaces reading and writing the same on-disk state: a Next.js web app, a native macOS menu-bar app (SwiftUI), and this MCP plugin for Claude Code / Codex. There is no server to deploy and no account — every surface resolves state under ~/.astronomer/ (settings, links, the scan cache, per-file shadow-git repos) directly on the machine it runs on. An earlier cloud build (Neon Postgres + Vercel Blob + Neon Auth) was amputated in April 2026; lib/db/client.ts still sits on disk as dormant, unused scaffolding for a possible future cloud rebuild, and throws immediately if called without a DATABASE_URL.

flowchart LR
  A["Scan roots<br/>~/.claude/skills, plugin cache,<br/>marketplaces, linked project folders"] --> B["Scanner<br/>lib/scanner.ts / plugin-scanner.ts<br/>walk + parse frontmatter"]
  B --> C["Catalog<br/>skills · agents · plugins<br/>djb2 id + sha256 content hash"]
  C --> D["scan-cache.json<br/>~/.astronomer/, cross-process locked"]
  D --> E1[Next.js web UI]
  D --> E2["macOS menu-bar app<br/>(Swift, mirror scanners)"]
  D --> E3["bin/aa CLI<br/>--json"]
  E3 --> F["MCP server<br/>plugin/mcp-server.mjs<br/>stdio JSON-RPC, no SDK"]
  F --> G["Claude Code / Codex<br/>aa_search, aa_stats, aa_similar,<br/>aa_drift, aa_scan_folder, aa_list, aa_graph"]
  C --> H["Trigram Jaccard similarity<br/>(pure stdlib, no embeddings)"]
  C --> I["Drift compute<br/>lib/library.ts + drift-cache.ts, 30s TTL"]
  J["Edit in web/Mac UI"] --> K["Shadow git repo per file<br/>lib/shadow-git.ts, spawns system git"]
  K -->|explicit promote| L["Real file on disk"]

How it works

  1. Each of the three surfaces reads the same .astronomer/settings.json scan-path list (default: a project folder, ~/.claude/skills/, the plugin cache, and installed marketplaces) and walks it for SKILL.md files (lib/scanner.ts) and plugin.json / .claude-plugin/plugin.json files (lib/plugin-scanner.ts), plus linked folders, single files, or shallow-cloned GitHub repos added via lib/links.ts.
  2. Each hit is parsed with a hand-rolled frontmatter parser (lib/parser.ts, no gray-matter dependency), assigned a stable djb2 id from its absolute path (lib/id.ts) and a sha256 content hash, and classified by platform (web/ios/macos/shared) via lib/platform-detect.ts.
  3. The walk result is written to a disk-backed, cross-process-locked cache (lib/scan-cache.ts, ~/.astronomer/scan-cache.json) so the Next.js app and the Swift Mac app can share one fingerprinted catalog instead of each re-walking the filesystem — lib/cross-process-lock.ts implements the lock itself with O_EXCL sentinel files and stale-holder detection, stdlib only.
  4. Three derived views run over that catalog on demand: lexical-similarity clustering for near-duplicate detection (lib/similarity.ts — character-trigram Jaccard with union-find, no embeddings, chosen because the catalog is ~100-200 items and O(n²) trigram comparison is cheap enough that adding a vector index would be solving a problem that doesn’t exist yet), drift detection for stale plugin-cache copies (lib/library.ts + lib/drift-cache.ts, 30-second TTL keyed on (id, contentHash, source, lastModified)), and usage/fork grouping by name and content hash (lib/skill-usage.ts).
  5. The web app and Mac app serve this catalog for browsing, search, and editing; edits default to a per-file shadow-git repo (lib/shadow-git.ts, keyed by djb2(realPath), shelling out to the system git binary via child_process.spawn rather than adding simple-git/isomorphic-git) so every change is versioned and revertible before it ever touches the real file — only an explicit “promote” write copies the shadow HEAD back to disk.
  6. The MCP plugin never touches the filesystem itself. plugin/mcp-server.mjs is a hand-rolled JSON-RPC 2.0 server over stdio (pure Node stdlib, no MCP SDK) that shells out to bin/aa <command> --json in a sibling clone of this repo and relays the CLI’s stdout back as the tool result — the same code path the web UI’s API routes call, so plugin answers can’t drift from the web catalog.
  7. bin/aa is a sh wrapper that runs aa.ts directly via Node 22+‘s --experimental-strip-types/--experimental-transform-types, with a tiny custom module-resolution hook so extensionless relative imports (./scanner./scanner.ts) work with zero build step and zero added npm dependencies on that path.

Models: none. No LLM, embedding model, or external inference call exists anywhere in the scan, similarity, drift, or MCP path — verified by grep across lib/, app/, plugin/, and bin/ for LLM/provider SDK usage, which returns nothing. Catalog matching is deterministic (hash equality, substring search, trigram Jaccard); nothing in this product is probabilistic.

Tools & infra — which, why

ComponentWhy
Node.js 22+, native TS strippingRuns .ts sources directly (--experimental-strip-types) — no tsc/tsx/build step for the CLI or MCP server
Next.js 16 + React 19Local web UI surface (catalog browse/search/edit, history, links, context)
Swift / SwiftUI (mac/)Native menu-bar/Dock app mirroring the same scanners in Swift, sharing scan-cache.json with the web app so both surfaces agree on catalog state
System git binary (via child_process.spawn)Per-file shadow-git version history and rollback — deliberately no simple-git/isomorphic-git dependency (AGENTS.md invariant)
Filesystem JSON store (.astronomer/*.json)Settings, links, propagation opt-outs, dependency graph — atomic temp-file+rename writes (lib/fs-atomic.ts) and per-key in-process mutexes (lib/mutex.ts) guard read-modify-write cycles
Cross-process file lock (lib/cross-process-lock.ts)Stdlib-only O_EXCL sentinel locking so the web app, Mac app, and CLI never interleave writes to shared .astronomer/ state
Neon Postgres + Drizzle ORM (@neondatabase/serverless, drizzle-orm)Dormant — kept on disk from the pre-April-2026 cloud build; getDb() throws unless DATABASE_URL is set, and nothing in the active app calls it
MCP (hand-rolled JSON-RPC 2.0 over stdio)No MCP SDK dependency — the plugin surface is a thin protocol shim over the CLI, not a second implementation of scanning logic

Tech stack

Next.js 16 + React 19 (web UI), Node.js 22+ with native TypeScript stripping (CLI, MCP server — zero build step, zero MCP-side npm deps), Swift/SwiftUI (native macOS menu-bar app, mac/), system git binary via child_process.spawn (shadow-git version history), filesystem JSON state under .astronomer/ with atomic writes and cross-process locking. Neon Postgres + Drizzle ORM are present but dormant (unused since the April 2026 cloud-to-local-first reshape). No LLM or embedding model anywhere in the stack.

MCP Tools

The plugin ships an MCP server that shells out to the bin/aa CLI in a sibling clone of the agent-astronomer repository. All reads go through the CLI, so plugin behavior matches the web UI.

ToolPurpose
aa_statsCatalog totals by kind with drift counts
aa_searchSubstring search over names, descriptions, triggers
aa_similarLexical-similarity clusters containing a given name
aa_driftStale plugin-cache copies
aa_scan_folderClassify candidates in a folder: new, duplicate, update, conflict
aa_listFull catalog list by kind
aa_graphDependency-graph slice for skills matching a name substring