How a published bot is stored in the library repo — gitbot-hq/Library — and why each part is
shaped the way it is.
A published bot has to satisfy two different readers at once:
- The discover page, which needs enough copy and artwork to render a card and a detail panel.
- The gitbot harness, which needs a real bot definition — the standing job, the agent, the permission mode — so that "Install" produces a working bot rather than a plausible-looking one.
One folder carries both.
One bot per folder, under bots/. The folder name is the bot's slug and is the stable key for
everything else.
bots/
pr-guardian/
bot.json required — all structured fields
instructions.md required — the bot's standing job, in prose
setup.md optional — machine preparation, only if genuinely needed
Nothing else goes in the folder. No images: the mascot is generated from body + color, and the
author avatar is derived from the GitHub handle. No licence file either — the repository's root
LICENSE is MIT and covers every bot, so there is no per-bot licence to declare and
no licence field in bot.json. That is deliberate: someone browsing the discover page should not
have to check the terms bot by bot before installing one.
At the repo root, index.json is generated by CI and is the only file the app fetches.
Contributors never edit it, which keeps concurrent pull requests from conflicting on the same file.
The current detail panel looks a bot up with DETAILS[bot.name] and renders
{bot && details && ...} — so when the two lists disagree by a single character, the card still
looks right and the panel opens blank. Keying on a slug that is also the folder name removes the
ability for the two halves to drift: they are the same directory entry.
The instructions are the bot. They are a few hundred words of prose that you, as the reviewer, have
to actually read — that is the entire substance of the review. In JSON they arrive as one escaped
line with \n in it, which is unreadable in a diff. In a .md file the pull request shows them as
paragraphs. CI inlines them into index.json at merge, so the app still gets one file.
{
"$schema": "../../schema/bot.schema.json",
"slug": "pr-guardian",
"name": "PR Guardian",
"description": "Reviews pull requests for risks before you merge.",
"category": "Code review",
"about": "A thoughtful second pair of eyes for your next pull request. Get a focused review that helps you understand what changed and where to look closer.",
"features": [
"Spot potential bugs and edge cases",
"Understand risky changes in context",
"Get clear, actionable review suggestions"
],
"examplePrompt": "Review my current changes. Focus on bugs, edge cases, and anything I should address before merging.",
"author": { "github": "mayachen", "name": "Maya Chen" },
"mascot": { "body": "bear", "color": "brand-sun", "activity": "thinking" },
"emoji": "🔍",
"agent": "claude-code",
"permissionMode": "ask-permissions"
}| Field | Required | Shown as | Limit |
|---|---|---|---|
slug |
yes | never shown; folder name and lookup key | ^[a-z0-9]+(-[a-z0-9]+)*$ |
name |
yes | card title, detail heading | ≤ 24 chars, unique across the library |
description |
yes | the one line on the card | ≤ 80 chars, one sentence, no trailing period needed |
category |
yes | eyebrow above the detail title | from the fixed list below |
about |
yes | the "About this bot" paragraph | 120–400 chars, 2–3 sentences |
features |
yes | checkmark bullets | exactly 3, ≤ 60 chars each |
examplePrompt |
yes | the quoted prompt with the copy button | ≤ 200 chars, written as a user would type it |
author |
yes | name beside the avatar | see below |
mascot |
no | silhouette and brand colour | defaults to ghost / brand-sun / idle |
All seven of the first group are required by the schema, which is the point: today a missing
about or features leaves the card looking correct and the panel blank. A required field turns
that silent runtime blank into a loud CI failure on the pull request, before you ever look at it.
category is a closed list so the discover page can group and filter. Start with:
Code review, Developer workflow, Releases, Maintenance, Repository care,
Code exploration, Testing, Documentation. Adding a category is a maintainer decision, made in
the schema — not something a contributor can do by typing a new string.
"author": { "github": "mayachen", "name": "Maya Chen" }github is the handle; the avatar is https://avatars.githubusercontent.com/mayachen and is
derived at index time, never stored. CI checks the handle matches the pull request author, so a
contributor cannot publish under someone else's name.
verified is deliberately not a field in bot.json. It lives in a root-level verified.json
that only maintainers can change (CODEOWNERS). A badge that a contributor can grant themselves is
not a badge.
"mascot": { "body": "bear", "color": "brand-sun", "activity": "thinking" }body— one of the 18 silhouettes in gitbot's mascot artwork:bear,belly,birdy,birdy-3,bunny,burdy2,cat,cloud,doggy,fire,flower,ghost,heart,moon,rectangle,star,triangle,tulip.color— a token name from gitbot's brand palette:brand-sun,brand-candy,brand-ember,brand-leaf,brand-sky,brand-honey. Not a hex value and notvar(--brand-sun). The index maps the token to the CSS variable, so themes stay coherent and no off-brand colour gets in.activity— the resting expression:idle,reading,listening,thinking,working,success,error,sleeping. This field is easy to forget when listing "the eight fields", but both the card and the detail hero pass it toMarketplaceMascot. Defaultidle.
Defaults mean a bot with no mascot block renders — as the same yellow ghost as every other bot
that skipped it. So CI treats a missing mascot as a warning on the pull request, not a
failure: it does not break the page, it just makes the library look monotonous. A gentle nudge in
review is the right pressure.
Caveat worth knowing: facePlacement in gitbot's mascot registry has tuned coordinates for 13 of the 18
bodies. ghost, cloud, birdy, burdy2 and rectangle fall back to default placement. They
are used in the shipped design and look fine — this is a note for whoever adds new artwork, not a
restriction on contributors.
These are what makes the installed bot real, and they mirror the Bot type in gitbot's
src/bot-store.ts exactly.
| Field | Required | Notes |
|---|---|---|
agent |
yes | claude-code, opencode or codex. codex supports no tool fence and no per-call approval — a bot that depends on either cannot use it. |
permissionMode |
yes | ask-permissions, auto-approve or plan. Library default is ask-permissions; reviewers and explainers should be plan. |
emoji |
yes | single emoji, used wherever the mascot is not rendered |
model |
no | required when agent is opencode, as provider/model |
allowedTools |
no | a fence: if set, these are the only tools the bot may use |
disallowedTools |
no | not enforced on codex |
auto-approve in a published bot deserves scrutiny in review — it is someone else's machine that
acts without asking. Pair it with an allowedTools fence or send it back.
The whole file is the bot's standing job. No frontmatter, no title heading — the text is used
verbatim as instructions, and gitbot wraps it in its own frame ("You are name, an agent with one
standing job…", see gitbot's
src/bot-prompt.ts).
That framing dictates how it must be written: imperative, addressed to the agent, and complete enough to start from a bare "hi" as the first message. gitbot's bot authoring prompt §2 covers this in full and is the reference for contributors.
One extra rule for published bots that does not apply to private ones: no paths, usernames, repo names or machine details from the author's computer. This text runs on strangers' machines.
Worth calling out for whoever wires the install path up: today gitbot's
marketplace-bot-details.tsx builds instructions on the fly out of the name, description and
feature bullets. That was a reasonable placeholder for eight hardcoded demos, but it means the
installed bot's actual behaviour is a side effect of marketing copy. Once bots come from the
library, install should use instructions from the index and nothing else.
Optional, and usually absent. Present only when the job is impossible without something installed or
configured on the machine — gh authenticated, ffmpeg on PATH.
The stakes are asymmetric and worth restating: a bot with setup instructions refuses all work
until a setup run reports SETUP_COMPLETE. An unnecessary or unachievable setup step does not
degrade the bot, it disables it, on every machine that installs it. In a library that is shared with
strangers, an empty setup.md is very often the correct answer.
Write each step as a condition plus the check that proves it — "gh is installed and authenticated;
confirm with gh auth status" — not as a command for one operating system. Anything only a human can
do (a password, a licence, a paid plan) is written as "ask the user for X and wait": the setup run is
an ordinary conversation and can ask.
Do not mention the SETUP_COMPLETE / SETUP_FAILED markers in the file. gitbot supplies them.
CI runs on every pull request and regenerates index.json on merge to main. The validator fails
the pull request when:
- a required field is missing or empty — the blank-panel bug, caught at source;
slugdoes not equal the folder name, or collides with another bot;namecollides with another bot (the UI keys on it in places, and two identical cards is a bad browsing experience regardless);featuresis not exactly 3 entries, or any length limit is exceeded;category,mascot.body,mascot.color,mascot.activity,agentorpermissionModeis outside its enum;agentisopencodeandmodelis absent;instructions.mdis missing, empty, or over ~600 words;author.githubdoes not match the pull request author;- the pull request touches more than one bot folder, or edits
index.jsonorverified.json; - the bot folder contains anything other than
bot.json,instructions.mdandsetup.md— a licence file included, since the root MIT licence is the only one; - the diff contains an absolute home-directory path or anything shaped like a credential.
Everything above is mechanical. What CI cannot judge — and what your review is actually for — is whether the instructions do what the description claims, and whether the permission mode is justified by the job.
An entry in the generated index is the flattened bot: every field from bot.json, plus
instructions and setupInstructions inlined from the Markdown files, plus the derived
authorPhoto and the resolved colour variable, plus verified from verified.json.