Skip to main content

heartreverie-create-plugin

Create a new plugin for the HeartReverie plugin system. Use when the user wants to create a plugin, add a new plugin, scaffold a plugin, or build a plugin for this project. Guides through plugin type selection, manifest creation, prompt fragments, backend/frontend modules, tag configuration, and README generation.

Zur Installation springen

Quellinformationen

Repository
jim60105/HeartReverie
Letzte Quellaktivität
4. Juni 2026 um 21:35
Erkannte Sprache von SKILL.md
Englisch
Sterne
4
Forks
1

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

Datei-Explorer
3 Dateien

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
heartreverie-create-plugin
description
Create a new plugin for the HeartReverie plugin system. Use when the user wants to create a plugin, add a new plugin, scaffold a plugin, or build a plugin for this project. Guides through plugin type selection, manifest creation, prompt fragments, backend/frontend modules, tag configuration, and README generation.
# Create Plugin Create a new plugin for the manifest-driven plugin system. Plugins live in `plugins/<name>/` with a `plugin.json` manifest that declares capabilities. For full manifest field reference, read `references/manifest-schema.md`. --- ## Step 1: Understand the Plugin Determine what the plugin does. Derive: - **Name**: kebab-case, e.g., `my-plugin`. Must be valid: no `..`, `\0`, `/`, `\`. - **Directory**: `plugins/<name>/` - **Purpose**: What it adds to the system ## Step 2: Determine Plugin Type Select type based on what the plugin needs: | Type | Use When | |------|----------| | `prompt-only` | Only injects text into the LLM system prompt | | `full-stack` | Needs any combination of: prompt fragments, backend hooks, frontend rendering | | `hook-only` | Only needs backend lifecycle hooks (no prompt injection) | | `frontend-only` | Only browser-side rendering | When uncertain, ask the user to choose from the four types. ## Step 3: Create the Manifest Create `plugins/<name>/plugin.json` with required fields: ```json { "name": "<name>", "displayName": "<人類可讀名稱>", "version": "1.0.0", "description": "Brief description", "type": "<type>" } ``` `name` is the slug (must match directory name); `displayName` is the label rendered in the reader sidebar and settings page heading. Both are **required** — a plugin missing or with a blank `displayName` is rejected during load. Then add type-appropriate optional fields per the patterns below. ### Pattern: prompt-only ```json { "name": "my-plugin", "displayName": "我的外掛", "version": "1.0.0", "description": "My prompt instructions", "type": "prompt-only", "promptFragments": [ { "file": "./instructions.md", "variable": "my_plugin", "priority": 100 } ] } ``` ### Pattern: full-stack (prompt + frontend + tags) ```json { "name": "my-plugin", "displayName": "我的外掛", "version": "1.0.0", "description": "My full-stack plugin", "type": "full-stack", "promptFragments": [ { "file": "./instructions.md", "variable": "my_plugin", "priority": 100 } ], "frontendModule": "./frontend.js", "tags": ["mytag"], "promptStripTags": ["mytag"], "displayStripTags": ["mytag"], "hooks": [ { "stage": "frontend-render", "reads": ["text"], "writes": ["text", "placeholderMap"] } ] } ``` ### Pattern: full-stack (backend + frontend + tags, no prompt) ```json { "name": "my-plugin", "displayName": "我的外掛", "version": "1.0.0", "description": "My processing plugin", "type": "full-stack", "backendModule": "./handler.js", "frontendModule": "./frontend.js", "tags": ["mytag"], "promptStripTags": ["mytag"], "hooks": [ { "stage": "post-response", "parallel": true, "readOnly": true, "reads": ["usage", "endpoint", "source", "pluginName", "correlationId"] }, { "stage": "frontend-render" } ] } ``` ### Pattern: hook-only ```json { "name": "my-plugin", "displayName": "我的外掛", "version": "1.0.0", "description": "My backend hook plugin", "type": "hook-only", "backendModule": "./handler.js", "hooks": [ { "stage": "post-response", "writes": ["content"] } ] } ``` **Critical**: The `name` field must match the directory name exactly. The `displayName` field is the user-facing label (any non-empty Unicode string after trim); UI surfaces such as the reader sidebar and `/settings/plugins/<name>` heading render `displayName` rather than the slug. **`hooks` is mandatory for new plugins.** Enumerate every `hooks.register("<stage>", ...)` call in `register()` here. The loader compares manifest vs runtime registration on startup and **rolls back the plugin load with a `declaredOnly`/`registeredOnly` error** on mismatch. The check also powers the Hook Inspector page (`/settings/hook-inspector`) and the `deno task introspect:hooks` CLI. Use `reads`/`writes` to participate in conflict detection (C1: two plugins writing the same field; C2: read with no writer). Omitting `hooks` entirely puts the plugin in legacy mode (no validation) — only use this for unmaintained third-party plugins during migration. For all fields and detailed examples, read `references/manifest-schema.md`. ## Step 4: Create Prompt Fragments (if applicable) For plugins with `promptFragments`: 1. Create each Markdown file declared in the manifest (e.g., `plugins/<name>/instructions.md`) 2. Write the LLM instructions content 3. If the fragment has a `variable`, add `{{ variable_name }}` to `system.md` at the desired position Priority guide: - `10` — Start of prompt (framing) - `100` — Normal (default) - `800` — Reinforcement (re-emphasize late in prompt) - `900` — End of prompt (final instructions) For reinforcement patterns (two fragments at different priorities), see the writestyle plugin in `references/manifest-schema.md`. ## Step 5: Configure Tags (if applicable) If the LLM outputs custom XML tags (e.g., `<mytag>...</mytag>`): 1. Add tag names to `tags` array 2. Add to `promptStripTags` — strip from `previousContext` so tags don't echo back to LLM 3. Add to `displayStripTags` — strip from frontend display (only if the tag should not be visible to readers) **Plain text** for simple tags: `"mytag"` → auto-wrapped as `<mytag>[\s\S]*?</mytag>` **Regex** for tags with attributes: ```json "/<mytag\\b[^>]+>[\\s\\S]*?<\\/mytag>/g" ``` Usually `promptStripTags` and `displayStripTags` use the same patterns. They differ when a tag should be stripped from the LLM prompt but kept visible in the reader (or vice versa). ## Step 6: Create Backend Module (if applicable) For plugins with `backendModule`, create the handler file. Backend modules register handlers via a context object. The module must export a `register` function that receives `{ hooks, logger, getSettings }` — a `PluginHooks` wrapper, a scoped `Logger`, and a **zero-arg** `getSettings()` (own-plugin only). The same own-plugin `getSettings()` is also present on the `getDynamicVariables(context)` context. `registerRoutes(context)` additionally exposes `saveSettings(values)` (validates against the schema then persists). Backend `getSettings` is NOT cross-plugin — only the frontend `hooks.getSettings(name?)` / `context.getSettings(name?)` can read other plugins' settings. **JavaScript (`handler.js`):** ```javascript export function register({ hooks, logger }) { hooks.register("post-response", async (context) => { const log = context.logger ?? logger; const { content, storyDir, rootDir } = context; log.info("Processing response", { contentLength: content.length }); // Process the LLM response }, 100); } ``` **TypeScript (`handler.ts`):** ```typescript import type { PluginRegisterContext } from "../../writer/types.ts"; export function register({ hooks, logger }: PluginRegisterContext): void { hooks.register("post-response", async (context) => { const log = context.logger ?? logger; const content = context.content as string; log.info("Processing response", { contentLength: content.length }); // Process the LLM response }, 100); } ``` For the active hook stages and their context parameters, read `references/hook-api.md`. Backend code style: ESM, **double quotes**, semicolons, `async/await`, JSDoc comments. Use `context.logger ?? logger` pattern in hook handlers for request-scoped logging. The same module MAY additionally export `registerRoutes(context)` (sync or async) to mount custom HTTP endpoints under `/api/plugins/<name>/*` — useful for proxying external services or backing `x-options-url` dropdowns in the settings page. See [`references/hook-api.md`](./references/hook-api.md#registerroutes-export) for the full `PluginRouteContext` contract. ## Step 7: Create Frontend Module (if applicable) For plugins with `frontendModule`, create the module. The `register` function receives **two** arguments — a per-plugin `hooks` proxy and a `context` object: ```javascript import { escapeHtml } from '../_shared/utils.js'; export function register(hooks, context) { hooks.register('frontend-render', (ctx) => { const settings = hooks.getSettings(); if (settings.enabled === false) return; let index = 0; ctx.text = ctx.text.replace( /<mytag>([\s\S]*?)<\/mytag>/gi, (_match, inner) => { const placeholder = `<!--MYTAG_BLOCK_${index++}-->`; ctx.placeholderMap.set(placeholder, `<div class="my-component">${escapeHtml(inner)}</div>`); return placeholder; } ); }, 100); } ``` Key points: - `register(hooks, context)` — both args are provided. Older plugins that only declare `register(hooks)` keep working. - `hooks.register(stage, handler, priority?)` and the equivalent alias `hooks.on(stage, handler, priority?)` are both available on the per-plugin proxy. New code may use either; some community plugins feature-detect via `(hooks.on ?? hooks.register)`. - `hooks.getSettings(name?)` and `context.getSettings(name?)` both return the live settings snapshot for `name` (defaults to the calling plugin). The reader hydrates settings on boot and, after a ~50 ms debounce on `plugin-settings:changed`, bumps the chapter render epoch — that re-runs render-pipeline hooks (`frontend-render`, `chapter:render:after`, `chapter:dom:ready`) and re-applies `displayStripTags`. The `notification` hook is NOT re-dispatched on settings change. - `register` MAY be `async` (the loader awaits it before flipping `pluginsReady`). - `hooks.register(stage, handler, priority?)` — the `originPluginName` is auto-curried by the loader proxy; do NOT pass it manually. - Frontend handlers are **synchronous** for most stages; `action-button:click` is the exception (async dispatch). Async handlers on synchronous stages are **rejected** by the dispatcher and surface a startup mismatch in Hook Inspector. If you must `await` something inside a sync stage, fire-and-forget via an IIFE: ```javascript hooks.register('frontend-render', (ctx) => { // Sync work that affects ctx must happen here, synchronously. queueMicrotask(async () => { // Side-effects that don't block the render pipeline. await refreshCacheElsewhere(); }); }); ``` - Use unique placeholder names that include the plugin name to avoid collisions. - Shared utilities live under `/plugins/_shared/`. Import them via relative paths (e.g. `import { escapeHtml } from '../_shared/utils.js';`); the server only serves files under `_shared/` and each plugin's declared `frontendModule` / `frontendStyles` / `frontendImports`. - **Sibling `.js` imports must be declared in `manifest.frontendImports`.** If `frontend.js` does `import { foo } from './helper.js';`, add `"./helper.js"` to `frontendImports` or the browser request for `/plugins/<name>/helper.js` returns `404`. Imports from `../_shared/*` do not need to be declared. See [`references/manifest-schema.md`](./references/manifest-schema.md#frontend-imports-allowlist) for the validator rules. - Frontend code style: ESM, **single quotes**, no build step, no framework — plugins ship as raw JS even though the reader itself is a Vue 3 + Vite SPA. ### Frontend Hook Stages (quick reference) | Stage | Mode | When | |-------|------|------| | `frontend-render` | sync | Custom XML extraction → placeholder map (Markdown not yet parsed) |
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen