This repo is six files and does nothing. That’s the post.
Two dozen of my projects ship an .agents/ directory — a skill that teaches an agent to drive the tool, and where the tool speaks MCP, a bridge plugin. Sitting in the repo, that’s already useful: Codex scans .agents/skills/ natively, so in a checkout of the repo the skill is just there, no install, invoked as $<skill>.
The problem is everyone who isn’t standing in the checkout.
The obvious answer is the wrong one
My first instinct was one marketplace per repo. Every project publishes its own, you add whichever you want, done. Decentralised, no extra repo to maintain, nothing to keep in sync.
It cannot work, and the reason is dumber than you’d hope:
A marketplace registers under its
name. A second marketplace claiming a registered name either replaces the first (Claude Code) or is rejected outright (Codex).
So two dozen repos each publishing “psyb0t” means all but one of them lose — silently overwriting each other in one client and hard-failing in the other. You’d add three of my tools and quietly end up with one.
Hence psyb0t/agents: one registered name, every plugin listed under it. Not an architecture decision. A namespace constraint with exactly one legal answer.
The catalog carries no code
Every entry is a pointer:
{
"name": "aigate",
"source": {
"source": "git-subdir",
"url": "https://github.com/psyb0t/aigate",
"path": ".agents"
},
"license": "WTFPL",
"category": "ai-infrastructure"
}A git-subdir source aimed at that repo’s own .agents/ directory. The plugin you install is whatever that repository last released — this catalog holds no copies, so it cannot go stale against the thing it describes. Nothing here declares hooks, executables, or MCP servers of its own. It’s an index.
Two marketplace files, because the two clients read different schemas at different paths: .claude-plugin/marketplace.json for Claude Code, .agents/plugins/marketplace.json for Codex. Same 24 entries, kept in lockstep.
claude plugin marketplace add psyb0t/agents
claude plugin install aigate@psyb0t
codex plugin marketplace add psyb0t/agents
codex plugin add aigate@psyb0tNote the verb changes. Claude Code uses plugin install; Codex uses plugin add. There is no codex plugin install — v1.0.2 exists entirely because the README told Codex users to register the marketplace and then never showed them how to install anything from it, which is a great way to make a working tool look broken.
Adding the marketplace on its own installs nothing. It registers the catalog. That trips people up often enough to be worth a sentence.
What’s actually in there
Twenty-five in the Claude Code catalog, twenty-six in the Codex one, spanning most of what I’ve built: aigate, audiolla, claudebox, codexbox, flickies, goenv, hybrids3, ibkr-httpapi, mailbox, mediaproc, mt5-httpapi, persistent-sshfs, pibox, planesnitch, predictalot, proxq, qwenspeak, servicepack, ssh-tunnel-swarm, stealthy-auto-browse, supervisor-config-gen, talkies, telethon-plus, wickworks, plus rankrat and loop.
Yes, goenv is in there. It’s a Go package with three functions that reads one environment variable. It has a plugin. I’ve made my peace with it.
One naming wrinkle worth recording: mailbox shipped as docker-mailbox in v1.0.0 — the only entry named after its repository instead of the tool it installs. Renamed in v1.0.1. The ClawHub skill is unaffected and stays @psyb0t/docker-mailbox, because those are different registries with different rules and pretending otherwise would break something.
Why the two catalogs don’t match
Twenty-five entries in Claude Code’s file, twenty-six in Codex’s. Not a sync bug — loop is a Codex CLI plugin, so there is literally nothing for Claude Code to install. Twenty-five of them register in both and install in either client; exactly one doesn’t.
That asymmetry also killed a column. The plugin table used to print claude plugin install <name>@psyb0t on every row, which read as though these were all Claude-Code-only. They aren’t. The column’s gone and the plugins are grouped into categories instead.
loop broke the source scheme too. Every other entry uses git-subdir pointed at that repo’s .agents/ directory — but loop’s plugin lives at the repository root, and git-subdir can’t express that: it requires a path, and Codex rejects root-equivalent values like "." or "./" by silently skipping the plugin, so codex plugin add just reports it missing. It’s the first entry using source: "url" instead.
There’s a maintenance tax hiding in here that’s worth naming. The catalog holds no code, but it does hold a description of every plugin — and descriptions rot. loop’s said it ran on Goal mode plus a session-owned background timer, which stopped being true the moment loop v0.3.0 switched to holding one turn open on clock.sleep. A stale description is worse than a missing one: it’s what people read before installing, so it sends them hunting for a Goal the plugin no longer creates.
Newest in is rankrat — a self-hosted MCP server over Google Search Console, Bing Webmaster Tools, GA4 and PageSpeed Insights, read-only by default and scoped to the properties configured on the server. Skill only, no bridge plugin: it speaks MCP over stdio natively, so there’s nothing to bridge.
Six files, zero logic, and more tags than commits some weeks. The whole thing is a lookup table that exists because two clients can’t both own the word “psyb0t.”
Boring infrastructure is still infrastructure. github.com/psyb0t/agents.