# Switchboard SDK Reference > `switchboard-sdk.js` — v0.30.2 The Switchboard SDK provides a unified API for surfaces and extensions. It wraps platform internals (API, Events, Theme, ChatPane, etc.) into a single `sw` namespace so authors never need to import platform files directly. ## Initialization ```js // Automatic — the SDK initializes on page load. // Access via the global `sw` alias: sw.api.get('/api/v1/channels').then(console.log); ``` The SDK emits a `sw:ready` CustomEvent on `document` when initialized. Extensions loaded after boot can listen for it: ```js document.addEventListener('sw:ready', (e) => { const sw = e.detail.sw; }); ``` --- ## sw.user Current authenticated user (read-only getter). ```js const u = sw.user; // { id, username, display_name, email, role, avatar } ``` Returns `null` if not authenticated. ## sw.isAdmin `true` if the current user has the `admin` role. --- ## sw.api REST client with automatic auth header injection and 401 retry. | Method | Signature | Returns | |--------|-----------|---------| | `get` | `(path, opts?)` | `Promise` | | `post` | `(path, body, opts?)` | `Promise` | | `put` | `(path, body, opts?)` | `Promise` | | `del` | `(path, opts?)` | `Promise` | | `stream` | `(path, body, signal?)` | `Promise` | `opts` accepts `{ signal: AbortSignal }` for cancellation. ### Examples ```js // List channels const channels = await sw.api.get('/api/v1/channels'); // Create a channel const ch = await sw.api.post('/api/v1/channels', { title: 'Support', type: 'direct' }); // Streaming completion const resp = await sw.api.stream('/api/v1/completions', { channel_id: ch.id, message: 'Hello' }); const reader = resp.body.getReader(); ``` --- ## sw.on / sw.once / sw.off / sw.emit EventBus integration. Supports wildcard patterns. ```js // Subscribe const unsub = sw.on('chat.message.*', (payload) => { console.log('New message:', payload); }); // One-shot sw.once('channel.created', (ch) => { ... }); // Unsubscribe sw.off('chat.message.*', handler); // Emit sw.emit('custom.event', { data: 123 }); ``` ### Common Events | Event | Payload | Description | |-------|---------|-------------| | `chat.message.sent` | `{ message }` | User sent a message | | `chat.message.received` | `{ message }` | AI response received | | `channel.created` | `{ channel }` | New channel created | | `channel.switched` | `{ channelId }` | Active channel changed | | `theme.changed` | `{ theme }` | Theme mode changed | --- ## sw.theme Theme observation and control. | Property/Method | Type | Description | |-----------------|------|-------------| | `current` | `string` (getter) | Resolved theme: `'dark'` or `'light'` | | `mode` | `string` (getter) | User preference: `'dark'`, `'light'`, or `'system'` | | `set(mode)` | `void` | Set theme mode | | `on('change', fn)` | `() => void` | Subscribe to theme changes; returns unsubscribe fn | ```js // React to theme changes const unsub = sw.theme.on('change', (resolved) => { document.body.classList.toggle('dark', resolved === 'dark'); }); ``` --- ## sw.toast / sw.confirm / sw.modal UI primitives for notifications and dialogs. ```js // Toast notification sw.toast('Saved successfully', 'success'); // types: success, error, info, warn sw.toast('Something went wrong', 'error'); // Confirmation dialog (returns Promise) const ok = await sw.confirm('Delete this item?'); // Modal sw.modal.open(htmlContentOrElementId); sw.modal.close(id); ``` --- ## sw.chat(container, opts) Mount a ChatPane instance into a DOM element. ```js const pane = sw.chat(document.getElementById('my-chat'), { channelId: 'abc-123', standalone: true, // default: true }); // pane.renderMessages(), pane.destroy(), etc. ``` **Options:** | Key | Type | Default | Description | |-----|------|---------|-------------| | `channelId` | `string` | `null` | Channel to load | | `standalone` | `boolean` | `true` | Standalone mode (own input area) | --- ## sw.notes(container, opts) Mount a NotePanel instance into a DOM element. ```js const panel = sw.notes(document.getElementById('my-notes'), { projectId: 'proj-123', }); // panel.loadNotesList(), panel.openNoteEditor(id), panel.destroy() ``` --- ## sw.panels() Access the PanelRegistry for sidebar panel management. ```js const panels = sw.panels(); panels.open('notes'); panels.toggle('editor'); panels.isOpen('notes'); // boolean panels.active(); // current panel name or null panels.cycle(); // cycle to next panel panels.register('custom', { ... }); ``` --- ## sw.workflow Workflow stage surface management (v0.30.2). ### sw.workflow(container, opts) Mount a workflow stage surface. ```js sw.workflow(document.getElementById('stage-mount'), { channelId: 'ch-abc', stageMode: 'form_only', // chat_only | form_only | form_chat | review surfacePkgId: 'my-surface', // optional: custom package surface formTemplate: { fields: [...] }, }); ``` ### sw.workflow.registerSurface(name, factory) Register a custom stage surface from a package. ```js sw.workflow.registerSurface('intake-form', (container, ctx) => { // ctx: { channelId, basePath, stageMode, formTemplate, ... } container.innerHTML = '

Custom Intake

'; return { mount() { /* called on attach */ }, unmount() { /* called on detach */ }, }; }); ``` ### sw.workflow.getContext(channelId) Fetch workflow instance status. ```js const status = await sw.workflow.getContext('ch-abc'); // { workflow_id, current_stage, stage_data, ... } ``` ### sw.workflow.advance(channelId, data?) Advance to the next stage, optionally merging data. ```js await sw.workflow.advance('ch-abc', { approved: true }); ``` ### sw.workflow.reject(channelId, reason) Reject the current stage (go back). ```js await sw.workflow.reject('ch-abc', 'Missing required documents'); ``` --- ## sw.pipe Filter pipeline for intercepting chat messages at three stages: pre-send, post-receive (stream), and post-render. ### Registration ```js // Pre-send filter (runs before message is sent to API) sw.pipe.pre(50, (ctx) => { // ctx: { message, channel, metadata } ctx.message += '\n\n[via my extension]'; return ctx; // return ctx to continue, null to halt }); // Stream filter (runs on each SSE chunk) sw.pipe.stream(50, (ctx) => { // ctx: { chunk, channel, accumulated } return ctx; }); // Render filter (runs after markdown rendering) sw.pipe.render(50, (ctx) => { // ctx: { html, message, channel } ctx.html = ctx.html.replace(/TODO/g, 'TODO'); return ctx; }); ``` **Priority:** lower numbers run first. Convention: 0-49 system, 50-99 extensions, 100+ user customizations. **Scope:** restrict a filter to specific channel types: ```js sw.pipe.render(50, myFilter, { scope: { channelType: ['direct', 'group'] }, source: 'my-extension', }); ``` ### Introspection ```js sw.pipe.list(); // { pre: [...], stream: [...], render: [...] } // Each entry: { priority, source, scope, calls, avgMs, errors } ```