taikutaiku
User GuidePluginsAPI Reference

Developer Reference

Runtime boundaries, persistence layers, SDK surfaces, and the parts of taiku that matter when you extend it.

This page is the technical reference for plugin developers. It covers the runtime architecture, persistence model, session-managed plugin coordination, and design patterns for building reliable plugins.

Runtime architecture

The plugin system spans four layers, each with a distinct role. Understanding how they interact is essential for making good architectural decisions in your plugin.

The browser host bridge

The host bridge is the central coordination point. It runs in the main taiku page (not in the iframe) and handles every request from every plugin. When a plugin iframe sends a postMessage, the host bridge:

  1. Validates that the message conforms to the taiku:request protocol.
  2. Looks up the plugin's registered permissions.
  3. Checks whether the requested method is allowed by those permissions.
  4. Executes the method against the local session state (shells, users, workspace, etc.).
  5. Sends a taiku:response back to the iframe with the result or error.

The host bridge has direct access to the Svelte stores that power the taiku UI -- session state, shell data, workspace layout, user list, chat messages, and agent tracking. This is why bridge calls are fast: they read from in-memory reactive state, not from the server.

Permission enforcement happens here. If a plugin with only session.read tries to call writeToShell, the bridge rejects the request before the method is even invoked. The rejection is not a 403 HTTP response -- it is an error message in the taiku:response postMessage.

The SDK client

The TaikuPlugin class in @taiku/plugin-sdk wraps the raw postMessage protocol into typed async methods. It handles:

  • Initialization: waits for the taiku:init message, extracts pluginId and sessionId.
  • Request/response correlation: generates unique request IDs, tracks pending promises, resolves them when responses arrive.
  • Timeouts: rejects pending requests after 10 seconds (configurable) if no response arrives.
  • Event dispatching: routes taiku:event messages to registered listeners, supports wildcard (*) subscriptions.

The SDK is intentionally thin. It does not cache data, retry failed requests, or manage UI state. Those decisions belong to the plugin author.

import { TaikuPlugin } from "@taiku/plugin-sdk";

const taiku = new TaikuPlugin({ timeout: 15_000 });
await taiku.ready();

// Every method returns a Promise that resolves when the host responds
const session = await taiku.getSession();
const shells = await taiku.getShells();

SDK testing helpers

The MockHost class lets you test plugin logic without a running taiku session. It simulates the host bridge by responding to postMessage requests with configurable mock data. This is useful for:

  • Unit testing plugin logic in CI
  • Developing UI components against predictable data
  • Testing error handling (you can configure the mock to reject specific requests)
import { MockHost } from "@taiku/plugin-sdk/testing";

const mock = new MockHost({
  pluginId: "my-org.my-plugin",
  sessionId: "test-session-abc",
});

// The mock responds to getSession, getShells, etc. with default data.
// Your plugin code running in a test environment will receive these responses.

Server routes

Some operations require server involvement. The taiku server exposes HTTP endpoints for:

  • User KV: Postgres-backed key-value storage scoped to user + plugin. Persists across sessions and devices.
  • Plugin file storage: Server-side file storage for binary data and larger assets.
  • Session-managed plugins: Server-coordinated plugin sets that stay synchronized across all clients in a session.
  • Filesystem access: Proxied access to the session's working directory (requires the CLI to be running with filesystem capabilities).

These endpoints are authenticated and scoped. A plugin cannot access another user's KV data or another plugin's file storage (unless it has kv.read permission, which allows cross-plugin session KV reads).

Generated API documentation

The API Reference page is auto-generated from the SDK type definitions and server route schemas. It provides the canonical reference for every bridge method, HTTP endpoint, request parameter, and response shape. Use it alongside this page -- this page explains the why and when; the API page covers the exact what.

Persistence model in depth

Taiku provides four storage layers. Each has different characteristics that determine when it is the right choice.

Local secrets and settings

Scope: Current browser profile, current plugin. Lifetime: Until the user clears browser data or removes the plugin. Backed by: localStorage. Size limit: Browser localStorage limits (typically 5-10 MB total per origin). Network: None -- never sent to the server.

