Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
7.0 KiB
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
// 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:
document.addEventListener('sw:ready', (e) => {
const sw = e.detail.sw;
});
sw.user
Current authenticated user (read-only getter).
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<object> |
post |
(path, body, opts?) |
Promise<object> |
put |
(path, body, opts?) |
Promise<object> |
del |
(path, opts?) |
Promise<object> |
stream |
(path, body, signal?) |
Promise<Response> |
opts accepts { signal: AbortSignal } for cancellation.
Examples
// 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.
// 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 |
// 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.
// Toast notification
sw.toast('Saved successfully', 'success'); // types: success, error, info, warn
sw.toast('Something went wrong', 'error');
// Confirmation dialog (returns Promise<boolean>)
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.
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.
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.
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.
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.
sw.workflow.registerSurface('intake-form', (container, ctx) => {
// ctx: { channelId, basePath, stageMode, formTemplate, ... }
container.innerHTML = '<h2>Custom Intake</h2>';
return {
mount() { /* called on attach */ },
unmount() { /* called on detach */ },
};
});
sw.workflow.getContext(channelId)
Fetch workflow instance status.
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.
await sw.workflow.advance('ch-abc', { approved: true });
sw.workflow.reject(channelId, reason)
Reject the current stage (go back).
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
// 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, '<mark>TODO</mark>');
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:
sw.pipe.render(50, myFilter, {
scope: { channelType: ['direct', 'group'] },
source: 'my-extension',
});
Introspection
sw.pipe.list();
// { pre: [...], stream: [...], render: [...] }
// Each entry: { priority, source, scope, calls, avgMs, errors }