### Node.js Backend Server Setup for CloudCLI Plugin Source: https://context7.com/cloudcli-ai/cloudcli-plugin-starter/llms.txt The backend server runs as a Node.js subprocess. It must signal readiness to the host by printing a JSON line with port information to stdout. This example demonstrates setting up an HTTP server that handles '/stats' GET requests and '/echo' POST requests. ```typescript import http from 'node:http'; interface StatsResponse { totalFiles: number; totalLines: number; totalSize: number; byExtension: [string, number][]; largest: { name: string; size: number }[]; recent: { name: string; mtime: number }[]; } const server = http.createServer((req, res) => { res.setHeader('Content-Type', 'application/json'); if (req.method === 'GET' && req.url?.startsWith('/stats')) { try { const { searchParams } = new URL(req.url, 'http://localhost'); const projectPath = searchParams.get('path') ?? ''; const stats = getStats(projectPath); res.end(JSON.stringify(stats)); } catch (err) { res.writeHead(400); res.end(JSON.stringify({ error: (err as Error).message })); } return; } if (req.method === 'POST' && req.url === '/echo') { let body = ''; req.on('data', (chunk) => { body += chunk; }); req.on('end', () => { const data = JSON.parse(body); res.end(JSON.stringify({ echo: data.greeting })); }); return; } res.writeHead(404); res.end(JSON.stringify({ error: 'Not found' })); }); // Listen on random port and signal readiness to host server.listen(0, '127.0.0.1', () => { const addr = server.address(); if (addr && typeof addr !== 'string') { // REQUIRED: This JSON line signals the host that the server is ready console.log(JSON.stringify({ ready: true, port: addr.port })); } }); ``` -------------------------------- ### Install Plugin via Git Source: https://github.com/cloudcli-ai/cloudcli-plugin-starter/blob/main/README.md Use this command to clone the plugin repository and install its dependencies. This is the recommended installation method. ```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 ``` -------------------------------- ### Backend Server Setup Source: https://context7.com/cloudcli-ai/cloudcli-plugin-starter/llms.txt Details on how to set up a backend server that runs as a Node.js subprocess and communicates its readiness to the host. ```APIDOC ## Backend Server Requirements ### Description The backend server must run as a Node.js subprocess and signal its readiness to the host by printing a JSON line containing port information to standard output. ### Server Readiness Signal The server should print a JSON object to stdout in the following format: ```json { "ready": true, "port": } ``` ### Example Server Implementation (Node.js) ```typescript import http from 'node:http'; interface StatsResponse { totalFiles: number; totalLines: number; totalSize: number; byExtension: [string, number][]; largest: { name: string; size: number }[]; recent: { name: string; mtime: number }[]; } const server = http.createServer((req, res) => { res.setHeader('Content-Type', 'application/json'); if (req.method === 'GET' && req.url?.startsWith('/stats')) { try { const { searchParams } = new URL(req.url, 'http://localhost'); const projectPath = searchParams.get('path') ?? ''; // Assume getStats function is defined elsewhere and returns StatsResponse const stats = getStats(projectPath); res.end(JSON.stringify(stats)); } catch (err) { res.writeHead(400); res.end(JSON.stringify({ error: (err as Error).message })); } return; } if (req.method === 'POST' && req.url === '/echo') { let body = ''; req.on('data', (chunk) => { body += chunk; }); req.on('end', () => { try { const data = JSON.parse(body); res.end(JSON.stringify({ echo: data.greeting })); } catch (err) { res.writeHead(400); res.end(JSON.stringify({ error: 'Invalid JSON body' })); } }); return; } res.writeHead(404); res.end(JSON.stringify({ error: 'Not found' })); }); // Listen on random port and signal readiness to host server.listen(0, '127.0.0.1', () => { const addr = server.address(); if (addr && typeof addr !== 'string') { // REQUIRED: This JSON line signals the host that the server is ready console.log(JSON.stringify({ ready: true, port: addr.port })); } }); // Placeholder for getStats function function getStats(projectPath: string): StatsResponse { console.log(`Fetching stats for: ${projectPath}`); // Replace with actual stats retrieval logic return { totalFiles: 100, totalLines: 10000, totalSize: 102400, byExtension: [['.ts', 50], ['.js', 50]], largest: [{ name: 'index.ts', size: 1024 }], recent: [{ name: 'app.ts', mtime: Date.now() }] }; } ``` ``` -------------------------------- ### Complete Plugin Module Example Source: https://context7.com/cloudcli-ai/cloudcli-plugin-starter/llms.txt This example shows a full plugin lifecycle, including context-aware rendering and RPC communication. It requires the PluginAPI and PluginContext types. ```typescript import type { PluginAPI, PluginContext } from './types.js'; interface ProjectStats { totalFiles: number; totalLines: number; totalSize: number; byExtension: [string, number][]; } export function mount(container: HTMLElement, api: PluginAPI): void { const root = document.createElement('div'); Object.assign(root.style, { height: '100%', padding: '24px', fontFamily: 'monospace', }); container.appendChild(root); async function loadAndRender(ctx: PluginContext): Promise { const isDark = ctx.theme === 'dark'; root.style.background = isDark ? '#08080f' : '#fafaf9'; root.style.color = isDark ? '#e2e0f0' : '#0f0e1a'; if (!ctx.project) { root.innerHTML = '

