Plugin System Overview

Learn how the CloudCLI UI plugin system works — architecture, capabilities, and what you can build.

The plugin system lets you add custom functionality to CloudCLI UI. A plugin is a small project you install from a Git repository. Once installed, it shows up as a new tab in the app and can do things like scan your project files, call external APIs, or display custom dashboards.

Where Plugins Appear

Plugins show up in two places:

  1. The tab bar — Each enabled plugin adds a tab next to the built-in tabs (Chat, Shell, Files, Git, Tasks). Click it to open the plugin.
  2. Settings → Plugins — Where you install, enable/disable, update, and uninstall plugins.

Plugin tabs sit at the same level as the built-in tabs. When you click a plugin tab, it takes over the main content area just like Chat or Files would.

What Plugins Can Do

Frontend (runs in the browser)

  • Render any UI inside their tab — plain HTML, styled components, charts, dashboards, forms, anything you can build with vanilla JavaScript and the DOM
  • Know which project is selected — receive the project name and filesystem path
  • Know which session is active — receive the session ID and title
  • Adapt to the theme — know whether dark or light mode is active and update in real time when it changes
  • Load assets — serve CSS, images, fonts, and other files from their plugin directory

Backend (optional Node.js server)

  • Access the filesystem — read files, scan directories, watch for changes
  • Call external APIs — use secret API keys that are injected securely into each request
  • Run computations — process data, run analysis, generate reports
  • Use npm packages — dependencies are installed automatically from your package.json
  • Keep state in memory — cache results between requests since the server stays running
  • Serve WebSocket connections — stream real-time data to the frontend via the host's authenticated WebSocket proxy

What Plugins Cannot Do

Plugins are scoped to their own tab. They cannot:

  • Modify the Chat, Shell, Files, Git, or Tasks tabs — plugins don't have access to the built-in UI
  • Send messages to Claude — there is no API to interact with the chat or agent
  • Appear in the sidebar, toolbar, or status bar — plugins can only live in the tab area
  • Access other plugins — each plugin is independent, with no inter-plugin communication
  • Read the user's auth tokens or session cookies — authentication is handled by the host and never exposed
  • Intercept or modify requests — plugins can't act as middleware on other features

How It Works

Plugins are written in TypeScript and compiled to ES modules. When you open the plugin tab, the host dynamically imports your compiled entry file and calls mount(container, api) with:

  • A DOM element (container) you render your UI into
  • An API object (api) with three things:
    • api.context — the current theme, project, and session
    • api.onContextChange(callback) — get notified when any of those change
    • api.rpc(method, path, body?) — call your backend server through the host's proxy

When the tab closes, unmount(container) is called so you can clean up.

TypeScript & Build Step

Plugins are authored in TypeScript under a src/ directory and compiled to JavaScript in dist/. The manifest.json points to the compiled files in dist/.

When you install a plugin via Settings → Plugins, the host automatically runs:

  1. npm install — installs dependencies (including TypeScript)
  2. npm run build — compiles TypeScript to JavaScript

This means plugin repositories contain only TypeScript source — compiled output is generated on install and not committed to git.

Communication: HTTP and WebSocket

Plugins can communicate with their backend server in two ways:

HTTP (via api.rpc) — for request/response style calls. The host proxies requests through /api/plugins/:name/rpc/*, adding authentication and any configured secrets as headers. Use this for fetching data, triggering actions, or any standard API call.

WebSocket (via /plugin-ws/:name) — for real-time bidirectional streaming. The host proxies WebSocket connections through /plugin-ws/:name to your plugin server's /ws endpoint. Authentication is enforced by the host before the connection is forwarded — your server receives only pre-authenticated connections. Use this for live data streams, terminal I/O, log tailing, or any scenario where push-based communication is needed.

To open a WebSocket from your frontend:

typescript
function connectWebSocket(): WebSocket {
  const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
  const token = localStorage.getItem('auth-token') || '';
  const qs = token ? '?token=' + encodeURIComponent(token) : '';
  return new WebSocket(proto + '//' + location.host + '/plugin-ws/my-plugin' + qs);
}

Your plugin server exposes a /ws endpoint — the host forwards connections there automatically:

typescript
import http from 'node:http';
import { WebSocketServer } from 'ws';

const server = http.createServer();
const wss = new WebSocketServer({ server, path: '/ws' });

wss.on('connection', (ws) => {
  ws.on('message', (data) => { /* handle messages */ });
  ws.send(JSON.stringify({ type: 'ready' }));
});