Secrets and settings are declared in the plugin manifest and configured by the user through the plugin settings UI. The plugin reads them at runtime:

// Reading a secret (API key, token, etc.)
const apiKey = await taiku.getSecret("api_key");
if (!apiKey) {
  await taiku.showToast(
    "Please configure your API key in plugin settings",
    "error",
  );
  return;
}

// Reading a setting with a manifest-defined default
const refreshInterval = await taiku.getSetting("refresh_interval");
// Returns the user's configured value, or the default from the manifest

// Reading all settings at once
const settings = await taiku.getSettings();
// { refresh_interval: 5000, theme: "dark", show_timestamps: true }

// Updating a setting programmatically
await taiku.setSetting("last_used_shell", 3);

Secrets are stored separately from settings in localStorage under the key taiku:plugin-secrets. They are keyed by plugin ID, so plugins cannot read each other's secrets.

Use secrets for: API keys, access tokens, passwords -- anything sensitive that should never leave the browser.

Use settings for: User preferences, display options, feature toggles -- anything the user might want to configure.

Session KV

Scope: Current session, current plugin (readable by other plugins with kv.read). Lifetime: Until the session ends. Backed by: In-memory store in the host bridge. Size limit: 64 KiB total per plugin per session. Network: Synchronized across session participants via the WebSocket connection.

Session KV is the primary mechanism for plugins to share state within a session. All participants see the same data. Writes from one client propagate to all others.

// Store collaborative state
await taiku.setPluginData("build_status", {
  state: "running",
  startedAt: Date.now(),
  startedBy: "user-42",
});

// Read it from any client in the session
const status = await taiku.getPluginData("build_status");
// { state: "running", startedAt: 1710000000000, startedBy: "user-42" }

// Get all keys at once
const allData = await taiku.getAllPluginData();
// { build_status: { ... }, config: { ... } }

// Delete a key
await taiku.deletePluginData("build_status");

// Read another plugin's KV data (requires kv.read permission)
const otherData = await taiku.getOtherPluginData(
  "taiku.music-player",
  "now_playing",
);

Use session KV for: Shared state that all participants need (playback position, build status, task state), lightweight plugin data that does not need to survive the session.

User KV

Scope: Current authenticated user, current plugin. Lifetime: Indefinite -- persists across sessions and devices. Backed by: Postgres database on the taiku server. Size limit: Reasonable per-key sizes (no enforced hard cap, but not designed for large blobs). Network: HTTP round-trip to the server on each read/write.

User KV is the right choice when state should follow the user, not the session. If a user logs in from a different machine, their user KV data is available.

// Save user preferences
await taiku.setUserData("preferred_layout", "vertical-split");
await taiku.setUserData("favorite_shells", [1, 3, 7]);

// Read them back -- works in any session, on any device
const layout = await taiku.getUserData("preferred_layout");
// "vertical-split"

// List all stored keys and values
const allUserData = await taiku.listUserData();
// { preferred_layout: "vertical-split", favorite_shells: [1, 3, 7] }

// Delete a key
await taiku.deleteUserData("favorite_shells");

Use user KV for: Personal preferences, saved configurations, workspace layouts, any state that should follow the user across sessions.

Plugin file storage

Scope: Current authenticated user, current plugin. Lifetime: Indefinite -- persists on the server. Backed by: Server-side file storage. Size limit: Per-file and total limits (check the API reference for current values). Network: HTTP round-trip to the server on each read/write.

Plugin file storage is designed for binary data and larger assets that do not fit well in a key-value model. The Music Player plugin uses it to store uploaded audio files. A reporting plugin might use it to store generated PDF reports.

// Write a text file
await taiku.writePluginFile(
  "config.json",
  JSON.stringify({
    version: 2,
    rules: [{ pattern: "error", color: "red" }],
  }),
);

// Write a binary file (ArrayBuffer)
const audioData = await fetchAudioBuffer();
await taiku.writePluginFile("notification.wav", audioData);

// List all stored files
const files = await taiku.listPluginFiles();
// [{ name: "config.json", size: 128 }, { name: "notification.wav", size: 44100 }]

