Yoki Plugin SDK

Erstelle ein Plugin in 5 Minuten

Alles läuft mit JavaScript — das SDK ist in Yoki integriert, kein npm install nötig. Diese Anleitung führt dich durch die Erstellung deines ersten Plugins.

Schritt 1: Erstelle einen Ordner

Jedes Plugin hat seinen eigenen Ordner in ~/yoki/plugins/. Erstelle einen:

mkdir ~/yoki/plugins/my-plugin

Oder nutze die CLI für automatische Generierung:

npx create-yoki-pluginGeneriert plugin.json + main.js sofort einsatzbereit

Schritt 2: Füge plugin.json hinzu

Diese Datei beschreibt dein Plugin — Name, Schlüsselwort und welche Skripte ausgeführt werden.

~/yoki/plugins/my-plugin/plugin.json
{
  "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
    }
  ]
}
keywordwas der Nutzer tippt um dein Plugin aufzurufen
modewie das Ergebnis angezeigt wird. detail = Karte, list = Liste
execwelche Skriptdatei ausgeführt wird
takes_querytrue = Nutzer kann Text nach dem Schlüsselwort eingeben

Schritt 3: Schreibe dein Skript

Dein Skript liest Eingaben von Yoki, verarbeitet sie und sendet eine Antwort zurück.

~/yoki/plugins/my-plugin/main.js
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()

Schritt 4: Teste es

Teste ohne Yoki — leite JSON in dein Skript:

echo '{"query":"hello world","command":"main","context":{}}' | node main.js

Du solltest sehen:

{"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"}]}

Öffne Yoki, tippe my hello world — du siehst eine Karte mit „HELLO WORLD". Enter kopiert.

Schritt 5: Was kommt als nächstes

Ändere mode zu "list" — mehrere scrollbare Elemente
Füge actions hinzu — Buttons zum Kopieren, URLs öffnen, Befehle ausführen
Füge metadata hinzu — Schlüssel-Wert-Paare in der Seitenleiste
Nutze background mode — für Aktionen ohne UI
Veröffentliche im Marketplace — push zu GitHub, einreichen bei Yoki

Lies weiter für die vollständige SDK-Referenz.

SDK-Referenz

Jede Funktion im Yoki SDK mit echten Beispielen. Kein Install nötig.

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

Signatur
async readInput(): Promise<object>
Get the user query
const input = await readInput()
const query = input.query  // "hello world"
Access the data directory (for saving files)
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 runs
Read credentials (for API plugins)
const input = await readInput()
const apiKey = input.context.credentials?.api_key || ""
if (!apiKey) {
  writeResponse(error("API key not configured"))
  return
}
Full input structure
// 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.

Signatur
writeResponse(resp: object): void
Basic usage
writeResponse(detail("<div>Hello!</div>"))
Send an error
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.

Signatur
detail(
  html: string,
  metadata?: [{ label: string, value: string }, ...],
  actions?:  [{ title: string, type: string, value?: string }, ...]
)
Simple card
writeResponse(detail(
  '<div style="padding:16px;font-size:20px">Hello, World!</div>'
))
Card with metadata sidebar
writeResponse(detail(
  '<div style="padding:16px"><h2>2 + 2 = 4</h2></div>',
  [
    { label: "Hex", value: "0x4" },
    { label: "Binary", value: "0b100" },
    { label: "Prime", value: "No" }
  ]
))
Card with copy button (Enter copies the result)
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" }]
))
Card with multiple action buttons
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.

Signatur
list(items: [{
  id: string, title: string, subtitle?: string,
  icon?: string, actions?: Action[]
}, ...])
Simple list
writeResponse(list([
  { id: "1", title: "First result", subtitle: "Description here" },
  { id: "2", title: "Second result", subtitle: "Another description" },
]))
List with actions (Enter opens, Cmd+C copies)
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" }
    ]
  }
]))
Filtered list (search)
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.

