SuperAPI AI Directory

extension-creator

Coding & development

View repository

Updated 10-15 22:14

About this skill

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

  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:

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

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

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:
    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.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:
    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(scopeId, taskId, type, content) — returns the created memory ID
  • Retrieve: memory.retrieveMemories(scopeId, query, limit?) — returns semantically similar memories
  • Types: Use MemoryEntryType enum ('task', 'user-preference', 'code-pattern')
  • Scope: use the project directory (context.getProjectDir()) for project-scoped memories, or agent-profile:{profileId} to share memories across projects for agents with memory isolation (useAgentMemoryScope) enabled
  • Note: Works outside of project/task scope — pass empty strings for scopeId/taskId if not applicable
  • Example:
    async onAgentFinished(event: AgentFinishedEvent, context: ExtensionContext) {
      const memory = context.getMemoryContext();
      if (!memory.isMemoryEnabled()) return;
    
      const scopeId = context.getProjectDir();
      const taskId = context.getTaskContext()?.data.id ?? '';
    
      await memory.storeMemory(scopeId, taskId, 'code-pattern', 'Always use clsx for conditional classes');
      const memories = await memory.retrieveMemories(scopeId, '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

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 for full details, MessageFilter type, and JSX examples

Situation: Extension needs custom actions in the message context menu

Pattern:

  • When: Extension wants to add extra actions to a message's vertical-dots dropdown menu (e.g. "undo changes")
  • Then: Use the task-message-bar-menu placement — components render as menu items below the built-in actions, separated by a divider
  • Props: Component receives message prop for the specific message
  • Must: Style JSX as a menu item (flex row, hover:bg-bg-tertiary, text-2xs) to match built-in menu entries
  • Note: The menu button appears when there are built-in menu actions or components registered for this placement; use task-message-bar for inline actions shown directly in the bar instead
  • Reference: ui-components.md

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 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 for full guide and examples

References

Target Selection

Flows

Technical Reference (all targets)

Assets