Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

TUIC SDK

The TUIC SDK provides window.tuic inside iframes hosted by TUICommander, enabling plugins and external pages to interact with the host app: open files, read content, launch terminals, copy to clipboard, receive theme updates, and more.

Two Injection Modes

1. Inline HTML Tabs (Plugins)

Plugins use html tabs — TUIC injects the SDK <script> directly into the iframe content. window.tuic is available immediately on load.

// Plugin panel — window.tuic is injected automatically
tuic.open("README.md");                         // relative to active repo
tuic.open("/absolute/path/file.txt", { pinned: true });
tuic.edit("src/App.tsx", { line: 42 });
tuic.terminal(tuic.activeRepo());

See Plugin Authoring Guide for full plugin details.

2. URL Tabs (External Pages)

Tabs created with url load content from a remote server in an iframe. The parent cannot inject scripts into cross-origin iframes, so the page must opt in to the SDK via a postMessage handshake.

Handshake Protocol

┌─────────────┐                            ┌─────────────┐
│  TUIC Host   │                            │  iframe URL  │
│  (parent)    │                            │  (child)     │
└──────┬──────┘                            └──────┬──────┘
       │  iframe onload                            │
       │── tuic:sdk-init ────────────────────────>│
       │── tuic:repo-changed ────────────────────>│
       │── tuic:theme-changed ───────────────────>│
       │                                           │ creates window.tuic
       │                                           │ dispatches "tuic:ready"
       │                                           │
       │  (fallback path — async listeners)        │
       │<── tuic:sdk-request ──────────────────────│
       │── tuic:sdk-init + repo + theme ─────────>│
       │                                           │
       │<── tuic:open, tuic:edit, ... ─────────────│ (on user action)
       │── tuic:get-file-result ─────────────────>│ (async response)
       │── tuic:host-message ────────────────────>│ (push from host)

Both paths are implemented in src/components/PluginPanel/PluginPanel.tsx. The version field carries TUIC_SDK_VERSION so the child can feature-detect.

Step 1: Child Page — Bootstrap Listener

Add this <script> in the <head> of your page, before any framework initialization. It must be synchronous so the listener is registered before the parent’s onload fires.

<script>
(function () {
  window.addEventListener("message", function (e) {
    if (!e.data || e.data.type !== "tuic:sdk-init") return;

    window.tuic = {
      version: "1.0",
      open: function (path, opts) {
        parent.postMessage({ type: "tuic:open", path: path, pinned: !!(opts && opts.pinned) }, "*");
      },
      edit: function (path, opts) {
        parent.postMessage({ type: "tuic:edit", path: path, line: (opts && opts.line) || 0 }, "*");
      },
      terminal: function (repoPath) {
        parent.postMessage({ type: "tuic:terminal", repoPath: repoPath }, "*");
      }
    };

    window.dispatchEvent(new Event("tuic:ready"));
  });
})();
</script>

Note: For URL-mode pages, only the basic methods (open, edit, terminal) are shown above. To use the full SDK (activeRepo, getFile, theme, etc.), copy the complete SDK from src/components/PluginPanel/tuicSdk.ts or use the inline HTML mode.

Step 2: Child Page — React to SDK Availability

Use the tuic:ready event to update your UI (e.g., show an “Open in TUIC” button):

// Alpine.js example
Alpine.data("myApp", () => ({
  _tuicReady: false,
  get hasTuic() { return this._tuicReady; },

  init() {
    window.addEventListener("tuic:ready", () => { this._tuicReady = true; });
    // If SDK was already initialized before Alpine mounted
    if (window.tuic) this._tuicReady = true;
  },

  tuicOpen(filePath) {
    if (window.tuic) window.tuic.open(filePath, { pinned: true });
  }
}));

API Reference

Files

tuic.open(path, opts?)

Open a file in a TUIC tab.

ParamTypeDescription
pathstringFile path — relative (resolved against active repo) or absolute
opts.pinnedbooleanPin the tab (default: false)

tuic.edit(path, opts?)

Open a file in the external editor.

ParamTypeDescription
pathstringFile path — relative or absolute
opts.linenumberLine number to jump to (default: 0)

tuic.getFile(path): Promise<string>

Read a file’s text content from the active repo.

ParamTypeDescription
pathstringFile path — relative or absolute

Returns a Promise that resolves with the file content string, or rejects with an Error if the file is not found, the path escapes the repo root, or no active repo is set.

tuic.getFile("package.json")
  .then(content => JSON.parse(content))
  .catch(err => console.error("Cannot read:", err.message));

Path Resolution

All file methods (open, edit, getFile) accept both relative and absolute paths:

  • Relative paths (e.g., "README.md", "src/App.tsx") are resolved against the active repository root.
  • Absolute paths (e.g., "/Users/me/code/repo/file.ts") are matched against known repositories (longest prefix wins).
  • Path traversal (../) that escapes the repo root is blocked and returns an error.
  • ./ prefixes are supported and normalized.