Signatur
background(hud: string, notification?: { title: string, body: string })
Simple HUD toast
writeResponse(background("Done!"))
HUD + system notification
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.

Signatur
error(message: string, details?: string)
Simple error
writeResponse(error("Not found"))
Error with details
writeResponse(error("API request failed", "Check your internet connection and try again"))
Graceful handling of missing input
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").

Signatur
stripKeyword(query: string, ...keywords: string[]): string
Remove subcommand
stripKeyword("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.

Signatur
escHtml(s: string): string
Escape user input before rendering
const input = await readInput()
const safe = escHtml(input.query)
writeResponse(detail(`<div>${safe}</div>`))

// input: "<b>bold</b>"
// safe:  "&lt;b&gt;bold&lt;/b&gt;"
// renders as text, not HTML

Protokoll v2

Wenn der Nutzer dein Schlüsselwort tippt, startet Yoki dein Skript, schreibt V2Input JSON nach stdin und liest V2Response von stdout. Verstecktes Fenster, 5-Sekunden-Timeout (konfigurierbar).

stdin → V2Input
{
  "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": {}
}
stdout → V2Response
{
  "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-Felder

versionImmer "2.0"
commandWelcher Befehl ausgelöst wurde
action"search" (Eingabe), "execute" (Enter), "refresh" (Auto-Refresh)
queryVollständige Nutzereingabe nach dem Schlüsselwort
context.yoki_versionHost-Version
context.osDerzeit immer "windows"
context.localeSprache des Nutzers
context.plugin_dirAbsoluter Pfad zum Plugin-Ordner
context.data_dirIsoliertes Datenverzeichnis (bleibt zwischen Ausführungen)
context.credentialsSchlüssel vom Credential-Service (oder leer)

Antwortmodi

Das type-Feld bestimmt wie Yoki deine Antwort rendert.

list
STANDARD

Scrollbare Elemente mit Icon, Titel, Untertitel und Aktionen.

detail

HTML-Karte mit Metadaten-Seitenleiste und Aktions-Buttons. Enter kopiert die erste Copy-Aktion.

grid

Visuelles Raster mit konfigurierbaren Spalten.

background
SEITENEFFEKT

Kein UI — Seiteneffekt mit HUD-Toast. Enter-gesteuert.

form
KOMMT BALD

Mehrteiliges Eingabeformular. Kommt bald.

error

Fehler mit optionalen Details. Plugin-Abstürze werden automatisch abgefangen.

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" }))

Berechtigungen

Plugins deklarieren was sie brauchen. Yoki zeigt einen Zustimmungsdialog.

network
Hostname-Whitelist für HTTPS — nur gelistete Domains.
filesystem
Lese-/Schreibpfade relativ zum Plugin-Datenverzeichnis. Isoliert.
notifications
Windows-Toast-Benachrichtigungen über das Host-System.
clipboard
Lesen/Schreiben der Systemzwischenablage.

Zugangsdaten

Plugins sollten niemals API-Schlüssel einbetten. Deklariere was du brauchst.

Manifest-Deklaration
{
  "name": "Spotify",
  "keyword": "sp",
  "protocol": "v2",
  "credentials": {
    "slug": "spotify",
    "service": "spotify",
    "keys": ["client_id"]
  },
  "commands": [...]
}
Plugin liest aus Kontext
# 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 ...
Sende einen PR oder schreibe yokirun@yoki.run.

Manifest-Referenz

Alle Felder die Yoki versteht. $schema für IDE-Autovervollständigung.

plugin.json — vollständiges Schema
{
  "$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"
    }
  ]
}

Aktionstypen

Setze action.type für Buttons oder Listen-Aktionen:

copy  valueWert in die Zwischenablage kopieren.
open_url  urlURL im Standardbrowser öffnen.
yoki_run  valueYoki-Abfrage ausführen.
exec  exec, argsEin anderes Plugin-Skript ausführen.
paste  valueText ins aktive Fenster einfügen.
notification  title, bodySystem-Toast-Benachrichtigung anzeigen.
close  Yoki-Launcher schließen.
refresh  Aktuellen Befehl erneut ausführen.