server.listen(0, '127.0.0.1', () => {
  const addr = server.address();
  if (addr && typeof addr !== 'string') {
    // Signal readiness to the host
    console.log(JSON.stringify({ ready: true, port: addr.port }));
  }
});
┌─────────────────────────────────────────────────────┐
│  CloudCLI UI (Browser)                              │
│                                                     │
│  ┌─────┬───────┬───────┬─────┬────────────────┐     │
│  │ Chat│ Shell │ Files │ Git │ YOUR PLUGIN ◄──┤─┐   │
│  └─────┴───────┴───────┴─────┴────────────────┘ │   │
│  ┌───────────────────────────────────────────┐   │   │
│  │  mount(container, api)                    │   │   │
│  │    ├─ api.rpc() → /api/plugins/:name/rpc  │   │   │
│  │    └─ WebSocket → /plugin-ws/:name        │   │   │
│  │              │                            │   │   │
│  │  ┌───────────▼────────────────────┐   │   │   │
│  │  │ Host proxy (authenticates,         │   │   │   │
│  │  │ then forwards to plugin server)    │   │   │   │
│  │  └───────────────────┬────────────────┘   │   │   │
│  └─────────────────────────────────────────┘ │   │   │
└────────────────────────┤──────────────────────┘   │   
                         │                              
┌────────────────────────▼──────────────────────┐   
│  Plugin server  (Node.js subprocess)              │   
│  127.0.0.1:<port>                                 │   
│  • HTTP endpoint for api.rpc() calls              │   
│  • /ws endpoint for WebSocket streams             │   
│  • Full filesystem + network access               │   
└───────────────────────────────────────────────┘   

Plugin Directory Structure

Plugins live in ~/.claude-code-ui/plugins/. Each plugin is a folder with TypeScript source in src/ and compiled output in dist/:

~/.claude-code-ui/plugins/
└── my-plugin/
    ├── manifest.json      # Required — tells the host about your plugin
    ├── package.json       # Dependencies and build script
    ├── tsconfig.json      # TypeScript configuration
    ├── src/
    │   ├── types.ts       # Plugin API type definitions
    │   ├── index.ts       # Your frontend code
    │   └── server.ts      # Your backend server (optional)
    ├── dist/              # Compiled output (auto-generated)
    │   ├── index.js       # Compiled frontend — referenced by manifest
    │   └── server.js      # Compiled backend — referenced by manifest
    └── icon.svg           # Optional custom icon

The manifest.json references the compiled files in dist/:

json
{
  "entry": "dist/index.js",
  "server": "dist/server.js"
}

Note: The src/ layout shown above is a recommended convention from the starter template. The plugin loader only reads the entry and server paths from your manifest.json — as long as your compiled files end up at those paths, you can organize your TypeScript source however you like (e.g. src/client/ + src/server/, or any other structure that suits your project).

Available Plugins

PluginDescriptionSource
Project StatsFile counts, lines of code, file-type breakdown, largest files, and recently modified filescloudcli-plugin-starter
Web TerminalFull xterm.js terminal with multi-tab support, themes, and WebSocket-based PTY sessionscloudcli-plugin-terminal

Built a plugin? Share it in the Show and Tell category on GitHub Discussions and we'll add it to the list.

Try It — Starter Plugin

The fastest way to see plugins in action is to install the Project Stats starter plugin. It scans your project and displays file counts, lines of code, a file-type breakdown chart, largest files, and recently modified files.

Install from Settings: Go to Settings → Plugins, paste the URL below, and click Install:

https://github.com/cloudcli-ai/cloudcli-plugin-starter

Or install manually:

bash
git clone https://github.com/cloudcli-ai/cloudcli-plugin-starter.git ~/.claude-code-ui/plugins/project-stats
cd ~/.claude-code-ui/plugins/project-stats
npm install
npm run build

Once installed, enable the plugin and open its tab. The starter plugin also serves as a template — click "Use this template" on GitHub to create your own plugin repo.

View the starter plugin on GitHub

Installing a Plugin

  1. Go to Settings → Plugins
  2. Paste a Git URL (e.g. https://github.com/user/cloudcli-plugin-my-plugin)
  3. Click Install

The host clones the repo, validates the manifest, installs dependencies, compiles TypeScript, starts the server (if any), and your tab appears. That's it.

Managing Plugins

From Settings → Plugins you can:

  • Enable/disable — toggle the switch to show or hide the plugin tab
  • Update — click the refresh button to pull the latest code from Git (dependencies are reinstalled and TypeScript is recompiled)
  • Uninstall — click the trash icon (requires confirmation) to remove completely

Plugin state is saved in ~/.claude-code-ui/plugins.json and persists across restarts.

What's in This Guide

PageWhat You'll Learn
Getting StartedBuild a working plugin step by step
Manifest ReferenceEvery field in manifest.json explained
Frontend APIThe api object — context, events, and RPC calls
Backend ServersRunning a server subprocess, secrets, and lifecycle
Security ModelHow plugins are isolated and secured
Starter PluginFork this repo to build your own plugin

Last updated March 18, 2026