Select a project to view stats

'; return; } root.innerHTML = '

Loading...

'; try { const stats = await api.rpc( 'GET', `stats?path=${encodeURIComponent(ctx.project.path)}` ) as ProjectStats; root.innerHTML = `

${ctx.project.name}

Files: ${stats.totalFiles.toLocaleString()}

Lines: ${stats.totalLines.toLocaleString()}

Size: ${(stats.totalSize / 1024 / 1024).toFixed(1)} MB

File Types

`; } catch (err) { root.innerHTML = `

Error: ${(err as Error).message}

`; } } loadAndRender(api.context); const unsub = api.onContextChange((ctx) => { loadAndRender(ctx); }); (container as any)._cleanup = unsub; } export function unmount(container: HTMLElement): void { (container as any)._cleanup?.(); container.innerHTML = ''; } ``` -------------------------------- ### RPC Communication Example Source: https://github.com/cloudcli-ai/cloudcli-plugin-starter/blob/main/README.md Demonstrates how to use the `api.rpc()` method for making GET and POST requests to the backend server. Ensure the server is configured to handle these requests. ```typescript const data = await api.rpc('GET', '/stats?path=/my/project'); const result = await api.rpc('POST', '/echo', { greeting: 'hi' }); ``` -------------------------------- ### Install CloudCLI Plugins via Git and npm Source: https://context7.com/cloudcli-ai/cloudcli-plugin-starter/llms.txt Instructions for manually installing CloudCLI plugins by cloning a git repository, installing dependencies with npm, and building the project. Includes commands for development with watch mode. ```bash # Automated installation via CloudCLI UI: # Open Settings > Plugins, paste repository URL, click Install # Manual installation: 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 # Development with watch mode: npm run dev ``` -------------------------------- ### Plugin Manifest Configuration Source: https://github.com/cloudcli-ai/cloudcli-plugin-starter/blob/main/README.md Example `manifest.json` file detailing essential plugin metadata such as name, display name, version, entry points for frontend and backend, and type. ```jsonc { "name": "project-stats", // Unique id — alphanumeric, hyphens, underscores "displayName": "Project Stats", // Shown in the UI "version": "1.0.0", "description": "Short description shown in settings.", "author": "Your Name", "icon": "icon.svg", // Custom SVG icon "type": "module", // Must be "module" "slot": "tab", // Where the plugin appears — only "tab" today "entry": "dist/index.js", // Compiled frontend entry file "server": "dist/server.js", // Optional — compiled backend entry file "permissions": [] // Reserved for future use } ``` -------------------------------- ### Making GET and POST requests with api.rpc() Source: https://context7.com/cloudcli-ai/cloudcli-plugin-starter/llms.txt Use the api.rpc() method to communicate with the plugin's backend server. Supports various HTTP methods and optional JSON bodies. Ensure proper error handling when making requests. ```typescript async function fetchStats(projectPath: string): Promise { const stats = await api.rpc( 'GET', `stats?path=${encodeURIComponent(projectPath)}` ) as ProjectStats; return stats; } ``` ```typescript async function echoMessage(message: string): Promise<{ echo: string }> { const result = await api.rpc( 'POST', '/echo', { greeting: message } ) as { echo: string }; return result; } ``` ```typescript try { const stats = await api.rpc('GET', `stats?path=${api.context.project?.path}`); renderStats(stats as ProjectStats); } catch (err) { container.innerHTML = `
Error: ${(err as Error).message}
`; } ``` -------------------------------- ### CloudCLI Plugin Host-Frontend Interaction Source: https://github.com/cloudcli-ai/cloudcli-plugin-starter/blob/main/README.md This diagram illustrates the communication flow between the CloudCLI UI host, the plugin's frontend module, and the backend subprocess. It highlights how the host manages plugin lifecycle and communication. ```text ┌─────────────────────────────────────────────────────┐ │ CloudCLI UI Host │ │ │ │ Plugin subprocess (dist/server.js): │ │ Runs as a child process with restricted env │ │ Listens on random local port │ │ Receives secrets via X-Plugin-Secret-* headers │ └───────────┬─────────────────────────┬───────────────┘ │ serves static files │ proxies RPC ┌───────────▼─────────────────────────▼───────────────┐ │ Frontend (browser) │ │ │ │ Plugin module (dist/index.js) │ │ import(url) → mount(container, api) │ │ api.context — theme / project / session │ │ api.onContextChange — subscribe to changes │ │ api.rpc(method, path, body) → Promise │ └─────────────────────────────────────────────────────┘ ``` -------------------------------- ### Plugin Structure Overview Source: https://github.com/cloudcli-ai/cloudcli-plugin-starter/blob/main/README.md This outlines the directory structure of a typical CloudCLI plugin. Key files include manifest.json for metadata, package.json for dependencies, and src/ for source code. ```text project-stats/ manifest.json # Required — plugin metadata package.json # Dependencies and build script tsconfig.json # TypeScript configuration src/ types.ts # Plugin API type definitions index.ts # Frontend entry point (ES module) server.ts # Backend entry point (Node.js subprocess) dist/ # Compiled output (auto-generated, not committed) index.js # Compiled frontend — referenced by manifest "entry" server.js # Compiled backend — referenced by manifest "server" icon.svg # Custom SVG icon for the tab ``` -------------------------------- ### Mount and Unmount Frontend Plugin Source: https://github.com/cloudcli-ai/cloudcli-plugin-starter/blob/main/README.md Implement the `mount` function to render UI within the provided container and `unmount` for cleanup. The `api` object provides context and event listeners. ```typescript import type { PluginAPI, PluginContext } from './types.js'; export function mount(container: HTMLElement, api: PluginAPI): void { const ctx: PluginContext = api.context; container.innerHTML = `