// Read a file back as ArrayBuffer
const buffer = await taiku.readPluginFile("config.json");
const text = new TextDecoder().decode(buffer);
const config = JSON.parse(text);

// Delete a file
await taiku.deletePluginFile("notification.wav");

// Check storage usage
const usage = await taiku.getStorageUsage();
// { totalBytes: 44228, fileCount: 1, files: [...] }

Use plugin file storage for: Audio files, images, generated reports, cached data, any binary or large content.

Storage decision matrix

QuestionAnswerUse this
Should other session participants see it?YesSession KV
Should it persist after the session ends?NoSession KV
Should it persist after the session ends?Yes, for this userUser KV
Is it sensitive (API key, token)?YesLocal secrets
Is it a user preference?YesLocal settings or User KV
Is it binary or larger than a few KB?YesPlugin file storage
Does it need to be fast (sub-millisecond)?YesSession KV or local storage

Session-managed plugins

By default, plugins are a browser-local concern -- each user enables and configures their own plugins independently. But taiku also supports session-managed plugins, where the server coordinates a shared plugin set for the session. When session-managed plugins are active:

  • The server broadcasts the plugin manifest set to all connected clients.
  • Clients automatically load and enable the session's plugins.
  • Plugin enablement is synchronized -- if an admin enables a plugin for the session, all participants get it.
  • The serverManaged flag on the RegisteredPlugin object indicates whether a plugin was loaded this way.

This matters for plugin developers because:

  1. Your plugin might be loaded without user action. In a session-managed context, the user did not explicitly choose to enable your plugin -- the session admin did. Design your plugin to work gracefully in this mode (do not assume the user has configured secrets or settings).
  2. Multiple instances run simultaneously. Every connected client creates its own iframe instance of your plugin. If your plugin writes to session KV, be aware that multiple instances may write concurrently.
  3. Lifecycle is tied to the session. When the session ends, session-managed plugins are unloaded. Do not rely on teardown hooks running reliably -- persist important state proactively.

Design patterns for good plugins

Minimal permissions

Request only the permissions your plugin actually uses. Every additional permission is a trust decision the user has to make. A plugin that requests terminal.write and fs.write but only uses them in an obscure edge case creates unnecessary risk perception.

// Good: this monitoring plugin only reads
{
  "permissions": ["session.read", "events.subscribe", "ui.panel"]
}

// Bad: this monitoring plugin requests write access it does not need
{
  "permissions": [
    "session.read", "session.write",
    "terminal.read", "terminal.write",
    "events.subscribe", "ui.panel"
  ]
}

Bridge-first architecture

Default to using bridge calls for everything interactive. Only reach for the HTTP API when you genuinely need persistence beyond the session.

// Good: use session KV for ephemeral collaborative state
await taiku.setPluginData("active_panel", { view: "logs", shellId: 3 });

// Bad: use user KV for ephemeral state that does not need to persist
await taiku.setUserData("preferred_panel", { view: "logs", shellId: 3 });
// This makes a network round-trip for data that will be stale in seconds

Graceful degradation

Handle missing permissions, missing data, and network errors gracefully. Your plugin should never show a blank panel because a single API call failed.

try {
  const output = await taiku.getShellOutput(shellId, 10);
  renderOutput(output);
} catch (err) {
  renderError("Could not read shell output. The shell may have closed.");
}

Event-driven updates

Subscribe to events instead of polling. Taiku's event system delivers real-time updates for shell data, user joins/leaves, chat messages, agent activity, and custom plugin events.

// Good: react to events
taiku.on("shell:data", (event) => {
  refreshShell(event.data.shellId);
});

// Bad: poll on a timer
setInterval(async () => {
  const shells = await taiku.getShells();
  for (const shell of shells) {
    const output = await taiku.getShellOutput(shell.id, 5);
    renderShell(shell.id, output);
  }
}, 1000);

Dark theme by default

Taiku's UI is dark-themed. Your plugin panels should match. Use #18181b for backgrounds, #a1a1aa for text, and #27272a for borders. Consult the built-in plugins in static/plugins/ for color reference.

On this page