# Extension Creator

> Create AiderDesk extensions by setting up extension files, defining metadata, implementing Extension interface methods, and updating documentation. Use when building a new extension, creating extension commands, tools, or event handlers.

- Skill: `hotovo/extension-creator` (Agent Skill, multi-file: 23 files)
- Install (CLI): `npx skillmds@latest add hotovo/extension-creator`
- Raw SKILL.md: https://api.skillmd.com/api/skills/hotovo/extension-creator/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Docs & Writing
- Author: hotovo (https://skillmd.com/u/hotovo)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/hotovo/extension-creator

---


# 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:
1. **Current Project** — Install to `.aider-desk/extensions/` in the current project (project-scoped)
2. **Global** — Install to `~/.aider-desk/extensions/` (available in all projects)
3. **In-Repo** — Create inside `packages/extensions/extensions/` (ships with AiderDesk app)

**If:** Working outside the AiderDesk project

**Then:** Offer two options:
1. **Current Project** — Install to `.aider-desk/extensions/` in the current project (project-scoped)
2. **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](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](references/project-global-flow.md):
1. Determine extension type (single-file or folder)
2. Create extension file(s) in the target directory (`.aider-desk/extensions/` or `~/.aider-desk/extensions/`)
3. Implement Extension interface methods
4. Export metadata and default class
5. Verify the extension loads (auto-discovered, no registry needed)

**If:** Target is **In-Repo**

**Then:** Follow the [In-Repo Flow](references/in-repo-flow.md):
1. Determine extension type (single-file or folder)
2. Create extension file(s) in `packages/extensions/extensions/`
3. Implement Extension interface methods
4. Export metadata and default class
5. **Register in `packages/extensions/extensions.json`**
6. **Document in `docs-site/docs/extensions/extensions-gallery.md`**
7. Install npm dependencies if folder extension
8. 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](references/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: Validate UI components after creation

**When:** Any UI or config component (.jsx file) was created or modified

**Then:** Run the validator script and fix all reported issues

**Must:** Run `node <skill-dir>/assets/scripts/validate-extension-ui.mjs <file-or-dir>` where `<skill-dir>` is this skill's directory

**Must:** Ensure every file passes — exit code 0, all lines say `PASS`

**Must:** Fix all `[SYNTAX]` issues (the component would crash at render time) and `[TYPE]` issues (typos in props, React API, or `ui.*` component names)

**If:** A file uses config-style props (`config`, `updateConfig`) but is not named `Config*.jsx`

**Then:** Add `--type=config` (conversely, `--type=ui` forces UI-component props)

**Note:** The script needs `sucrase`, `typescript`, and `@types/react` resolvable (they are, when the aider-desk repo is available); otherwise it degrades to whatever checks are possible and says so in a warning

### 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:

1. Ask user: Current Project or Global?
2. Determine extension type (single-file or folder)
3. Create extension file or directory structure in target dir
4. Implement Extension interface methods
5. Export metadata and default class
6. 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)

**After any .jsx file is created:**
- Validate it: `node <skill-dir>/assets/scripts/validate-extension-ui.mjs <extension-dir>` — all files must pass

### For In-Repo target:

1. Confirm user wants In-Repo (only available in aider-desk project)
2. Determine extension type (single-file or folder)
3. Create extension file or directory structure in `packages/extensions/extensions/`
4. Implement Extension interface methods
5. Export metadata and default class
6. **Register in `packages/extensions/extensions.json`**
7. **Document in `docs-site/docs/extensions/extensions-gallery.md`**
8. Run `npm install` in `packages/extensions/` (folder extensions)
9. Verify with type checking

**Between steps 3 and 5:**
- Same optional files as Project/Global flow above

**After any .jsx file is created:**
- Validate it: `node <skill-dir>/assets/scripts/validate-extension-ui.mjs <extension-dir>` — all files must pass

## 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](https://raw.githubusercontent.com/hotovo/aider-desk/refs/heads/main/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](https://raw.githubusercontent.com/hotovo/aider-desk/refs/heads/main/packages/common/src/types/context.ts) — ContextMessage, ContextFile, message part types
- **Task/common types:** [packages/common/src/types/common.ts](https://raw.githubusercontent.com/hotovo/aider-desk/refs/heads/main/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](https://github.com/hotovo/aider-desk/blob/main/docs-site/docs/extensions/extensions-gallery.md) — 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](references/install-targets.md) for target options
- [references/event-types.md](references/event-types.md) for event types
- [references/command-definition.md](references/command-definition.md) for command structure

## Postconditions

After completing this skill, verify:

- Extension implements Extension interface correctly
- Static `metadata` property on the class includes all required fields (name, version)
- Default export is the extension class
- No `@/` imports used
- All UI/config .jsx components pass `assets/scripts/validate-extension-ui.mjs`
- **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:
  ```typescript
  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](https://raw.githubusercontent.com/hotovo/aider-desk/refs/heads/main/packages/common/src/types/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.CodeBlock` for syntax-highlighted code, JSON, and diffs; use `props.ui.ExpandableMessageBlock` for 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). Use `context.log` only for internal diagnostics.
- Note: `context.log` is always available; `getTaskContext()` returns `null` outside a task, so always use optional chaining (`?.`)