Hello! Theme: ${ctx.theme}

`; const unsub = api.onContextChange((ctx) => { container.style.background = ctx.theme === 'dark' ? '#111' : '#fff'; }); (container as any)._cleanup = unsub; } export function unmount(container: HTMLElement): void { (container as any)._cleanup?.(); container.innerHTML = ''; } ``` -------------------------------- ### Plugin mount() Function Implementation (TypeScript) Source: https://context7.com/cloudcli-ai/cloudcli-plugin-starter/llms.txt Called when the plugin tab is activated. Renders initial UI, subscribes to context changes, and stores cleanup functions. Ensure proper DOM manipulation and event handling. ```typescript import type { PluginAPI, PluginContext } from './types.js'; export function mount(container: HTMLElement, api: PluginAPI): void { const ctx: PluginContext = api.context; // Render initial UI container.innerHTML = `

Hello from plugin!

Theme: ${ctx.theme}

Project: ${ctx.project?.name ?? 'none selected'}

`; // Subscribe to context changes const unsubscribe = api.onContextChange((newCtx) => { container.style.background = newCtx.theme === 'dark' ? '#111' : '#fff'; container.style.color = newCtx.theme === 'dark' ? '#e2e0f0' : '#0f0e1a'; if (newCtx.project) { console.log(`Project changed to: ${newCtx.project.name}`); } }); // Store cleanup function for unmount (container as any)._cleanup = unsubscribe; } ``` -------------------------------- ### Plugin Manifest Configuration (JSON) Source: https://context7.com/cloudcli-ai/cloudcli-plugin-starter/llms.txt Defines plugin metadata, entry points, and permissions required by the CloudCLI host. Ensure 'name', 'displayName', 'version', and 'entry' are correctly set. ```json { "name": "my-plugin", "displayName": "My Custom Plugin", "version": "1.0.0", "description": "Short description shown in settings.", "author": "Your Name", "icon": "icon.svg", "type": "module", "slot": "tab", "entry": "dist/index.js", "server": "dist/server.js", "permissions": [] } ``` -------------------------------- ### Context Listener and Request Source: https://github.com/cloudcli-ai/cloudcli-plugin-starter/blob/main/index.html This code listens for context updates from the host application and requests the initial context upon loading. It updates the UI based on theme, project, and session information. ```javascript // ── Context listener ──────────────────────────────────────────── window.addEventListener('message', (event) => { if (!event.data || event.data.type !== 'ccui:context') return; const { theme, project, session } = event.data; document.body.className = theme || 'light'; document.getElementById('ctx-theme').textContent = theme || '—'; document.getElementById('ctx-project').textContent = project ? project.name : '(none)'; document.getElementById('ctx-session').textContent = session ? session.title || session.id : '(none)'; }); // Request context on load window.parent.postMessage({ type: 'ccui:request-context' }, '*'); ``` -------------------------------- ### Server Ready Signal Source: https://github.com/cloudcli-ai/cloudcli-plugin-starter/blob/main/README.md The backend server subprocess must print a JSON line to stdout indicating it's ready and specifying the port it's listening on. ```json {"ready": true, "port": 12345} ``` -------------------------------- ### Build and Watch Commands Source: https://github.com/cloudcli-ai/cloudcli-plugin-starter/blob/main/README.md These npm scripts are used for compiling TypeScript code to JavaScript. `npm run build` compiles once, while `npm run dev` recompiles on file changes, useful during development. ```bash npm run build ``` ```bash npm run dev ``` -------------------------------- ### PluginContext Interface and Usage (TypeScript) Source: https://context7.com/cloudcli-ai/cloudcli-plugin-starter/llms.txt Provides access to the current application state, including theme, project, and session information. Use the 'api.context' property to access these details. ```typescript interface PluginContext { theme: 'dark' | 'light'; project: { name: string; path: string } | null; session: { id: string; title: string } | null; } // Example usage: accessing context properties const ctx: PluginContext = api.context; console.log(`Theme: ${ctx.theme}`); console.log(`Project: ${ctx.project?.name ?? 'none'}`); console.log(`Session: ${ctx.session?.title ?? 'none'}`); ``` -------------------------------- ### RPC Demo Button Event Listeners Source: https://github.com/cloudcli-ai/cloudcli-plugin-starter/blob/main/index.html These event listeners are attached to buttons to trigger RPC calls to the '/hello' and '/echo' endpoints. The results are then displayed in the UI. ```javascript // ── RPC demo buttons ──────────────────────────────────────────── document.getElementById('btn-hello').addEventListener('click', async () => { const result = await callBackend('GET', '/hello'); document.getElementById('rpc-result').textContent = JSON.stringify(result, null, 2); }); document.getElementById('btn-echo').addEventListener('click', async () => { const result = await callBackend('POST', '/echo', { greeting: 'Hello from the plugin!' }); document.getElementById('rpc-result').textContent = JSON.stringify(result, null, 2); }); ``` -------------------------------- ### PluginAPI Interface (TypeScript) Source: https://context7.com/cloudcli-ai/cloudcli-plugin-starter/llms.txt The main API object passed to the plugin's mount function. It provides context access, change subscriptions, and RPC communication capabilities. ```typescript interface PluginAPI { readonly context: PluginContext; onContextChange(callback: (ctx: PluginContext) => void): () => void; rpc(method: string, path: string, body?: unknown): Promise; } ``` -------------------------------- ### RPC Communication with Plugin Backend Source: https://context7.com/cloudcli-ai/cloudcli-plugin-starter/llms.txt The `api.rpc()` method facilitates communication with the plugin's backend server subprocess via the host proxy. It supports various HTTP methods and can include an optional JSON body. ```APIDOC ## GET /stats ### Description Fetches project statistics from the plugin's backend server. ### Method GET ### Endpoint `/stats?path=` ### Query Parameters - **path** (string) - Required - The path to the project for which to fetch statistics. ### Response #### Success Response (200) - **ProjectStats** (object) - An object containing various statistics about the project. ### Request Example ```typescript // GET request with query parameters async function fetchStats(projectPath: string): Promise { const stats = await api.rpc( 'GET', `stats?path=${encodeURIComponent(projectPath)}` ) as ProjectStats; return stats; } ``` ## POST /echo ### Description Sends a message to the plugin's backend server to be echoed back. ### Method POST ### Endpoint `/echo` ### Request Body - **greeting** (string) - Required - The message to be echoed. ### Request Example ```typescript // POST request with JSON body async function echoMessage(message: string): Promise<{ echo: string }> { const result = await api.rpc( 'POST', '/echo', { greeting: message } ) as { echo: string }; return result; } ``` ### Response #### Success Response (200) - **echo** (string) - The echoed message. ### Response Example ```json { "echo": "Hello, World!" } ``` ### Usage with Error Handling ```typescript try { const stats = await api.rpc('GET', `stats?path=${api.context.project?.path}`); renderStats(stats as ProjectStats); } catch (err) { container.innerHTML = `
Error: ${(err as Error).message}
`; } ``` ``` -------------------------------- ### RPC Helper Function Source: https://github.com/cloudcli-ai/cloudcli-plugin-starter/blob/main/index.html Use this function to send RPC requests to the plugin's server subprocess via the postMessage bridge. It handles request IDs and listens for responses. ```javascript // ── RPC helper ────────────────────────────────────────────────── // Sends a request through the host's postMessage bridge, which // proxies it to this plugin's server subprocess. function callBackend(method, path, body) { return new Promise((resolve) => { const requestId = Math.random().toString(36).slice(2); function handler(event) { if (event.data?.type === 'ccui:rpc-response' && event.data.requestId === requestId) { window.removeEventListener('message', handler); resolve(event.data); } } window.addEventListener('message', handler); window.parent.postMessage({ type: 'ccui:rpc', requestId, method, path, body: body || undefined, }, '*'); }); } ``` -------------------------------- ### Implement Recursive Filesystem Scanner in TypeScript Source: https://context7.com/cloudcli-ai/cloudcli-plugin-starter/llms.txt This TypeScript code implements a recursive directory scanner that collects file statistics, respecting common ignore patterns. It includes utility sets for skipped directories and text file extensions. Use this for generating project statistics. ```typescript import fs from 'node:fs'; import path from 'node:path'; const SKIP_DIRS = new Set([ 'node_modules', '.git', 'dist', 'build', '.next', '.nuxt', 'coverage', '.cache', '__pycache__', '.venv', 'venv', 'target', 'vendor', '.turbo', 'out', '.output', 'tmp', ]); const TEXT_EXTS = new Set([ '.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.vue', '.svelte', '.css', '.scss', '.sass', '.less', '.html', '.json', '.yaml', '.md', '.txt', '.py', '.rb', '.go', '.rs', '.java', '.c', '.cpp', ]); interface FileInfo { full: string; rel: string; ext: string; size: number; mtime: number; } function scan(dir: string, max = 5000): FileInfo[] { const files: FileInfo[] = []; (function walk(d: string, depth: number): void { if (depth > 6 || files.length >= max) return; let entries: fs.Dirent[]; try { entries = fs.readdirSync(d, { withFileTypes: true }); } catch { return; } for (const e of entries) { if (files.length >= max) break; if (e.name.startsWith('.') && e.name !== '.env') continue; const full = path.join(d, e.name); if (e.isDirectory()) { if (!SKIP_DIRS.has(e.name)) walk(full, depth + 1); } else if (e.isFile()) { try { const stat = fs.statSync(full); files.push({ full, rel: path.relative(dir, full), ext: path.extname(e.name).toLowerCase() || '(none)', size: stat.size, mtime: stat.mtimeMs, }); } catch { /* skip unreadable */ } } } })(dir, 0); return files; } // Usage: Generate project statistics function getStats(projectPath: string): StatsResponse { if (!projectPath || !path.isAbsolute(projectPath)) { throw new Error('Invalid path'); } const files = scan(projectPath); const byExt: Record = {}; let totalLines = 0; let totalSize = 0; for (const f of files) { byExt[f.ext] = (byExt[f.ext] || 0) + 1; totalSize += f.size; if (TEXT_EXTS.has(f.ext)) { totalLines += countLines(f.full, f.size); } } return { totalFiles: files.length, totalLines, totalSize, byExtension: Object.entries(byExt).sort((a, b) => b[1] - a[1]).slice(0, 12), largest: [...files].sort((a, b) => b.size - a.size).slice(0, 6) .map((f) => ({ name: f.rel, size: f.size })), recent: [...files].sort((a, b) => b.mtime - a.mtime).slice(0, 6) .map((f) => ({ name: f.rel, mtime: f.mtime })), }; } ``` -------------------------------- ### Reacting to Context Changes with api.onContextChange() (TypeScript) Source: https://context7.com/cloudcli-ai/cloudcli-plugin-starter/llms.txt Subscribes to context changes to react to theme switches, project selections, or session updates. Returns an unsubscribe function that must be called during unmount. ```typescript export function mount(container: HTMLElement, api: PluginAPI): void { let currentProject = api.context.project; const unsubscribe = api.onContextChange((ctx) => { // React to theme changes if (ctx.theme === 'dark') { container.classList.add('dark-mode'); container.classList.remove('light-mode'); } else { container.classList.add('light-mode'); container.classList.remove('dark-mode'); } // React to project changes if (ctx.project?.path !== currentProject?.path) { currentProject = ctx.project; if (ctx.project) { loadProjectData(ctx.project.path); } } }); // Store for cleanup in unmount (container as any)._contextUnsub = unsubscribe; } ``` -------------------------------- ### Plugin Context Interface Source: https://github.com/cloudcli-ai/cloudcli-plugin-starter/blob/main/README.md Defines the structure of the `PluginContext` object available through the `api.context` property, including theme, project, and session details. ```typescript interface PluginContext { theme: 'dark' | 'light'; project: { name: string; path: string } | null; session: { id: string; title: string } | null; } ``` -------------------------------- ### Plugin unmount() Function Implementation (TypeScript) Source: https://context7.com/cloudcli-ai/cloudcli-plugin-starter/llms.txt Called when the plugin tab is deactivated. Cleans up event listeners, subscriptions, and DOM content. Ensure all resources are released to prevent memory leaks. ```typescript export function unmount(container: HTMLElement): void { // Call stored cleanup function (context change unsubscribe) if (typeof (container as any)._cleanup === 'function') { (container as any)._cleanup(); delete (container as any)._cleanup; } // Clear rendered content container.innerHTML = ''; } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.