Aktions-Icons

Drei Formen für das icon-Feld:

<svg ...>...</svg> Inline-SVG-Markup, DOMPurify-bereinigt.
data:image/svg+xml;base64,... Data-URL oder https:// URL — als <img> gerendert.
"file" / "folder" / "music" / ... Name aus Yokis eingebautem Icon-Set.

Plugin-Erkennung

Yoki scannt ~/yoki/plugins/ beim Start und bei Fensterfokus.
Jedes Unterverzeichnis mit plugin.json ist ein Plugin.
Plugins bekommen ein isoliertes data/-Verzeichnis.
Ladefehler werden im Plugin-Manager angezeigt.
Doppelte Schlüsselwörter werden abgelehnt.

Lokales Testen

# Teste jedes Plugin ohne Yoki
echo '{"query":"test","command":"main","context":{}}' | node main.js

Showcase

Offizielle Plugins mit dem JavaScript SDK. Klone eines als Startpunkt.

Spotify · Node.js · sp

Volle Spotify-Steuerung — aktueller Track, Suche, Wiedergabe, Geräte, Playlists.

sp  Now Playing card
sp play [query]  Search & play
sp pause / next / prev  Playback controls
sp s [query]  Search tracks
sp d  Devices
sp pl  Playlists
Funktionen
  • OAuth 2.0 PKCE in pure stdlib
  • Auto-refresh detail card
  • Inline SVG icons
  • Credentials service integration
Extended Math · Node.js · math

Erweiterter Rechner — Trigonometrie, Integrale, Ableitungen, ASCII-Plots.

math 2^8 + sqrt(144)  Calculate
math trig table 60  Trig table
math int x^2 0 10  Integrate
math der sin(x^2)  Derivative
math plot sin(x) -pi pi  ASCII plot
Funktionen
  • Safe expression parser (no eval)
  • Symbolic differentiation (chain rule)
  • Numerical integration (Simpson)
  • ASCII function plots
Bookmarks · Node.js · bm

Sofortige Suche in Chrome, Edge, Brave, Vivaldi Lesezeichen.

bm  List all bookmarks
bm github  Search bookmarks
Funktionen
  • Reads local bookmark files (no API)
  • Multi-browser detection
  • Multi-word fuzzy search
  • Open in browser or copy URL

Was kommt

Das Plugin-System ist produktionsreif.

FERTIG
v2 protocol + 5 response modes
stdin/stdout JSON, alle Antwortmodi, Aktions-Buttons, Auto-Refresh, Hot Reload.
FERTIG
Bundled JavaScript SDK
Zero-Install SDKs im Yoki-Binary eingebettet.
FERTIG
Plugin Marketplace
In-App-Browse + Ein-Klick-Installation. Kuratierte Überprüfung.
FERTIG
Credentials service
Plugins deklarieren Bedürfnisse, Yoki holt Geheimnisse zur Laufzeit.
FERTIG
Scaffold CLI (npx create-yoki-plugin)
Interaktives Scaffold — npx create-yoki-plugin ausführen, fertiges JavaScript-Plugin in Sekunden.
FERTIG
JSON Schema for plugin.json
IDE-Autovervollständigung für alle Manifest-Felder.
NÄCHSTES
Plugin settings UI (form mode)
Plugins deklarieren Einstellungsfelder, Yoki rendert eine Einstellungsseite.
NÄCHSTES
Auto-update for plugins
Versionsvergleich mit Marketplace-Tag, Update-Badge, Ein-Klick-Update.
SPÄTER
AI tool integration
Plugins als KI-Tools die Yoki AI direkt aufrufen kann.
Erstelle dein erstes Plugin
SDK ist stabil, drei Referenz-Plugins sind live.
Loslegen
Yoki Plugin SDK · v2 · yoki.run