원클릭으로
plugins-guide
Best practices for working with plugins in the kcosr/assistant repository.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Best practices for working with plugins in the kcosr/assistant repository.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
| name | plugins-guide |
| description | Best practices for working with plugins in the kcosr/assistant repository. |
This guide captures reusable patterns for building panel plugins: panel chrome, operations vs. WebSocket usage, binary endpoints, icons, CLI packaging, and cross‑platform behavior.
All panels should render the standard chrome header and let the shared PanelChromeController wire up controls. The header is built from three sections:
<div class="panel-header panel-chrome-row" data-role="chrome-row">
<div class="panel-header-main">
<span class="panel-header-label" data-role="chrome-title">Panel Title</span>
<div class="panel-chrome-instance" data-role="instance-actions">
<!-- Instance dropdown markup here -->
</div>
</div>
<div class="panel-chrome-plugin-controls" data-role="chrome-plugin-controls">
<!-- plugin-specific buttons go here -->
</div>
<div class="panel-chrome-frame-controls" data-role="chrome-controls">
<!-- standard move/reorder/menu/close buttons -->
</div>
</div>
Copy the structure from existing panels like notes or scheduled-sessions (includes toggle, move, reorder, menu, close). The PanelChromeController attaches handlers based on data-action attributes.
The controller:
You must pass the instance dropdown container with:
<div class="panel-chrome-instance-dropdown" data-role="instance-dropdown-container">…</div>
Then initialize:
chromeController = new PanelChromeController({
root: container,
host,
title: 'Panel Title',
onInstanceChange: (instanceId) => {
selectedInstanceId = instanceId;
persistState();
void refreshList();
},
});
Instance IDs now map to shared profiles declared at the top level profiles config.
When you want multi‑profile selection in a panel, opt into it explicitly:
chromeController = new PanelChromeController({
root: container,
host,
title: 'Notes',
instanceSelectionMode: 'multi',
onInstanceChange: (instanceIds) => {
selectedInstanceIds = instanceIds;
activeInstanceId = instanceIds[0] ?? 'default';
persistState();
void refreshList();
},
});
chromeController.setInstances(instances, selectedInstanceIds);
Context: include both the active instance and the full selection:
host.setContext(contextKey, {
instance_id: activeInstanceId,
instance_ids: selectedInstanceIds,
contextAttributes: {
'instance-id': activeInstanceId,
'instance-ids': selectedInstanceIds.join(','),
},
});
Notes:
default is always available (implicit).Panel icons shown in the header dock come from the panel manifest. The manifest icon value must match a key in packages/web-client/src/utils/icons.ts.
Example:
"icon": "fileText"
If the icon name is invalid, the UI falls back to panelGrid (window/grid icon).
Use HTTP operations for CRUD and WebSocket events only for broadcast updates:
list, create, update, delete → HTTP operationspanel_updateThis avoids startup races when WebSocket isn’t ready yet (common on mobile).
async function callOperation<T>(operation: string, body: Record<string, unknown>): Promise<T> {
const response = await apiFetch(`/api/plugins/<pluginId>/operations/${operation}`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(body),
});
const payload = (await response.json()) as { ok: true; result: T } | { error: string };
if (!response.ok || 'error' in payload) throw new Error(payload.error);
return payload.result;
}
Operations always return JSON. For binary endpoints (file download/preview), use extraHttpRoutes in the plugin module:
extraHttpRoutes: [
async (_context, req, res, url, segments) => {
if (req.method === 'GET' && segments[3] === 'files') {
res.setHeader('Content-Disposition', 'inline; filename="..."');
res.end(fileBuffer);
return true;
}
return false;
},
]
This is documented in docs/PLUGIN_SDK.md.
When serving files, support both:
?download=1)This enables an “open in browser” action and a “download” action with the same endpoint.
When building URLs for fetch/open, use helpers in web-client/src/utils/api.ts:
apiFetch() for relative API callsgetApiBaseUrl() for full URLs when opening externallyThis ensures URLs work in Tauri, Capacitor, and web.
window.open(url, '_blank') for view<a download> for downloadplugin:shell|open@capacitor/browser@capacitor/filesystem + optional @capacitor/sharePlugins can ship a custom CLI by placing bin/cli.ts in the plugin directory.
Build system compiles this file instead of auto‑generating a CLI from operations. Use this for:
--file)By default, npm run build:plugins exports skills to dist/skills/<pluginId>/.
To opt out for a specific plugin, set:
{
"skills": { "autoExport": false }
}
Passing --skills <pluginId> still forces export for that plugin.
Generated SKILL.md frontmatter includes metadata.author and metadata.version,
populated from the root package.json.
Use shared theme variables:
--color-text-primary--color-text-secondary--color-bg-hover--color-bg-activeAvoid custom fallbacks like --text-primary or prefers-color-scheme blocks.
Some dialogs are rendered by shared web-client controllers but styled in multiple plugin bundles. For example, list metadata dialog styles live in both:
packages/plugins/official/lists/web/styles.csspackages/plugins/official/notes/web/styles.cssWhen adjusting .list-metadata-* rules (or similar shared dialog styles), update both files to keep
Lists and Notes consistent.
Panels can provide context that is injected into user chat messages and shown in the context preview above the input. Use the panel context key and include selection data.
const contextKey = getPanelContextKey(host.panelId());
host.setContext(contextKey, {
type: 'list', // or 'artifacts', 'note', etc.
id: activeContainerId,
name: activeContainerName,
description: activeContainerDescription ?? '',
selectedItemIds,
selectedItems, // [{ id, title }]
selectedItemCount: selectedItemIds.length,
contextAttributes: {
'instance-id': selectedInstanceId,
// add custom attributes like 'selected-text' for text selections
},
});
services.notifyContextAvailabilityChange();
type, id, and name are used to build the <context ... /> line in chat.selectedItemIds/selectedItems populate selection context (e.g., list rows).contextAttributes is a key/value bag for extra metadata (keys should be kebab-case).services.notifyContextAvailabilityChange() after updates so the preview refreshes.assistant:clear-context-selection if you support selection clearing.On mount:
On visibility change:
This guide is meant as a reusable checklist for future plugin work (operations + binary endpoints, panel chrome, icons, CLI, and cross‑platform behaviors).