### Run Plugin SDK Tests Source: https://github.com/lilimozi/openhanako/blob/main/examples/plugins/sdk-showcase/README.md Execute tests for the plugin SDK examples from the repository root. ```bash npm test -- tests/plugin-sdk-examples.test.ts ``` -------------------------------- ### Basic Plugin UI with Components Source: https://github.com/lilimozi/openhanako/blob/main/PLUGIN_SDK.md Create a simple plugin panel using components from `@hana/plugin-components`. This example demonstrates setting up a theme provider and a card with a button. ```tsx import { Button, CardShell, HanaThemeProvider } from '@hana/plugin-components'; import '@hana/plugin-components/styles.css'; export function Panel() { return ( ); } ``` -------------------------------- ### Development Commands for OpenHanako Source: https://github.com/lilimozi/openhanako/blob/main/README_EN.md Common commands for installing dependencies, starting the application in different modes, running tests, and type checking. ```bash npm install ``` ```bash npm start ``` ```bash npm run start:vite ``` ```bash npm run server ``` ```bash npm run cli ``` ```bash npm test ``` ```bash npm run typecheck ``` -------------------------------- ### Example Commit Message Format Source: https://github.com/lilimozi/openhanako/blob/main/skills2set/skill-creator/SKILL.md An example of how to format commit messages, including a feature addition. ```markdown ## Commit message format **Example 1:** Input: Added user authentication with JWT tokens Output: feat(auth): implement JWT-based authentication ``` -------------------------------- ### Configuration Schema Example Source: https://github.com/lilimozi/openhanako/blob/main/PLUGINS_EN.md An example of a JSON Schema for plugin configuration, defining properties like 'interval' and 'sound' with their types and default values. Configuration is managed via `ctx.config`. ```json { "contributes": { "configuration": { "properties": { "interval": { "type": "number", "default": 25, "title": "Work interval (minutes)" }, "sound": { "type": "boolean", "default": true, "title": "Completion sound" } } } } } ``` -------------------------------- ### Launch Evaluation Viewer Source: https://github.com/lilimozi/openhanako/blob/main/skills2set/skill-creator/SKILL.md Start the evaluation viewer to review qualitative and quantitative results. It can be launched in the background. ```bash nohup python /eval-viewer/generate_review.py \ /iteration-N \ --skill-name "my-skill" \ --benchmark /iteration-N/benchmark.json \ > /dev/null 2>&1 & VIEWER_PID=$! ``` -------------------------------- ### Full Plugin Runtime Example Source: https://github.com/lilimozi/openhanako/blob/main/packages/plugin-runtime/README.md Demonstrates the usage of various helper functions for creating agents, sessions, sampling text, sending messages, subscribing to events, generating images and media, and transcribing audio within a plugin's onload function. ```typescript import { createAgent, createSession, generateMedia, generateImage, transcribeAudio, sampleText, sendSessionMessage, subscribeSessionEvents, } from '@hana/plugin-runtime'; export default definePlugin({ async onload(ctx, { register }) { const agent = await createAgent(ctx, { name: 'Tavern Character', visibility: 'plugin_private', memoryPolicy: { enabled: true }, }); const session = await createSession(ctx, { agentId: (agent as any).agent.id, kind: 'tavern', visibility: 'plugin_private', cwd: ctx.dataDir, }); const query = await sampleText(ctx, { operation: 'tavern-rag-query', messages: [{ role: 'user', content: 'Extract world-lore keywords for this turn' }], maxTokens: 80, }); await sendSessionMessage(ctx, (session as any).sessionPath, { text: 'I push the door open.', context: { beforeUser: [ { label: 'world', text: 'Rainy city night; the old theater is still open.' }, { label: 'rag_query', text: (query as any).text }, ], }, }); register(subscribeSessionEvents(ctx, (session as any).sessionPath, (event) => { ctx.log.info('session event', event); })); await generateImage(ctx, { sessionPath: (session as any).sessionPath, prompt: 'A handwritten character card on warm paper', referenceImages: [ { kind: 'session_file', fileId: 'sf_reference_a' }, { kind: 'session_file', fileId: 'sf_reference_b' }, ], ratio: '3:2', }); await generateMedia(ctx, { kind: 'video', sessionPath: (session as any).sessionPath, prompt: 'A slow page-turn animation on warm paper', }); const transcription = await transcribeAudio(ctx, { sessionPath: (session as any).sessionPath, fileId: 'session-file-id', }); ctx.log.info('transcription', transcription); }, }); ``` -------------------------------- ### Agent Template Example Source: https://github.com/lilimozi/openhanako/blob/main/PLUGINS_EN.md Defines a basic agent with a name, system prompt, default model, and default tools. ```json { "name": "Translator", "systemPrompt": "You are a translator.", "defaultModel": "gpt-4o", "defaultTools": ["web-search"] } ``` -------------------------------- ### Basic Plugin Panel Example Source: https://github.com/lilimozi/openhanako/blob/main/packages/plugin-components/README.md Demonstrates the basic structure of a plugin panel using various components from @hana/plugin-components. Ensure styles are imported for proper rendering. ```tsx import { Button, CardShell, HanaThemeProvider, SettingRow, Switch, } from '@hana/plugin-components'; import '@hana/plugin-components/styles.css'; export function PluginPanel() { return ( } /> ); } ``` -------------------------------- ### Initialize and Interact with Hana SDK Source: https://github.com/lilimozi/openhanako/blob/main/packages/plugin-sdk/README.md Initializes the SDK, accesses assets, resizes the UI, and shows a toast message. This snippet demonstrates basic SDK setup and common interactions. ```typescript import { hana } from '@hana/plugin-sdk'; hana.ready(); const logoUrl = hana.assets.url('images/logo.svg'); hana.ui.resize({ height: 320 }); await hana.toast.show({ message: 'Saved', type: 'success' }); await hana.external.open('https://example.com'); await hana.clipboard.writeText('Copied text'); ``` -------------------------------- ### Initialize Hana SDK and Show Toast Source: https://github.com/lilimozi/openhanako/blob/main/PLUGIN_SDK.md Import and initialize the Hana SDK, then display a toast message. This is a basic setup for interacting with Hana's core functionalities. ```typescript import { hana } from '@hana/plugin-sdk'; hana.ready(); hana.ui.resize({ height: 320 }); await hana.toast.show({ message: 'Ready' }); ``` -------------------------------- ### Full Plugin Manifest Example Source: https://github.com/lilimozi/openhanako/blob/main/PLUGINS_EN.md A comprehensive manifest file detailing plugin ID, version, trust level, activation events, capabilities, UI host capabilities, contribution schema, and dependencies. ```json { "manifestVersion": 1, "id": "my-plugin", "name": "My Plugin", "version": "1.0.0", "description": "What this plugin does", "trust": "full-access", "activationEvents": ["onToolCall:search"], "capabilities": ["session", "agent", "model.sample", "media.generate"], "sensitiveCapabilities": ["filesystem.write"], "ui": { "hostCapabilities": ["external.open"] }, "contributes": { "configuration": { ... } }, "depends": { "capabilities": ["bridge:send"] } } ``` -------------------------------- ### Scaffold a Beginner Direct Plugin Source: https://github.com/lilimozi/openhanako/blob/main/skills2set/hana-plugin-creator/SKILL.md Generate a basic Hana plugin using the 'direct' template, suitable for beginners. This command creates a plugin with no npm install or build step required. ```bash python3 skills2set/hana-plugin-creator/scripts/create_hana_plugin.py "My Plugin" --path examples/plugins --audience beginner --template direct ``` -------------------------------- ### Request-Level Context Example Source: https://github.com/lilimozi/openhanako/blob/main/PLUGINS_EN.md Demonstrates accessing the request-level context ('pluginRequestContext') to interact with the session bus, such as creating a new session. ```javascript app.post("/create-session", async (c) => { const reqCtx = c.get("pluginRequestContext"); // reqCtx.principal identity behind this request (owner device / this plugin's iframe surface…), null when invoked directly in tests // reqCtx.agentId current agent id injected by the proxy layer // reqCtx.capabilityGrant { accessLevel, declaredPermissions, legacyDeclaration } const result = await reqCtx.bus.request("session:create", { agentId: reqCtx.agentId }); return c.json(result); }); ``` -------------------------------- ### Hello Tool Plugin Example Source: https://github.com/lilimozi/openhanako/blob/main/PLUGINS_EN.md A basic JavaScript tool plugin that responds to a 'hello' command with a personalized greeting. It defines the tool's name, description, parameters, and execution logic. ```javascript // tools/hello.js export const name = "hello"; export const description = "Say hello to someone"; export const parameters = { type: "object", properties: { name: { type: "string" } }, required: ["name"], }; export async function execute(input) { return `Hello, ${input.name}!`; } ``` -------------------------------- ### Plugin Panel Component Source: https://github.com/lilimozi/openhanako/blob/main/PLUGINS_EN.md Example of a Plugin Panel component using HanaThemeProvider, CardShell, SettingRow, Switch, and Button from @hana/plugin-components. Ensure @hana/plugin-components/styles.css is imported. ```javascript import { Button, CardShell, HanaThemeProvider, SettingRow, Switch } from "@hana/plugin-components"; import "@hana/plugin-components/styles.css"; export function PluginPanel() { return ( } /> ); } ``` -------------------------------- ### GET /webhook Source: https://github.com/lilimozi/openhanako/blob/main/PLUGINS.md A webhook endpoint that demonstrates accessing plugin context via Hono middleware. ```APIDOC ## GET /webhook ### Description A webhook endpoint that demonstrates accessing plugin context via Hono middleware. ### Method GET ### Endpoint /api/plugins/{pluginId}/webhook ### Response #### Success Response (200) - **ok** (boolean) - Indicates success. - **pluginId** (string) - The ID of the plugin. #### Response Example { "ok": true, "pluginId": "your-plugin-id" } ``` -------------------------------- ### Register a Background Task Instance Source: https://github.com/lilimozi/openhanako/blob/main/PLUGINS_EN.md Register a task instance every time a background task starts. This allows HanaAgent to track and manage the task. ```js await this.ctx.bus.request("task:register", { taskId: "my-task-123", type: "my-task-type", parentSessionPath: sessionPath, meta: { type: "my-task", prompt: "..." }, }); ``` -------------------------------- ### Reference Host-Provided Theme CSS Source: https://github.com/lilimozi/openhanako/blob/main/PLUGINS.md Plugins can reference the host-provided theme CSS to maintain visual consistency by dynamically getting the 'hana-css' parameter from the URL. ```html ``` -------------------------------- ### Initialize Plugin SDK and Send Host Requests Source: https://github.com/lilimozi/openhanako/blob/main/PLUGINS.md Use the @hana/plugin-sdk to initialize the plugin, resize the UI, display toasts, open external links, and interact with the clipboard. Ensure necessary capabilities are declared in the manifest. ```javascript import { hana } from '@hana/plugin-sdk'; hana.ready(); hana.ui.resize({ height: 320 }); await hana.toast.show({ message: '已刷新', type: 'success' }); await hana.external.open('https://example.com'); await hana.clipboard.writeText('复制内容'); ``` -------------------------------- ### Initialize Plugin SDK and Host Interactions Source: https://github.com/lilimozi/openhanako/blob/main/PLUGINS_EN.md Use the @hana/plugin-sdk to signal readiness, resize the plugin iframe, display toasts, open external links, and interact with the clipboard. Ensure necessary host capabilities are declared in the manifest. ```javascript import { hana } from '@hana/plugin-sdk'; hana.ready(); hana.ui.resize({ height: 320 }); await hana.toast.show({ message: 'Refreshed', type: 'success' }); await hana.external.open('https://example.com'); await hana.clipboard.writeText('Copied text'); ``` -------------------------------- ### Initialize and Render Benchmark Source: https://github.com/lilimozi/openhanako/blob/main/skills2set/skill-creator/eval-viewer/viewer.html Calls the initialization function and then the renderBenchmark function to display the benchmark results. This is the main entry point for rendering. ```javascript init(); renderBenchmark(); ``` -------------------------------- ### GET /status Source: https://github.com/lilimozi/openhanako/blob/main/PLUGINS.md Retrieves the plugin's status, including its ID, using the register export syntax. ```APIDOC ## GET /status ### Description Retrieves the plugin's status, including its ID. ### Method GET ### Endpoint /api/plugins/{pluginId}/status ### Response #### Success Response (200) - **pluginId** (string) - The ID of the plugin. #### Response Example { "pluginId": "your-plugin-id" } ``` -------------------------------- ### Build Packages Source: https://github.com/lilimozi/openhanako/blob/main/examples/plugins/sdk-showcase/README.md Build the necessary packages for the plugin SDK showcase. ```bash npm run build:packages ``` -------------------------------- ### Get Asset URL Source: https://github.com/lilimozi/openhanako/blob/main/PLUGIN_SDK.md Generate a URL for a file bundled within the plugin's assets directory. Use this for referencing images, scripts, or other static files in the browser. ```typescript const logoUrl = hana.assets.url('images/logo.svg'); ``` -------------------------------- ### Class-based Stateful Plugin with Lifecycle Methods Source: https://github.com/lilimozi/openhanako/blob/main/PLUGINS_EN.md The traditional class-based plugin form is supported, allowing explicit definition of onload and onunload methods for resource management and setup. ```javascript import { HANA_BUS_SKIP } from "@hana/plugin-runtime"; export default class MyPlugin { async onload() { // ctx is injected by PluginManager: // this.ctx.bus — EventBus (full: emit/subscribe/request/handle) // this.ctx.config — Config read/write (get/set) // this.ctx.dataDir — Private data directory path // this.ctx.log — Logger with pluginId prefix // this.ctx.pluginId — Plugin ID // this.ctx.pluginDir — Plugin installation directory // this.ctx.registerTool — Dynamic tool registration (returns cleanup function) // Resources registered via register() are auto-cleaned on unload (reverse order) this.register( this.ctx.bus.handle("bridge:send", async (payload) => { if (payload.platform !== "feishu") return HANA_BUS_SKIP; await this.sendToFeishu(payload); return { sent: true }; }) ); this.ws = await this.connect(); } async onunload() { // Resources from register() are auto-cleaned, no manual unhandle needed // Only clean up things the framework can't manage this.ws?.close(); } } ``` -------------------------------- ### Create Agent and Session Source: https://github.com/lilimozi/openhanako/blob/main/PLUGINS_EN.md Demonstrates creating a private agent and a private session for a specific kind. ```javascript import { createAgent, createSession, generateMedia, generateImage, transcribeAudio, sampleText, sendSessionMessage, } from "@hana/plugin-runtime"; const agent = await createAgent(ctx, { name: "Tavern Character", visibility: "plugin_private", memoryPolicy: { enabled: true }, }); const session = await createSession(ctx, { agentId: agent.agent.id, kind: "tavern", visibility: "plugin_private", cwd: ctx.dataDir, }); ``` -------------------------------- ### Generate Image with Response Delivery Source: https://github.com/lilimozi/openhanako/blob/main/packages/plugin-runtime/README.md Example of generating an image using the `generateImage` helper with `delivery: { mode: 'response' }`. This mode omits `sessionPath` and requires polling for task completion. ```typescript const result = await generateImage(ctx, { prompt: 'A small icon on transparent background', delivery: { mode: 'response' }, }); ``` -------------------------------- ### Provider Plugin with Local CLI Runtime Source: https://github.com/lilimozi/openhanako/blob/main/PLUGINS.md Configure a provider that runs a local command-line interface for media generation. It specifies the command, arguments, and output pattern. ```javascript export const id = "my-image-cli"; export const displayName = "My Image CLI"; export const authType = "none"; export const runtime = { kind: "local-cli", protocolId: "local-cli-media", command: { executable: "my-image-cli", args: [ { literal: "generate" }, { option: "--prompt", from: "prompt" }, { option: "--model", from: "modelId" }, { option: "--output", from: "outputDir" }, ], timeoutMs: 120000, output: { kind: "file_glob", directory: "outputDir", pattern: "*.png" }, }, }; export const capabilities = { chat: { projection: "none" }, media: { imageGeneration: { models: [ { id: "my-image-model", displayName: "My Image Model", protocolId: "local-cli-media", inputs: ["text"], outputs: ["image"], }, ], }, }, }; ``` -------------------------------- ### Runtime Session and Agent API Usage Source: https://github.com/lilimozi/openhanako/blob/main/PLUGIN_SDK.md Demonstrates how to create an agent and a session, send a message with contextual information, and subscribe to session events using the `@hana/plugin-runtime` SDK. Use `visibility: 'plugin_private'` to keep plugin-owned agents and sessions out of default lists. ```javascript import { createAgent, createSession, getAgentProfile, listSessions, sendSessionMessage, subscribeSessionEvents, } from '@hana/plugin-runtime'; const agent = await createAgent(ctx, { name: 'Tavern Character', visibility: 'plugin_private', memoryPolicy: { enabled: true }, }); const session = await createSession(ctx, { agentId: agent.agent.id, kind: 'tavern', visibility: 'plugin_private', memoryEnabled: true, cwd: ctx.dataDir, }); await sendSessionMessage(ctx, session.sessionPath, { text: 'Hello', context: { beforeUser: [ { label: 'world', text: 'The city is under moonlit rain.' }, { label: 'mood', text: 'Keep the reply intimate and quiet.' }, ], }, }); const off = subscribeSessionEvents(ctx, session.sessionPath, (event) => { ctx.log.debug('session event', event); }); ``` -------------------------------- ### Extension API for Event Interception Source: https://github.com/lilimozi/openhanako/blob/main/PLUGINS.md Intercept LLM call chain events using the ExtensionAPI provided by the Pi SDK. This example removes empty tools from the payload before a provider request. ```javascript // extensions/strip-empty-tools.js export default function(pi) { pi.on("before_provider_request", (event) => { const p = event.payload; if (p && Array.isArray(p.tools) && p.tools.length === 0) { delete p.tools; } return p; }); } ``` -------------------------------- ### Stage and Deliver Local Files Source: https://github.com/lilimozi/openhanako/blob/main/PLUGINS_EN.md Stage a local file as a SessionFile and return it in details.media.items for framework delivery. Ensure local files are registered via stageFile() and not bypassed. ```javascript import { createMediaDetails } from "@hana/plugin-runtime"; const staged = toolCtx.stageFile({ sessionPath: toolCtx.sessionPath, filePath: "/path/to/image.png", label: "image.png", }); return { content: [{ type: "text", text: "Image generated" }], details: createMediaDetails([staged]), }; ``` -------------------------------- ### SKILL.md File Structure Source: https://github.com/lilimozi/openhanako/blob/main/skills2set/skill-creator/SKILL.md Illustrates the basic directory structure for a skill, including the required SKILL.md file and optional bundled resources like scripts, references, and assets. ```markdown skill-name/ ├── SKILL.md (required) │ ├── YAML frontmatter (name, description required) │ └── Markdown instructions └── Bundled Resources (optional) ├── scripts/ - Executable code for deterministic/repetitive tasks ├── references/ - Docs loaded into context as needed └── assets/ - Files used in output (templates, icons, fonts) ``` -------------------------------- ### Scaffold a Developer Professional React Plugin Source: https://github.com/lilimozi/openhanako/blob/main/skills2set/hana-plugin-creator/SKILL.md Generate a Hana plugin with a professional React setup, including Vite and SDK starter. This option is for developers who expect package scripts and typed UI code. ```bash python3 skills2set/hana-plugin-creator/scripts/create_hana_plugin.py "My Plugin" --path examples/plugins --audience developer --template professional-react --sdk-mode workspace ``` -------------------------------- ### Correct Session Operations in Hana Plugins Source: https://github.com/lilimozi/openhanako/blob/main/PLUGINS_EN.md Demonstrates the correct way to perform session operations like sending messages and aborting sessions by explicitly including the sessionPath. Also shows how to attach per-turn context. ```javascript // Correct: explicitly specify sessionPath and attach per-turn context if needed await bus.request("session:send", { text: "Hello", sessionPath: "/path/to/session.jsonl", context: { beforeUser: [{ label: "world", text: "The character is in a quiet archive." }], }, }); await bus.request("session:abort", { sessionPath: "/path/to/session.jsonl", }); ``` ```javascript // Wrong: omitting sessionPath may target the wrong session under concurrency await bus.request("session:send", { text: "Hello" }); ``` -------------------------------- ### Package Skill Source: https://github.com/lilimozi/openhanako/blob/main/skills2set/skill-creator/SKILL.md Use this command to package a skill folder into a .skill file. This requires the `stage_files` tool to be available. ```bash python -m scripts.package_skill ``` -------------------------------- ### List and Subscribe to Usage Events Source: https://github.com/lilimozi/openhanako/blob/main/packages/plugin-runtime/README.md Demonstrates how to list past LLM usage entries and subscribe to new usage events using `listUsageEntries` and `subscribeUsageEvents` helpers. Requires `usage.read` capability. ```typescript import { definePlugin, listUsageEntries, subscribeUsageEvents } from '@hana/plugin-runtime'; export default definePlugin({ async onload(ctx, { register }) { const usage = await listUsageEntries(ctx, { since: '2026-05-01T00:00:00.000Z', limit: 100, }); ctx.log.info('usage records', usage.entries.length); register(subscribeUsageEvents(ctx, (entry, meta) => { ctx.log.info('new usage entry', entry.requestId, meta.sessionPath); })); }, }); ``` -------------------------------- ### Media API - SDK Helpers Source: https://github.com/lilimozi/openhanako/blob/main/PLUGIN_SDK.md The Media API provides SDK helpers for interacting with configured media providers. Functions like `listMediaProviders`, `resolveMediaModel`, `generateImage`, `generateVideo`, `generateMedia`, and `transcribeAudio` allow plugins to manage and generate media content. ```APIDOC ## Media SDK Helpers ### Description Provides functions to interact with Hana's media provider stack for listing providers, resolving models, generating images/videos, and transcribing audio. ### Available Functions * `listMediaProviders()`: Reads configured image/video/speech-capable providers. * `resolveMediaModel(modelId)`: Resolves a specific media model. * `generateImage(ctx, options)`: Submits an image generation task. * `generateVideo(ctx, options)`: Submits a video generation task. * `generateMedia(ctx, options)`: Submits a general media generation task. * `transcribeAudio(ctx, options)`: Transcribes audio input. ### `generateImage` Options * `sessionPath` (string, Optional): Path for session files. Omit when `delivery.mode` is 'response'. * `prompt` (string): The prompt for image generation. * `referenceImages` (Array<{ kind: string, fileId: string }>, Optional): References to images for generation. * `ratio` (string, Optional): The aspect ratio of the generated image. * `resolution` (string, Optional): The resolution of the generated image. * `delivery` ({ mode: string }): Specifies the delivery mode. If 'response', `sessionPath` can be omitted and no `SessionFile` records are created. ### `transcribeAudio` Options * `sessionPath` (string): Required for ASR tasks. * `fileId` (string): Required for ASR tasks. ### Response for `transcribeAudio` * `ok` (boolean): Indicates success. * `transcription` (string): The transcribed text. ``` -------------------------------- ### Media API - Direct HTTP Endpoints Source: https://github.com/lilimozi/openhanako/blob/main/PLUGIN_SDK.md Plugins with host HTTP credentials can directly submit requests to the native media facade endpoints for media generation and audio transcription. ```APIDOC ## Native Media Facade Endpoints ### Description Direct HTTP endpoints for submitting media generation and audio transcription tasks. ### Endpoints * `POST /api/media/generate` * `POST /api/media/image/generate` * `POST /api/media/video/generate` * `POST /api/media/asr/transcribe` ### Common Parameters * `prompt` (string): Required for image/video generation. * `sessionPath` (string): Required for image/video generation with `delivery.mode = "session"` and for all ASR tasks. * `delivery` ({ mode: string }): Specifies the delivery mode. Defaults to 'session'. If 'response', `sessionPath` can be omitted for image/video requests. ### Request Body Example (Image Generation with Response Delivery) ```json { "prompt": "A small icon on transparent background", "delivery": { "mode": "response" } } ``` ### Request Body Example (ASR Transcription) ```json { "sessionPath": "path/to/session", "fileId": "sf_audio_file" } ``` ### Notes * Image reference fields accept only `SessionFile` references (e.g., `{ "kind": "session_file", "fileId": "sf_..." }`). * These routes require chat-scope host credentials. ``` -------------------------------- ### Create Hana Plugin Scaffold Source: https://github.com/lilimozi/openhanako/blob/main/PLUGINS_EN.md A bash command to scaffold a new Hana plugin using the hana-plugin-creator tool. It specifies the plugin name, output path, and kind (e.g., 'full' access). ```bash python3 skills2set/hana-plugin-creator/scripts/create_hana_plugin.py "My Plugin" --path examples/plugins --kind full ``` -------------------------------- ### Define a Tool to Render an Image Source: https://github.com/lilimozi/openhanako/blob/main/packages/plugin-runtime/README.md Use `defineTool` to create a tool that stages a local file and returns media details. The `stageFile` function is used for plugin-generated local files, and `createMediaDetails` normalizes file information for various clients. ```typescript import { createMediaDetails, defineTool } from '@hana/plugin-runtime'; export const renderImage = defineTool({ name: 'render_image', description: 'Render an image', async execute(_input, ctx) { const staged = ctx.stageFile?.({ sessionPath: ctx.sessionPath, filePath: '/absolute/path/to/image.png', label: 'image.png', }); if (!staged) throw new Error('stageFile unavailable'); return { content: [{ type: 'text', text: 'Image generated' }], details: createMediaDetails([staged]), }; }, }); ``` -------------------------------- ### Define a Tool and Plugin Source: https://github.com/lilimozi/openhanako/blob/main/packages/plugin-runtime/README.md Defines a 'search' tool with parameters and registers it within a plugin's lifecycle. Use this to create reusable tools that can be invoked by the Hana runtime. ```typescript import { definePlugin, defineTool } from '@hana/plugin-runtime'; export const searchTool = defineTool({ name: 'search', description: 'Search project data', parameters: { type: 'object', properties: { query: { type: 'string' }, }, required: ['query'], }, async execute(input, ctx) { ctx.log.info('searching', input); return `results for ${input.query}`; }, }); export default definePlugin({ async onload(ctx, { register }) { if (ctx.registerTool) { register(ctx.registerTool(searchTool)); } }, }); ``` -------------------------------- ### Define Local CLI Media Provider Source: https://github.com/lilimozi/openhanako/blob/main/PLUGIN_SDK.md Use defineProvider for TypeScript-friendly authoring of a local CLI media provider. Specify command details and argument mapping. ```javascript import { defineProvider } from '@hana/plugin-runtime'; const provider = defineProvider({ id: 'my-image-cli', displayName: 'My Image CLI', authType: 'none', runtime: { kind: 'local-cli', protocolId: 'local-cli-media', command: { executable: 'my-image-cli', args: [ { literal: 'generate' }, { option: '--prompt', from: 'prompt' }, { option: '--model', from: 'modelId' }, { option: '--output', from: 'outputDir' }, ], timeoutMs: 120000, }, }); ``` -------------------------------- ### Domain-Specific Skill Organization Source: https://github.com/lilimozi/openhanako/blob/main/skills2set/skill-creator/SKILL.md Demonstrates how to organize skills that support multiple domains or frameworks by creating separate reference files for each, ensuring Claude reads only the relevant documentation. ```markdown cloud-deploy/ ├── SKILL.md (workflow + selection) └── references/ ├── aws.md ├── gcp.md └── azure.md ``` -------------------------------- ### Run Skill Optimization Loop Source: https://github.com/lilimozi/openhanako/blob/main/skills2set/skill-creator/SKILL.md Execute the optimization loop using a Python script. This process splits the eval set, evaluates the current description, and iteratively proposes improvements. ```bash python -m scripts.run_loop \ --eval-set \ --skill-path \ --model \ --max-iterations 5 \ --verbose ``` -------------------------------- ### Lazy Activation with Activation Events Source: https://github.com/lilimozi/openhanako/blob/main/PLUGINS_EN.md Configure a plugin to activate on specific events, such as a tool call, to defer execution until needed. ```json { "id": "lazy-search", "trust": "full-access", "activationEvents": ["onToolCall:search"], "contributes": { "page": { "title": "Search", "route": "/search" } } } ``` -------------------------------- ### Define a Provider for Image Generation Source: https://github.com/lilimozi/openhanako/blob/main/packages/plugin-runtime/README.md Use `defineProvider` to register a local CLI provider for image generation. This includes specifying the command to execute, arguments, and expected output. Ensure chat and media capabilities are explicitly defined. ```typescript import { defineProvider } from '@hana/plugin-runtime'; const provider = defineProvider({ id: 'my-image-cli', displayName: 'My Image CLI', authType: 'none', runtime: { kind: 'local-cli', protocolId: 'local-cli-media', command: { executable: 'my-image-cli', args: [ { literal: 'generate' }, { option: '--prompt', from: 'prompt' }, { option: '--model', from: 'modelId' }, { option: '--output', from: 'outputDir' }, ], timeoutMs: 120_000, output: { kind: 'file_glob', directory: 'outputDir', pattern: '*.png' }, }, }, capabilities: { chat: { projection: 'none' }, media: { imageGeneration: { models: [ { id: 'my-image-model', displayName: 'My Image Model', protocolId: 'local-cli-media', inputs: ['text'], outputs: ['image'], }, ], }, }, }, }); export const { id, displayName, authType, runtime, capabilities } = provider; ``` -------------------------------- ### Run Environment Preflight Check Source: https://github.com/lilimozi/openhanako/blob/main/skills2set/hana-plugin-creator/SKILL.md Execute the Node.js preflight script to verify the environment before scaffolding a Hana plugin. This script checks for Python 3.10+ and other necessary dependencies. ```bash node skills2set/hana-plugin-creator/scripts/check_env.mjs --capability scaffold ``` -------------------------------- ### Define and Handle Bus Capabilities with Schemas Source: https://github.com/lilimozi/openhanako/blob/main/PLUGINS_EN.md Use defineBusHandler for creating capabilities with explicit input/output schemas, permissions, and error definitions. This promotes type safety and clear contracts for inter-plugin communication. ```javascript import { defineBusHandler, HANA_BUS_SKIP, requestBus } from "@hana/plugin-runtime"; // Plugin A (full-access): register a capability const bridgeSend = defineBusHandler({ type: "bridge:send", async handle(payload) { if (payload.platform !== "telegram") return HANA_BUS_SKIP; await telegramBot.send(payload.chatId, payload.text); return { sent: true }; }, }); this.register(this.ctx.bus.handle( bridgeSend.type, (payload) => bridgeSend.handle(payload, this.ctx), { capability: { title: "Bridge send", description: "Send text to a bridge platform.", inputSchema: { type: "object", properties: { platform: { type: "string" }, chatId: { type: "string" }, text: { type: "string" }, }, required: ["platform", "text"], }, outputSchema: { type: "object" }, permission: "bridge.send", errors: ["NO_HANDLER", "TIMEOUT", "INTERNAL_ERROR"], owner: "plugin:my-plugin", stability: "experimental", }, }, )); // Plugin B (any permission): call the capability const capability = this.ctx.bus.getCapability?.("bridge:send"); if (capability?.available) { const result = await requestBus(this.ctx, "bridge:send", { platform: "telegram", chatId: "123", text: "Hello", }, { timeout: 5000 }); } ``` -------------------------------- ### Declare Host Capabilities in Manifest Source: https://github.com/lilimozi/openhanako/blob/main/packages/plugin-sdk/README.md Declares necessary host capabilities in the 'manifest.json' file for features like opening external links or accessing the clipboard. ```json { "manifestVersion": 1, "ui": { "hostCapabilities": ["external.open", "clipboard.writeText"] } } ``` -------------------------------- ### Run Full Validation Suite Source: https://github.com/lilimozi/openhanako/blob/main/tests/README.md Execute the complete test suite, type checking, and linting before committing or handing off changes. This ensures all aspects of the project are validated. ```bash npm test npm run typecheck npm run lint ``` -------------------------------- ### Execute Baseline Run Command Source: https://github.com/lilimozi/openhanako/blob/main/skills2set/skill-creator/SKILL.md Command to execute a task as a baseline, either without a skill or using an older version. ```bash Baseline run (same prompt, but the baseline depends on context): - Creating a new skill: no skill at all. Same prompt, no skill path, save to `without_skill/outputs/`. - Improving an existing skill: the old version. Before editing, snapshot the skill (`cp -r /skill-snapshot/`), then point the baseline subagent at the snapshot. Save to `old_skill/outputs/`. ``` -------------------------------- ### Global and Body Styles Source: https://github.com/lilimozi/openhanako/blob/main/desktop/src/splash.html Sets up box-sizing, resets margins and padding, defines full height for html and body, and styles the body with a specific font, background, border-radius, flexbox alignment, and a fade-in animation. ```css HanaAgent *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } html, body { height: 100%; overflow: hidden; } body { font-family: 'Songti SC', 'Georgia', serif; background: #F4F0E4; border-radius: 12px; display: flex; align-items: center; justify-content: center; -webkit-font-smoothing: antialiased; -webkit-app-region: drag; animation: fadeIn 0.3s ease-out; } ``` -------------------------------- ### Execute With-Skill Run Command Source: https://github.com/lilimozi/openhanako/blob/main/skills2set/skill-creator/SKILL.md Command to execute a task with a specific skill, saving outputs to a designated path. ```bash Execute this task: - Skill path: - Task: - Input files: - Save outputs to: /iteration-/eval-/with_skill/outputs/ - Outputs to save: ``` -------------------------------- ### Call Text Model for Background Work Source: https://github.com/lilimozi/openhanako/blob/main/PLUGIN_SDK.md Use the sampleText utility to call the configured text model for operations like routing, summarization, or RAG query rewriting. Specify the operation, messages, and maxTokens. ```javascript const { text } = await sampleText(ctx, { operation: 'world-lore-query', messages: [{ role: 'user', content: 'Extract search keywords for this scene.' }], maxTokens: 120, }); ``` -------------------------------- ### Import Core Plugin Runtime Functions Source: https://github.com/lilimozi/openhanako/blob/main/PLUGIN_SDK.md Import essential functions from the `@hana/plugin-runtime` package for Node-side plugin development. These functions are used for creating agents, sessions, defining plugins and tools, and managing tasks. ```javascript import { createAgent, createSession, definePlugin, defineTool, generateImage, registerTask, requestBus, sampleText, sendSessionMessage, } from '@hana/plugin-runtime'; ```