Swaps hardcoded "pi" / ".pi" / "π" for the existing APP_NAME and
CONFIG_DIR_NAME extension points at four sites the original APP_NAME
rollout missed:
- /quit slash-command description
- process.title (cli.ts and bun/cli.ts)
- project-local extensions dir in loader.ts
For the terminal title, adds an APP_TITLE export in config.ts that
keeps pi's "π" glyph when piConfig.name is unset and falls back to
APP_NAME otherwise. Presence of piConfig.name acts as the "has been
rebranded" signal, so pi's own package.json drops the redundant
"name": "pi" (the existing `|| "pi"` fallback in config.ts makes this
a no-op for APP_NAME).
No behavior change for pi itself: process.title, /quit description,
terminal title prefix, and project-local extensions dir all resolve
to the same values as before.
closes#3476
* fix(typebox): migrate to v1 with extension compat
Replace AJV-based validation with TypeBox-native validation, keep legacy extension imports working (including @sinclair/typebox/compiler), and restore coercion for serialized/plain JSON schemas.
This change closes#3112.
* fix(typebox): use canonical imports and harden coercion
Switch first-party code to canonical typebox imports while retaining legacy extension aliases in the loader.
Remove obsolete runtime codegen guards, expand serialized JSON-schema coercion coverage, and update related tests and fixtures.
Fixes#3112.
---------
Co-authored-by: Mario Zechner <badlogicgames@gmail.com>
The previous fix (83b57924) replaced require.resolve() with bare
specifier fallbacks to avoid ERR_PACKAGE_PATH_NOT_EXPORTED for ESM-only
packages like pi-ai. However, bare specifiers are not valid jiti alias
targets, breaking extension loading in global npm installs.
Use import.meta.resolve() as the fallback instead. It respects the
"import" exports condition and returns a real file path.
- Package deduplication: same package in global+project, project wins
- Collision detection for skills, prompts, and themes with ResourceCollision type
- PathMetadata tracking with parent directory lookup for file paths
- Display improvements: section headers, sorted groups, accent colors for packages
- pi list shows full paths below package names
- Extension loader discovers files in directories without index.ts
- In-memory SettingsManager properly tracks project settings
fixes#645
- Add ResourceLoader interface and DefaultResourceLoader implementation
- Add PackageManager for npm/git extension sources with install/remove/update
- Add session.reload() and session.bindExtensions() APIs
- Add /reload command in interactive mode
- Add CLI flags: --skill, --theme, --prompt-template, --no-themes, --no-prompt-templates
- Add pi install/remove/update commands for extension management
- Refactor settings.json to use arrays for skills, prompts, themes
- Remove legacy SkillsSettings source flags and filters
- Update SDK examples and documentation for ResourceLoader pattern
- Add theme registration and loadThemeFromPath for dynamic themes
- Add getShellEnv to include bin dir in PATH for bash commands
- Remove loader exports from index.ts to break circular dependency
- Static import of index.js in loader.ts for virtualModules
- Add @mariozechner/pi-agent-core to virtualModules
- Update @mariozechner/jiti to 2.6.5 (bundles babel for Bun binary)
- Update SDK docs to use correct extension terminology
- Switch from jiti to @mariozechner/jiti fork
- Use virtualModules option for bundled packages in compiled binary
- Disable tryNative so jiti handles all nested imports
- Lazy-load pi-coding-agent to avoid circular dependency
When running from source with tsx, the jiti aliases pointed to .js files
that don't exist (only .ts files exist). This caused jiti to fall back to
different module resolution, loading a separate instance of theme.ts where
initTheme() was never called, resulting in undefined theme.
Now checks if .js exists and falls back to .ts for dev mode compatibility.
Breaking change: pi.getAllTools() now returns Array<{ name, description }>
instead of string[]. Extensions needing just names can use .map(t => t.name).
Removes redundant getToolInfo() method added in original PR.
Fixes#647
- Add SessionInfoEntry type for session metadata
- Add /name <name> command to set session display name
- Add pi.setSessionName() and pi.getSessionName() extension API
- Session selector shows name (in warning color) instead of first message when set
- Session name included in fuzzy search
- /session command displays name when set
closes#650
- Replace per-extension closures with shared ExtensionRuntime
- Split context actions: ExtensionContextActions (required) + ExtensionCommandContextActions (optional)
- Rename LoadedExtension to Extension, remove setter methods
- Change runner.initialize() from options object to positional params
- Derive hasUI from uiContext presence (no separate param)
- Add warning when extensions override built-in tools
- RPC and print modes now provide full command context actions
BREAKING CHANGE: Extension system types and initialization API changed.
See CHANGELOG.md for migration details.
Extensions can now use async initialization, enabling:
- Dynamic imports (e.g., loading tools from external packages)
- Async setup (config fetching, service connections)
- Lazy-loaded dependencies
Changes:
- ExtensionFactory type now returns void | Promise<void>
- loadExtensionFromFactory is now async, returns Promise<LoadedExtension>
- All factory(api) calls are now awaited
Backwards compatible: sync extensions continue to work unchanged.
- ExtensionAPI: setModel(), getThinkingLevel(), setThinkingLevel() methods
- New preset.ts example with plan/implement presets for model/thinking/tools switching
- Export all UI components from pi-coding-agent for extension use
- docs/tui.md: Common Patterns section with copy-paste code for SelectList, BorderedLoader, SettingsList, setStatus, setWidget, setFooter
- docs/tui.md: Key Rules section for extension UI development
- docs/extensions.md: Exhaustive example links for all ExtensionAPI methods and events
- System prompt now references docs/tui.md for TUI development
Fixes#509, relates to #347
Adds sendUserMessage() to the extension API, allowing extensions to send
actual user messages (role: user) rather than custom messages. Unlike
sendMessage(), this always triggers a turn and behaves as if the user
typed the message.
- Add SendUserMessageHandler type and sendUserMessage() to ExtensionAPI
- Wire handler through loader, runner, and all modes
- Implement via prompt() with expandPromptTemplates: false
- Add send-user-message.ts example with /ask, /steer, /followup commands
- Document in extensions.md
fixes#483
Extensions can now replace the built-in footer with a custom component:
- setFooter(factory) replaces with custom component
- setFooter(undefined) restores built-in footer
Includes example extension demonstrating context usage display.
Closes#481
- Accept ExtensionFactory[] for inline extensions (merged with discovery)
- Mark preloadedExtensions as @internal (CLI implementation detail)
- Update sdk.md with inline extension example
- Update CHANGELOG
Discovery rules:
1. extensions/*.ts or *.js - direct files
2. extensions/*/index.ts or index.js - subdirectory with index
3. extensions/*/package.json with pi field - load declared paths
No recursion beyond one level. Complex packages use package.json manifest.
Added PiManifest type for future theme/skill bundling support.
17 tests covering all discovery scenarios.
refs #454
New src/core/extensions/ directory with:
- types.ts: merged types from hooks and custom-tools
- loader.ts: single loader for extensions
- runner.ts: ExtensionRunner for event emission
- wrapper.ts: tool wrapping utilities
- index.ts: exports
Key changes from old system:
- Single ExtensionAPI with registerTool() for LLM-callable tools
- Tools use ExtensionContext (has UI access)
- No onSession callback on tools (use pi.on events instead)
refs #454