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:
- 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.
- 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 sessionapi.onContextChange(callback)— get notified when any of those changeapi.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:
npm install— installs dependencies (including TypeScript)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:
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:
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 iconThe manifest.json references the compiled files in dist/:
{
"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 theentryandserverpaths from yourmanifest.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
| Plugin | Description | Source |
|---|---|---|
| Project Stats | File counts, lines of code, file-type breakdown, largest files, and recently modified files | cloudcli-plugin-starter |
| Web Terminal | Full xterm.js terminal with multi-tab support, themes, and WebSocket-based PTY sessions | cloudcli-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-starterOr install manually:
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 buildOnce 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
- Go to Settings → Plugins
- Paste a Git URL (e.g.
https://github.com/user/cloudcli-plugin-my-plugin) - 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
| Page | What You'll Learn |
|---|---|
| Getting Started | Build a working plugin step by step |
| Manifest Reference | Every field in manifest.json explained |
| Frontend API | The api object — context, events, and RPC calls |
| Backend Servers | Running a server subprocess, secrets, and lifecycle |
| Security Model | How plugins are isolated and secured |
| Starter Plugin | Fork this repo to build your own plugin |