Extension Creator
Create AiderDesk extensions that extend functionality through events, commands, tools, agents, and modes.
When to Use
Use this skill when:
- Building a new AiderDesk extension
- Creating extension commands, tools, or event handlers
- Implementing the Extension interface
- Setting up extension metadata and documentation
Do not use when:
- Simply activating an existing extension
- Making general code changes unrelated to extensions
- Running tests or builds
Rules
Rule: Choose installation target first
When: Starting extension creation
Then: Ask the user where to install the extension
If: Working inside the AiderDesk project (the current project is the aider-desk repository)
Then: Offer three options:
- Current Project — Install to
.aider-desk/extensions/in the current project (project-scoped) - Global — Install to
~/.aider-desk/extensions/(available in all projects) - In-Repo — Create inside
packages/extensions/extensions/(ships with AiderDesk app)
If: Working outside the AiderDesk project
Then: Offer two options:
- Current Project — Install to
.aider-desk/extensions/in the current project (project-scoped) - Global — Install to
~/.aider-desk/extensions/(available in all projects)
Must: Wait for user's choice before proceeding. The chosen target determines the entire workflow.
Reference: references/install-targets.md for full details on each target.
Rule: Follow the correct flow for the chosen target
When: User has chosen an installation target
If: Target is Current Project or Global
Then: Follow the Project / Global Flow:
- Determine extension type (single-file or folder)
- Create extension file(s) in the target directory (
.aider-desk/extensions/or~/.aider-desk/extensions/) - Implement Extension interface methods
- Export metadata and default class
- Verify the extension loads (auto-discovered, no registry needed)
If: Target is In-Repo
Then: Follow the In-Repo Flow:
- Determine extension type (single-file or folder)
- Create extension file(s) in
packages/extensions/extensions/ - Implement Extension interface methods
- Export metadata and default class
- Register in
packages/extensions/extensions.json - Document in
docs-site/docs/extensions/extensions-gallery.md - Install npm dependencies if folder extension
- Verify with type checking
Rule: Determine extension type
When: Creating extension files (after target is chosen)
Then: Check if extension needs npm dependencies or multiple files
If: Extension needs dependencies or multiple files
Then: Create folder extension
If: Extension is simple with no dependencies
Then: Create single-file extension
Rule: Implement Extension interface
When: Creating extension file
Then: Implement required methods from Extension interface
Must: Define static metadata property on the class with name, version, description, author, capabilities
Must: Export class as default (e.g., export default class MyExtension implements Extension)
Never: Use @/ imports in extension files
Rule: Add UI components when needed
When: Extension needs to display UI elements in task page placements
Then: Implement getUIComponents() method
Must: Return array of UIComponentDefinition objects with id, placement, jsx
Must: Define components as JSX strings or load from .jsx files
If: Component needs data, set loadData: true and implement getUIExtensionData()
If: Component triggers actions, implement executeUIExtensionAction()
Rule: Use external libraries when UI components need npm packages
When: Extension UI components need third-party npm packages (charts, calendars, kanban boards, etc.)
Then: Implement getUIComponentsLibraries() returning a Record<string, string> mapping camelCase keys to npm package specs
Must: Access loaded libraries in JSX via props.libraries.<key>
Must: Handle the loading state — libraries are async and props.libraries.<key> will be undefined on first render
Never: Bundle or import React in library specs — AiderDesk's React instance is externalized automatically
Reference: external-libraries.md for full details, loading patterns, and examples
Rule: Add config component for extension settings
When: Extension needs user-configurable settings (shown in gear icon dialog)
Then: Implement three methods: getConfigComponent(), getConfigData(), saveConfigData()
Must: Return JSX string from getConfigComponent() — use external .jsx file for components > 20 lines
Must: Load/merge defaults in getConfigData(), persist merged data in saveConfigData()
Must: Store config file in extension directory via join(__dirname, 'config.json')
Must: Use ui.* components (ui.Input, ui.Checkbox, etc.) instead of raw HTML elements
Should: Avoid inner state (useState/useEffect) for simple form fields — read directly from config prop, call updateConfig on change. Only use local state for derived values or transient UI state.
Never: Use 'extension-settings' placement — it was removed; use the dedicated config API instead
Rule: Update registry and docs (In-Repo only)
When: Target is In-Repo and extension file is created
Then: Add entry to packages/extensions/extensions.json
Must: Include id, name, description, file or folder path, type, capabilities
Must: Set hasDependencies: true for folder extensions
Then: Add entry to docs-site/docs/extensions/extensions-gallery.md table
Must: Include extension name, description, capabilities, and type
When: Target is Project or Global
Then: Do NOT modify extensions.json or extensions-gallery.md — these are only for built-in extensions
Rule: Use proper TypeScript config for folders
When: Creating folder extension
Then: Include tsconfig.json with module: ES2020+
Must: Include package.json with name, version, main, dependencies
Rule: Store config in extension directory
When: Extension needs persistent config
Then: Store config files in extension directory
Never: Store config outside extension directory
Process Overview
For Project / Global targets:
- Ask user: Current Project or Global?
- Determine extension type (single-file or folder)
- Create extension file or directory structure in target dir
- Implement Extension interface methods
- Export metadata and default class
- Verify extension loads (auto-discovered)
Between steps 3 and 5:
- If extension needs config storage, create config.ts (or use inline getConfigData/saveConfigData)
- If extension has a settings UI (config component), create ConfigComponent.jsx and implement the three config methods
- If extension needs logging, create logger.ts
- If extension needs constants, create constants.ts
- If extension has placement-based UI components, create .jsx files for components (recommended for components > 20 lines)
For In-Repo target:
- Confirm user wants In-Repo (only available in aider-desk project)
- Determine extension type (single-file or folder)
- Create extension file or directory structure in
packages/extensions/extensions/ - Implement Extension interface methods
- Export metadata and default class
- Register in
packages/extensions/extensions.json - Document in
docs-site/docs/extensions/extensions-gallery.md - Run
npm installinpackages/extensions/(folder extensions) - Verify with type checking
Between steps 3 and 5:
- Same optional files as Project/Global flow above
Preconditions
Before using this skill, verify:
- Installation target has been chosen by the user
- Extension purpose and required capabilities are clear
- Extension type (single-file or folder) is determined
- Extension interface and types are understood
IMPORTANT: The reference files below are comprehensive but may lag behind the latest code. For the authoritative and complete API, always refer to these source files:
- Types source: packages/common/src/extensions.ts — Extension interface, all contexts (ExtensionContext, TaskContext, ProjectContext), event payloads, UI component definitions
- Context message types: packages/common/src/types/context.ts — ContextMessage, ContextFile, message part types
- Task/common types: packages/common/src/types/common.ts — TaskData, Model, AgentProfile, CreateTaskParams, etc.
When you need a method or type that's not in the reference docs, fetch the raw source first before guessing.
Built-in extension examples are available at:
- Online extensions gallery — documented overview
- Source code for each built-in extension is in the AiderDesk repo at
packages/extensions/extensions/[extension-name]/— browse these for real-world patterns
Reference docs
- references/install-targets.md for target options
- references/event-types.md for event types
- references/command-definition.md for command structure
Postconditions
After completing this skill, verify:
- Extension implements Extension interface correctly
- Static
metadataproperty on the class includes all required fields (name, version) - Default export is the extension class
- No
@/imports used - If In-Repo: extensions.json updated correctly
- If In-Repo: docs-site/docs/extensions/extensions-gallery.md updated
- If In-Repo: Type checking passes
- Extension loads without errors
- Extension appears in extensions list
- Extension capabilities work as expected
Success metrics:
- Extension loads without errors
- Extension appears in extensions list
- Extension capabilities work as expected
Common Situations
Situation: Extension needs to handle events
Pattern:
- When: Extension needs to modify agent behavior
- Then: Implement event handler methods (onAgentStarted, onToolCalled, etc.)
- Return: Partial event object to modify behavior
Situation: Extension needs to create subtasks and coordinate between them
Pattern:
- Create:
const newTask = await projectContext.createTask({ parentId: currentTaskId, name: 'Subtask' }) - Access another task:
const subtaskContext = projectContext.getTask(newTask.id) - Execute commands in subtask:
await subtaskContext?.runCustomCommand('scope:start') - Read subtask conversation:
const messages = await subtaskContext?.getContextMessages() - List all tasks:
const allTasks = await projectContext.getTasks() - Reload tasks from disk after external task-folder changes:
const allTasks = await projectContext.reloadTasks() - Fork at specific message:
await projectContext.forkTask(taskId, messageId) - Duplicate a task:
await projectContext.duplicateTask(taskId) - Delete a task:
await projectContext.deleteTask(taskId)
Situation: Extension needs to make direct LLM calls without the full agent loop
Pattern:
- Get model ID:
const profile = await taskContext.getTaskAgentProfile(); const modelId = profile ? \${profile.provider}/${profile.model}` : 'openai/gpt-4o'` - Generate text:
const result = await taskContext.generateText(modelId, 'You are a helpful assistant', 'Summarize this code') - Generate structured data:
const result = await taskContext.generateObject(modelId, systemPrompt, prompt, z.object({ category: z.string() }))
Situation: Extension needs to read conversation history
Pattern:
- Get messages:
const messages = await taskContext.getContextMessages() - Extract text: ContextMessage content can be a string or an array of parts. For simple text extraction:
function extractText(content: ContextMessage['content']): string { if (typeof content === 'string') return content; if (Array.isArray(content)) { return content .filter((part): part is TextPart => part.type === 'text') .map(part => part.text) .join('\n'); } return ''; } - Reference: context.ts for full ContextMessage type
Situation: Extension needs to run shell commands
Pattern: Node.js built-in modules (fs, path, child_process, os, etc.) are available in extensions.
- Synchronous:
import { execSync } from 'node:child_process'; const output = execSync('git status', { encoding: 'utf-8' }); - Asynchronous:
import { exec } from 'node:child_process'; import { promisify } from 'node:util'; const execAsync = promisify(exec); - File operations:
import { readFileSync, writeFileSync } from 'node:fs'; import { join } from 'node:path';
Situation: Extension needs to manage todos
Pattern:
- Get todos:
const todos = await taskContext.getTodos() - Add todo:
const todos = await taskContext.addTodo('Implement feature X') - Update todo:
const todos = await taskContext.updateTodo('Implement feature X', { completed: true }) - Delete todo:
const todos = await taskContext.deleteTodo('Implement feature X') - Replace all:
await taskContext.setTodos([{ name: 'Task 1', completed: false }])
Situation: Extension needs to register commands
Pattern:
- When: Extension provides slash commands
- Then: Implement getCommands() method
- Return: Array of CommandDefinition objects
Situation: Extension needs to add UI components
Pattern:
- When: Extension needs to display information in UI
- Then: Implement getUIComponents() method
- Return: Array of UIComponentDefinition objects
- Use: JSX strings or external .jsx files
- Built-ins: Use
props.ui.CodeBlockfor syntax-highlighted code, JSON, and diffs; useprops.ui.ExpandableMessageBlockfor collapsible tool-style message renderers - Load data: Implement getUIExtensionData() if component needs data
- Handle actions: Implement executeUIExtensionAction() for user interactions
Situation: Extension needs to log messages
Pattern:
- If: Debug/internal logging (developer diagnostics, not shown to users)
- Then: Use
context.log(message, type)— logs to backend console only - If: User-visible output (showing results, status, timing info in the chat)
- Then: Use
context.getTaskContext()?.addLogMessage(level, message)— displays in task's chat UI - When ambiguous: If the user says "log", "show", "display", or "report" something, default to
addLogMessage(user-visible). Usecontext.logonly for internal diagnostics. - Note:
context.logis always available;getTaskContext()returnsnulloutside a task, so always use optional chaining (?.)
Situation: Extension needs to store or retrieve memories
Pattern:
- When: Extension wants to persist knowledge across tasks (user preferences, code patterns, architectural decisions)
- Then: Use
context.getMemoryContext()to access the Memory API - Check: Always call
isMemoryEnabled()before using memory operations - Store:
memory.storeMemory(projectId, taskId, type, content)— returns the created memory ID - Retrieve:
memory.retrieveMemories(projectId, query, limit?)— returns semantically similar memories - Types: Use
MemoryEntryTypeenum ('task', 'user-preference', 'code-pattern') - Note: Works outside of project/task scope — pass empty strings for
projectId/taskIdif not applicable - Example:
async onAgentFinished(event: AgentFinishedEvent, context: ExtensionContext) { const memory = context.getMemoryContext(); if (!memory.isMemoryEnabled()) return; const projectId = context.getProjectDir(); const taskId = context.getTaskContext()?.data.id ?? ''; await memory.storeMemory(projectId, taskId, 'code-pattern', 'Always use clsx for conditional classes'); const memories = await memory.retrieveMemories(projectId, 'React class naming'); context.log(`Found ${memories.length} relevant memories`, 'info'); }
Situation: Extension UI components need third-party npm packages
Pattern:
- When: Extension needs a library like a chart, calendar, kanban board, etc. that isn't in the built-in
uiprop - Then: Implement
getUIComponentsLibraries()returning{ key: 'package@^version' } - Access: Use
props.libraries.<key>in JSX — check forundefined(async loading) - Example:
getUIComponentsLibraries() { return { chart: 'recharts@^2.12.0' } } - Reference: external-libraries.md
Situation: Extension needs to customize message rendering
Pattern:
- When: Extension wants to replace how messages (user, assistant, tool, log, etc.) are displayed
- Then: Use the
task-messageplacement with amessageFilterto specify which messages to handle - Must: Set
messageFilter.typesto the message types to match (e.g.'user','response','assistant-group','tool','log','loading') - Must: For tool-specific filters, set
messageFilter.serverNameand/ormessageFilter.toolName - Props: Component receives
messageprop — forassistant-grouptype, accessmessage.responseMessageandmessage.toolMessages - Style: Prefer
props.ui.ExpandableMessageBlockfor the standard collapsible tool-message layout andprops.ui.CodeBlockfor code or structured results - Reference: ui-components.md for full details,
MessageFiltertype, and JSX examples
Situation: Extension needs a floating panel
Pattern:
- When: Extension needs a draggable, resizable panel (dashboard, inspector, toggle panel)
- Then: Use the
floatingplacement and setnamefor the panel title - Must: Set
nameonUIComponentDefinition— used as the floating panel title bar text - Must: Use
loadData: true+getUIExtensionData()for panel data; implementexecuteUIExtensionAction()for actions - Must: Use
context.triggerUIDataRefresh(componentId)to refresh panel data,context.triggerUIComponentsReload()to re-register components after state changes - Reference: ui-components.md for full details, including toggle panel pattern
Situation: Extension needs config storage
Pattern:
- Check: Extension needs persistent settings
- If yes: Create config.ts with loadConfig and saveConfig functions
- Store: Config files in extension directory
Situation: Extension needs a settings UI (config component)
Pattern:
- When: Extension has user-configurable options that should appear in the Settings dialog
- Then: Implement
getConfigComponent(),getConfigData(),saveConfigData()methods - JSX: Return a
.jsxfile content viareadFileSync(join(__dirname, './ConfigComponent.jsx'), 'utf-8') - Props: Component receives
{ config, updateConfig, ui, icons, models, providers, ... } - Flow: Dialog opens → loads data via getConfigData → renders JSX with props → user edits → Save calls saveConfigData
- Reference: config-components.md for full guide and examples
References
Target Selection
- install-targets.md - All three installation targets, when to use each, key differences
Flows
- project-global-flow.md - Step-by-step for Project and Global installations
- in-repo-flow.md - Step-by-step for In-Repo (packages/extensions/) installations
Technical Reference (all targets)
- packages/common/src/extensions.ts - Extension types and interfaces
- extension-interface.md - Full Extension interface, ExtensionContext, TaskContext, ProjectContext, MemoryContext, and all supporting types
- extension-types.md - Single-file vs folder extensions, examples, extensions.json format
- event-types.md - All event types, payloads, and extension method mappings
- command-definition.md - Command structure
- ui-components.md - UI component system, placements, and available components
- external-libraries.md - Loading third-party npm libraries in UI components (getUIComponentsLibraries)
- config-components.md - Config component API (settings UI), methods, JSX format, and patterns
- examples-gallery.md - Real extension examples
Assets
- templates/single-file.ts.template - Single-file template
- templates/folder-extension/ - Folder template (basic)
- templates/folder-extension-with-config/index.ts.template - Folder template with config component API
- templates/ui-component.ts.template - UI component inline template
- templates/ui-component-external.ts.template - UI component with external JSX
- templates/Component.jsx.template - Placement-based JSX component template
- templates/ConfigComponent.jsx.template - Config/settings JSX component template