Borrowing it
Nothing to install: this file belongs to nicoverbruggen/kobopatch-webui. Take a copy, put it at the same path in your own repository, and replace the rules that are about this project with yours.
curl -O https://raw.githubusercontent.com/nicoverbruggen/kobopatch-webui/main/AGENTS.mdgit clone --depth 1 https://github.com/nicoverbruggen/kobopatch-webuiWrote this? Show the measurements
A badge with what this costs and how it scanned, read live from this page, so it follows the numbers instead of freezing them. Markdown for a README, HTML for a documentation site or a project page.
[](https://agentmods.dev/instructions/nicoverbruggen/kobopatch-webui/agents-md)<a href="https://agentmods.dev/instructions/nicoverbruggen/kobopatch-webui/agents-md"><img src="https://agentmods.dev/badge/instructions/nicoverbruggen/kobopatch-webui/agents-md.svg" alt="Measured on agentmods" height="20"></a>What it costs to keep this loaded
Counted locally with the o200k_base tokenizer, which is exact for GPT models; Claude uses its own tokenizer and its counts differ. Treat this as one consistent yardstick across the catalogue rather than a bill. Prices are per million input tokens.
| Model | Per session | Once invoked |
|---|---|---|
| Fable 5.1 | $0.06344 | $0.06344 |
| Opus 5 | $0.03172 | $0.03172 |
| Sonnet 5 | $0.01269 | $0.01269 |
| Haiku 4.5 | $0.00634 | $0.00634 |
Grade A, and why
kobopatch-webui AGENTS.md scanned grade A with 0 findings against 26 rules in 11 categories — prompt injection, anti-refusal, data exfiltration, privilege escalation, supply chain, agent snooping, system-prompt leakage, SSRF and excessive agency — measured 8d ago.
A static scan of the body, not an audit. Every finding is printed with the line that produced it so you can judge whether it matters here. A mod is markdown that instructs an agent; that is exactly why what it instructs is worth reading.
Nothing flagged
None of the 26 patterns this scan looks for appear in this file: no shell pipes, no recursive deletes, no credential paths, no hidden text, no instruction-override or anti-refusal phrasing, no agent-config snooping. That is not a guarantee, it is the absence of the things that are checkable.
How it starts
The opening of the file, as written. The whole thing — 73 lines — stays where its author put it; the contents beside it link to each section on GitHub.
AGENTS.md
Guidance for agents working in this repository. For the full maintainer notes, see PROJECT.md.
Project shape
- This is a static web app for customising Kobo e-readers. It can write directly to a connected Kobo through the File System Access API, so changes around device writes should be treated as high risk.
src/js/app.jsis the orchestrator and stays thin: it assembles the sharedSession(wizard state) plus the long-lived services, kicks off the async resource loads, wires the flows/shell screens together, and boots the wizard. Per-step behavior does not live here. The front of the wizard is itself split into flows —flows/connect-flow.js(browser-support detection, direct device connection, device-info display, restore shortcut),flows/manual-flow.js(manual version/model selection),flows/mode-flow.js(patches-vs-NickelMenu selection, owns the sharedstate.goToModeSelection) — and the shared error step plus global error handling live inshell/error-screen.js, while the modal dialogs, mobile warning, environment pill, and preview banner live inshell/global-ui.js.src/js/flows/contains the user journeys. Keep flow-specific behavior inside the relevant flow file. Cross-flow navigation goes throughstate.*callbacks (state.goToModeSelection,state.showError,state.goBackToDeviceStep,state.goToManualVersionStep); direct flow APIs are injected explicitly byapp.jswhere one flow drives another. A flow declares its steps to the step machine (shell/step-machine.jscreateFlow) and reaches its build→write/download tail through the shared terminal (shell/terminal.jscreateTerminal); do not hand-assemblesetNavStep/setNavLabels/showStepcalls or reimplement the feedback/ZIP/device-write+audit tail inside a flow.src/js/nickelmenu/contains NickelMenu domain logic. Installer code belongs ininstaller.js, removal code belongs inuninstaller.js, feature modules belong infeatures/, the device-domain reads the flow needs (existing-install / preset-conflict / legacy-items / optional-cleanup / Kobo-user-count probes) belong inprobes.js, the menu-icon customization dialog plus its image processing (canvas resize, SVG→PNG) belong incustomization-dialog.js, and the sectioned feature/cleanup checkbox-list rendering belongs incheckbox-list.js(renderNmCheckboxList— the flow builds the item descriptors, this renders them) — not in the flow file.- Keep a feature's behavior and logic colocated in its
features/<feature>/module. A feature owns everything specific to it — generated files (install), the Toggle-menu entries it contributes (menuItems), KoboRoot.tgz payload it merges in (koboRootEntries), output adjustments such as device-conditional changes (postProcess), declarativeKobo eReader.confsettings (confSettings), its user-facing copy (reviewNotices), and metadata such astitle,description,section, a requiredanalyticsEvent(the anonymousadd-*Umami event tracked when the feature is part of an install, or an explicitnullwhen an install event carries no signal, e.g. the requiredcustom-menu;featureAnalyticsEventsinfeatures/index.jsdedupes so related features may share one event, like the hiders'add-minimal-home, andtests/unit/nickelmenu-analytics.test.jsfails if a feature omits the key — see "Analytics" in PROJECT.md), an optionalhint(a URL or plain text; renders a "?" badge in the feature list that opens the link in a new tab, or shows the text in a popup), an optionalminimumVersion(a Kobo software version like4.31; the flow disables the feature with a red explanation when the connected device's firmware is older), an optionalunsupportedDeviceReason(deviceInfo)hook (returns a reason string when the feature does not support the connected device — the flow disables the checkbox with it andfeaturesToInstalldrops the feature — ornullwhen supported or when the device cannot be identified; e.g. NickelDissolve's hardware-UUID allowlist), an optionalhiddenflag (omits the feature from the install catalogue and prevents installation while keeping its removal detection working), an optionaldisabledflag (a maintainer's temporary kill switch: the feature stays listed and is never installed — while its removal detection keeps working; flip it in the feature module when e.g. a release turns out to be broken — set totruefor the generic "Temporarily unavailable." text, or to a string to show that reason verbatim instead;selection.js'sfeatureDisabledReason(feature, deviceInfo)derives the shown reason, and adisabledreason outranks the device-specific ones), and an optionalexperimental: trueflag (renders a muted-amber "Experimental" pill at the right of the feature row, just before the "?" hint badge, to mark a feature as still unstable; purely presentational — it does not change availability or install behavior). The flow and installer must stay generic: they invoke these feature hooks and render/apply whatever a feature declares, rather than special-casing an individual feature by name. - The device processes a single
.kobo/KoboRoot.tgzper boot, so a feature that ships its own KoboRoot.tgz payload (e.g.nickelclock, a Qt imageformats plugin that can't be expressed as ordinary onboard files) declares akoboRootEntries(ctx)hook returning tar entries ({ path, data, mode }).installer.js'sbuildKoboRootTgz(features)merges them into NickelMenu's base archive (archive.js'sparseTarGz/buildTarGz, which preserve executable modes) and writes one combined tarball; with no contributing feature it returns the base tgz verbatim. Such a feature is removed like any other add-on viacleanup(NickelClock self-removes its root-filesystem plugin on reboot once its.adds/nickelclockmarker is gone). Add the asset totools/installables/installables.mjs, gate it with a runtimeavailablecheck inflows/nickelmenu-flow.js(which reads the baked-ininstallablesManifest()and flips each matching feature'savailable/version, like the reading apps), and keep it out of any preset-conflict list it can coexist with. A payload folded into a feature that must stay available without it (Better typography and fixes bundles NickelTypeFix, but its conf settings and toggle work on their own) instead self-gates insidekoboRootEntries— returning no entries when the deployment lacks the asset — rather than flipping feature-levelavailable. - The NickelMenu config file (written to
.adds/nm/webui-preset, defined byNM_ITEMS_FILEinconstants.js) is generated, not a static asset. It is prefixed with a# Generated by KoboPatch Web UIcomment for identification. A feature contributes entries viamenuItems(ctx), returning{ id, lines }objects;installer.jscollects them from every selected feature, orders them by each id's position inMENU_ITEM_ORDER(the single ordered list of menu-item ids infeatures/menu-order.js— the sole source of truth for menu order; an id missing from it throws), and renders the file. Device-conditional items are a simple "don't include this entry" (e.g. custom-menu drops Dark Mode on unsupported hardware). The base Toggle menu and its tab header are owned by thecustom-menufeature. Features that injectexperimental:NickelMenu config lines do so inpostProcess, which runs after the config file is assembled. - Every on-device "toggle + reboot" Toggle item (and the
.adds/nm/scripts/*.shit runs) is owned by the feature it toggles — no capability flags, nocustom-menucoordination, no name-based special-casing.simplify-tabscontributes its "Simple Tabs" entry and shipstoggle_tabs.sh. The three home-screen hiders are generated from one table infeatures/hide-home-content/(each appends a distincthide_home_*_enabledflag inpostProcess) and all contribute the same shared "Minimal Home" entry plus the same universaltoggle_hidden_home.sh(which flips everyhide_home_*_enabledflag at once). To make several features safely contribute one shared toggle,installer.jsde-duplicatesmenuItemsentries byidand install files bypath(keeping the first), so the shared item and script appear exactly once however many hiders are selected. Small feature-owned assets are Vite-tracked URLs declared in the feature module withnew URL('./asset', import.meta.url)and loaded throughctx.bundledAsset(url), which goes through one per-run asset cache so shared assets fetch only once. KOReader/Cadmus/NickelClock insteadfetch()their large archives directly, since each is fetched by a single feature. - Every feature hook receives a context object that always includes
deviceInfoand the selectedfeatures, so features can adapt to the connected Kobo and to what else is being installed. Installer-time hooks (install,postProcess,menuItems,koboRootEntries) additionally getbundledAssetandprogress(exceptmenuItems, which only needsdeviceInfo/features).install()returns{ path, data }file descriptors the installer writes. A descriptor's file name must avoid the extensions Chromium's File System Access API refuses to create, such as.ini(see "File System Access write restrictions" in PROJECT.md); payloads that must land at such a path go throughkoboRootEntriesinstead.reviewNoticesis a(ctx)function returning the feature's notices (an empty array when none apply).confSettingsis a(ctx)function returning{ section, key, value }entries the installer applies toKobo eReader.confwhen a device is connected; itsctxalso includes the selectedfeaturesand thefontsCustomizationso a feature can adapt (e.g. only set a default font when that font is part of the fonts selection being installed). - A feature's
cleanupdeclares how it is removed and how that removal is presented. It can be detected by files (detect) and removed by deleting files (paths). Conf-setting removal is not re-declared here: a setting a feature both applies and owns for removal is declared once inconfSettingswithrevertable: true(and an optionalrevertTo, default removing the line); the flow detects the feature by those revertable settings and the uninstaller reverts them (only when the current value still equals what was set, so user edits afterwards are never overwritten).installer.js'srevertableConfSettings(feature, ctx)is the single helper that derives this subset, so the flow and uninstaller stay in sync. AconfSettingsentry withoutrevertableis applied once and never clawed back (a general preference). Optional cleanups also declare atitle(the noun shown in the removal review) and aremoveLabel(the checkbox wording) — the flow never constructs removal copy itself. src/assets/contains external installable assets (NickelMenu, NickelClock, KOReader, Cadmus, and the two ebook-fonts archiveskobo-core-fonts.zip/kobo-extra-fonts.zip). The archives are gitignored;installables.lock(committed) pins each one's version/url/sha256.npm run setup:installablesfetches exactly what the lock pins and verifies the hash (reproducible);npm run update:installablesresolves latest upstream and rewrites the lock (commit it) and then regenerates the committed font catalogue (src/js/nickelmenu/features/additional-fonts/catalogue.js, family → collection + .ttf files) from the font archives viatools/installables/generate-font-catalogue.mjs; theverify/testpipeline fails when catalogue and archives drift (--check). Both setup and update also derive the servedassets/font-previews.json(gitignored, per target) — a pre-rendered SVG type specimen per family that the "Select fonts" dialog fetches lazily; see PROJECT.md "Installable assets". The build bakes a manifest of these (id → version, available) into the bundle (globalThis.__INSTALLABLES__), read viasrc/js/nickelmenu/installables.js— there is no runtime*-release.jsonfetch, and add-on download URLs are version-suffixed (?v=<version>). Both setup and update also write a servedsrc/assets/index.json(id → asset/version/size, gitignored and regenerated from the lock like the archives) so the app can show each add-on's expected download size — read viainstallableSize()and used bydownloadProgressto keep the percentage working even where the proxy gzips the archive and stripsContent-Length. See PROJECT.md "Build And Assets".src/js/kobo/contains Kobo device/version/firmware URL/configuration logic. Keep File System Access wrappers indevice.js, pure version parsing inversion.js, model-capability data such as the Dark mode support blacklist indark-mode.js,Kobo eReader.confparsing plusExcludeSyncFoldersgeneration inconfiguration.jsandsync-exclusions.js, UI-language detection (theCurrentLocaleread from[ApplicationPreferences], pluslocaleLanguage/isEnglishLocale/localeDisplayNamehelpers) inlocale.js, and the on-device audit log (AuditLog) inaudit-log.js.eject-watch.jspolls a connected device until it stops responding, which is how the NickelMenu done step knows the Kobo has been unplugged; a safe eject and a pulled cable are indistinguishable through the File System Access API, so its result is presented as the device having disconnected, never as a confirmed safe eject. On connect,device.jsreadsCurrentLocaleontodeviceInfo.uiLocale(null when manual/unknown); the connect flow shows it in the device overview and features can adapt to it (e.g.simplify-tabslocalizes its tab labels; for a known language it has no translation for it omits them entirely so that non-English device keeps its own tab names, but for an unknown locale — the manual/download flow — it falls back to the English defaults so the tabs are still renamed to "Books / Stats / Notes" rather than left as the device's "My Books" names; seedefaultTabLabels).- Connected devices are identified by hardware UUID in
version.js; the serial prefix is only a consistency/display check. Firmware download URLs inpatches/downloads.jsonare keyed by software version and firmware channel (kobo12,kobo13, etc.), not by serial prefix or UUID. - The audit log records each install/removal step (KoboRoot.tgz write, per-feature file writes, removals, conf edits) to a timestamped
.kobopatch-webui/log-yy-mm-dd_hh-mm.logat the Kobo onboard root, one file per run. The flow always constructs anAuditLogand passes it toinstallToDevice/executeNickelMenuRemovalduring device writes and removals (but not download packages). The installer/uninstaller record steps and write it best-effort — a log failure never aborts the operation. src/js/shell/contains app-shell helpers shared by flows: the declarative step machine (step-machine.js— owns the visible step, the back-stack, and the breadcrumb; flows declare step descriptors withnavIndex/navLabels/onEnter/back/transient/recoveryStep, wherenavIndex/navLabelsmay be functions of the session andonEntermust be idempotent since back-navigation re-enters steps), the wizardSession(session.js— the mutable state's declared shape with onereset()/resetDeviceContext()), the shared result terminal (terminal.js— feedback wiring,flow-endanalytics, ZIP bundling, and the device-write + audit-log + error-routing sequence), plus navigation/breadcrumb rendering, DOM utilities, strings, and analytics.instructions.jsis the single source of truth for the plain-text manual-install guidance: both download flows bundle its output asinstructions.txtinside the ZIP (NickelMenu viainstaller.js'sbuildInstructionsText, custom patches inline), and it mirrors the on-screen steps plus a credit header (app version + timestamp) and the hard-lock recovery disclaimer.src/js/patches/contains the custom-patch code, split by responsibility:patch-yaml.jsis the pure kobopatch-YAML parsing/serialization (parsePatchYAML/replacePatchLines/yamlScalar/parsePatchConfig, also used by the unit tests);ui.jsis thePatchUImodel (loaded patch state, blacklist, selections, edit tracking, reload-manifest application, config generation) and exposesrender()as the entry into the view;patch-list-view.jsowns the DOM rendering/search of the patch list;patch-editor.jsowns the "edit patch values" modal and its YAML validation;patch-metadata.jsis the webui-only presentation layer (PATCH_CATEGORIES,PATCH_META,getPatchMeta) keyed by the exact YAML patch name — it decides each patch's user-facing theme/section, display label, author credit, and prose (description/note/editor tips), leaving the YAML untouched as the behavior source of truth;runner.jsis the WASM patcher wrapper. Keep parsing, model state, DOM rendering, and the editor dialog in their own files. The patch list and the incompatible-patches modal group byPATCH_CATEGORIEStheme (a trailing "Other" section catches anything uncategorized), not by patch file. An "original format" checkbox in the patches step's Advanced section flips both the list and that modal back to grouping by source file (PATCH_FILE_LABELS) under the raw YAML names — the way patches are listed on MobileRead; the preference lives on#patch-container'sdataset.originalFormatso it survives re-renders, anddisplayName/sectionBuckets/blacklistGroupsinpatch-list-view.jsswitch on it. The "Incompatible patches" button that opens that modal lives in the Advanced section under a "Patch History" header (static#btn-patch-blacklistinstep-patches.html, wired bypatches-flow.jsto the exportedopenBlacklistDialog), not in the rendered list. A section's "X / Y enabled" tally counts a mutually-exclusive PatchGroup as a single choice (bucketCounts), so e.g. the single-group "Keyboard" theme reads0 / 1, not0 / 3.scripts/check-patch-metadata.mjs(npm run check:patch-metadata, a quick phase ofverify/test) fails if any catalog patch lacks a metadatacategoryand warns on orphanPATCH_METAentries, so the layer can't drift as the YAML changes.- A custom-patches install persists a manifest (
.kobopatch-webui/custom-patches.json) recording the patch selections (overrides), manual edits (customized), and the file list, plus a companion Additional Files archive (.kobopatch-webui/custom-patches-files.tgz) holding the bytes of the user's Advanced-section Additional Files. The two share one base name (patchManifestBaseNameinpatches/additional-files.jsderivespatchManifestNameandadditionalFilesArchiveName, the single source of truth — no inlinecustom-patches.jsonliterals).buildPatchesManifest(flows/patches-execute.js) records the archive asadditionalFilesArchive: { path, sha256, size }; both the device-write and download-ZIP paths persist the archive, built once viabuildAdditionalFilesTgzand hashed withsha256Hex(patches-flow.js'sbuildManifestArtifactskeeps them in lockstep). On reconnect,maybeOfferReloadreads the archive, verifies itssha256/sizebefore trusting it (a missing, mismatched, or corrupt archive is silently ignored — older manifests predate it), andPatchUI.addRestoredAdditionalFilesre-adds the files to the Advanced section as part of the existing reload action so the next build re-merges them. The reload summary dialog shows a restored or unavailable note (RELOAD_SUMMARY_ADDITIONAL_FILES_RESTORED/_UNAVAILABLE) accordingly. tests/e2e/contains Playwright integration tests.tests/unit/contains Node unit tests for pure logic and mocked device-write behavior.patches/contains the patch catalog and patch source YAML files served by the app.tools/contains app-specific tooling such as installable asset setup and the kobopatch WASM wrapper.- Keep JavaScript carefully organized by responsibility. Avoid letting flow logic, domain parsing, DOM rendering, and device-write orchestration bleed into one another.
What this file has done since we first saw it
Hashed on every crawl. A supply-chain change to an agent config is a question of when, not whether, so the history is kept rather than the latest state alone.
- 8d ago First seen · 73 lines · 6,344 tokens per session scan A 3da4a4aa4448
kobopatch-webui AGENTS.md is an instructions file published in the GitHub repository nicoverbruggen/kobopatch-webui (135 stars, last pushed yesterday), licensed MIT. It adds 6,344 tokens to every session, about $0.0317 per session on Opus 5. A static security scan graded it A with 0 findings. No closer match exists in the catalogue, so it is treated as the original; first seen 2026-08-30.
Other instructions, from other repositories
next.js AGENTS.md
AGENTS.md instructions for vercel/next.js, covering next.js development guide, codebase structure, monorepo overview, core package: packages/next and other important packages.
codex AGENTS.md
AGENTS.md instructions for openai/codex, covering rust/codex-rs, the codex-core crate, code review rules, crate api surface and model visible context.
vscode buildNext.instructions.md
Working notes and architecture documentation for the new esbuild-based build system in build/next. Use when making changes to the new build pipeline (transpile/bundle commands, NLS plugin, source-map handling, resource copying, or self-hosting watch tasks).
vscode oss-third-party-notices.instructions.md
Instructions for microsoft/vscode, covering vs code oss third-party-notices pipeline, architecture, pipeline flow in ci, applying the notice (cutover) and fallback chain (never fail the build).
langchain AGENTS.md
AGENTS.md instructions for langchain-ai/langchain, covering global development guidelines for the langchain monorepo, corridor security analysis, project architecture and context, monorepo structure and development tools & commands.
spec-kit AGENTS.md
AGENTS.md instructions for github/spec-kit, covering agents.md, about spec kit and specify, quickstart — add a new integration in 5 steps, integration architecture and integrationmanifest — file tracking.