Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,18 @@
"source": "./plugins/image-peek",
"description": "Preview pasted images at their prompt markers on macOS. Ghostty, plus experimental iTerm2 and Herdr support with a startup override. Reach L2: local clipboard capture and temporary files, no model or network calls.",
"category": "development",
"tags": ["mods", "images", "clipboard", "ghostty", "macos"]
"tags": [
"mods",
"images",
"clipboard",
"ghostty",
"macos"
]
},
{
"name": "mod-builder",
"source": "./plugins/mod-builder",
"description": "A skill that plans, writes, validates and reviews a Claude Mod. Every idea must justify each process, file, network, model or UI capability it requests: plan first, smallest $ surface, validator output pasted, a five-line threat model. A brainstorm mode checks ideas against every mod already on GitHub.",
"description": "A skill that plans, writes, validates, proves, reviews, migrates and publishes Claude Code mods. Plan first with the smallest $ surface, a footprint diffed against the plan, a test seen failing, an isolated load that leaves evidence files, a five-line threat model, and a handoff that names what was and was not proven. Brainstorm mode checks ideas against every mod already on GitHub.",
"category": "development",
"tags": [
"mods",
Expand Down
23 changes: 23 additions & 0 deletions .github/workflows/mod-builder.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: mod-builder scripts

on:
push:
paths:
- 'plugins/mod-builder/**'
- '.github/workflows/mod-builder.yml'
pull_request:
paths:
- 'plugins/mod-builder/**'
- '.github/workflows/mod-builder.yml'

jobs:
offline:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
# The live section of the suite needs a real `claude` at or above the floor;
# on this runner it prints a skip line and the offline section still has to pass.
- run: node --test 'plugins/mod-builder/skills/mod-builder/tests/*.test.mjs'
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# written by /plugin-types inside a plugin folder; version-specific, never committed
# Claude Code writes a plugin's type declarations into .claude-plugin/types/ on every load;
# that folder carries its own .gitignore, but a stray copy from an older build layout is excluded too.
.claude-plugin/types/
.claude/types/
node_modules/
.DS_Store
27 changes: 14 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Claude Mods and the tools to build them. Four plugins install from this marketpl
- [image-peek](plugins/image-peek): move the cursor onto a pasted image marker for a large preview pane, with an inline fallback in narrow windows. macOS and Ghostty, with experimental iTerm2 and Herdr support via a startup override.
- [fable-pin](plugins/fable-pin): pin subagents to one model.
- [cache-tax](https://github.com/karanb192/cache-tax): price cold sends and keep the prompt cache warm for a window you set.
- [mod-builder](plugins/mod-builder): plan a mod's capabilities, write it, and validate its footprint.
- [mod-builder](plugins/mod-builder): plan a mod's capabilities, write it, diff its footprint against the plan, prove it loads in an isolated run, and hand over with evidence.

Each mod's README includes validator output and a threat model. The [landing page](https://claude-code-mods.karanbansal.in/) lists the plugins.

Expand All @@ -25,19 +25,19 @@ a session, or type `/mod-builder`.

## What a mod is

A Claude Mod is a Claude Code plugin whose `hooks/hooks.json` names a TypeScript module. The module exports `register(on)`, and each hook is `on("event", matcher, async ($, e, next) => result)`. `$` is the engine interface, an object of nouns with verbs on each (`$.ui.log`, `$.fs.write`, `$.process.run`, `$.http.fetch`, `$.model.classify`, `$.agent.spawn`). `e` is the event. `next(e)` runs every hook beneath and then the engine, and returning without `next` answers in the engine's place. The code runs inside Claude Code's own process, with the process's reach, and costs no tokens unless it calls the model.
A Claude Mod is a Claude Code plugin whose `hooks/hooks.json` names a JavaScript or TypeScript hooks module. The module exports `register(on, options)`, and each hook is `on("event", matcher, async ($, e, next) => result)`. `$` is the engine interface, an object of nouns with verbs on each (`$.ui.log`, `$.fs.write`, `$.process.run`, `$.http.fetch`, `$.model.classify`, `$.agent.spawn`). `e` is the event. `next(e)` runs every hook beneath and then the engine, and returning without `next` answers in the engine's place. The code runs inside Claude Code's own process, with the process's reach, and costs no tokens unless it calls the model.

Mods require Claude Code 2.1.287 or later and are enabled by default. The old `CLAUDE_CODE_ENABLE_FUNCTION_HOOKS` flag is ignored by these versions. See [Anthropic's mod documentation](https://code.claude.com/docs/en/plugins/mods/overview#turn-mods-on-or-off).

## Why a budget

Every `$` call is spelled out in full, so `claude plugin validate` can print what a mod will touch before any of its code runs. That printout is the mod's footprint. The skill treats it as a budget. Every idea must justify each process, file, network, model or UI capability it requests, and the plan states the resulting reach level before code exists. After the validator runs, the printed `calls:` line has to match the plan. A call that is not in the plan gets removed, or the reason it stays gets written down. Nothing widens silently.

Reach levels come from the scanner behind https://github.com/karanb192/awesome-claude-code-mods, which grades every mod on GitHub nightly. L0 draws and remembers. L1 reads files, settings, environment or the transcript. L2 writes files, runs processes or drives Claude. L3 reaches the network. The post that explains the scoreboard is https://karanbansal.in/blog/claude-mods-scoreboard/.
Reach levels come from the scanner behind https://github.com/karanb192/awesome-claude-code-mods, which grades every mod on GitHub nightly. L0 draws and remembers. L1 reads files, settings, environment or the transcript. L2 writes files, runs processes or drives Claude. L3 reaches the network. The scoreboard is https://mods.aidojo.si/ and the post that explains it is https://karanbansal.in/blog/claude-mods-scoreboard/.

## What the skill asks and produces

Build mode asks one thing before code, which events the mod must observe and what it must be able to do, then shows the surface and the reach level and waits for a yes. It writes `.claude-plugin/plugin.json`, `hooks/hooks.json` and `hooks/register.ts`, adds a tsconfig and a surface module only if the mod draws, runs the validator, pastes the output, diffs the calls against the plan, and ends with a five-line threat model: what it reads, what it runs, what it sends, what it persists, and what happens when its input is hostile.
Build mode starts with a gate: it checks the installed Claude Code can load mods, generates this build's type declarations with a headless load, and reports whether the skill's own references have drifted from them. Then it asks one thing before code, which events the mod must observe and what it must be able to do, shows the surface and the reach level, and waits for a yes. Every event and method in the plan is grepped in the generated types before a line is written. It writes `.claude-plugin/plugin.json`, `hooks/hooks.json`, a typed `hooks/register.ts`, one test on the official kit, a README and a DECISIONS file; runs the validator and diffs every call, env name and state key against the plan; requires the test to be seen failing once; proves the mod in an isolated child process with its own config dir (validate, load, typecheck, test, command, and on request an interactive terminal stage and an install smoke test), each stage leaving an evidence file; and ends with a five-line threat model and a handoff whose status words come only from the script.

Brainstorm mode starts from a workflow problem, fetches the nightly scan of every mod on GitHub, and proposes 10 to 20 ideas. Each carries its trigger event, the benefit, the `$` calls, the reach level, the privacy risk in one line, whether it belongs in a mod, a shell hook or an external tool, and the mod that already does it if one exists. The list is ranked by benefit per unit of reach and ends with one pick.

Expand Down Expand Up @@ -91,20 +91,21 @@ Threat model for hello-mod (reach L0, draws and remembers)
5. Hostile input: a crafted Bash command can only match or not match one literal string; nothing from e reaches a process, a file or the network
```

Load it for one session with `claude --plugin-dir .`. The same mod, originally validated on Claude Code 2.1.272, lives at https://github.com/karanb192/awesome-claude-code-mods/tree/main/examples/hello-mod.
Load it for one session with `claude --plugin-dir .`. The same mod lives at https://github.com/karanb192/awesome-claude-code-mods/tree/main/examples/hello-mod.

## What is in the skill

- `SKILL.md`, the pipeline, under 300 lines.
- `references/events.md`, every engine event with payload and result, derived from Anthropic's cheat sheet and architecture paper.
- `references/nouns.md`, every `$` noun and verb tagged with its reach level, the same rules the scanner uses.
- `references/gotchas.md`, what people who shipped mods put in their READMEs, each with the URL.
- `references/threat-model.md`, the five-line template with worked examples.
- `references/templates.md`, the file shapes that validate as written.
- `references/reading.md`, every link.
- `scripts/footprint.mjs`, runs the validator, grades reach, diffs calls against the plan, exits 1 on a widened footprint.
- `SKILL.md`, the pipeline, under 200 lines; the only file loaded when the skill triggers.
- `references/`, one file per step: plan, build, events, nouns, ui-and-state, limits, testing, proof, threat-model, composing, workflows, migrate, sources, invitation. Each restated fact carries a stamp naming its source, the build it was checked on and what would make it stale. Shapes are never restated; the references give the grep into the generated types.
- `data/api-map.json`, the baseline of every event, method, element, site and limit extracted from the build the skill was verified on; `data/api-assertions.json`, value-level checks; `data/reach-rules.json`, the grader.
- `scripts/gate.mjs`, step 0: version floor, can mods load, the types for this build, drift against the baseline, toolchain state.
- `scripts/footprint.mjs`, runs the validator, grades reach, diffs calls, env names and state keys against the plan, exits 1 on a widened footprint or an ungraded call.
- `scripts/api-check.mjs`, reads the running build's types and reports every name in the references that no longer exists, plus what the build added since the baseline.
- `scripts/prove.mjs`, the isolated harness: copies the mod, drives a child `claude` under its own config dir, writes one evidence file per stage and a status block the skill pastes unchanged.
- `scripts/list-mods.mjs`, fetches the nightly scan and filters it by keyword.
- `scripts/star-invitation.mjs`, records the optional invitation before asking.
- `assets/probe-mod/`, the tiny mod the gate loads to make the engine write the types.
- `tests/`, `node --test` suites: offline against a stub `claude`, live when a real one is on PATH.

## Optional invitation

Expand Down
15 changes: 1 addition & 14 deletions plugins/fable-pin/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,14 +1 @@
{
"compilerOptions": {
"target": "es2023",
"lib": ["es2023"],
"types": [],
"module": "esnext",
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"strict": true,
"noEmit": true,
"skipLibCheck": true
},
"include": [".claude/types", "hooks", "tests"]
}
{ "extends": "./.claude-plugin/types/tsconfig.json" }
1 change: 0 additions & 1 deletion plugins/image-peek/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
.claude-plugin/types/
.claude/types/
node_modules/
4 changes: 2 additions & 2 deletions plugins/mod-builder/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "mod-builder",
"displayName": "Mod Builder",
"version": "0.2.3",
"description": "A skill that explains, finds, plans, writes, migrates, validates, reviews, debugs and publishes Claude Mods. Plan first, smallest $ surface, validator output pasted, a typed test on the official kit, a five-line threat model. Brainstorm mode checks ideas against every mod already on GitHub.",
"version": "1.0.0",
"description": "A skill that plans, writes, validates, proves, reviews, migrates and publishes Claude Code mods. Plan first with the smallest $ surface, a footprint diffed against the plan, a test seen failing, an isolated load that leaves evidence files, a five-line threat model, and a handoff that names what was and was not proven. Brainstorm mode checks ideas against every mod already on GitHub.",
"skills": [
"./skills/mod-builder"
],
Expand Down
80 changes: 80 additions & 0 deletions plugins/mod-builder/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# mod-builder

A Claude Code skill that plans, writes, validates, proves, reviews, migrates and publishes mods: plugins whose `hooks/hooks.json` names a hooks module exporting `register(on, options)`. Every build ends with evidence files from an isolated load and a handoff that says what was proven and what was not.

## Why it exists next to the built-in skill

Claude Code ships a built-in `plugin-authoring` skill. It tells Claude what a mod is, where the exact type declarations for your build are written, and how to run a mod from a folder, and it writes the mod into a session folder that loads live. Use it to try an idea in the session you are in. mod-builder is for a mod you want to keep or ship. It reads the same generated types for every API shape and does not repeat them. What it adds is the process around the code: a gate that checks your build can load mods at all; a plan that lists every `$` call and names the reach level before code exists; a footprint check that compares `claude plugin validate` with that plan and stops on anything extra; a test that was seen failing before it passed; an isolated harness that loads the mod in a separate Claude Code process and keeps the evidence (the engine's own load and call lines, a typecheck against your build's types, the test run, and a scripted terminal session when the mod draws); a five-line threat model written from the validator's output; a DECISIONS file that lists what was not verified; and a handoff whose status words cannot say a stage passed when it did not run. It also checks itself: `api-check.mjs` reads your build's declarations and reports which names and facts in the skill no longer match, so the skill says "the types win here" instead of being quietly wrong.

## Install

```sh
claude plugin marketplace add karanb192/claude-code-mods
claude plugin install mod-builder@claude-code-mods
```

Run `/reload-plugins` in a session that is already open. Then ask for what you want: "build a mod that ...", "find a mod for ...", "brainstorm mods", "review my mod", "debug my mod", "prove my mod", "migrate my mod", "publish my mod", or type `/mod-builder`.

Requirements: Claude Code 2.1.287 or later (mods are on by default; no flag is needed), Node 18 or later, and TypeScript for the typecheck stage (a global `tsc`, or the skill runs `npx -y -p typescript tsc`). tmux is optional and only needed to drive a mod that draws.

## What a build hands back

The mod's files in the folder you name (or `./<name>/`), never in Claude Code's session folder unless you ask for a live try. Then, in order: the plan, the gate lines, the validator footprint and its diff against the plan, the shapes the code relies on, the proof status block with its run folder, the test result with "seen failing", the threat model, a DECISIONS summary, how to run and keep the mod, and a stamp naming the Claude Code build it was proven on. The skill never installs, enables or copies the mod anywhere itself.

## Stages that need a login

The proof harness runs a separate Claude Code with its own config folder, so it never writes to your real `~/.claude` (it only reads it to check that nothing changed). One stage, and sometimes a second, needs that separate config to be logged in once.

| Stage | Needs a login | Needs tmux |
|---|---|---|
| validate (footprint against the plan) | no | no |
| load (headless load, debug log) | no | no |
| typecheck (against the generated types) | no | no |
| test (`claude plugin test`) | no | no |
| command (runs the mod's slash command headlessly) | only when the command's hook reaches the model | no |
| interactive (scripted terminal session, screen captures) | yes | yes |
| install-smoke (installs through a temporary marketplace) | no | no |
| isolation (checks nothing outside the run folder changed) | no | no |

To log the harness in once:

```sh
CLAUDE_CONFIG_DIR=~/.cache/mod-builder/config claude auth login
```

Without it, the interactive stage reports `unverified (harness home not logged in)`, and a command whose hook reaches the model reports `unverified (harness home not logged in: ...)`. Neither reports a pass it did not earn.

## The harness home

Everything the scripts write lives in `~/.cache/mod-builder/`, or in `$MOD_BUILDER_HOME` if you set it. The folder is created with owner-only permissions, and the scripts refuse to use it if it is a symlink, owned by someone else, or readable by others.

- `config/`: the separate Claude Code config the harness runs with (and its login, if you made one).
- `types/<version>/`: the declarations for each Claude Code build, generated once by loading a tiny probe mod.
- `probe/`: copies of that probe mod.
- `runs/<timestamp>-<name>/`: one folder per proof run, with a copy of the mod, `evidence/`, `status.txt` and `run.json`.
- `strikes.json`: repeated-failure counts, so a stage that fails three times the same way stops the run.

To remove it, log the harness out if you logged it in, then delete the folder:

```sh
CLAUDE_CONFIG_DIR=~/.cache/mod-builder/config claude auth logout
rm -rf ~/.cache/mod-builder
```

Nothing else on your machine depends on it. The next run recreates it.

## What is in the skill

- `skills/mod-builder/SKILL.md`: the modes, the gate, the build pipeline, the handoff contract and the forbidden claims. It is the only file loaded when the skill triggers.
- `skills/mod-builder/references/`: one file per step or topic (plan, build, events, nouns, ui-and-state, limits, testing, proof, threat-model, composing, workflows, migrate, sources, invitation), each read only when its step needs it.
- `skills/mod-builder/scripts/`: `gate.mjs` (can this build load mods, where are the types), `footprint.mjs` (validator output graded and diffed against the plan), `prove.mjs` (the isolated harness), `api-check.mjs` (the skill's names and facts against your build's types; `--mod` lists early-access spellings in a mod), `list-mods.mjs` (the nightly catalogue of every mod on GitHub), `star-invitation.mjs` (records the optional invitation below).
- `skills/mod-builder/data/`: the baseline API map, value checks over the declarations, and the reach rules.
- `skills/mod-builder/tests/`: `node --test` self-tests for the scripts, offline with a stub `claude` and live when a real one is on PATH.

## Optional invitation

After a useful result, the skill may offer one optional star invitation. It records the offer in `~/.cache/claude-code-mods/star-invitation.json` (or under `XDG_CACHE_HOME`) before asking, so later conversations skip it. Clearing the cache or using another machine can reset the record. Starring through the GitHub CLI needs an explicit yes. If the helper cannot run or write its record, the skill skips the invitation.

## License

MIT.
Loading
Loading