kobopatch-webui: Instructions file for Codex

AGENTS.md

kobopatch-webui AGENTS.md is an instructions file for Codex, OpenCode from nicoverbruggen/kobopatch-webui. It costs 6,344 tokens per session, scanned A, original, MIT.

Project instructions for nicoverbruggen/kobopatch-webui, a static web app for customizing Kobo e-readers. They describe the project structure, naming rules, exports, safety priorities, and how the app handles connected-device writes.

In plain words
What is it for?
Use them when modifying the app’s connection flows, setup wizard, error handling, shared services, or code that can write files directly to a connected Kobo.
Why use it?
They help an agent change the repository without putting device-writing code in the wrong place or violating the project’s safety and architecture rules.

Instructions file for CodexOpenCode

Written for Codex and OpenCode: the file is AGENTS.md. Also seen: mentions AGENTS.md.

This is nicoverbruggen/kobopatch-webui's own configuration. It tells Codex and OpenCode how to work on kobopatch-webui itself, so it is not a mod to install elsewhere. Copy it as a starting point and replace the rules that are about this project. Everything kobopatch-webui configures →

Reuse

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.

Copy the file
curl -O https://raw.githubusercontent.com/nicoverbruggen/kobopatch-webui/main/AGENTS.md
Clone the repo
git clone --depth 1 https://github.com/nicoverbruggen/kobopatch-webui

Made for: Codex, OpenCode.

Wrote 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.

agentmods badge for kobopatch-webui AGENTS.md

