5分でプラグインを作成
すべてJavaScriptで動作 — SDKはYokiに内蔵、npm installは不要。このガイドで最初のプラグインを一から作成できます。
ステップ1:フォルダを作成
各プラグインは ~/yoki/plugins/ 内の専用フォルダにあります:
mkdir ~/yoki/plugins/my-pluginまたはCLIで自動生成:
npx create-yoki-pluginplugin.json + main.js をすぐに生成ステップ2:plugin.jsonを追加
このファイルはプラグインの情報をYokiに伝えます — 名前、キーワード、実行するスクリプト。
{
"name": "My Plugin",
"keyword": "my",
"icon": "star",
"version": "1.0.0",
"protocol": "v2",
"commands": [
{
"name": "main",
"title": "My Plugin",
"mode": "detail",
"exec": "main.js",
"takes_query": true
}
]
}ステップ3:スクリプトを書く
スクリプトはYokiから入力を読み取り、処理してレスポンスを返します。
const { readInput, writeResponse, detail } = require("@yoki/plugin-sdk")
async function main() {
const input = await readInput()
const query = input.query || ""
if (!query) {
writeResponse(detail('<div style="padding:16px;color:#888">Type something after <b>my</b></div>'))
return
}
writeResponse(detail(
`<div style="padding:16px">
<div style="font-size:24px;font-weight:bold;color:#4FC3F7">${query.toUpperCase()}</div>
<div style="margin-top:8px;color:#888">Your input, uppercased</div>
</div>`,
[{ label: "Original", value: query }, { label: "Result", value: query.toUpperCase() }],
[{ title: "Copy result", type: "copy", value: query.toUpperCase() }]
))
}
main()ステップ4:テスト
Yokiなしでテスト — JSONをスクリプトにパイプ:
echo '{"query":"hello world","command":"main","context":{}}' | node main.js次のように表示されます:
{"type":"detail","markdown":"<div style=\"padding:16px\"><div style=\"font-size:24px;font-weight:bold;color:#4FC3F7\">HELLO WORLD</div><div style=\"margin-top:8px;color:#888\">Your input, uppercased</div></div>","metadata":[{"label":"Original","value":"hello world"},{"label":"Result","value":"HELLO WORLD"}],"actions":[{"title":"Copy result","type":"copy","value":"HELLO WORLD"}]}Yokiを開き、my hello world と入力 — 「HELLO WORLD」のカードが表示されます。Enterでコピー。
ステップ5:次のステップ
SDK全関数のリファレンスを読み進めてください。
SDKリファレンス
Yoki SDKの全関数と実例。インストール不要。
const { readInput, writeResponse, detail, list, background, error, stripKeyword, escHtml } = require("@yoki/plugin-sdk")readInput() — Reads what the user typed and context from Yoki.writeResponse(resp) — Sends your response back to Yoki.detail(html, metadata?, actions?) — Shows a rich card with HTML content, a metadata sidebar, and action buttons.list(items) — Shows a scrollable list of items.background(hud, notification?) — Runs an action without showing any UI.error(message, details?) — Shows an error message to the user.stripKeyword(query, ...keywords) — Removes a keyword prefix from the query string.escHtml(str) — Escapes HTML special characters (&, <, >, ").readInput()
Reads what the user typed and context from Yoki. Call this once at the start of your script. Returns an object with the user's query, which command was triggered, and context (paths, locale, credentials).
async readInput(): Promise<object>const input = await readInput()
const query = input.query // "hello world"const input = await readInput()
const dataDir = input.context.data_dir
// e.g. "C:/Users/you/yoki/plugins/my-plugin/data"
// Use this to store config, cache, tokens — it persists across runsconst input = await readInput()
const apiKey = input.context.credentials?.api_key || ""
if (!apiKey) {
writeResponse(error("API key not configured"))
return
}// input = {
// query: "hello world", — what the user typed after keyword
// command: "main", — which command (from plugin.json)
// action: "search", — "search" | "execute" | "refresh"
// context: {
// yoki_version: "1.0.8.2",
// os: "windows",
// locale: "en", — user's language
// plugin_dir: "C:/.../my-plugin",
// data_dir: "C:/.../my-plugin/data",
// credentials: { ... } — from credentials service
// },
// preferences: {}
// }writeResponse(resp)
Sends your response back to Yoki. Call this once at the end of your script. Pass the result from any builder function — detail(), list(), error(), etc.
writeResponse(resp: object): voidwriteResponse(detail("<div>Hello!</div>"))writeResponse(error("Something went wrong", "Please try again later"))detail(html, metadata?, actions?)
Shows a rich card with HTML content, a metadata sidebar, and action buttons. When the user presses Enter, the first action with type "copy" is triggered.
detail(
html: string,
metadata?: [{ label: string, value: string }, ...],
actions?: [{ title: string, type: string, value?: string }, ...]
)writeResponse(detail(
'<div style="padding:16px;font-size:20px">Hello, World!</div>'
))writeResponse(detail(
'<div style="padding:16px"><h2>2 + 2 = 4</h2></div>',
[
{ label: "Hex", value: "0x4" },
{ label: "Binary", value: "0b100" },
{ label: "Prime", value: "No" }
]
))writeResponse(detail(
'<div style="padding:16px;font-size:24px;color:#4FC3F7">42</div>',
[{ label: "Expression", value: "6 * 7" }],
[{ title: "Copy result", type: "copy", value: "42" }]
))writeResponse(detail(
'<div style="padding:16px"><h2>Now Playing</h2><p>Mind Mirage — Windows 96</p></div>',
[{ label: "Duration", value: "5:00" }],
[
{ title: "Pause", type: "yoki_run", value: "sp pause", variant: "primary" },
{ title: "Next", type: "yoki_run", value: "sp next" },
{ title: "Copy track", type: "copy", value: "Mind Mirage — Windows 96" },
{ title: "Open in Spotify", type: "open_url", url: "https://open.spotify.com/track/..." }
]
))list(items)
Shows a scrollable list of items. Each item has a title, subtitle, icon, and optional actions. Use for search results, bookmarks, file lists.
list(items: [{
id: string, title: string, subtitle?: string,
icon?: string, actions?: Action[]
}, ...])writeResponse(list([
{ id: "1", title: "First result", subtitle: "Description here" },
{ id: "2", title: "Second result", subtitle: "Another description" },
]))writeResponse(list([
{
id: "gh",
title: "GitHub",
subtitle: "https://github.com · Bookmarks Bar",
icon: "🌐",
actions: [
{ title: "Open", shortcut: "enter", type: "open_url", url: "https://github.com" },
{ title: "Copy URL", shortcut: "cmd+c", type: "copy", value: "https://github.com" }
]
}
]))const input = await readInput()
const query = input.query.toLowerCase()
const allItems = [
{ id: "1", title: "GitHub", subtitle: "Code hosting" },
{ id: "2", title: "Google", subtitle: "Search engine" },
{ id: "3", title: "Gmail", subtitle: "Email" },
]
const filtered = query
? allItems.filter(i => i.title.toLowerCase().includes(query))
: allItems
writeResponse(list(filtered))background(hud, notification?)
Runs an action without showing any UI. Shows a brief HUD toast. Optionally sends a system notification. The user must press Enter to trigger it (never fires on keystroke). Use for play/pause, save, toggle.
background(hud: string, notification?: { title: string, body: string })writeResponse(background("Done!"))writeResponse(background(
"Playing: Mind Mirage",
{ title: "Spotify", body: "Now playing: Mind Mirage — Windows 96" }
))error(message, details?)
Shows an error message to the user. Use when something goes wrong — missing input, API failure, runtime not installed. Plugin crashes are also caught automatically.
error(message: string, details?: string)writeResponse(error("Not found"))writeResponse(error("API request failed", "Check your internet connection and try again"))const input = await readInput()
if (!input.query) {
writeResponse(error("No query", "Type something after the keyword"))
return
}stripKeyword(query, ...keywords)
Removes a keyword prefix from the query string. Useful when your plugin has subcommands (e.g. "search", "play", "list").
stripKeyword(query: string, ...keywords: string[]): stringstripKeyword("search hello world", "search", "s")
// → "hello world"
stripKeyword("s hello world", "search", "s")
// → "hello world" (matches short alias too)
stripKeyword("search", "search", "s")
// → "" (exact match returns empty string)escHtml(str)
Escapes HTML special characters (&, <, >, "). Use this when inserting user input into your HTML to prevent broken rendering or XSS.
escHtml(s: string): stringconst input = await readInput()
const safe = escHtml(input.query)
writeResponse(detail(`<div>${safe}</div>`))
// input: "<b>bold</b>"
// safe: "<b>bold</b>"
// renders as text, not HTMLプロトコル v2
ユーザーがキーワードを入力すると、YokiがスクリプトをV2Input JSONをstdinに書き込み、stdoutからV2Responseを読みます。非表示ウィンドウ、5秒タイムアウト(設定可能)。
{
"version": "2.0",
"command": "greet",
"action": "search",
"query": "Ada",
"context": {
"yoki_version": "1.0.8.2",
"os": "windows",
"locale": "en",
"plugin_dir": "C:/Users/you/yoki/plugins/hello",
"data_dir": "C:/Users/you/yoki/plugins/hello/data",
"credentials": {}
},
"preferences": {}
}{
"type": "detail",
"markdown": "<div style=\"padding:16px\"><h2>Hello, Ada!</h2></div>",
"metadata": [
{ "label": "Name", "value": "Ada" }
],
"actions": [
{ "title": "Copy", "type": "copy", "value": "Hello, Ada!" }
]
}V2Inputフィールド
レスポンスモード
typeフィールドがYokiのレスポンス表示方法を決定します。
アイコン、タイトル、サブタイトル、アクション付きのスクロール可能なアイテム。
メタデータサイドバーとアクションボタン付きのHTMLカード。Enterで最初のcopyアクションをコピー。
列数設定可能なビジュアルグリッド。
UIなし — HUDトーストの副作用。Enterで起動。
複数フィールド入力フォーム。近日公開。
オプション詳細付きエラー。プラグインクラッシュは自動的にキャッチ。
detail mode
writeResponse(detail(
'<div style="padding:16px"><h2>Now Playing</h2><p>Mind Mirage — Windows 96</p></div>',
[{ label: "Duration", value: "5:00" }, { label: "Album", value: "Enchanted Instrumentals" }],
[{ title: "Pause", type: "yoki_run", value: "sp pause", variant: "primary" },
{ title: "Copy", type: "copy", value: "Mind Mirage" }]
))list mode
writeResponse(list([
{ id: "1", title: "Mind Mirage", subtitle: "Windows 96 · 5:00", icon: "music",
actions: [{ title: "Play", type: "yoki_run", value: "sp play spotify:track:xyz" },
{ title: "Copy", type: "copy", value: "Mind Mirage" }] }
]))background mode
writeResponse(background("Playing: Mind Mirage", { title: "Spotify", body: "Now playing" }))権限
プラグインはマニフェストで必要なものを宣言します。
認証情報
プラグインはAPIキーを埋め込まないでください。必要なものを宣言するだけ。
{
"name": "Spotify",
"keyword": "sp",
"protocol": "v2",
"credentials": {
"slug": "spotify",
"service": "spotify",
"keys": ["client_id"]
},
"commands": [...]
}# Plugin reads credentials from V2Input.context — never embeds them.
import sys, json
inp = json.loads(sys.stdin.read())
client_id = (inp.get("context") or {}).get("credentials", {}).get("client_id")
if not client_id:
# Yoki couldn't fetch creds (offline / not signed in) — surface a
# graceful setup-needed response instead of crashing.
print(json.dumps({"type": "error", "error": "Sign in to Yoki to use this plugin"}))
sys.exit(0)
# ... rest of plugin uses client_id ...マニフェストリファレンス
Yokiが理解する全フィールド。$schemaでIDE補完。
{
"$schema": "https://raw.githubusercontent.com/yoki-run/yoki/main/plugin.schema.json",
"name": "string (required) display name",
"keyword": "string (required) trigger word, e.g. 'sp'",
"icon": "string icon name or emoji",
"description": "string shown in Plugins Manager",
"version": "string semver, e.g. '1.0.0'",
"author": "string",
"homepage": "string url",
"license": "string spdx, e.g. 'MIT'",
"categories": "string[] e.g. ['media', 'tools']",
"yoki_min": "string min Yoki version, e.g. '1.0.8.0'",
"protocol": "'v2'",
"permissions": {
"network": "string[] hostname whitelist for HTTPS",
"filesystem": "string[] 'read:./data', 'write:./data'",
"clipboard": "boolean",
"notifications": "boolean",
"shell": "boolean",
"ai": "boolean"
},
"credentials": {
"slug": "string defaults to keyword",
"service": "string informational",
"keys": "string[] credential names"
},
"commands": [
{
"name": "string (required) internal id",
"title": "string (required) user-facing label",
"mode": "'list' | 'detail' | 'grid' | 'background' | 'form'",
"exec": "string (required) script path relative to plugin dir",
"keywords": "string[] sub-keyword aliases",
"takes_query": "boolean true = query passed verbatim",
"refresh": "number ms — auto re-run interval",
"timeout": "number ms — default 5000"
}
]
}アクションタイプ
ボタンやリストアイテムのaction.typeを設定:
アクションアイコン
iconフィールドの3つの形式:
プラグイン検出
ローカルテスト
# Yokiなしでプラグインをテスト
echo '{"query":"test","command":"main","context":{}}' | node main.jsショーケース
JavaScript SDKで構築された公式プラグイン。どれでもクローンして始められます。
spSpotify完全制御 — 再生中、検索、再生、デバイス、プレイリスト。
- OAuth 2.0 PKCE in pure stdlib
- Auto-refresh detail card
- Inline SVG icons
- Credentials service integration
math高度な計算機 — 三角関数、積分、微分、ASCIIプロット。
- Safe expression parser (no eval)
- Symbolic differentiation (chain rule)
- Numerical integration (Simpson)
- ASCII function plots
bmChrome, Edge, Brave, Vivaldiのブックマークを即座に検索。
- Reads local bookmark files (no API)
- Multi-browser detection
- Multi-word fuzzy search
- Open in browser or copy URL
次のステップ
プラグインシステムは本番対応済み。