tuic.open("README.md");                    // → /active/repo/README.md
tuic.open("src/../README.md");             // → /active/repo/README.md
tuic.open("/Users/me/repo/file.ts");       // → absolute, matched to repo
tuic.getFile("../../../etc/passwd");       // → rejected (traversal)

HTML <a> tags with tuic:// href are automatically intercepted:

<a href="tuic://open/README.md">View README</a>
<a href="tuic://edit/src/main.rs?line=42">Edit main.rs:42</a>
<a href="tuic://terminal?repo=/path/to/repo">Open terminal</a>

Link pathnames are treated as relative paths (the leading / from URL parsing is stripped).

Repository

tuic.activeRepo(): string | null

Returns the path of the currently active repository, or null if none is active.

var repo = tuic.activeRepo();
// "/Users/me/code/myproject" or null

tuic.onRepoChange(callback)

Register a listener that fires when the active repo changes.

ParamTypeDescription
callback(repoPath: string | null) => voidCalled with the new active repo path

tuic.offRepoChange(callback)

Unregister a previously registered repo-change listener.

tuic.terminal(repoPath)

Open a terminal in the given repository.

ParamTypeDescription
repoPathstringRepository root path (absolute)

UI Feedback

tuic.toast(title, opts?)

Show a native toast notification in the host app.

ParamTypeDescription
titlestringToast title (required)
opts.messagestringOptional body text
opts.level"info" | "warn" | "error"Severity (default: "info")
opts.soundbooleanPlay a notification sound (default: false). Each level has a distinct tone: info = soft blip, warn = double beep, error = descending sweep.
tuic.toast("Import complete", { message: "42 items imported" });
tuic.toast("Rate limited", { message: "Try again in 30s", level: "warn", sound: true });

tuic.clipboard(text)

Copy text to the system clipboard. Works from sandboxed iframes (which cannot access navigator.clipboard directly).

ParamTypeDescription
textstringText to copy

Messaging

tuic.send(data)

Send structured data to the host. The host receives it via pluginRegistry.handlePanelMessage().

ParamTypeDescription
dataanyJSON-serializable payload

tuic.onMessage(callback)

Register a listener for messages pushed from the host.

ParamTypeDescription
callback(data: any) => voidCalled with the message payload

tuic.offMessage(callback)

Unregister a previously registered message listener.

Theme

tuic.theme: object | null

Read-only property containing the current theme as a key-value object. Keys are camelCase versions of CSS custom properties (e.g., --bg-primarybgPrimary).

var theme = tuic.theme;
// { bgPrimary: "#1e1e2e", fgPrimary: "#cdd6f4", accent: "#89b4fa", ... }

tuic.onThemeChange(callback)

Register a listener that fires when the host theme changes.

ParamTypeDescription
callback(theme: object) => voidCalled with the new theme object

tuic.offThemeChange(callback)

Unregister a previously registered theme-change listener.

Version

tuic.version: string

The SDK version string (currently "1.0").

Testing the SDK

An interactive test page is included at docs/examples/sdk-test.html. It runs automatic verification of all SDK methods and provides buttons for interactive testing.

How to launch it

From an AI agent (Claude Code, etc.):

Use the TUIC MCP ui tool to open it as an inline HTML tab:

mcp__tuicommander__ui action=tab id="sdk-test" title="SDK Test Suite" html="<contents of docs/examples/sdk-test.html>" pinned=false focus=true

From a plugin:

Register a plugin that serves the HTML content as a panel tab. The SDK is injected automatically into inline HTML tabs.

From JavaScript (dev console or app code):

// Read the file and open as a tab
const html = await invoke("fs_read_file", { repoPath: "/path/to/tuicommander", file: "docs/examples/sdk-test.html" });
mdTabsStore.addHtml("sdk-test", "SDK Test Suite", html);

The test page verifies:

  • SDK presence and version
  • activeRepo() return value
  • onRepoChange listener registration
  • Theme delivery and onThemeChange
  • onMessage listener registration
  • getFile("README.md") reads file content
  • getFile("../../../etc/passwd") is blocked by traversal guard

Interactive buttons test: open, edit, terminal, toast (all levels), clipboard, getFile, and send.

Timing Notes

The <script> bootstrap in the child page must be synchronous and in <head> to guarantee the message listener is registered before the parent’s iframe.onload fires tuic:sdk-init. If your page loads the bootstrap asynchronously (e.g., as an ES module), there is a race condition — the init message may arrive before the listener exists.

If you cannot guarantee synchronous loading, implement a retry: have the child send { type: "tuic:sdk-request" } to the parent on DOMContentLoaded (or whenever the listener is registered), and the parent will respond with tuic:sdk-init. This fallback is fully supported by the host.

Source Files

FileDescription
src/components/PluginPanel/tuicSdk.tsSDK script injected into iframes
src/components/PluginPanel/PluginPanel.tsxHost-side message handlers
src/components/PluginPanel/resolveTuicPath.tsPath resolution (relative + traversal guard)
docs/examples/sdk-test.htmlInteractive test/example page