Rename all four packages/pi-* directories to forge-native names, stripping the 'pi' identity and establishing forge's own: - packages/pi-coding-agent → packages/coding-agent - packages/pi-ai → packages/ai - packages/pi-agent-core → packages/agent-core - packages/pi-tui → packages/tui Package names updated: - @singularity-forge/pi-coding-agent → @singularity-forge/coding-agent - @singularity-forge/pi-ai → @singularity-forge/ai - @singularity-forge/pi-agent-core → @singularity-forge/agent-core - @singularity-forge/pi-tui → @singularity-forge/tui All import references, bare string references, path references, internal variable names (_bundledPi*), and dist files updated. @mariozechner/pi-* third-party compat aliases preserved. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
5.3 KiB
| name | description |
|---|---|
| create-sf-extension | Create, debug, and iterate on SF extensions (TypeScript modules that add tools, commands, event hooks, custom UI, and providers to SF). Use when asked to build an extension, add a tool the LLM can call, register a slash command, hook into SF events, create custom TUI components, or modify SF behavior. Triggers on "create extension", "build extension", "add a tool", "register command", "hook into sf", "custom tool", "sf plugin", "sf extension". |
<essential_principles>
Extensions are TypeScript modules that hook into SF'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.
SF extension paths (community/user-installed extensions):
- Global:
~/.pi/agent/extensions/*.tsor~/.pi/agent/extensions/*/index.ts - Project-local:
.sf/extensions/*.tsor.sf/extensions/*/index.ts
Note: ~/.sf/agent/extensions/ is reserved for bundled extensions synced from the sf 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
StringEnumfrom@singularity-forge/aifor string enum params (NOTType.Union/Type.Literal— breaks Google's API) - Truncate tool output to 50KB / 2000 lines max (use
truncateHead/truncateTailfrom@singularity-forge/coding-agent) - Store stateful tool state in
detailsfor branching support - Check
signal?.abortedin long-running tool executions - Use
pi.exec()notchild_processfor shell commands - Check
ctx.hasUIbefore 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 exceedwidth— usetruncateToWidth() - Use theme from callback params, never import directly
- Strip leading
@from path params in custom tools (some models add it)
Available imports:
| Package | Purpose |
|---|---|
@singularity-forge/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 |
@singularity-forge/ai |
StringEnum (required for string enums), Type re-export |
@singularity-forge/tui |
Text, Box, Container, Spacer, Markdown, SelectList, Input, matchesKey, Key, truncateToWidth, visibleWidth |
| Node.js built-ins | node:fs, node:path, node:child_process, etc. |
</essential_principles>
Based on user intent, route to the appropriate workflow: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.
<reference_index>
All domain knowledge in references/:
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 SF extension guide
Gotchas: key-rules-gotchas.md
</reference_index>
<workflows_index>
| Workflow | Purpose |
|---|---|
| create-extension.md | Build a new extension from scratch |
| add-capability.md | Add tools, commands, hooks, UI to an existing extension |
| debug-extension.md | Diagnose and fix extension issues |
| </workflows_index> |
<success_criteria> Extension is complete when:
extension-manifest.jsonexists with accurateprovideslisting all registered tools/commands/hooks/shortcuts- TypeScript compiles without errors (jiti handles this at runtime)
- Extension loads on SF startup or
/reloadwithout errors - Tools appear in the LLM's system prompt and are callable
- Commands respond to
/commandinput - Event hooks fire at the expected lifecycle points
- Custom UI renders correctly within terminal width
- State persists correctly across session restarts (if stateful)
- Output is truncated to safe limits (if tools produce variable output) </success_criteria>