@aliou/pi-dev-kit 0.6.4 → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +11 -7
- package/src/skills/pi-extension/SKILL.md +18 -12
- package/src/skills/pi-extension/references/additional-apis.md +42 -3
- package/src/skills/pi-extension/references/hooks.md +4 -4
- package/src/skills/pi-extension/references/messages.md +27 -10
- package/src/skills/pi-extension/references/structure.md +92 -38
- package/src/skills/pi-extension/references/tools.md +46 -25
- package/src/tools/changelog-tool.ts +225 -230
- package/src/tools/docs-tool.ts +125 -128
- package/src/tools/package-manager-tool.ts +146 -145
- package/src/tools/version-tool.ts +45 -49
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aliou/pi-dev-kit",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -35,18 +35,19 @@
|
|
|
35
35
|
"README.md"
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@aliou/pi-utils-ui": "^0.1.0"
|
|
39
|
-
"@sinclair/typebox": "^0.34.41"
|
|
38
|
+
"@aliou/pi-utils-ui": "^0.1.0"
|
|
40
39
|
},
|
|
41
40
|
"peerDependencies": {
|
|
42
|
-
"@mariozechner/pi-coding-agent": "0.
|
|
43
|
-
"@mariozechner/pi-tui": "0.
|
|
41
|
+
"@mariozechner/pi-coding-agent": "0.70.0",
|
|
42
|
+
"@mariozechner/pi-tui": "0.70.0",
|
|
43
|
+
"typebox": ">=1.1.24"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@biomejs/biome": "^2.3.13",
|
|
47
47
|
"@changesets/cli": "^2.27.11",
|
|
48
|
-
"@mariozechner/pi-coding-agent": "0.
|
|
49
|
-
"@mariozechner/pi-tui": "0.
|
|
48
|
+
"@mariozechner/pi-coding-agent": "0.70.0",
|
|
49
|
+
"@mariozechner/pi-tui": "0.70.0",
|
|
50
|
+
"typebox": "1.1.24",
|
|
50
51
|
"@types/node": "^25.0.10",
|
|
51
52
|
"husky": "^9.1.7",
|
|
52
53
|
"typescript": "^5.9.3"
|
|
@@ -57,6 +58,9 @@
|
|
|
57
58
|
},
|
|
58
59
|
"@mariozechner/pi-tui": {
|
|
59
60
|
"optional": true
|
|
61
|
+
},
|
|
62
|
+
"typebox": {
|
|
63
|
+
"optional": true
|
|
60
64
|
}
|
|
61
65
|
},
|
|
62
66
|
"scripts": {
|
|
@@ -14,7 +14,7 @@ Pi injects these packages via jiti at runtime. Extensions do not need to install
|
|
|
14
14
|
- `@mariozechner/pi-coding-agent` — core types, utilities, and extension APIs
|
|
15
15
|
- `@mariozechner/pi-tui` — TUI components
|
|
16
16
|
- `@mariozechner/pi-ai` — AI utilities (`StringEnum`, etc.)
|
|
17
|
-
-
|
|
17
|
+
- `typebox` — TypeBox 1.x schema definitions for tool parameters and related types. Do not use `@sinclair/typebox` in new code.
|
|
18
18
|
|
|
19
19
|
```typescript
|
|
20
20
|
// Tool UI components (from @aliou/pi-utils-ui)
|
|
@@ -42,12 +42,12 @@ import { Container, Markdown, Text } from "@mariozechner/pi-tui";
|
|
|
42
42
|
### Creating a New Extension
|
|
43
43
|
|
|
44
44
|
1. Read `references/structure.md` for the project layout and package.json template.
|
|
45
|
-
2.
|
|
46
|
-
|
|
47
|
-
- **
|
|
48
|
-
- **
|
|
49
|
-
- **
|
|
50
|
-
|
|
45
|
+
2. Decide what the extension provides and create one feature entry point per directory:
|
|
46
|
+
- **Tools** (LLM-callable): `src/tools/index.ts`; read `references/tools.md`.
|
|
47
|
+
- **Commands** (user-invoked): `src/commands/index.ts`; read `references/commands.md`.
|
|
48
|
+
- **Providers** (LLM backends): `src/providers/index.ts`; read `references/providers.md`.
|
|
49
|
+
- **Hooks** (event handlers): `src/hooks/index.ts`; read `references/hooks.md`. Includes both `tool_call` blocking hooks and spawn hooks for transparent command rewriting via `createBashTool`.
|
|
50
|
+
3. Add each feature entry point to `package.json` `pi.extensions`. Each entry point exports a default function that receives `ExtensionAPI` and calls `pi.registerTool`, `pi.registerCommand`, `pi.registerProvider`, or `pi.on` directly.
|
|
51
51
|
4. Read `references/modes.md` for mode-awareness guidelines. Every extension must handle Interactive, RPC, and Print modes.
|
|
52
52
|
5. If the extension displays rich UI: Read `references/components.md` for TUI components and `references/messages.md` for message display patterns.
|
|
53
53
|
6. If the extension tracks state: Read `references/state.md`.
|
|
@@ -104,24 +104,28 @@ When implementing, look at these existing extensions for patterns:
|
|
|
104
104
|
8. **Deterministic call rendering**: Build `renderCall` with a stable extraction order (action → main arg → option args → long args), process-style. Same input should produce same header layout.
|
|
105
105
|
9. **Long args placement**: Put long prompt/task/question/context strings on following lines. Keep first line scannable.
|
|
106
106
|
10. **Result layout**: In `renderResult(result, options, theme)`, handle `isPartial` first with a stable tool-scoped message. Detect errors by checking for missing expected fields in `details` (framework sets `details: {}` on throw). Use `ToolBody` from `@aliou/pi-utils-ui` with `showCollapsed` fields. Use `ToolFooter` conditionally (omit when empty). Use `Container`/`Markdown` for rich content.
|
|
107
|
-
11. **
|
|
107
|
+
11. **Tool definitions use `defineTool()`**: Define standalone tools with `defineTool({...})` from `@mariozechner/pi-coding-agent` so `execute`, `renderCall`, and `renderResult` infer typed params from the `parameters` field without casts or explicit generic arguments. Also define `type MyToolParams = Static<typeof parameters>` at the top of each tool file and use it everywhere.
|
|
108
108
|
12. **Tool metadata**: Every tool must have `label` (required). Add `promptSnippet` for system prompt tool listing. Add `promptGuidelines` for usage instructions, but write them as standalone global bullets that name the exact tool. These replace system-prompt hooks for simple tools.
|
|
109
109
|
13. **Output truncation**: For tools returning large text, use `truncateHead()` from `@mariozechner/pi-coding-agent`. Write full content to temp file. Append footer with line/byte counts and temp file path.
|
|
110
110
|
14. **Core/lib pattern**: Extract domain logic into modules (`client.ts`, `manager.ts`) that don't import from Pi. Tools are thin wrappers. Core modules are unit-testable with vitest.
|
|
111
111
|
15. **Humanize messages**: Show display names first, IDs in dim/parens. `"Started \"backend\" (proc_42)"` not `"Started proc_42"`.
|
|
112
|
-
16. **peerDependencies**: Pi injects `@mariozechner/pi-coding-agent`, `@mariozechner/pi-tui`, `@mariozechner/pi-ai`, and
|
|
112
|
+
16. **peerDependencies**: Pi injects `@mariozechner/pi-coding-agent`, `@mariozechner/pi-tui`, `@mariozechner/pi-ai`, and `typebox` via jiti at runtime. Any of these that your extension imports must be listed in `peerDependencies` with `optional: true` in `peerDependenciesMeta`. Without `optional: true`, npm 7+ auto-installs peers, adding hundreds of packages on every install even though Pi already provides them. Keep them in `devDependencies` too for local type checking — `pnpm install` installs peers, so development is unaffected. Use `>=CURRENT_VERSION` range, not `*`. Pi 0.69+ uses `typebox` 1.x; do not import from `@sinclair/typebox`.
|
|
113
113
|
17. **Check existing components**: Before creating a new TUI component, check if `pi-tui` or `pi-coding-agent` already exports one that fits.
|
|
114
114
|
18. **Forward abort signals**: Always pass `signal` through to `fetch()`, `pi.exec()`, and API client methods. A tool that ignores its signal prevents cancellation from reaching the underlying operation. Never prefix with `_signal` unless the tool truly has no async work to cancel.
|
|
115
115
|
19. **Never use Node child_process APIs**: Do not use `child_process.exec`, `execSync`, `spawn`, `spawnSync`, `execFile`, or `execFileSync` to run binaries or shell scripts. Always use `pi.exec()`. `pi.exec` handles CWD, signal propagation, and output capture consistently. The only exception is if you need a long-lived streaming process with stdin/stdout piping that `pi.exec` cannot support — document the reason in code comments.
|
|
116
116
|
20. **Never use `homedir()` for pi paths**: Use the SDK helpers from `@mariozechner/pi-coding-agent` instead. They respect the `PI_CODING_AGENT_DIR` env var which is used for testing and custom setups. Key functions: `getAgentDir()`, `getSettingsPath()`, `getSessionsDir()`, `getPromptsDir()`, `getToolsDir()`, `getCustomThemesDir()`, `getModelsPath()`, `getAuthPath()`, `getBinDir()`, `getDebugLogPath()`. All exported from the main package entry point.
|
|
117
117
|
21. **Config uses the interface pattern**: `config.ts` defines two TypeScript interfaces (`RawConfig` with all fields optional, `ResolvedConfig` with all fields required) and a `ConfigLoader<Raw, Resolved>` instance. Do not use TypeBox schemas for config types. For config migrations, use `ConfigLoader` `migrations` option. For settings UI, use `registerSettingsCommand` from `@aliou/pi-utils-settings`.
|
|
118
|
-
22. **Entry point deviations must be documented**: The standard entry point pattern is load config → check `enabled` → register
|
|
118
|
+
22. **Entry point deviations must be documented**: The standard entry point pattern for each feature entry is load config → check `enabled` → register with `pi`. Deviations (no config, API-key-first ordering, no `enabled` toggle) are acceptable when justified, but must be noted in `AGENTS.md`.
|
|
119
|
+
23. **Session replacement uses `withSession`**: After `ctx.newSession()`, `ctx.fork()`, or `ctx.switchSession()`, captured old `pi`, command `ctx`, and `ctx.sessionManager` are stale and may throw. Put post-switch work in `withSession` and use only that fresh callback context.
|
|
119
120
|
|
|
120
121
|
## Checklist
|
|
121
122
|
|
|
122
123
|
Before considering an extension complete:
|
|
123
124
|
|
|
124
|
-
- [ ]
|
|
125
|
+
- [ ] Each feature entry point listed in `package.json` `pi.extensions` has the correct default export signature.
|
|
126
|
+
- [ ] Tools, commands, providers, and hooks are separate feature entry directories; no root `src/index.ts` fan-out registrar.
|
|
127
|
+
- [ ] TUI components are colocated with the feature that uses them, or in `src/components/` only when genuinely shared; they are not listed in `pi.extensions`.
|
|
128
|
+
- [ ] Every standalone tool is wrapped in `defineTool()` so typed params flow into `execute`, `renderCall`, and `renderResult` without casts.
|
|
125
129
|
- [ ] All tools have correct execute parameter order.
|
|
126
130
|
- [ ] All `onUpdate` calls use optional chaining.
|
|
127
131
|
- [ ] No `.js` file extensions in imports.
|
|
@@ -153,5 +157,7 @@ Before considering an extension complete:
|
|
|
153
157
|
- [ ] `@mariozechner/pi-tui` (and any other Pi-provided package) is in `peerDependencies` with `optional: true` if imported at runtime, not just `devDependencies`.
|
|
154
158
|
- [ ] `prepare` script is `"[ -d .git ] && husky || true"`, not bare `"husky"`.
|
|
155
159
|
- [ ] `config.ts` uses `ConfigLoader<Raw, Resolved>` with TypeScript interfaces, not TypeBox schemas.
|
|
156
|
-
- [ ] If deviating from the standard entry
|
|
160
|
+
- [ ] If deviating from the standard feature entry pattern (load-config → check-enabled → register), the reason is documented in `AGENTS.md`.
|
|
157
161
|
- [ ] Settings use `registerSettingsCommand` from `@aliou/pi-utils-settings` when the extension has user-configurable settings.
|
|
162
|
+
- [ ] New code imports TypeBox from `typebox`, not `@sinclair/typebox`.
|
|
163
|
+
- [ ] Any session replacement code uses `withSession` for post-switch work and does not reuse stale session-bound objects.
|
|
@@ -94,10 +94,14 @@ Setting active tools restricts which tools the LLM can use.
|
|
|
94
94
|
|
|
95
95
|
```typescript
|
|
96
96
|
// Set the active model
|
|
97
|
-
|
|
97
|
+
const model = ctx.modelRegistry.find("anthropic", "claude-sonnet-4-5");
|
|
98
|
+
if (model) {
|
|
99
|
+
const success = await pi.setModel(model);
|
|
100
|
+
if (!success) ctx.ui.notify("No API key for this model", "error");
|
|
101
|
+
}
|
|
98
102
|
|
|
99
103
|
// Get/set thinking level
|
|
100
|
-
const level = pi.getThinkingLevel(); // "
|
|
104
|
+
const level = pi.getThinkingLevel(); // "off" | "minimal" | "low" | "medium" | "high" | "xhigh"
|
|
101
105
|
pi.setThinkingLevel("high");
|
|
102
106
|
```
|
|
103
107
|
|
|
@@ -112,7 +116,7 @@ pi.on("before_agent_start", async (_event, ctx) => {
|
|
|
112
116
|
});
|
|
113
117
|
```
|
|
114
118
|
|
|
115
|
-
The system prompt resets each turn, so modifications are not cumulative.
|
|
119
|
+
The system prompt resets each turn, so modifications are not cumulative. In `before_agent_start`, `ctx.getSystemPrompt()` reflects prompt changes from earlier handlers, and the event includes `systemPromptOptions` for structured prompt inputs.
|
|
116
120
|
|
|
117
121
|
### Guidance Injection Pattern
|
|
118
122
|
|
|
@@ -239,6 +243,23 @@ Call `registerGuidance(pi)` from your hooks setup function.
|
|
|
239
243
|
- `pi-linear` — Uses `guidance.ts` + `before_agent_start` hook (cross-tool workflow instructions + dynamic workspace context).
|
|
240
244
|
- `pi-processes` — Uses both: `promptSnippet`/`promptGuidelines` on tools for basic guidance, plus system prompt hook for complex multi-tool orchestration patterns.
|
|
241
245
|
|
|
246
|
+
## Session Replacement
|
|
247
|
+
|
|
248
|
+
`ctx.newSession()`, `ctx.fork()`, and `ctx.switchSession()` invalidate captured pre-replacement session-bound objects after the replacement. Use `withSession` for post-switch work and only use the fresh callback context there.
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
await ctx.newSession({
|
|
252
|
+
setup: async (sm) => {
|
|
253
|
+
sm.appendCustomMessageEntry("my-source", "Seed context", true);
|
|
254
|
+
},
|
|
255
|
+
withSession: async (ctx) => {
|
|
256
|
+
await ctx.sendUserMessage("Continue from the new session");
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Do not reuse captured `pi`, command `ctx`, or `ctx.sessionManager` after a session replacement.
|
|
262
|
+
|
|
242
263
|
## Compaction
|
|
243
264
|
|
|
244
265
|
Trigger compaction programmatically:
|
|
@@ -303,4 +324,22 @@ ctx.ui.setEditorComponent((tui, theme, kb) => {
|
|
|
303
324
|
|
|
304
325
|
// Prefill the editor
|
|
305
326
|
ctx.ui.setEditorText("Prefilled content");
|
|
327
|
+
|
|
328
|
+
// Customize the streaming working indicator
|
|
329
|
+
ctx.ui.setWorkingIndicator({ frames: [ctx.ui.theme.fg("accent", "●")] }); // static
|
|
330
|
+
ctx.ui.setWorkingIndicator({ frames: [] }); // hidden
|
|
331
|
+
ctx.ui.setWorkingIndicator(); // restore default
|
|
332
|
+
|
|
333
|
+
// Stack autocomplete on top of built-in slash/path completion
|
|
334
|
+
ctx.ui.addAutocompleteProvider((current) => ({
|
|
335
|
+
async getSuggestions(lines, cursorLine, cursorCol) {
|
|
336
|
+
const beforeCursor = (lines[cursorLine] ?? "").slice(0, cursorCol);
|
|
337
|
+
const match = beforeCursor.match(/(?:^|[ \t])#([^\s#]*)$/);
|
|
338
|
+
if (!match) return current.getSuggestions(lines, cursorLine, cursorCol);
|
|
339
|
+
return [{ label: `#${match[1]}123`, value: `#${match[1]}123` }];
|
|
340
|
+
},
|
|
341
|
+
shouldTriggerFileCompletion(input) {
|
|
342
|
+
return current.shouldTriggerFileCompletion?.(input) ?? false;
|
|
343
|
+
},
|
|
344
|
+
}));
|
|
306
345
|
```
|
|
@@ -11,14 +11,14 @@ Hooks let extensions react to lifecycle events. They are registered with `pi.on(
|
|
|
11
11
|
| `session_start` | Session starts, reloads, or is replaced | No | `{ reason: "startup" \| "reload" \| "new" \| "resume" \| "fork", previousSessionFile? }` |
|
|
12
12
|
| `session_before_switch` | Before `/new` or `/resume` replaces the current session | Yes (`{ cancel: true }`) | `{ reason: "new" \| "resume", targetSessionFile? }` |
|
|
13
13
|
| `session_before_fork` | Before forking a session | Yes (`{ cancel: true }`) | `{ entryId }` |
|
|
14
|
-
| `session_shutdown` | Current session runtime is shutting down or being replaced | No | `{}` |
|
|
14
|
+
| `session_shutdown` | Current session runtime is shutting down or being replaced | No | `{ reason: "quit" | "reload" | "new-session" | "resume" | "fork", targetSessionFile? }` |
|
|
15
15
|
| `session_before_compact` | Before compaction | Yes (cancel or provide custom compaction) | event-specific compaction data |
|
|
16
16
|
|
|
17
17
|
### Agent Events
|
|
18
18
|
|
|
19
19
|
| Event | When | Payload |
|
|
20
20
|
|---|---|---|
|
|
21
|
-
| `before_agent_start` | Before agent turn starts | `{}` |
|
|
21
|
+
| `before_agent_start` | Before agent turn starts | `{ systemPrompt, systemPromptOptions }` |
|
|
22
22
|
| `agent_start` | Agent turn started | `{}` |
|
|
23
23
|
| `turn_start` | Turn begins processing | `{}` |
|
|
24
24
|
| `turn_end` | Turn finishes processing | `{}` |
|
|
@@ -133,7 +133,7 @@ pi.on("user_bash", async (event, ctx) => {
|
|
|
133
133
|
|
|
134
134
|
This event fires before each agent turn. It is commonly used to modify the system prompt.
|
|
135
135
|
|
|
136
|
-
The handler receives a `BeforeAgentStartEvent` with
|
|
136
|
+
The handler receives a `BeforeAgentStartEvent` with `systemPrompt` and `systemPromptOptions` fields. `systemPromptOptions` exposes the structured inputs used to build the prompt. Return a `{ systemPrompt }` object to replace it. If multiple extensions return a modified prompt, they are chained. `ctx.getSystemPrompt()` reflects changes made by earlier `before_agent_start` handlers.
|
|
137
137
|
|
|
138
138
|
```typescript
|
|
139
139
|
pi.on("before_agent_start", async (event) => {
|
|
@@ -151,7 +151,7 @@ To access flags inside hooks, use `pi.getFlag()` (see `references/additional-api
|
|
|
151
151
|
|
|
152
152
|
## Bash Spawn Hook (Command Rewriting)
|
|
153
153
|
|
|
154
|
-
The `createBashTool` function lets you replace the built-in bash tool with one that transparently rewrites commands before shell execution. This is different from `tool_call` blocking -- the agent never sees that the command was rewritten.
|
|
154
|
+
The `createBashTool(cwd, options)` function lets you replace the built-in bash tool with one that transparently rewrites commands before shell execution. Always pass an explicit cwd. This is different from `tool_call` blocking -- the agent never sees that the command was rewritten.
|
|
155
155
|
|
|
156
156
|
Use spawn hooks when you have a clear rewrite target (e.g. `npm` -> `pnpm`). Use `tool_call` blocking when you need to stop a command entirely or show a confirmation dialog.
|
|
157
157
|
|
|
@@ -36,19 +36,36 @@ pi.sendMessage({
|
|
|
36
36
|
Registers a custom renderer for messages with a specific `customType`:
|
|
37
37
|
|
|
38
38
|
```typescript
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
import type {
|
|
40
|
+
ExtensionAPI,
|
|
41
|
+
MessageRenderOptions,
|
|
42
|
+
Theme,
|
|
43
|
+
} from "@mariozechner/pi-coding-agent";
|
|
44
|
+
import { Text } from "@mariozechner/pi-tui";
|
|
45
|
+
|
|
46
|
+
interface BalanceDetails {
|
|
47
|
+
balance?: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default function (pi: ExtensionAPI) {
|
|
51
|
+
pi.registerMessageRenderer<BalanceDetails>(
|
|
52
|
+
"balance-result",
|
|
53
|
+
(message, _options: MessageRenderOptions, theme: Theme) => {
|
|
54
|
+
const balance = message.details?.balance;
|
|
55
|
+
const text =
|
|
56
|
+
typeof balance === "number"
|
|
57
|
+
? `Account Balance: $${balance.toFixed(2)}`
|
|
58
|
+
: message.content;
|
|
59
|
+
|
|
60
|
+
return new Text(theme.fg("success", text), 0, 0);
|
|
61
|
+
},
|
|
62
|
+
);
|
|
63
|
+
}
|
|
47
64
|
```
|
|
48
65
|
|
|
49
|
-
The renderer receives the full message object and
|
|
66
|
+
The renderer receives the full message object, render options, and theme. It returns a TUI `Component` such as `Text`, `Box`, `Markdown`, or a custom component. If no renderer is registered for a `customType`, the message's `content` field is displayed as plain text.
|
|
50
67
|
|
|
51
|
-
|
|
68
|
+
For larger renderers, keep the renderer implementation next to the hook or command that emits that `customType`. See `pi-processes/src/hooks/message-renderer.ts` for a compact lifecycle-message renderer and `pi-harness/extensions/breadcrumbs/lib/session-link.ts` for richer persistent session-link renderers.
|
|
52
69
|
|
|
53
70
|
## Custom Message Design Guide (breadcrumbs-style)
|
|
54
71
|
|
|
@@ -7,27 +7,27 @@ This covers the standalone repository structure for a Pi extension. This is the
|
|
|
7
7
|
```
|
|
8
8
|
my-extension/
|
|
9
9
|
src/
|
|
10
|
-
index.ts # Entry point (default export)
|
|
11
10
|
config.ts # Config schema (types) + loader + defaults
|
|
12
11
|
client.ts # API client (if wrapping a third-party API)
|
|
13
12
|
tools/
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
render.ts # Separate render module (when rendering is complex)
|
|
22
|
-
types.ts # Serialized types for tool details
|
|
13
|
+
index.ts # Tool extension entry point (default export, calls pi.registerTool)
|
|
14
|
+
actions/ # Optional one file per action for multi-action tools
|
|
15
|
+
create.ts
|
|
16
|
+
list.ts
|
|
17
|
+
show.ts
|
|
18
|
+
render.ts # Optional separate render module
|
|
19
|
+
types.ts # Optional serialized types for tool details
|
|
23
20
|
commands/
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
index.ts # Command extension entry point (default export, calls pi.registerCommand)
|
|
22
|
+
components.ts # Optional command UI components
|
|
23
|
+
hooks/
|
|
24
|
+
index.ts # Hook extension entry point (default export, calls pi.on)
|
|
25
|
+
components/ # Optional shared TUI components used by tools/commands/hooks
|
|
26
|
+
my-component.ts
|
|
27
27
|
providers/
|
|
28
|
-
index.ts
|
|
29
|
-
models.ts
|
|
30
|
-
utils/
|
|
28
|
+
index.ts # Provider extension entry point (default export, calls pi.registerProvider)
|
|
29
|
+
models.ts # Model definitions
|
|
30
|
+
utils/ # Internal helpers (matching, parsing, etc.)
|
|
31
31
|
my-helper.ts
|
|
32
32
|
package.json
|
|
33
33
|
tsconfig.json
|
|
@@ -38,12 +38,14 @@ my-extension/
|
|
|
38
38
|
README.md
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
Not every extension needs every directory. A simple extension with one tool might only have `src/index.ts`
|
|
41
|
+
Not every extension needs every directory. A simple extension with one tool might only have `src/tools/index.ts` plus `src/config.ts` if it has settings.
|
|
42
42
|
|
|
43
43
|
### Organization principles
|
|
44
44
|
|
|
45
|
-
-
|
|
46
|
-
-
|
|
45
|
+
- **Each Pi feature directory is its own extension entry point.** `tools/index.ts`, `commands/index.ts`, `hooks/index.ts`, and `providers/index.ts` each export a default function that receives `ExtensionAPI` and registers that feature with `pi`.
|
|
46
|
+
- **`config.ts`** stays at root and is shared by feature entry points. Each entry point loads config and gates itself with `enabled` when applicable.
|
|
47
|
+
- **Do not use a single root `src/index.ts` that imports and registers everything.** Declare each feature entry point in `package.json` under `pi.extensions` instead.
|
|
48
|
+
- **Components are not entry points.** TUI components are support modules used by tools, commands, or hooks. Keep them colocated with the feature that uses them, or in `src/components/` only when genuinely shared.
|
|
47
49
|
- **Config types live in `config.ts`**, not a separate `types.ts` or `config-schema.ts`. The config file exports both the types (raw and resolved) and the config loader instance.
|
|
48
50
|
- **Utility/helper files** go in `utils/`. This includes pattern matching, shell parsing, event helpers, migrations, etc. Anything that is not a tool, command, component, provider, or hook.
|
|
49
51
|
- **No separate `types.ts`** unless the extension has shared types unrelated to config (rare). Config types are the most common shared types, and they belong in `config.ts`.
|
|
@@ -70,7 +72,12 @@ Not every extension needs every directory. A simple extension with one tool migh
|
|
|
70
72
|
},
|
|
71
73
|
"files": ["src", "README.md"],
|
|
72
74
|
"pi": {
|
|
73
|
-
"extensions": [
|
|
75
|
+
"extensions": [
|
|
76
|
+
"./src/tools/index.ts",
|
|
77
|
+
"./src/commands/index.ts",
|
|
78
|
+
"./src/hooks/index.ts",
|
|
79
|
+
"./src/providers/index.ts"
|
|
80
|
+
],
|
|
74
81
|
"skills": ["./skills"],
|
|
75
82
|
"themes": ["./themes"],
|
|
76
83
|
"prompts": ["./prompts"],
|
|
@@ -80,13 +87,13 @@ Not every extension needs every directory. A simple extension with one tool migh
|
|
|
80
87
|
"@mariozechner/pi-coding-agent": ">=CURRENT_VERSION",
|
|
81
88
|
"@mariozechner/pi-ai": ">=CURRENT_VERSION",
|
|
82
89
|
"@mariozechner/pi-tui": ">=CURRENT_VERSION",
|
|
83
|
-
"
|
|
90
|
+
"typebox": ">=1.1.24"
|
|
84
91
|
},
|
|
85
92
|
"peerDependenciesMeta": {
|
|
86
93
|
"@mariozechner/pi-coding-agent": { "optional": true },
|
|
87
94
|
"@mariozechner/pi-ai": { "optional": true },
|
|
88
95
|
"@mariozechner/pi-tui": { "optional": true },
|
|
89
|
-
"
|
|
96
|
+
"typebox": { "optional": true }
|
|
90
97
|
},
|
|
91
98
|
"devDependencies": {
|
|
92
99
|
"@aliou/biome-plugins": "^0.3.0",
|
|
@@ -95,7 +102,7 @@ Not every extension needs every directory. A simple extension with one tool migh
|
|
|
95
102
|
"@mariozechner/pi-ai": "CURRENT_VERSION",
|
|
96
103
|
"@mariozechner/pi-coding-agent": "CURRENT_VERSION",
|
|
97
104
|
"@mariozechner/pi-tui": "CURRENT_VERSION",
|
|
98
|
-
"
|
|
105
|
+
"typebox": "1.1.24",
|
|
99
106
|
"@types/node": "^25.0.0",
|
|
100
107
|
"husky": "^9.0.0",
|
|
101
108
|
"typescript": "^5.8.0"
|
|
@@ -120,7 +127,7 @@ Not every extension needs every directory. A simple extension with one tool migh
|
|
|
120
127
|
}
|
|
121
128
|
```
|
|
122
129
|
|
|
123
|
-
Replace `CURRENT_VERSION` with the actual installed version of pi (e.g., `0.
|
|
130
|
+
Replace `CURRENT_VERSION` with the actual installed version of pi (e.g., `0.70.0`).
|
|
124
131
|
|
|
125
132
|
Only include `pi` sub-fields that are actually used. `skills`, `themes`, `prompts`, and `video` are optional.
|
|
126
133
|
|
|
@@ -130,7 +137,7 @@ Only include `pi` sub-fields that are actually used. `skills`, `themes`, `prompt
|
|
|
130
137
|
|
|
131
138
|
| Field | Description |
|
|
132
139
|
|---|---|
|
|
133
|
-
| `extensions` | Array of entry point paths. Each
|
|
140
|
+
| `extensions` | Array of extension entry point paths. Each file or directory `index.ts` exports a default function receiving `ExtensionAPI`. Prefer one entry point per feature directory (`tools/index.ts`, `commands/index.ts`, etc.). |
|
|
134
141
|
| `skills` | Array of directories containing skill definitions. Optional. |
|
|
135
142
|
| `themes` | Array of directories containing theme files. Optional. |
|
|
136
143
|
| `prompts` | Array of directories containing prompt files. Optional. |
|
|
@@ -141,9 +148,9 @@ Only include `pi` sub-fields that are actually used. `skills`, `themes`, `prompt
|
|
|
141
148
|
- `@mariozechner/pi-coding-agent` — core types, utilities, and extension APIs
|
|
142
149
|
- `@mariozechner/pi-tui` — TUI components
|
|
143
150
|
- `@mariozechner/pi-ai` — AI utilities (`StringEnum`, etc.)
|
|
144
|
-
-
|
|
151
|
+
- `typebox` — TypeBox 1.x schema definitions for tool parameters and related types
|
|
145
152
|
|
|
146
|
-
List any of these you import at runtime in `peerDependencies` as optional peers. This prevents npm from installing duplicate copies when a user installs your extension. Use `>=` with the current version when creating.
|
|
153
|
+
List any of these you import at runtime in `peerDependencies` as optional peers. Pi 0.69+ uses `typebox` 1.x; do not import from `@sinclair/typebox` in new code. This prevents npm from installing duplicate copies when a user installs your extension. Use `>=` with the current version when creating.
|
|
147
154
|
|
|
148
155
|
**`peerDependenciesMeta`**: Marks peer dependencies as optional. Without `optional: true`, npm 7+ auto-installs peers that are not already present, which defeats the purpose — Pi already provides them.
|
|
149
156
|
|
|
@@ -405,33 +412,78 @@ const wizard = new Wizard({
|
|
|
405
412
|
|
|
406
413
|
Each step receives a `WizardStepContext` with `markComplete()`/`markIncomplete()` to control navigation gates. See `pi-linear/src/commands/auth-wizard.ts` for a full example with async validation and spinner.
|
|
407
414
|
|
|
408
|
-
## Entry
|
|
415
|
+
## Extension Entry Points
|
|
409
416
|
|
|
410
|
-
|
|
417
|
+
Each extension entry point is a default export function that receives the `ExtensionAPI` object. Do not centralize registration in one root `src/index.ts`; instead, make each feature directory an entry point and list those paths in `package.json` `pi.extensions`.
|
|
411
418
|
|
|
412
419
|
### Standard Pattern
|
|
413
420
|
|
|
414
421
|
```typescript
|
|
415
|
-
|
|
416
|
-
import {
|
|
417
|
-
import {
|
|
418
|
-
import {
|
|
419
|
-
import {
|
|
422
|
+
// src/tools/index.ts
|
|
423
|
+
import type { AgentToolResult, ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
424
|
+
import { defineTool } from "@mariozechner/pi-coding-agent";
|
|
425
|
+
import { type Static, Type } from "typebox";
|
|
426
|
+
import { configLoader } from "../config";
|
|
427
|
+
|
|
428
|
+
const parameters = Type.Object({
|
|
429
|
+
query: Type.String({ description: "Search query" }),
|
|
430
|
+
});
|
|
431
|
+
type MyToolParams = Static<typeof parameters>;
|
|
432
|
+
interface MyToolDetails {
|
|
433
|
+
results: string[];
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const myTool = defineTool({
|
|
437
|
+
name: "my_tool",
|
|
438
|
+
label: "My Tool",
|
|
439
|
+
description: "Search for items",
|
|
440
|
+
parameters,
|
|
441
|
+
async execute(_toolCallId, params, signal, onUpdate, ctx): Promise<AgentToolResult<MyToolDetails>> {
|
|
442
|
+
const results = await search(params.query, { signal });
|
|
443
|
+
return {
|
|
444
|
+
content: [{ type: "text", text: JSON.stringify(results) }],
|
|
445
|
+
details: { results },
|
|
446
|
+
};
|
|
447
|
+
},
|
|
448
|
+
});
|
|
420
449
|
|
|
421
450
|
export default async function (pi: ExtensionAPI) {
|
|
422
451
|
await configLoader.load();
|
|
423
452
|
const config = configLoader.getConfig();
|
|
424
453
|
if (!config.enabled) return;
|
|
425
454
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
455
|
+
pi.registerTool(myTool);
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
Declare the entry point directly:
|
|
460
|
+
|
|
461
|
+
```json
|
|
462
|
+
{
|
|
463
|
+
"pi": {
|
|
464
|
+
"extensions": ["./src/tools/index.ts"]
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
For multiple features, list multiple entries:
|
|
470
|
+
|
|
471
|
+
```json
|
|
472
|
+
{
|
|
473
|
+
"pi": {
|
|
474
|
+
"extensions": [
|
|
475
|
+
"./src/tools/index.ts",
|
|
476
|
+
"./src/commands/index.ts",
|
|
477
|
+
"./src/hooks/index.ts",
|
|
478
|
+
"./src/providers/index.ts"
|
|
479
|
+
]
|
|
480
|
+
}
|
|
429
481
|
}
|
|
430
482
|
```
|
|
431
483
|
|
|
432
484
|
### Acceptable Exceptions
|
|
433
485
|
|
|
434
|
-
Not all
|
|
486
|
+
Not all entry points follow the standard pattern exactly. These deviations are valid:
|
|
435
487
|
|
|
436
488
|
**No config**: Extensions that use environment variables exclusively and have no user-configurable settings skip config loading entirely. The entry point reads the env var directly and gates registration on its presence.
|
|
437
489
|
|
|
@@ -439,6 +491,8 @@ Not all extensions follow the standard pattern exactly. These deviations are val
|
|
|
439
491
|
|
|
440
492
|
**No `enabled` check**: Extensions that are always active by design omit the `enabled` field and the early-return check. The entry point still loads config for other settings. Document this decision in `AGENTS.md`.
|
|
441
493
|
|
|
494
|
+
**Shared setup required**: If multiple feature entry points need a shared setup step, extract a helper module (for example `src/bootstrap.ts`) and call it from each entry. Do not reintroduce a central root entry point just to fan out registration.
|
|
495
|
+
|
|
442
496
|
When deviating from the standard pattern, note the reason in the extension's `AGENTS.md`.
|
|
443
497
|
|
|
444
498
|
## API Key Pattern
|