This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Community-contributed themes, plugins, grammars, and recipes for the Claudette desktop app. This repo is the source of truth that the in-app Claudette registry will consume (registry feature: see issues).
Claudette ships a small set of bundled extensions. This repo collects the larger universe of community contributions across the same extension surface. Each contribution lands as a directory under the appropriate top-level kind directory.
AGENTS.md is a symlink to this file — edit CLAUDE.md and every agent harness picks up the same content.
themes/ # CSS palettes ([data-theme] blocks)
plugins/
scm/ # SCM provider Lua plugins
env-providers/ # Env-provider Lua plugins
language-grammars/ # TextMate grammar plugins (declarative)
slash-commands/ # (forthcoming) reusable /commands
mcp-recipes/ # (forthcoming) MCP server presets
registry.json # auto-generated index — DO NOT hand-edit (run `bun run generate`)
registry.schema.json # JSON schema for registry.json
manifest.schema.json # JSON schema for theme.json / plugin.json
revocations.json # security revocation list
scripts/
generate-registry.ts # walks contributions, computes hashes, emits registry.json
validate.ts # validates manifests against manifest.schema.json
.github/ISSUE_TEMPLATE/ # contribution issue templates
.github/workflows/ # CI: validate (per-PR), regen (push to main), mirror (cron)
Each kind directory contains a README.md describing its manifest schema and an _example/ starter that contributors copy.
The schemas this repo's contributions must conform to live in the Claudette repo, not here:
| Kind | Defined in (Claudette) |
|---|---|
Plugin manifest (plugin.json) |
src/plugin_runtime/manifest.rs (PluginManifest) |
| Plugin kinds | PluginKind enum: scm / env-provider / language-grammar |
| Lua host API surface | src/plugin_runtime/host_api.rs |
| Theme | src/ui/src/styles/theme.css ([data-theme="..."] blocks) + src/ui/src/styles/themes/index.ts (BuiltinThemeMeta) |
When making changes that touch contribution schemas, mirror the upstream type. Do not invent new fields here — propose them upstream first.
- Plugins run in a sandboxed Luau VM.
os,io,package,require,loadfile,dofileare not available — don't generate Lua that uses them. - The only host surface is the
hosttable (full list inplugins/scm/README.md).host.execis restricted to CLIs listed in the manifest'srequired_clisand times out at 30s. language-grammarplugins do not ship aninit.luaand no Lua VM is spawned — they're pure JSON plusgrammars/*.tmLanguage.json.- The grammar loader rejects
pathvalues that escape the plugin directory via..or symlinks.
- Conventional Commits — same as Claudette (
feat:,fix:,docs:,chore:…). Header max 100 chars. - PR title scopes match the kind:
feat(theme):,feat(scm):,feat(env-provider):,feat(language-grammar):. Maintainers apply one kind label (theme,plugin:scm,plugin:env-provider,plugin:language-grammar,slash-command,mcp-recipe) plussubmissionandneeds-review. registry.jsonis auto-generated bybun run generate— never hand-edit. The validator workflow rejects PRs whose committedregistry.jsonis out of sync with the contribution tree.- After adding/editing a contribution, run
bun run check(validate + generate --check) locally before pushing. Bothvalidateandgeneratewalk the kind directories and skip anything starting with_. - New contributions: drop into the right kind directory as a sibling of
_example/. Don't modify_example/itself unless you're improving the starter for everyone. - The
_example/directories are copy-from templates. Their manifests deliberately usename: "_example", which is invalid for a real submission (names must be kebab-case and unique) — copy them, don't try to "fix" them in place. - A
NOTICEfile is required inside alanguage-grammarcontribution when the grammar is lifted from upstream — cite source URL, commit SHA, and license.
bun install # install deps (ajv, ajv-formats)
bun run validate # schema-validate every manifest
bun run generate # rebuild registry.json from the current tree
bun run generate -- --check # exit non-zero if registry.json is stale
bun run check # validate + generate --check (CI's full PR check)The content hash for each contribution is sha256(JSON.stringify([{path, sha256}, ...].sortByPath)) — deterministic across runtimes, no tar dependency. Documented in scripts/generate-registry.ts.
CI pins Bun to 1.3.11 (.github/workflows/{validate,regen}.yml). Match that locally to avoid bun.lock churn.
generate -- --check deliberately ignores two fields when comparing committed registry.json against a fresh generation:
generated_at— wall-clock timestamp, drifts every run.sha(per-entry) — the last-touch commit. A contributor's pre-commit generate sees commit N-1; CI's post-merge generate sees commit N. Theregen.ymlworkflow on push to main corrects this drift.
The trust anchor is sha256 (the content hash). If contribution files changed, sha256 changes and --check fails correctly. Don't try to "fix" sha mismatches by hand-editing registry.json — let regen.yml reconcile after merge.
Both validate.ts and generate-registry.ts re-check these — bypass one, the other still trips:
- Directory name ==
manifest.name(plugins) ormanifest.id(themes). manifest.kindmatches the kind directory it lives under (plugins/scm/...⇒kind: "scm").- License is in the v1 allowlist (
MIT,Apache-2.0,BSD-2-Clause,BSD-3-Clause,MPL-2.0). - For
language-grammar: at least one entry each inlanguagesandgrammars; everygrammars[].pathresolves inside the plugin directory (no.., no leading/).
The schema sets kind default to "scm" if omitted, but contributions should declare it explicitly — the kind/dir cross-check relies on it.
registry.json is signed with a minisign key after every push to main. The
signature lives next to it as registry.json.sig; the corresponding public
key is committed at keys/community-registry.pub for transparency, and the
same bytes are embedded in the Claudette binary. Claudette refuses any
registry whose signature does not verify — there is no fallback to unsigned
fetch.
The flow:
- PRs never have to update
registry.json.sig. Contributors don't have the secret key.validate.ymlwarns if the sig doesn't match a freshly changedregistry.json, but does not fail. - Push to
main:regen.ymlregeneratesregistry.json, signs it with the secret stored inCOMMUNITY_REGISTRY_MINISIGN_SECRET_KEY, locally verifies with the public key, and pushes both files directly back tomainif they drifted. The push uses[skip ci]and is gated on committer email so it cannot loop on itself. - Healthcheck:
health.ymlruns every 15 minutes and re-runsminisign -VagainstmainHEAD. If it ever fails — meaning regen itself broke or hasn't yet fired — it opens / updates a tracking issue. This is the safety net for the window between a contribution merge and regen completing.
Why direct push instead of a corrective PR? The corrective PR
historically sat unmerged for ~16 hours after a contribution merge,
during which every Claudette client saw a signature failure (the
binary fails closed on signature mismatch). The push is mechanical —
local minisign -V is the real safety check — and direct push closes
that window.
Anyone can verify locally:
minisign -V -p keys/community-registry.pub -m registry.json -x registry.json.sigDon't hand-edit registry.json.sig — let CI regenerate it. See keys/README.md
for key-rotation procedure.
revocations.jsonis fail-closed per TDD #567: Claudette treats an empty/missing/truncated file as a fetch failure, not "nothing revoked." Don't delete it or leave it blank when revoking — add an explicit entry.registry.schema.jsonpermitssource.type: "external"for plugins that live outside this repo. None exist yet, so themirrors/directory is also absent.mirror.yml(hourly cron) is a working stub that will fail loudly if an external plugin is added before the mirror logic is implemented — that's intentional, not a bug to "fix" by hiding the failure.
- Each contribution is a self-contained directory with the manifest + assets.
- Copy
_example/rather than starting from scratch. - Test the plugin/theme against a current Claudette dev build before submitting.
- Include a
README.mdinside your contribution directory describing what it does.
schemars-generated schemas — Claudette PR #2 (per TDD #567) will replace the hand-writtenmanifest.schema.json/registry.schema.jsonwith Rust-derived ones uploaded as a CI artifact.- Lua syntax linting —
validate.tsdoesn't run a Lua parser yet. Add when the first community SCM/env-provider plugin lands. - Author existence check via the GitHub API — soft-validation only today, deferred to avoid PAT/rate-limit complexity in CI.
- Per-version pinning at install time — registry surfaces only the latest version of each contribution in v1.
- Author signing (cosign / sigstore) — v2.
When in doubt, file an issue rather than guessing at conventions.