
Heartreverie Create Plugin
- 2 installs
- 4 repo stars
- Updated August 1, 2026
- jim60105/heartreverie
Scaffolds a new plugin for the HeartReverie manifest-driven plugin system, guiding type selection, manifest fields, and modules.
About
Walks through creating a HeartReverie plugin including type selection, plugin.json manifest, prompt fragments, backend/frontend modules, and tag configuration. A developer uses it when adding a new plugin to the HeartReverie plugin system.
- Four plugin types: prompt-only, full-stack, hook-only, frontend-only
- Manifest patterns per type with promptFragments, modules, and strip tags
Heartreverie Create Plugin by the numbers
- 2 all-time installs (skills.sh)
- Ranked #611 of 782 Skill Development skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/jim60105/heartreverie --skill heartreverie-create-pluginAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2 |
|---|---|
| repo stars | ★ 4 |
| Last updated | August 1, 2026 |
| Repository | jim60105/heartreverie ↗ |
What it does
Scaffolds a new plugin for the HeartReverie manifest-driven plugin system, guiding type selection, manifest fields, and modules.
Files
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:
{
"name": "<name>",
"version": "1.0.0",
"description": "Brief description",
"type": "<type>"
}Then add type-appropriate optional fields per the patterns below.
Pattern: prompt-only
{
"name": "my-plugin",
"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)
{
"name": "my-plugin",
"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"]
}Pattern: full-stack (backend + frontend + tags, no prompt)
{
"name": "my-plugin",
"version": "1.0.0",
"description": "My processing plugin",
"type": "full-stack",
"backendModule": "./handler.js",
"frontendModule": "./frontend.js",
"tags": ["mytag"],
"promptStripTags": ["mytag"]
}Pattern: hook-only
{
"name": "my-plugin",
"version": "1.0.0",
"description": "My backend hook plugin",
"type": "hook-only",
"backendModule": "./handler.js"
}Critical: The name field must match the directory name exactly.
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:
"/<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 } — a PluginHooks wrapper and a scoped Logger.
JavaScript (`handler.js`):
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`):
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 3 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.
Step 7: Create Frontend Module (if applicable)
For plugins with frontendModule, create the module using the Extract → Placeholder → Reinsert pattern:
export function register(hooks) {
hooks.register('frontend-render', (context) => {
let index = 0;
context.text = context.text.replace(
/<mytag>([\s\S]*?)<\/mytag>/gi,
(_match, inner) => {
const placeholder = `<!--MYTAG_BLOCK_${index++}-->`;
const html = renderMyTag(inner);
context.placeholderMap.set(placeholder, html);
return placeholder;
}
);
}, 100);
}
function renderMyTag(content) {
return `<div class="my-component">${escapeHtml(content)}</div>`;
}Key points:
- Frontend handlers are synchronous (no
async) - Use unique placeholder names (include plugin name prefix)
- Import
escapeHtmlfrom'/js/utils.js'for safe rendering - Frontend code style: ESM, single quotes, no build step, no framework
Notification Hook
Frontend modules can also register a notification hook, dispatched by the system on events such as chat:done. The context is { event, data, notify }:
event(string): Event name (e.g.,'chat:done')data(object): Event-specific datanotify(function): Call to show a notification — accepts{ title, body?, level?, position?, channel?, duration? }
Example (from the response-notify plugin):
export function register(hooks) {
hooks.register('notification', (context) => {
if (context.event !== 'chat:done') return;
if (typeof context.notify !== 'function') return;
const channel = document.visibilityState === 'hidden' ? 'auto' : 'in-app';
context.notify({
title: '故事生成完成',
body: '新的章節已經寫入完成',
level: 'success',
channel,
});
}, 100);
}For the full frontend hook API, read references/hook-api.md.
Step 8: Generate README.md
Create plugins/<name>/README.md in Traditional Chinese (zh-TW):
- Use full-width punctuation(,、。:;「」)
- Add space between Chinese and alphanumeric characters
- Sections:
概述、manifest 欄位說明、檔案說明、使用方式or運作方式
Template:
# <name>
## 概述
<Description in zh-TW>
## manifest 欄位說明
| 欄位 | 說明 |
|------|------|
| ... | ... |
## 檔案說明
| 檔案 | 說明 |
|------|------|
| `plugin.json` | Plugin manifest |
| ... | ... |
## 使用方式
<Usage instructions in zh-TW>Step 9: Validate
Run these checks before considering the plugin complete:
1. Name match: plugin.json name field matches directory name 2. Valid JSON: plugin.json parses without errors 3. File existence: All files referenced in manifest exist (promptFragments[].file, backendModule, frontendModule) 4. Path safety: All file paths resolve within plugins/<name>/ (no ../ traversal) 5. system.md integration: If prompt fragments use named variables, confirm {{ variable_name }} exists in system.md 6. Run tests: deno test --allow-read --allow-write --allow-env --allow-net to verify nothing is broken
Hook API Reference
Table of Contents
- Backend Hooks
- Hook Stages
- Registration Pattern
- Stage Details
- Priority System
- Error Handling
- Plugin Logger
- Frontend Hooks
- Frontend Registration Pattern
- The Placeholder Pattern
- Notification Hook
- Security Notes
- Code Style
---
Backend Hooks
Backend modules register handlers via a context object. The module must export a register function that receives { hooks, logger } — a PluginHooks interface for hook registration and a pre-scoped Logger for structured logging.
Hook Stages
| Stage | When Fired | Context Parameters |
|---|---|---|
prompt-assembly | During system prompt rendering | { previousContext, rawChapters, storyDir, series, name } |
pre-write | After LLM response, before file write | { message, chapterPath, storyDir, series, name, preContent } |
post-response | After LLM response complete | { content, storyDir, series, name, rootDir } |
Note: The runtime also definesresponse-streamandstrip-tagsas valid stage names, but they are not currently dispatched by any code path. Plugins registered on these stages will load without error but their handlers will never fire. They exist for potential future use.
Registration Pattern
JavaScript (`handler.js`):
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`):
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;
const storyDir = context.storyDir as string;
log.info("Processing response", { contentLength: content.length });
// Process the LLM response
}, 100);
}Stage Details
prompt-assembly
Runs during system prompt rendering. Use to modify previousContext or inject dynamic content.
hooks.register("prompt-assembly", async (context) => {
const log = context.logger;
const previousContext = context.previousContext as string[];
const storyDir = context.storyDir as string;
const name = context.name as string;
// Modify previousContext in-place or read rawChapters for unstripped content
}, 100);Context is mutable — modify arrays in-place (e.g., previousContext.length = 0; previousContext.push(...newItems)).
pre-write
Runs after the full LLM response is received but before it is written to the chapter file. Use to prepend or modify content before writing.
hooks.register("pre-write", async (context) => {
const message = context.message as string;
if (typeof message === "string" && message.length > 0) {
// Prepend content before the LLM response in the chapter file
context.preContent = `<my_tag>\n${message}\n</my_tag>\n\n`;
}
}, 100);post-response
Runs after the LLM response is complete and written. Use for side effects: running external tools, updating state files, logging.
hooks.register("post-response", async (context) => {
const log = context.logger;
const { content, storyDir, rootDir } = context;
// Run external binary, update files, etc.
}, 100);Priority System
hooks.register(stage, handler, priority)- Lower priority number = runs first
- Default:
100 - Multiple handlers on the same stage run sequentially in priority order
- Typical values:
50(early),100(normal),200(late)
Error Handling
- Each handler runs in a try/catch
- Exceptions are logged via the structured logger but do not block other handlers
- A failing handler does not prevent subsequent handlers from executing
- The (possibly mutated) context is returned regardless of errors
// From HookDispatcher.dispatch():
// for (const { handler } of handlers) {
// try { await handler(context); }
// // Errors are logged via the structured logger (category: "plugin") but do not block other handlers
// }Plugin Logger
Each plugin receives a pre-scoped Logger instance via the register context. The logger has { plugin: "<name>" } in its baseData, so all log entries automatically include the plugin name.
During hook dispatch, context.logger is always injected — it is derived from the plugin's base logger with a correlationId when available (from chat requests). Use the pattern:
const log = context.logger ?? logger;Logger methods: debug(message, data?), info(message, data?), warn(message, data?), error(message, data?), withContext(ctx).
log.info("Compaction applied", { chapters: 5, removed: 2 });
log.debug("Processing chapter", { index: 3 });
log.warn("Binary not found", { path: "/usr/bin/tool" });
log.error("Execution failed", { exitCode: 1, stderr: "..." });---
Frontend Hooks
Frontend modules are ES modules loaded by the browser. They register synchronous handlers via FrontendHookDispatcher.
Frontend Hook Stages
| Stage | Purpose | Context Parameters |
|---|---|---|
frontend-render | Custom tag extraction and rendering | { text, placeholderMap, options } |
notification | Browser notification when events occur (e.g., chat:done) | { event, data, notify } |
text(string): The raw LLM output text before Markdown parsingplaceholderMap(Map<string, string>): Map of placeholder strings → rendered HTMLoptions(object): Render options (e.g.,{ isLastChapter })
Frontend Registration Pattern
export function register(hooks) {
hooks.register('frontend-render', (context) => {
// 1. Extract custom XML blocks from context.text
// 2. Replace with placeholder comments
// 3. Add placeholder → HTML mappings to context.placeholderMap
}, 100);
}Important: Frontend handlers are synchronous (no async).
The Placeholder Pattern
Frontend rendering follows the Extract → Placeholder → Reinsert pattern:
1. Extract XML blocks (e.g., <options>...</options>) from context.text 2. Replace each block with a unique HTML comment placeholder (e.g., <!--OPTIONS_BLOCK_0-->) 3. Store the mapping in context.placeholderMap.set(placeholder, renderedHtml) 4. After all hooks run, the system runs Markdown parsing + DOMPurify on context.text 5. The system reinserts rendered HTML by replacing placeholders in the final HTML
Example implementation:
export function register(hooks) {
hooks.register('frontend-render', (context) => {
let index = 0;
context.text = context.text.replace(
/<mytag>([\s\S]*?)<\/mytag>/gi,
(_match, inner) => {
const placeholder = `<!--MYTAG_BLOCK_${index++}-->`;
const html = renderMyTag(inner);
context.placeholderMap.set(placeholder, html);
return placeholder;
}
);
}, 100);
}
function renderMyTag(content) {
// Return sanitized HTML string
return `<div class="my-component">${escapeHtml(content)}</div>`;
}Key points:
- Always use unique placeholder names (include plugin name to avoid collisions)
- Use
escapeHtml()from/js/utils.jsfor any user content in rendered HTML - Priority controls rendering order — lower priorities extract first
Notification Hook
The notification hook is dispatched by the system on events such as chat:done. Use it to surface browser or in-app notifications for lifecycle events.
When it fires: dispatched by the system on events like chat:done.
Context parameters:
event(string): Event name (e.g.,'chat:done')data(object): Event-specific datanotify(function): Call to show a notification. Accepts an options object:title(string, required)body(string, optional)level('info' | 'success' | 'warning' | 'error', optional)position(string, optional)channel('in-app' | 'system' | 'auto', optional)duration(number, optional)
Example (from the response-notify plugin):
export function register(hooks) {
hooks.register('notification', (context) => {
if (context.event !== 'chat:done') return;
if (typeof context.notify !== 'function') return;
const channel = document.visibilityState === 'hidden' ? 'auto' : 'in-app';
context.notify({
title: '故事生成完成',
body: '新的章節已經寫入完成',
level: 'success',
channel,
});
}, 100);
}---
Security Notes
- Module path containment:
backendModuleandfrontendModulepaths must resolve within the plugin directory. Paths with../traversal are rejected. - Frontend module serving: Only files declared as
frontendModulein the manifest are served via/plugins/:name/:file. No other files in the plugin directory are accessible from the browser. - Backend imports: Backend modules are loaded via dynamic
import()withfile://URLs (Deno). The resolved path is validated before import.
Code Style
Backend (writer/)
- ESM modules (
import/export) - Double quotes for strings
- Semicolons always used
async/awaitfor all async operations#prefix for private class fields- JSDoc comments on functions
- TypeScript (
.ts) or JavaScript (.js) — both supported
Frontend (reader/js/)
- ESM modules, no build step, no bundler, no framework
- Single quotes for strings
- Semicolons always used
- JSDoc
@param/@returnson exported functions - Import from absolute paths (e.g.,
'/js/utils.js')
Plugin Manifest Schema (plugin.json)
Table of Contents
- Required Fields
- Plugin Types
- Optional Fields
- Prompt Fragments
- Tag Strip Patterns
- Parameters
- Security Constraints
- Complete Examples by Type
---
Required Fields
| Field | Type | Description |
|---|---|---|
name | string | Unique identifier. Must match directory name exactly. |
version | string | Semver (e.g., "1.0.0") |
description | string | Brief description of the plugin's purpose |
type | string | One of: prompt-only, full-stack, hook-only, frontend-only |
Plugin Types
| Type | When to Use | Has Prompt? | Has Backend? | Has Frontend? |
|---|---|---|---|---|
prompt-only | Only injects text into the LLM system prompt | ✅ | ❌ | ❌ |
full-stack | Needs prompt fragments + backend processing + frontend rendering (or any combination) | ✅/❌ | ✅/❌ | ✅/❌ |
hook-only | Only backend lifecycle hooks, no prompt injection | ❌ | ✅ | ❌ |
frontend-only | Only browser-side rendering | ❌ | ❌ | ✅ |
Note: Thetypefield is a semantic annotation. The system does not enforce capability restrictions based on type — aprompt-onlyplugin with afrontendModulewill still load. Use type accurately for documentation purposes.
Optional Fields
| Field | Type | Description |
|---|---|---|
promptFragments | array | Markdown files to inject as Vento template variables |
backendModule | string | Path to backend module (relative to plugin dir), e.g., "./handler.js" or "./handler.ts" |
frontendModule | string | Path to frontend module (relative to plugin dir). Must be `"./frontend.js"` — the runtime loader hardcodes this filename. |
frontendStyles | array<string> | Relative paths to CSS files injected into the frontend <head> as <link rel="stylesheet"> elements. Each entry must end with .css, must not be absolute, and must not contain .. segments. |
tags | array<string> | XML tag names managed by this plugin (used for metadata/API response) |
promptStripTags | array | Tags/regex to strip from previousContext when building prompts |
displayStripTags | array | Tags/regex to strip from frontend display |
parameters | array | Custom Vento template parameters exposed to the template editor |
Prompt Fragments
Each entry in the promptFragments array:
{ "file": "./my-instructions.md", "variable": "my_var", "priority": 100 }| Property | Required | Description |
|---|---|---|
file | ✅ | Path to Markdown file, relative to plugin directory |
variable | ❌ | Vento variable name — accessible as {{ my_var }} in system.md |
priority | ❌ | Sort order (default: 100). Lower = earlier in prompt |
Variable vs No Variable
- With `variable`: Becomes a named Vento variable. Use
{{ variable_name }}insystem.md. - Without `variable`: Added to the
plugin_fragmentsarray. Access via{{ for item of plugin_fragments }}.
Priority Conventions
| Priority | Purpose |
|---|---|
| 10 | Start of prompt — framing instructions |
| 100 | Normal — standard instructions (default) |
| 800 | Reinforcement — re-emphasize at end of prompt |
| 900 | End of prompt — final instructions |
Adding to system.md
After creating a prompt fragment with a named variable, add {{ variable_name }} to system.md at the desired position. The template engine replaces it with the file content at render time.
Tag Strip Patterns
Both promptStripTags and displayStripTags accept the same two formats:
Plain Text (Simple Tags)
Provide the tag name as a string. The system auto-wraps it as <tagname>[\s\S]*?</tagname>:
{
"promptStripTags": ["options", "status"],
"displayStripTags": ["user_message", "imgthink"]
}Use for tags without attributes (e.g., <options>...</options>).
Regex (Tags with Attributes)
Start the pattern with / and end with /flags. The system parses it as a RegExp:
{
"promptStripTags": ["/<T-task\\b[^>]+>[\\s\\S]*?<\\/T-task>/g"],
"displayStripTags": ["/<T-task\\b[^>]+>[\\s\\S]*?<\\/T-task>/g"]
}Use when tags may have attributes (e.g., <T-task type="think">).
Safety notes:
- Empty patterns (
//g) are skipped with a warning - Invalid regex syntax is caught and skipped
- Frontend
displayStripTagsundergo ReDoS safety checks; dangerous patterns are skipped
When to Use Each
| Tag Pattern | Format | Example |
|---|---|---|
<mytag>content</mytag> | Plain text: "mytag" | options, status, user_message |
<mytag attr="val">content</mytag> | Regex: "/<mytag\\b[^>]+>... | T-task |
Frontend Styles
The frontendStyles array lists CSS files to inject into the frontend <head> as <link rel="stylesheet"> elements. Styles are loaded before JS modules so component rendering sees the correct styles on first paint.
- Format: Array of paths relative to the plugin directory (e.g.,
"./styles/panel.css") - Serving: Each file is served at
/plugins/<name>/<path>and injected as a<link>tag - Load order: Injected before JS frontend modules
- Validation:
- Each entry must end with
.css - Absolute paths are rejected
..segments are rejected- The resolved path must remain within the plugin directory
Example:
{
"frontendStyles": ["./styles/panel.css", "./styles/toast.css"]
}Parameters
The parameters array declares custom Vento template parameters that appear in the frontend template editor:
{
"parameters": [
{ "name": "my_param", "type": "string", "description": "Description for the editor" }
]
}| Property | Required | Description |
|---|---|---|
name | ✅ | Parameter name (used in Vento templates as {{ name }}) |
type | ❌ | Data type (default: "string") |
description | ❌ | Shown in the frontend template editor |
Note: Prompt fragment variables are automatically registered as parameters. Only use the parameters field for non-fragment variables that your backend hook injects into the template context.Security Constraints
Name Validation
- Must not contain:
..,\0,/,\ - Must match the directory name exactly
- Validated by
isValidPluginName()on load
Path Containment
All file paths (promptFragments[].file, backendModule, frontendModule) are resolved with path.resolve() and must remain within the plugin directory. Paths like ../../etc/passwd are rejected.
Frontend Module Access
The /plugins/:name/:file route only serves files declared as frontendModule in the manifest. Arbitrary files in the plugin directory are not accessible.
---
Complete Examples by Type
prompt-only — Simple (single fragment)
{
"name": "de-robotization",
"version": "1.0.0",
"description": "De-robotization prompt fragment",
"type": "prompt-only",
"promptFragments": [
{ "file": "./de-robotization.md", "variable": "de_robotization", "priority": 100 }
]
}prompt-only — Multi-fragment with Reinforcement
{
"name": "writestyle",
"version": "1.0.0",
"description": "Writing style instructions for the LLM",
"type": "prompt-only",
"promptFragments": [
{ "file": "./writestyle.md", "variable": "writestyle", "priority": 100 },
{ "file": "./writestyle-reinforce.md", "variable": "writestyle_reinforce", "priority": 800 }
]
}prompt-only — With Tag Stripping (Regex)
{
"name": "t-task",
"version": "1.0.0",
"description": "T-task prompt fragment with frontend tag stripping",
"type": "prompt-only",
"promptFragments": [
{ "file": "./T-task.md", "variable": "t_task", "priority": 100 },
{ "file": "./T-task_think_format.md", "variable": "t_task_think_format", "priority": 100 }
],
"displayStripTags": ["/<T-task\\b[^>]+>[\\s\\S]*?<\\/T-task>/g"],
"tags": ["T-task"],
"promptStripTags": ["/<T-task\\b[^>]+>[\\s\\S]*?<\\/T-task>/g"]
}full-stack — Prompt + Frontend + Tags
{
"name": "options",
"version": "1.0.0",
"description": "Options panel extraction, rendering, and prompt fragment",
"type": "full-stack",
"promptFragments": [
{ "file": "./options.md", "variable": "options", "priority": 100 }
],
"frontendModule": "./frontend.js",
"tags": ["options"],
"promptStripTags": ["options"]
}full-stack — Backend + Frontend + Tags + Prompt
{
"name": "state",
"version": "1.0.0",
"description": "A complete state tracking system.",
"type": "full-stack",
"backendModule": "./handler.js",
"frontendModule": "./frontend.js",
"tags": ["UpdateVariable", "update"],
"promptStripTags": ["UpdateVariable"],
"promptFragments": [
{ "file": "./state.md", "variable": "state", "priority": 100 }
]
}full-stack — Backend + Tags + Display Stripping (No Prompt, No Frontend)
{
"name": "user-message",
"version": "1.0.0",
"description": "User message lifecycle: wrap input in tags, strip from context and display",
"type": "full-stack",
"backendModule": "./handler.ts",
"displayStripTags": ["user_message"],
"tags": ["user_message"],
"promptStripTags": ["user_message"]
}frontend-only
{
"name": "response-notify",
"version": "1.0.0",
"description": "Browser notification when LLM response generation completes",
"type": "frontend-only",
"frontendModule": "./frontend.js"
}full-stack — Prompt + Backend + Both Strip Types
{
"name": "context-compaction",
"version": "1.0.0",
"description": "Tiered context compaction via inline chapter summaries",
"type": "full-stack",
"promptFragments": [
{ "file": "./chapter-summary-instruction.md", "variable": "context_compaction", "priority": 800 }
],
"promptStripTags": ["chapter_summary"],
"displayStripTags": ["chapter_summary"],
"backendModule": "./handler.ts"
}