Extensions are TypeScript modules that hook into GSD's runtime (built on pi). They export a default function receiving ExtensionAPI and use it to subscribe to events, register tools/commands/shortcuts, and interact with the session.
GSD extension paths (community/user-installed extensions):
- Global:
~/.pi/agent/extensions/*.ts or ~/.pi/agent/extensions/*/index.ts
- Project-local:
.gsd/extensions/*.ts or .gsd/extensions/*/index.ts
Note: ~/.gsd/agent/extensions/ is reserved for bundled extensions synced from the gsd-pi package. Community extensions placed there are silently ignored by the loader.
The three primitives:
- Events — Listen and react (
pi.on("event", handler)). Can block tool calls, modify messages, inject context.
- Tools — Give the LLM new abilities (
pi.registerTool()). LLM calls them autonomously.
- Commands — Give users slash commands (
pi.registerCommand()). Users type /mycommand.
Non-negotiable rules:
- Use
StringEnum from @gsd/pi-ai for string enum params (NOT Type.Union/Type.Literal — breaks Google's API)
- Truncate tool output to 50KB / 2000 lines max (use
truncateHead/truncateTail from @gsd/pi-coding-agent)
- Store stateful tool state in
details for branching support
- Check
signal?.aborted in long-running tool executions
- Use
pi.exec() not child_process for shell commands
- Check
ctx.hasUI before dialog methods (non-interactive modes exist)
- Session control methods (
waitForIdle, newSession, fork, navigateTree, reload) are ONLY available in command handlers — they deadlock in event handlers
- Lines from
render() must not exceed width — use truncateToWidth()
- Use theme from callback params, never import directly
- Strip leading
@ from path params in custom tools (some models add it)
Available imports:
| Package |
Purpose |
@gsd/pi-coding-agent |
ExtensionAPI, ExtensionContext, Theme, event types, tool utilities, DynamicBorder, BorderedLoader, CustomEditor, highlightCode |
@sinclair/typebox |
Type.Object, Type.String, Type.Number, Type.Optional, Type.Boolean, Type.Array |
@gsd/pi-ai |
StringEnum (required for string enums), Type re-export |
@gsd/pi-tui |
Text, Box, Container, Spacer, Markdown, SelectList, Input, matchesKey, Key, truncateToWidth, visibleWidth |
| Node.js built-ins |
node:fs, node:path, node:child_process, etc. |
Building a new extension:
- "Create an extension", "build a tool", "I want to add a command" →
workflows/create-extension.md
Adding capabilities to an existing extension:
- "Add a tool to my extension", "add event hook", "add custom rendering" →
workflows/add-capability.md
Debugging an extension:
- "My extension doesn't work", "tool not showing up", "event not firing" →
workflows/debug-extension.md
If user intent is clear from context, skip the question and go directly to the workflow.
Core architecture: extension-lifecycle.md, events-reference.md
API surface: extensionapi-reference.md, extensioncontext-reference.md
Capabilities: custom-tools.md, custom-commands.md, custom-ui.md, custom-rendering.md
Patterns: state-management.md, system-prompt-modification.md, compaction-session-control.md
Infrastructure: model-provider-management.md, remote-execution-overrides.md, packaging-distribution.md, mode-behavior.md
Spec: docs/extension-sdk/manifest-spec.md — manifest format, tiers, validation
Testing: docs/extension-sdk/testing.md — mock patterns, test conventions
SDK: docs/extension-sdk/ — the authoritative GSD-2 extension guide
Gotchas: key-rules-gotchas.md
1---2name: create-gsd-extension3description: Create, debug, and iterate on GSD extensions (TypeScript modules that add tools, commands, event hooks, custom UI, and providers to GSD). Use when asked to build an extension, add a tool the LLM can call, register a slash command, hook into GSD events, create custom TUI components, or modify GSD behavior. Triggers on "create extension", "build extension", "add a tool", "register command", "hook into gsd", "custom tool", "gsd plugin", "gsd extension".4---56<essential_principles>78**Extensions are TypeScript modules** that hook into GSD's runtime (built on pi). They export a default function receiving `ExtensionAPI` and use it to subscribe to events, register tools/commands/shortcuts, and interact with the session.910**GSD extension paths (community/user-installed extensions):**11- Global: `~/.pi/agent/extensions/*.ts` or `~/.pi/agent/extensions/*/index.ts`12- Project-local: `.gsd/extensions/*.ts` or `.gsd/extensions/*/index.ts`1314Note: `~/.gsd/agent/extensions/` is reserved for bundled extensions synced from the gsd-pi package. Community extensions placed there are silently ignored by the loader.1516**The three primitives:**171. **Events** — Listen and react (`pi.on("event", handler)`). Can block tool calls, modify messages, inject context.182. **Tools** — Give the LLM new abilities (`pi.registerTool()`). LLM calls them autonomously.193. **Commands** — Give users slash commands (`pi.registerCommand()`). Users type `/mycommand`.2021**Non-negotiable rules:**22- Use `StringEnum` from `@gsd/pi-ai` for string enum params (NOT `Type.Union`/`Type.Literal` — breaks Google's API)23- Truncate tool output to 50KB / 2000 lines max (use `truncateHead`/`truncateTail` from `@gsd/pi-coding-agent`)24- Store stateful tool state in `details` for branching support25- Check `signal?.aborted` in long-running tool executions26- Use `pi.exec()` not `child_process` for shell commands27- Check `ctx.hasUI` before dialog methods (non-interactive modes exist)28- Session control methods (`waitForIdle`, `newSession`, `fork`, `navigateTree`, `reload`) are ONLY available in command handlers — they deadlock in event handlers29- Lines from `render()` must not exceed `width` — use `truncateToWidth()`30- Use theme from callback params, never import directly31- Strip leading `@` from path params in custom tools (some models add it)3233**Available imports:**3435| Package | Purpose |36|---------|---------|37| `@gsd/pi-coding-agent` | `ExtensionAPI`, `ExtensionContext`, `Theme`, event types, tool utilities, `DynamicBorder`, `BorderedLoader`, `CustomEditor`, `highlightCode` |38| `@sinclair/typebox` | `Type.Object`, `Type.String`, `Type.Number`, `Type.Optional`, `Type.Boolean`, `Type.Array` |39| `@gsd/pi-ai` | `StringEnum` (required for string enums), `Type` re-export |40| `@gsd/pi-tui` | `Text`, `Box`, `Container`, `Spacer`, `Markdown`, `SelectList`, `Input`, `matchesKey`, `Key`, `truncateToWidth`, `visibleWidth` |41| Node.js built-ins | `node:fs`, `node:path`, `node:child_process`, etc. |4243</essential_principles>4445<routing>46Based on user intent, route to the appropriate workflow:4748**Building a new extension:**49- "Create an extension", "build a tool", "I want to add a command" → `workflows/create-extension.md`5051**Adding capabilities to an existing extension:**52- "Add a tool to my extension", "add event hook", "add custom rendering" → `workflows/add-capability.md`5354**Debugging an extension:**55- "My extension doesn't work", "tool not showing up", "event not firing" → `workflows/debug-extension.md`5657**If user intent is clear from context, skip the question and go directly to the workflow.**58</routing>5960<reference_index>61All domain knowledge in `references/`:6263**Core architecture:** extension-lifecycle.md, events-reference.md64**API surface:** extensionapi-reference.md, extensioncontext-reference.md65**Capabilities:** custom-tools.md, custom-commands.md, custom-ui.md, custom-rendering.md66**Patterns:** state-management.md, system-prompt-modification.md, compaction-session-control.md67**Infrastructure:** model-provider-management.md, remote-execution-overrides.md, packaging-distribution.md, mode-behavior.md68**Spec:** `docs/extension-sdk/manifest-spec.md` — manifest format, tiers, validation69**Testing:** `docs/extension-sdk/testing.md` — mock patterns, test conventions70**SDK:** `docs/extension-sdk/` — the authoritative GSD-2 extension guide71**Gotchas:** key-rules-gotchas.md72</reference_index>7374<workflows_index>75| Workflow | Purpose |76|----------|---------|77| create-extension.md | Build a new extension from scratch |78| add-capability.md | Add tools, commands, hooks, UI to an existing extension |79| debug-extension.md | Diagnose and fix extension issues |80</workflows_index>8182<success_criteria>83Extension is complete when:84- `extension-manifest.json` exists with accurate `provides` listing all registered tools/commands/hooks/shortcuts85- TypeScript compiles without errors (jiti handles this at runtime)86- Extension loads on GSD startup or `/reload` without errors87- Tools appear in the LLM's system prompt and are callable88- Commands respond to `/command` input89- Event hooks fire at the expected lifecycle points90- Custom UI renders correctly within terminal width91- State persists correctly across session restarts (if stateful)92- Output is truncated to safe limits (if tools produce variable output)93</success_criteria>