notabene setup (install · reconfigure · run)
Get a user from nothing (or from a config they want changed) to an open review server,
with zero npm/build commands typed by hand, on any repo. The renderer is fetched and
run through npx — nothing is installed into the user's repo; only data lives
there (notabene.config.mjs + the .notabene/ store + their docs).
Everything routes on doctor (read-only state) — you encode no defaults yourself.
This skill only sets things up; processing comments is the notabene skill (hand off
at the end).
The forwarder — how you call the tool
Never call npx notabene (unscoped — that package does not exist on npm) and never
hardcode a version. Always go through the plugin forwarder, which pins the right renderer:
node "${CLAUDE_PLUGIN_ROOT}/bin/nb.mjs" <subcommand> --root <repo-root> [flags…]
<repo-root> = the current project directory (the repo whose docs you're reviewing). Use
its absolute path. nb.mjs forwards to npx -y --prefer-offline @z29k/notabene@<pinned>.
Golden rules (no exceptions)
- Never commit / push / rewrite history without an explicit request. Tell the user to
commit the config +
.notabene/ store; don't do it for them. (Offering git init on a
non-repo is fine — that's not a commit.)
- Never expose the LAN (
host: true / --host) unless the user explicitly asks. It's
loopback-only by default; keep it that way.
- Never
rm -rf the store or regenerate a config from the template over an existing
one — those hold the user's real comments and settings.
- Confirm every config write/edit by showing the diff first — in express mode,
still print the config you wrote (show, don't ask); never write invisibly.
- Re-entrant & read-only-first:
doctor writes nothing, init is idempotent. Safe to
re-run at any point.
Step 0 — Preflight (always)
Run once and read the JSON:
node "${CLAUDE_PLUGIN_ROOT}/bin/nb.mjs" doctor --root <repo-root> --json
{ "error": "npx-unavailable" } → the fallback branch. npx isn't on PATH (Claude
Code's bundled Node isn't always exposed). Guide the user to install Node — nodejs.org,
their OS package manager, or nvm — then re-run Step 0. Don't proceed silently.
node.ok: false → their Node is older than the required ≥ 22.12. init may still
work but the server (Astro) will fail silently in the daemon log. Warn and point to the
Node requirement before proceeding — same install fixes as npx-unavailable.
- First run is slow (~30 s, ~100 MB): the first
npx fetches the renderer + Astro.
Say so before/while it runs, so the wait isn't mistaken for a hang.
- Then check
git.isRepo. If false, offer git init (the store is meant to be committed).
- On a configured repo the report also carries
protocol ({ present, version, current }
— the <store>/protocol.md copy) and agents ({ present, block } — the AGENTS.md
entry point). Anything missing or current: false → re-run init, it repairs both.
- Route on the config state:
config.exists: false → Branch A (fresh install).
config.exists: true, config.valid: true → Branch B (reconfigure).
config.exists: true, config.valid: false → Branch C (repair).
Branch A — fresh install (config.exists: false)
The report has no config/store/port blocks yet (there's no config to resolve) — it
has docs.detected instead.
Confirm the doc folders. Present docs.detected. Keep only folders that are
actually documentation; drop stray matches (a top-level README, CHANGELOG,
generated dirs). If docs.detected is empty, ask where the docs live (don't guess).
Choose the shape — confirm with the user:
roots[]: one entry per doc space, each { key (unique url slug), label, path, exclude }. Derive readable key/label from the paths; keep keys unique.
store: put it under the primary root (e.g. docs/.notabene, or <root>/.notabene
if there's no docs/). It gets committed.
format: commonmark by default (lighter, no MDX-safety traps). Write it
explicitly — the renderer's code default is mdx. Use mdx only if .mdx
files exist or the user asks.
Write notabene.config.mjs at the repo root — show the diff and confirm first.
Keep the safety default host: false. Shape:
// notabene.config.mjs — points the generic docs review tool at YOUR docs.
// Your data (docs, comments, journal) lives in your git, not in the tool.
export default {
siteName: "Docs",
tagline: "docs",
locale: "en", // UI language + nav sort collation
format: "commonmark", // "mdx" only if you have .mdx files
roots: [
{ key: "docs", label: "Docs", path: "docs", exclude: [".notabene/**"] },
],
store: "docs/.notabene", // comments + journal; commit it
port: 3009,
host: false, // true / --host exposes the LAN — trusted networks only
verify: [], // extra post-edit checks the review loop runs
review: "auto", // "approve" = you validate each edit (with a diff) at /review
// pdf: { enabled: true, pageSize: "A4", margin: "18mm" }, // PDF export (optional)
// publish: { site: "https://user.github.io", base: "/repo" }, // `build --public` target (optional)
// home: "docs/home.md", // custom landing page above the space cards (optional)
// branding: { logo: "assets/logo.svg", favicon: "assets/favicon.svg" }, // identity (optional)
// theme: { tokens: { accent: "#7c3aed" }, css: "docs/theme.css" }, // --nb-* overrides (optional)
// nav: { header: [{ label: "GitHub", href: "https://github.com/OWNER/REPO", icon: "github", iconOnly: true }] }, // outbound links (optional)
// editPattern: "https://github.com/OWNER/REPO/edit/main/{path}", // "Edit this page" footer link (builds/published sites; optional)
// edit: { enabled: true, requireGit: true }, // in-page editor under `dev` — on by default; requireGit:false writes untracked files (optional)
// author: "Alex", authorEmail: "alex@x.io", // comment identity default (else git user.name/.email)
};
Create the store: node "${CLAUDE_PLUGIN_ROOT}/bin/nb.mjs" init --root <repo-root>
(idempotent; reads store from the config you just wrote, makes the dir + meta.json).
It also writes the agent entry point: <store>/protocol.md (the full review
protocol, so any agent — not just Claude Code — can run the loop offline) and a bounded
<!-- notabene:begin -->…<!-- notabene:end --> block in the repo's AGENTS.md
(created if absent, appended otherwise; nothing outside the markers is touched).
Say what it wrote and offer --no-protocol / --no-agents-md if the user would
rather not have them. Remind the user to commit .notabene/ + AGENTS.md — but
don't commit them.
Check it's not ignored: git check-ignore <store>. A repo that ignores dotfolders
could silently exclude .notabene/ (the contract is that it's committed). Warn if so.
Go to Launch & handoff.
Branch B — reconfigure (config.exists: true, config.valid: true)
The user wants to change an existing setup. Do not re-run onboarding and do not
regenerate from the template.
- Restate the current config from the report (
config.roots/format/port/host/review/ store), then ask what to change.
- Edit in place (Edit tool) — leave untouched fields and comments alone, show the
diff, confirm. Common changes: add/remove/rename a
roots[] entry; format; port;
review (auto ↔ approve); siteName/tagline/locale; author/authorEmail; pdf
(PDF export); publish ({ site, base, exclude } — the notabene build --public
target: a read-only static site for public hosting; scope with roots[].publish: false
/ exclude globs / per-page frontmatter publish: false; the optional pagefind
dev dep upgrades search — dev app AND public artifact — to full-text); home (a
repo-relative
Markdown file — or per-locale map — rendered as the landing page above the space
cards); branding ({ logo, logoDark, favicon, socialImage } — repo-relative image
files); theme ({ tokens, css, assets, code, mermaid } — --nb-* design-token
overrides, validated, and/or a consumer stylesheet, a repo folder of fonts/images
served at /_nb/assets/…, and a Shiki code theme; only the --nb-* contract, never
internal variables); nav ({ header, sidebar, footer } — outbound links in the
topbar, a titled sidebar block and a site footer: one item shape
{ label, href, icon, iconOnly, publish }, labels may be per-locale maps, and
publish: false keeps a link out of public builds);
editPattern (a URL template with a literal {path} placeholder — renders the page
footer's "Edit this page" link, in builds and published sites only: under dev the
in-page editor supersedes it); edit ({ enabled, requireGit } — the in-page editor,
dev-only and on by default; requireGit: false lets it write a file git isn't
tracking, which is the only undo it has, so leave it on unless asked; per space,
roots[].edit: false makes that space read-only); verify[]; host (⚠ security —
only on explicit request).
- Surface the consequences — never silently (some edits orphan comments):
store moved → existing comments stay at the old path. Offer to move the store dir.
roots[].key renamed → changes URL slugs and the stored space/page prefix →
re-anchoring breaks. Warn; offer to migrate the affected page values, or advise
against it.
format mdx ↔ commonmark → different globbing + MDX strictness. Flag the
MDX-safety implications.
- Re-run
init after any config edit — it's idempotent and it refreshes the agent
entry point (<store>/protocol.md + the AGENTS.md block), which otherwise still
points at the old store path or space keys. doctor reports the drift
(protocol.current: false, agents.block: false).
- Most changes (port, roots, format, host…) take effect only after a restart. Go to
Launch & handoff and restart the server (see there).
Branch C — repair (config.exists: true, config.valid: false)
The config is present but failed to load. Do not overwrite it with the template.
- Show
config.error (from doctor) — it's the import/syntax failure.
- Read the file, fix the syntax (a stray comma, a bad quote, an accidental
import), and
re-run Step 0 to confirm it now resolves (valid: true), then continue on Branch B.
Non-interactive mode (power user)
Detect an express signal — invoked as /notabene:setup … --yes, or the user gives
explicit roots/format, or the phrasing is terse ("just set it up on docs/") — and cut
the confirmations: defaults-first, write the config (print it — show, don't ask),
init, launch, return the URL, no questions. Only stop on an undefaultable
ambiguity (e.g. no doc folder detected
and none given). Safety rails are never bypassed even with --yes: host, committing,
and rm stay explicit.
Launch & handoff
Launch a detached daemon. dev --detach self-selects a free port, starts a real
background server that survives this session, is idempotent (reuses a live one
instead of starting a second), and prints the URL itself once the port answers — so
run it as a normal Bash call (not run_in_background); it returns on its own:
node "${CLAUDE_PLUGIN_ROOT}/bin/nb.mjs" dev --detach --root <repo-root>
Pass --port <n> only to pin a specific port (e.g. port.suggested from doctor); to
persist a port, edit the config instead. The first run also does the ~30 s renderer
fetch, so this call can take a bit — say so. Give the user the http://localhost:<port>
URL from its output.
Lifecycle (managed across sessions):
node "${CLAUDE_PLUGIN_ROOT}/bin/nb.mjs" status --json → { running, pid, port, url }
(or /notabene:status).
node "${CLAUDE_PLUGIN_ROOT}/bin/nb.mjs" stop → stop it (or /notabene:stop).
On Branch B, most changes take effect only after a restart: stop then
dev --detach. Confirm the current state first with status.
Hand off. Explain the loop in one line — leave comments on the rendered docs; the
agent reads them, edits, journals, verifies — and point to the notabene skill (or
/notabene:review) for processing those comments. If doctor reported
store.openComments > 0 (typically a Branch B reconfigure of a populated repo), say so —
N comment(s) already waiting; run /notabene:review. Setup's job ends here.
Fallback — npx unavailable
If Step 0 returns npx-unavailable, notabene can't run yet. Explain plainly: notabene runs
via npx, which comes with Node, and Node isn't on PATH. Offer the concrete fixes
(nodejs.org installer / OS package manager / nvm install --lts), then re-run Step 0. Don't
attempt any renderer command until doctor succeeds.
1---2name: notabene-setup3description: Install, configure, OR reconfigure notabene (the docs review tool) and start its review server — for a user who drives everything through the agent, on any stack (Rust/Python/Go/JS, no toolchain needed). Use to GET STARTED / LAUNCH: "set up notabene", "install notabene", "set up docs review/commenting", "start the review server", "open the review UI", "I want to leave comments on / review my docs", "get notabene running". Use to RECONFIGURE (a notabene.config.mjs already exists): "add/remove/rename a docs folder", "move the store", "switch review to approve/validation mode", "change notabene's port", "rename the site", "change the language", "add a verify step", "expose it on the LAN". This skill does NOT act on existing comments — processing review feedback is the `notabene` skill, which it hands off to once the server is up. Writes/edits notabene.config.mjs (shown first), creates the store, launches the server. Never commits; never exposes the LAN without an explicit request.4---56# notabene setup (install · reconfigure · run)78Get a user from nothing (or from a config they want changed) to an open review server,9**with zero npm/build commands typed by hand**, on any repo. The renderer is fetched and10run **through `npx`** — nothing is installed into the user's repo; only **data** lives11there (`notabene.config.mjs` + the `.notabene/` store + their docs).1213Everything routes on **`doctor`** (read-only state) — you encode **no** defaults yourself.14This skill only sets things up; **processing comments is the `notabene` skill** (hand off15at the end).1617## The forwarder — how you call the tool1819Never call `npx notabene` (unscoped — that package does not exist on npm) and never20hardcode a version. Always go through the plugin forwarder, which pins the right renderer:2122```bash23node "${CLAUDE_PLUGIN_ROOT}/bin/nb.mjs" <subcommand> --root <repo-root> [flags…]24```2526`<repo-root>` = the current project directory (the repo whose docs you're reviewing). Use27its absolute path. `nb.mjs` forwards to `npx -y --prefer-offline @z29k/notabene@<pinned>`.2829## Golden rules (no exceptions)3031- **Never commit / push / rewrite history** without an explicit request. Tell the user to32 commit the config + `.notabene/` store; don't do it for them. (Offering `git init` on a33 non-repo is fine — that's not a commit.)34- **Never expose the LAN** (`host: true` / `--host`) unless the user explicitly asks. It's35 loopback-only by default; keep it that way.36- **Never `rm -rf` the store** or regenerate a config from the template over an existing37 one — those hold the user's real comments and settings.38- **Confirm every config write/edit** by showing the diff first — in **express mode**,39 still **print** the config you wrote (show, don't ask); never write invisibly.40- **Re-entrant & read-only-first**: `doctor` writes nothing, `init` is idempotent. Safe to41 re-run at any point.4243## Step 0 — Preflight (always)4445Run once and read the JSON:4647```bash48node "${CLAUDE_PLUGIN_ROOT}/bin/nb.mjs" doctor --root <repo-root> --json49```5051- **`{ "error": "npx-unavailable" }`** → the fallback branch. `npx` isn't on PATH (Claude52 Code's bundled Node isn't always exposed). Guide the user to install Node — nodejs.org,53 their OS package manager, or `nvm` — then re-run Step 0. Don't proceed silently.54- **`node.ok: false`** → their Node is older than the required ≥ 22.12. `init` may still55 work but the server (Astro) will fail silently in the daemon log. Warn and point to the56 Node requirement **before proceeding** — same install fixes as `npx-unavailable`.57- **First run is slow (~30 s, ~100 MB)**: the first `npx` fetches the renderer + Astro.58 **Say so** before/while it runs, so the wait isn't mistaken for a hang.59- Then check `git.isRepo`. If false, offer `git init` (the store is meant to be committed).60- On a configured repo the report also carries **`protocol`** (`{ present, version, current }`61 — the `<store>/protocol.md` copy) and **`agents`** (`{ present, block }` — the `AGENTS.md`62 entry point). Anything missing or `current: false` → re-run `init`, it repairs both.63- **Route on the config state**:64 - `config.exists: false` → **Branch A** (fresh install).65 - `config.exists: true, config.valid: true` → **Branch B** (reconfigure).66 - `config.exists: true, config.valid: false` → **Branch C** (repair).6768## Branch A — fresh install (`config.exists: false`)6970The report has no `config`/`store`/`port` blocks yet (there's no config to resolve) — it71has **`docs.detected`** instead.72731. **Confirm the doc folders.** Present `docs.detected`. Keep only folders that are74 *actually* documentation; drop stray matches (a top-level `README`, `CHANGELOG`,75 generated dirs). If `docs.detected` is empty, ask where the docs live (don't guess).762. **Choose the shape** — confirm with the user:77 - `roots[]`: one entry per doc space, each `{ key (unique url slug), label, path,78 exclude }`. Derive readable `key`/`label` from the paths; keep `key`s unique.79 - `store`: put it under the primary root (e.g. `docs/.notabene`, or `<root>/.notabene`80 if there's no `docs/`). It gets committed.81 - `format`: **`commonmark`** by default (lighter, no MDX-safety traps). Write it82 **explicitly** — the renderer's *code* default is `mdx`. Use `mdx` only if `.mdx`83 files exist or the user asks.843. **Write `notabene.config.mjs`** at the repo root — **show the diff and confirm first**.85 Keep the safety default `host: false`. Shape:8687 ```js88 // notabene.config.mjs — points the generic docs review tool at YOUR docs.89 // Your data (docs, comments, journal) lives in your git, not in the tool.90 export default {91 siteName: "Docs",92 tagline: "docs",93 locale: "en", // UI language + nav sort collation94 format: "commonmark", // "mdx" only if you have .mdx files95 roots: [96 { key: "docs", label: "Docs", path: "docs", exclude: [".notabene/**"] },97 ],98 store: "docs/.notabene", // comments + journal; commit it99 port: 3009,100 host: false, // true / --host exposes the LAN — trusted networks only101 verify: [], // extra post-edit checks the review loop runs102 review: "auto", // "approve" = you validate each edit (with a diff) at /review103 // pdf: { enabled: true, pageSize: "A4", margin: "18mm" }, // PDF export (optional)104 // publish: { site: "https://user.github.io", base: "/repo" }, // `build --public` target (optional)105 // home: "docs/home.md", // custom landing page above the space cards (optional)106 // branding: { logo: "assets/logo.svg", favicon: "assets/favicon.svg" }, // identity (optional)107 // theme: { tokens: { accent: "#7c3aed" }, css: "docs/theme.css" }, // --nb-* overrides (optional)108 // nav: { header: [{ label: "GitHub", href: "https://github.com/OWNER/REPO", icon: "github", iconOnly: true }] }, // outbound links (optional)109 // editPattern: "https://github.com/OWNER/REPO/edit/main/{path}", // "Edit this page" footer link (builds/published sites; optional)110 // edit: { enabled: true, requireGit: true }, // in-page editor under `dev` — on by default; requireGit:false writes untracked files (optional)111 // author: "Alex", authorEmail: "alex@x.io", // comment identity default (else git user.name/.email)112 };113 ```1141154. **Create the store**: `node "${CLAUDE_PLUGIN_ROOT}/bin/nb.mjs" init --root <repo-root>`116 (idempotent; reads `store` from the config you just wrote, makes the dir + `meta.json`).117 It also writes the **agent entry point**: `<store>/protocol.md` (the full review118 protocol, so any agent — not just Claude Code — can run the loop offline) and a bounded119 `<!-- notabene:begin -->…<!-- notabene:end -->` block in the repo's **`AGENTS.md`**120 (created if absent, appended otherwise; nothing outside the markers is touched).121 **Say what it wrote** and offer `--no-protocol` / `--no-agents-md` if the user would122 rather not have them. Remind the user to **commit `.notabene/` + `AGENTS.md`** — but123 don't commit them.1245. **Check it's not ignored**: `git check-ignore <store>`. A repo that ignores dotfolders125 could silently exclude `.notabene/` (the contract is that it's committed). Warn if so.1266. Go to **Launch & handoff**.127128## Branch B — reconfigure (`config.exists: true, config.valid: true`)129130The user wants to change an existing setup. **Do not** re-run onboarding and **do not**131regenerate from the template.1321331. **Restate the current config** from the report (`config.roots/format/port/host/review/134 store`), then ask **what to change**.1352. **Edit in place** (Edit tool) — leave untouched fields and comments alone, **show the136 diff, confirm**. Common changes: add/remove/rename a `roots[]` entry; `format`; `port`;137 `review` (auto ↔ approve); `siteName`/`tagline`/`locale`; `author`/`authorEmail`; `pdf`138 (PDF export); `publish` (`{ site, base, exclude }` — the `notabene build --public`139 target: a read-only static site for public hosting; scope with `roots[].publish: false`140 / `exclude` globs / per-page frontmatter `publish: false`; the optional `pagefind`141 dev dep upgrades search — dev app AND public artifact — to full-text); `home` (a142 repo-relative143 Markdown file — or per-locale map — rendered as the landing page above the space144 cards); `branding` (`{ logo, logoDark, favicon, socialImage }` — repo-relative image145 files); `theme` (`{ tokens, css, assets, code, mermaid }` — `--nb-*` design-token146 overrides, validated, and/or a consumer stylesheet, a repo folder of fonts/images147 served at `/_nb/assets/…`, and a Shiki code theme; only the `--nb-*` contract, never148 internal variables); `nav` (`{ header, sidebar, footer }` — outbound links in the149 topbar, a titled sidebar block and a site footer: one item shape150 `{ label, href, icon, iconOnly, publish }`, labels may be per-locale maps, and151 `publish: false` keeps a link out of public builds);152 `editPattern` (a URL template with a literal `{path}` placeholder — renders the page153 footer's "Edit this page" link, in builds and published sites only: under `dev` the154 in-page editor supersedes it); `edit` (`{ enabled, requireGit }` — the in-page editor,155 dev-only and on by default; `requireGit: false` lets it write a file git isn't156 tracking, which is the only undo it has, so leave it on unless asked; per space,157 `roots[].edit: false` makes that space read-only); `verify[]`; `host` (⚠ security —158 only on explicit request).1593. **Surface the consequences — never silently** (some edits orphan comments):160 - **`store` moved** → existing comments stay at the old path. Offer to move the store dir.161 - **`roots[].key` renamed** → changes URL slugs and the stored `space`/`page` prefix →162 **re-anchoring breaks**. Warn; offer to migrate the affected `page` values, or advise163 against it.164 - **`format` mdx ↔ commonmark** → different globbing + MDX strictness. Flag the165 MDX-safety implications.1664. **Re-run `init`** after any config edit — it's idempotent and it **refreshes the agent167 entry point** (`<store>/protocol.md` + the `AGENTS.md` block), which otherwise still168 points at the old store path or space keys. `doctor` reports the drift169 (`protocol.current: false`, `agents.block: false`).1705. Most changes (port, roots, format, host…) take effect **only after a restart**. Go to171 **Launch & handoff** and restart the server (see there).172173## Branch C — repair (`config.exists: true, config.valid: false`)174175The config is present but failed to load. **Do not** overwrite it with the template.1761771. Show `config.error` (from `doctor`) — it's the import/syntax failure.1782. Read the file, fix the syntax (a stray comma, a bad quote, an accidental `import`), and179 **re-run Step 0** to confirm it now resolves (`valid: true`), then continue on Branch B.180181## Non-interactive mode (power user)182183Detect an **express** signal — invoked as `/notabene:setup … --yes`, or the user gives184explicit roots/format, or the phrasing is terse ("just set it up on docs/") — and **cut185the confirmations**: defaults-first, write the config (**print it** — show, don't ask),186`init`, launch, return the URL, **no questions**. Only stop on an **undefaultable187ambiguity** (e.g. no doc folder detected188and none given). Safety rails are **never** bypassed even with `--yes`: `host`, committing,189and `rm` stay explicit.190191## Launch & handoff1921931. **Launch a detached daemon.** `dev --detach` **self-selects a free port**, starts a real194 background server that **survives this session**, is **idempotent** (reuses a live one195 instead of starting a second), and **prints the URL itself** once the port answers — so196 run it as a normal Bash call (not `run_in_background`); it returns on its own:197198 ```bash199 node "${CLAUDE_PLUGIN_ROOT}/bin/nb.mjs" dev --detach --root <repo-root>200 ```201202 Pass `--port <n>` only to pin a specific port (e.g. `port.suggested` from `doctor`); to203 **persist** a port, edit the config instead. The first run also does the ~30 s renderer204 fetch, so this call can take a bit — say so. Give the user the `http://localhost:<port>`205 URL from its output.2062. **Lifecycle** (managed across sessions):207 - `node "${CLAUDE_PLUGIN_ROOT}/bin/nb.mjs" status --json` → `{ running, pid, port, url }`208 (or `/notabene:status`).209 - `node "${CLAUDE_PLUGIN_ROOT}/bin/nb.mjs" stop` → stop it (or `/notabene:stop`).2103. **On Branch B**, most changes take effect only after a restart: **`stop` then211 `dev --detach`**. Confirm the current state first with `status`.2124. **Hand off.** Explain the loop in one line — *leave comments on the rendered docs; the213 agent reads them, edits, journals, verifies* — and point to the **`notabene`** skill (or214 `/notabene:review`) for processing those comments. If `doctor` reported215 `store.openComments > 0` (typically a Branch B reconfigure of a populated repo), say so —216 *N comment(s) already waiting; run `/notabene:review`.* Setup's job ends here.217218## Fallback — `npx` unavailable219220If Step 0 returns `npx-unavailable`, notabene can't run yet. Explain plainly: notabene runs221via `npx`, which comes with Node, and Node isn't on PATH. Offer the concrete fixes222(nodejs.org installer / OS package manager / `nvm install --lts`), then re-run Step 0. Don't223attempt any renderer command until `doctor` succeeds.