singularity-forge/src/resources/skills/create-sf-extension/SKILL.md
Mikael Hugo 02a4339a51 refactor: rename pi-* packages to forge-native names (Phase 1)
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>
2026-05-10 11:28:01 +02:00

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/*.ts or ~/.pi/agent/extensions/*/index.ts
  • Project-local: .sf/extensions/*.ts or .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:

  1. Events — Listen and react (pi.on("event", handler)). Can block tool calls, modify messages, inject context.
  2. Tools — Give the LLM new abilities (pi.registerTool()). LLM calls them autonomously.
  3. Commands — Give users slash commands (pi.registerCommand()). Users type /mycommand.

Non-negotiable rules:

  • Use StringEnum from @singularity-forge/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 @singularity-forge/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
@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.json exists with accurate provides listing all registered tools/commands/hooks/shortcuts
  • TypeScript compiles without errors (jiti handles this at runtime)
  • Extension loads on SF startup or /reload without errors
  • Tools appear in the LLM's system prompt and are callable
  • Commands respond to /command input
  • 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>