**Situation:** Extension needs to manage resource cleanup (timers, watchers, child processes)

**Pattern:**
- When: Extension creates resources in `onLoad`, `onProjectStarted`, or event handlers that need cleanup
- Then: Use `context.addDisposable()` to co-locate setup and cleanup logic
- Setup runs immediately; if it returns a function, that function is called on unload (LIFO order). The cleanup may be sync or async (`Promise`) — async cleanups are awaited during unload
- Disposers run **before** `onUnload` is called, so both patterns work together
- Example:
  ```typescript
  async onLoad(context: ExtensionContext) {
    // Setup and cleanup are co-located — no need to track in onUnload
    context.addDisposable(() => {
      const timer = setInterval(() => doWork(), 1000);
      return () => clearInterval(timer);
    });
  }
  // onUnload still works as a fallback for anything not registered via addDisposable
  ```
- If setup returns `void`, no cleanup is registered — useful for fire-and-forget side effects

**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 `MemoryEntryType` enum ('task', 'user-preference', 'code-pattern')
- Note: Works outside of project/task scope — pass empty strings for `projectId`/`taskId` if not applicable
- Example:
  ```typescript
  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 `ui` prop
- Then: Implement `getUIComponentsLibraries()` returning `{ key: 'package@^version' }`
- Access: Use `props.libraries.<key>` in JSX — check for `undefined` (async loading)
- Example: `getUIComponentsLibraries() { return { chart: 'recharts@^2.12.0' } }`
- Reference: [external-libraries.md](references/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-message` placement with a `messageFilter` to specify which messages to handle
- Must: Set `messageFilter.types` to the message types to match (e.g. `'user'`, `'response'`, `'assistant-group'`, `'tool'`, `'log'`, `'loading'`)
- Must: For tool-specific filters, set `messageFilter.serverName` and/or `messageFilter.toolName`
- Props: Component receives `message` prop — for `assistant-group` type, access `message.responseMessage` and `message.toolMessages`
- Style: Prefer `props.ui.ExpandableMessageBlock` for the standard collapsible tool-message layout and `props.ui.CodeBlock` for code or structured results
- Reference: [ui-components.md](references/ui-components.md) for full details, `MessageFilter` type, and JSX examples

**Situation:** Extension needs a floating panel

**Pattern:**
- When: Extension needs a draggable, resizable panel (dashboard, inspector, toggle panel)
- Then: Use the `floating` placement and set `name` for the panel title
- Must: Set `name` on `UIComponentDefinition` — used as the floating panel title bar text
- Must: Use `loadData: true` + `getUIExtensionData()` for panel data; implement `executeUIExtensionAction()` for actions
- Must: Use `context.triggerUIDataRefresh(componentId)` to refresh panel data, `context.triggerUIComponentsReload()` to re-register components after state changes
- Reference: [ui-components.md](references/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 `.jsx` file content via `readFileSync(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](references/config-components.md) for full guide and examples

## References

### Target Selection

- [install-targets.md](references/install-targets.md) - All three installation targets, when to use each, key differences

### Flows

- [project-global-flow.md](references/project-global-flow.md) - Step-by-step for Project and Global installations
- [in-repo-flow.md](references/in-repo-flow.md) - Step-by-step for In-Repo (packages/extensions/) installations

### Technical Reference (all targets)

- [packages/common/src/extensions.ts](https://raw.githubusercontent.com/hotovo/aider-desk/refs/heads/main/packages/common/src/extensions.ts) - Extension types and interfaces
- [extension-interface.md](references/extension-interface.md) - Full Extension interface, ExtensionContext, TaskContext, ProjectContext, MemoryContext, and all supporting types
- [extension-types.md](references/extension-types.md) - Single-file vs folder extensions, examples, extensions.json format
- [event-types.md](references/event-types.md) - All event types, payloads, and extension method mappings
- [command-definition.md](references/command-definition.md) - Command structure
- [ui-components.md](references/ui-components.md) - UI component system, placements, and available components
- [external-libraries.md](references/external-libraries.md) - Loading third-party npm libraries in UI components (getUIComponentsLibraries)
- [config-components.md](references/config-components.md) - Config component API (settings UI), methods, JSX format, and patterns
- [examples-gallery.md](references/examples-gallery.md) - Real extension examples

## Assets

- [scripts/validate-extension-ui.mjs](assets/scripts/validate-extension-ui.mjs) - UI component validator (syntax + type check)
- [templates/single-file.ts.template](assets/templates/single-file.ts.template) - Single-file template
- [templates/folder-extension/](assets/templates/folder-extension/) - Folder template (basic)
- [templates/folder-extension-with-config/index.ts.template](assets/templates/folder-extension-with-config/index.ts.template) - Folder template with config component API
- [templates/ui-component.ts.template](assets/templates/ui-component.ts.template) - UI component inline template
- [templates/ui-component-external.ts.template](assets/templates/ui-component-external.ts.template) - UI component with external JSX
- [templates/Component.jsx.template](assets/templates/Component.jsx.template) - Placement-based JSX component template
- [templates/ConfigComponent.jsx.template](assets/templates/ConfigComponent.jsx.template) - Config/settings JSX component template