README.md
[![agentmods](https://agentmods.dev/badge/instructions/nicoverbruggen/kobopatch-webui/agents-md.svg)](https://agentmods.dev/instructions/nicoverbruggen/kobopatch-webui/agents-md)
Your own site
<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>
Per session 6,344 This file is loaded in full into every session.
When invoked 6,344 The same file — it is already loaded in full.
Security scan A 0 findings. A grade says what 26 rules found in the file — not that it is safe.
Origin original No closer match found in the catalogue.
Token cost

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.

ModelPer sessionOnce 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

Measured 8d ago against content hash 3da4a4aa4448, method: parsed. Prices are Anthropic first-party input rates as of 2026-09-08, from the pricing page.

Security

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.

AGENTS.md · 73 lines

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.js is the orchestrator and stays thin: it assembles the shared Session (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 shared state.goToModeSelection) — and the shared error step plus global error handling live in shell/error-screen.js, while the modal dialogs, mobile warning, environment pill, and preview banner live in shell/global-ui.js.
  • src/js/flows/ contains the user journeys. Keep flow-specific behavior inside the relevant flow file. Cross-flow navigation goes through state.* callbacks (state.goToModeSelection, state.showError, state.goBackToDeviceStep, state.goToManualVersionStep); direct flow APIs are injected explicitly by app.js where one flow drives another. A flow declares its steps to the step machine (shell/step-machine.js createFlow) and reaches its build→write/download tail through the shared terminal (shell/terminal.js createTerminal); do not hand-assemble setNavStep/setNavLabels/showStep calls or reimplement the feedback/ZIP/device-write+audit tail inside a flow.
  • src/js/nickelmenu/ contains NickelMenu domain logic. Installer code belongs in installer.js, removal code belongs in uninstaller.js, feature modules belong in features/, the device-domain reads the flow needs (existing-install / preset-conflict / legacy-items / optional-cleanup / Kobo-user-count probes) belong in probes.js, the menu-icon customization dialog plus its image processing (canvas resize, SVG→PNG) belong in customization-dialog.js, and the sectioned feature/cleanup checkbox-list rendering belongs in checkbox-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), declarative Kobo eReader.conf settings (confSettings), its user-facing copy (reviewNotices), and metadata such as title, description, section, a required analyticsEvent (the anonymous add-* Umami event tracked when the feature is part of an install, or an explicit null when an install event carries no signal, e.g. the required custom-menu; featureAnalyticsEvents in features/index.js dedupes so related features may share one event, like the hiders' add-minimal-home, and tests/unit/nickelmenu-analytics.test.js fails if a feature omits the key — see "Analytics" in PROJECT.md), an optional hint (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 optional minimumVersion (a Kobo software version like 4.31; the flow disables the feature with a red explanation when the connected device's firmware is older), an optional unsupportedDeviceReason(deviceInfo) hook (returns a reason string when the feature does not support the connected device — the flow disables the checkbox with it and featuresToInstall drops the feature — or null when supported or when the device cannot be identified; e.g. NickelDissolve's hardware-UUID allowlist), an optional hidden flag (omits the feature from the install catalogue and prevents installation while keeping its removal detection working), an optional disabled flag (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 to true for the generic "Temporarily unavailable." text, or to a string to show that reason verbatim instead; selection.js's featureDisabledReason(feature, deviceInfo) derives the shown reason, and a disabled reason outranks the device-specific ones), and an optional experimental: true flag (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.tgz per 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 a koboRootEntries(ctx) hook returning tar entries ({ path, data, mode }). installer.js's buildKoboRootTgz(features) merges them into NickelMenu's base archive (archive.js's parseTarGz/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 via cleanup (NickelClock self-removes its root-filesystem plugin on reboot once its .adds/nickelclock marker is gone). Add the asset to tools/installables/installables.mjs, gate it with a runtime available check in flows/nickelmenu-flow.js (which reads the baked-in installablesManifest() and flips each matching feature's available/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 inside koboRootEntries — returning no entries when the deployment lacks the asset — rather than flipping feature-level available.
  • The NickelMenu config file (written to .adds/nm/webui-preset, defined by NM_ITEMS_FILE in constants.js) is generated, not a static asset. It is prefixed with a # Generated by KoboPatch Web UI comment for identification. A feature contributes entries via menuItems(ctx), returning { id, lines } objects; installer.js collects them from every selected feature, orders them by each id's position in MENU_ITEM_ORDER (the single ordered list of menu-item ids in features/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 the custom-menu feature. Features that inject experimental: NickelMenu config lines do so in postProcess, which runs after the config file is assembled.
  • Every on-device "toggle + reboot" Toggle item (and the .adds/nm/scripts/*.sh it runs) is owned by the feature it toggles — no capability flags, no custom-menu coordination, no name-based special-casing. simplify-tabs contributes its "Simple Tabs" entry and ships toggle_tabs.sh. The three home-screen hiders are generated from one table in features/hide-home-content/ (each appends a distinct hide_home_*_enabled flag in postProcess) and all contribute the same shared "Minimal Home" entry plus the same universal toggle_hidden_home.sh (which flips every hide_home_*_enabled flag at once). To make several features safely contribute one shared toggle, installer.js de-duplicates menuItems entries by id and install files by path (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 with new URL('./asset', import.meta.url) and loaded through ctx.bundledAsset(url), which goes through one per-run asset cache so shared assets fetch only once. KOReader/Cadmus/NickelClock instead fetch() their large archives directly, since each is fetched by a single feature.
  • Every feature hook receives a context object that always includes deviceInfo and the selected features, so features can adapt to the connected Kobo and to what else is being installed. Installer-time hooks (install, postProcess, menuItems, koboRootEntries) additionally get bundledAsset and progress (except menuItems, which only needs deviceInfo/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 through koboRootEntries instead. reviewNotices is a (ctx) function returning the feature's notices (an empty array when none apply). confSettings is a (ctx) function returning { section, key, value } entries the installer applies to Kobo eReader.conf when a device is connected; its ctx also includes the selected features and the fontsCustomization so 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 cleanup declares 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 in confSettings with revertable: true (and an optional revertTo, 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's revertableConfSettings(feature, ctx) is the single helper that derives this subset, so the flow and uninstaller stay in sync. A confSettings entry without revertable is applied once and never clawed back (a general preference). Optional cleanups also declare a title (the noun shown in the removal review) and a removeLabel (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 archives kobo-core-fonts.zip/kobo-extra-fonts.zip). The archives are gitignored; installables.lock (committed) pins each one's version/url/sha256. npm run setup:installables fetches exactly what the lock pins and verifies the hash (reproducible); npm run update:installables resolves 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 via tools/installables/generate-font-catalogue.mjs; the verify/test pipeline fails when catalogue and archives drift (--check). Both setup and update also derive the served assets/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 via src/js/nickelmenu/installables.js — there is no runtime *-release.json fetch, and add-on download URLs are version-suffixed (?v=<version>). Both setup and update also write a served src/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 via installableSize() and used by downloadProgress to keep the percentage working even where the proxy gzips the archive and strips Content-Length. See PROJECT.md "Build And Assets".
  • src/js/kobo/ contains Kobo device/version/firmware URL/configuration logic. Keep File System Access wrappers in device.js, pure version parsing in version.js, model-capability data such as the Dark mode support blacklist in dark-mode.js, Kobo eReader.conf parsing plus ExcludeSyncFolders generation in configuration.js and sync-exclusions.js, UI-language detection (the CurrentLocale read from [ApplicationPreferences], plus localeLanguage/isEnglishLocale/localeDisplayName helpers) in locale.js, and the on-device audit log (AuditLog) in audit-log.js. eject-watch.js polls 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.js reads CurrentLocale onto deviceInfo.uiLocale (null when manual/unknown); the connect flow shows it in the device overview and features can adapt to it (e.g. simplify-tabs localizes 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; see defaultTabLabels).
  • Connected devices are identified by hardware UUID in version.js; the serial prefix is only a consistency/display check. Firmware download URLs in patches/downloads.json are 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.log at the Kobo onboard root, one file per run. The flow always constructs an AuditLog and passes it to installToDevice/executeNickelMenuRemoval during 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 with navIndex/navLabels/onEnter/back/transient/recoveryStep, where navIndex/navLabels may be functions of the session and onEnter must be idempotent since back-navigation re-enters steps), the wizard Session (session.js — the mutable state's declared shape with one reset()/resetDeviceContext()), the shared result terminal (terminal.js — feedback wiring, flow-end analytics, ZIP bundling, and the device-write + audit-log + error-routing sequence), plus navigation/breadcrumb rendering, DOM utilities, strings, and analytics. instructions.js is the single source of truth for the plain-text manual-install guidance: both download flows bundle its output as instructions.txt inside the ZIP (NickelMenu via installer.js's buildInstructionsText, 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.js is the pure kobopatch-YAML parsing/serialization (parsePatchYAML/replacePatchLines/yamlScalar/parsePatchConfig, also used by the unit tests); ui.js is the PatchUI model (loaded patch state, blacklist, selections, edit tracking, reload-manifest application, config generation) and exposes render() as the entry into the view; patch-list-view.js owns the DOM rendering/search of the patch list; patch-editor.js owns the "edit patch values" modal and its YAML validation; patch-metadata.js is 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.js is 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 by PATCH_CATEGORIES theme (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's dataset.originalFormat so it survives re-renders, and displayName/sectionBuckets/blacklistGroups in patch-list-view.js switch on it. The "Incompatible patches" button that opens that modal lives in the Advanced section under a "Patch History" header (static #btn-patch-blacklist in step-patches.html, wired by patches-flow.js to the exported openBlacklistDialog), 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 reads 0 / 1, not 0 / 3. scripts/check-patch-metadata.mjs (npm run check:patch-metadata, a quick phase of verify/test) fails if any catalog patch lacks a metadata category and warns on orphan PATCH_META entries, 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 (patchManifestBaseName in patches/additional-files.js derives patchManifestName and additionalFilesArchiveName, the single source of truth — no inline custom-patches.json literals). buildPatchesManifest (flows/patches-execute.js) records the archive as additionalFilesArchive: { path, sha256, size }; both the device-write and download-ZIP paths persist the archive, built once via buildAdditionalFilesTgz and hashed with sha256Hex (patches-flow.js's buildManifestArtifacts keeps them in lockstep). On reconnect, maybeOfferReload reads the archive, verifies its sha256/size before trusting it (a missing, mismatched, or corrupt archive is silently ignored — older manifests predate it), and PatchUI.addRestoredAdditionalFiles re-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.

Read the full file on GitHub · 73 lines

Changes

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.

  1. 8d ago First seen · 73 lines · 6,344 tokens per session scan A 3da4a4aa4448

Subscribe to this mod's changes

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.

Related

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.

vercel/next.js · 7,296 tokens

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.

openai/codex · 5,153 tokens

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).

microsoft/vscode · 6,785 tokens

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).

microsoft/vscode · 5,001 tokens

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.

langchain-ai/langchain · 4,469 tokens

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.

github/spec-kit · 7,104 tokens