@counterposition/skill-pi 0.1.0 → 0.2.0
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/README.md +5 -0
- package/package.json +13 -11
- package/skills/pi/README.md +10 -4
- package/skills/pi/SKILL.md +58 -25
- package/skills/pi/references/extensions.md +149 -242
- package/skills/pi/references/packages.md +89 -59
- package/skills/pi/references/patterns.md +4 -4
- package/skills/pi/references/providers.md +101 -153
- package/skills/pi/references/sdk.md +105 -125
- package/skills/pi/references/settings.md +121 -99
- package/skills/pi/references/skills.md +83 -73
|
@@ -1,30 +1,42 @@
|
|
|
1
1
|
# Extensions
|
|
2
2
|
|
|
3
|
-
Extensions are TypeScript modules that
|
|
3
|
+
Extensions are TypeScript modules that extend Pi's behavior. They run with the user's full permissions, so treat them like normal application code.
|
|
4
4
|
|
|
5
5
|
## Placement & Discovery
|
|
6
6
|
|
|
7
|
-
| Location | Scope |
|
|
8
|
-
|
|
9
|
-
| `~/.pi/agent/extensions/*.ts` | Global
|
|
10
|
-
|
|
|
11
|
-
|
|
|
12
|
-
|
|
|
7
|
+
| Location | Scope |
|
|
8
|
+
|----------|-------|
|
|
9
|
+
| `~/.pi/agent/extensions/*.ts` | Global |
|
|
10
|
+
| `~/.pi/agent/extensions/*/index.ts` | Global |
|
|
11
|
+
| `.pi/extensions/*.ts` | Project |
|
|
12
|
+
| `.pi/extensions/*/index.ts` | Project |
|
|
13
|
+
| `settings.json` → `extensions` | Additional local paths |
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
Use `pi -e ./my-extension.ts` or `pi --extension ...` for quick tests. Put stable extensions in auto-discovered locations if you want `/reload` to pick them up.
|
|
15
16
|
|
|
16
|
-
##
|
|
17
|
+
## Common Imports
|
|
17
18
|
|
|
18
19
|
```typescript
|
|
19
20
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
21
|
+
import { Type } from "@sinclair/typebox";
|
|
22
|
+
import { StringEnum } from "@mariozechner/pi-ai";
|
|
23
|
+
import { Text } from "@mariozechner/pi-tui";
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
- Use `@sinclair/typebox` for schemas
|
|
27
|
+
- Use `StringEnum` from `@mariozechner/pi-ai` for Google-compatible string enums
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
33
|
+
import { Type } from "@sinclair/typebox";
|
|
20
34
|
|
|
21
35
|
export default function (pi: ExtensionAPI) {
|
|
22
|
-
// Subscribe to lifecycle events
|
|
23
36
|
pi.on("session_start", async (_event, ctx) => {
|
|
24
|
-
ctx.ui.notify("Extension loaded
|
|
37
|
+
if (ctx.hasUI) ctx.ui.notify("Extension loaded", "info");
|
|
25
38
|
});
|
|
26
39
|
|
|
27
|
-
// Register a tool the LLM can call
|
|
28
40
|
pi.registerTool({
|
|
29
41
|
name: "greet",
|
|
30
42
|
label: "Greet",
|
|
@@ -32,312 +44,207 @@ export default function (pi: ExtensionAPI) {
|
|
|
32
44
|
parameters: Type.Object({
|
|
33
45
|
name: Type.String({ description: "Name to greet" }),
|
|
34
46
|
}),
|
|
35
|
-
async execute(_toolCallId, params
|
|
36
|
-
const { name } = params as { name: string };
|
|
47
|
+
async execute(_toolCallId, params) {
|
|
37
48
|
return {
|
|
38
|
-
content: [{ type: "text", text: `Hello, ${name}!` }],
|
|
39
|
-
details: {
|
|
49
|
+
content: [{ type: "text", text: `Hello, ${params.name}!` }],
|
|
50
|
+
details: {},
|
|
40
51
|
};
|
|
41
52
|
},
|
|
42
53
|
});
|
|
43
54
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
description: "Do something",
|
|
55
|
+
pi.registerCommand("hello", {
|
|
56
|
+
description: "Say hello",
|
|
47
57
|
async handler(args, ctx) {
|
|
48
|
-
ctx.ui.notify(`
|
|
58
|
+
if (ctx.hasUI) ctx.ui.notify(`Hello ${args || "world"}!`, "info");
|
|
49
59
|
},
|
|
50
60
|
});
|
|
51
61
|
}
|
|
52
62
|
```
|
|
53
63
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
### ExtensionContext
|
|
109
|
-
|
|
110
|
-
```typescript
|
|
111
|
-
interface ExtensionContext {
|
|
112
|
-
ui: ExtensionUIContext; // UI interaction methods
|
|
113
|
-
cwd: string; // Working directory
|
|
114
|
-
session: AgentSession; // Session access
|
|
115
|
-
sessionManager: SessionManager; // JSONL session persistence
|
|
116
|
-
modelRegistry: ModelRegistry; // Available models
|
|
117
|
-
hasUI: boolean; // Whether interactive mode is available
|
|
118
|
-
isIdle(): boolean; // Whether the agent is idle
|
|
119
|
-
abort(): void; // Stop current generation
|
|
120
|
-
getContextUsage(): ContextUsage; // Token usage info
|
|
121
|
-
getSystemPrompt(): string; // Current system prompt
|
|
122
|
-
}
|
|
123
|
-
```
|
|
64
|
+
## Event Surface
|
|
65
|
+
|
|
66
|
+
Useful session events:
|
|
67
|
+
|
|
68
|
+
- `session_directory`
|
|
69
|
+
- `session_start`
|
|
70
|
+
- `session_before_switch`
|
|
71
|
+
- `session_switch`
|
|
72
|
+
- `session_before_fork`
|
|
73
|
+
- `session_fork`
|
|
74
|
+
- `session_before_compact`
|
|
75
|
+
- `session_compact`
|
|
76
|
+
- `session_before_tree`
|
|
77
|
+
- `session_tree`
|
|
78
|
+
- `session_shutdown`
|
|
79
|
+
- `resources_discover`
|
|
80
|
+
|
|
81
|
+
Useful agent events:
|
|
82
|
+
|
|
83
|
+
- `input`
|
|
84
|
+
- `before_agent_start`
|
|
85
|
+
- `agent_start`
|
|
86
|
+
- `agent_end`
|
|
87
|
+
- `turn_start`
|
|
88
|
+
- `turn_end`
|
|
89
|
+
- `message_start`
|
|
90
|
+
- `message_update`
|
|
91
|
+
- `message_end`
|
|
92
|
+
- `context`
|
|
93
|
+
- `before_provider_request`
|
|
94
|
+
- `model_select`
|
|
95
|
+
|
|
96
|
+
Useful tool events:
|
|
97
|
+
|
|
98
|
+
- `tool_execution_start`
|
|
99
|
+
- `tool_call`
|
|
100
|
+
- `tool_execution_update`
|
|
101
|
+
- `tool_result`
|
|
102
|
+
- `tool_execution_end`
|
|
103
|
+
|
|
104
|
+
## Extension Context
|
|
105
|
+
|
|
106
|
+
`ctx` gives extensions access to:
|
|
107
|
+
|
|
108
|
+
- `ctx.ui` for interactive UI hooks
|
|
109
|
+
- `ctx.cwd`
|
|
110
|
+
- `ctx.session`
|
|
111
|
+
- `ctx.sessionManager`
|
|
112
|
+
- `ctx.modelRegistry`
|
|
113
|
+
- `ctx.hasUI`
|
|
114
|
+
- `ctx.isIdle()`
|
|
115
|
+
- `ctx.abort()`
|
|
116
|
+
- `ctx.getContextUsage()`
|
|
117
|
+
- `ctx.getSystemPrompt()`
|
|
124
118
|
|
|
125
119
|
## Tool Registration
|
|
126
120
|
|
|
127
121
|
```typescript
|
|
128
122
|
pi.registerTool({
|
|
129
|
-
name: "tool_name",
|
|
130
|
-
label: "Tool Name",
|
|
131
|
-
description: "What this tool does",
|
|
132
|
-
parameters: Type.Object({
|
|
123
|
+
name: "tool_name",
|
|
124
|
+
label: "Tool Name",
|
|
125
|
+
description: "What this tool does",
|
|
126
|
+
parameters: Type.Object({
|
|
133
127
|
arg1: Type.String({ description: "..." }),
|
|
134
|
-
arg2: Type.Optional(Type.Number()),
|
|
135
128
|
}),
|
|
136
129
|
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
// onUpdate: callback for streaming partial results
|
|
142
|
-
// ctx: ExtensionContext
|
|
130
|
+
// Runs before schema validation. Useful for migrating old tool-call shapes.
|
|
131
|
+
prepareArguments(rawArgs) {
|
|
132
|
+
return rawArgs;
|
|
133
|
+
},
|
|
143
134
|
|
|
135
|
+
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
144
136
|
return {
|
|
145
137
|
content: [{ type: "text", text: "result" }],
|
|
146
|
-
details: {
|
|
138
|
+
details: { persisted: true },
|
|
147
139
|
};
|
|
148
140
|
},
|
|
149
|
-
|
|
150
|
-
// Optional: custom TUI rendering for tool calls
|
|
151
|
-
renderCall(args, theme, context) {
|
|
152
|
-
return new Text(theme.fg("accent", `tool ${args.arg1}`), 0, 0);
|
|
153
|
-
},
|
|
154
|
-
|
|
155
|
-
// Optional: custom TUI rendering for tool results
|
|
156
|
-
renderResult(result, { expanded }, theme, context) {
|
|
157
|
-
return new Text(result.content[0].text, 0, 0);
|
|
158
|
-
},
|
|
159
141
|
});
|
|
160
142
|
```
|
|
161
143
|
|
|
162
|
-
|
|
144
|
+
Important rules:
|
|
163
145
|
|
|
164
|
-
1.
|
|
165
|
-
2.
|
|
166
|
-
3.
|
|
167
|
-
4.
|
|
168
|
-
5.
|
|
146
|
+
1. Truncate large output.
|
|
147
|
+
2. Respect `signal.aborted`.
|
|
148
|
+
3. Throw on failure instead of returning fake success.
|
|
149
|
+
4. Use the file-mutation queue for write/edit style tools.
|
|
150
|
+
5. Put reconstructable state in `details`; it persists in session history.
|
|
169
151
|
|
|
170
152
|
## UI Methods
|
|
171
153
|
|
|
172
|
-
|
|
154
|
+
Check `ctx.hasUI` first. Non-interactive modes may not support UI.
|
|
173
155
|
|
|
174
156
|
```typescript
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
const
|
|
178
|
-
const
|
|
179
|
-
const edited = await ctx.ui.editor("Edit this text", initialContent);
|
|
157
|
+
const choice = await ctx.ui.select("Pick one:", ["A", "B"]);
|
|
158
|
+
const ok = await ctx.ui.confirm("Delete?", "This cannot be undone");
|
|
159
|
+
const text = await ctx.ui.input("Name:", "default");
|
|
160
|
+
const edited = await ctx.ui.editor("Edit:", "prefilled text");
|
|
180
161
|
|
|
181
|
-
|
|
182
|
-
ctx.ui.notify("Something happened", "info"); // "info" | "warning" | "error"
|
|
162
|
+
ctx.ui.notify("Done", "info");
|
|
183
163
|
|
|
184
|
-
|
|
185
|
-
ctx.ui.
|
|
186
|
-
ctx.ui.setWidget("
|
|
187
|
-
ctx.ui.setFooter(
|
|
164
|
+
ctx.ui.setStatus("my-ext", "Processing...");
|
|
165
|
+
ctx.ui.setWorkingMessage("Thinking deeply...");
|
|
166
|
+
ctx.ui.setWidget("my-widget", ["Line 1", "Line 2"]);
|
|
167
|
+
ctx.ui.setFooter((tui, theme) => new Text(theme.fg("dim", "Custom footer"), 0, 0));
|
|
168
|
+
ctx.ui.setTitle("pi - custom");
|
|
188
169
|
|
|
189
|
-
// Editor control
|
|
190
170
|
ctx.ui.setEditorText("prefilled text");
|
|
191
|
-
ctx.ui.
|
|
171
|
+
const current = ctx.ui.getEditorText();
|
|
172
|
+
ctx.ui.pasteToEditor("extra text");
|
|
192
173
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
174
|
+
ctx.ui.setToolsExpanded(true);
|
|
175
|
+
|
|
176
|
+
const result = await ctx.ui.custom((tui, theme, keybindings, done) => {
|
|
177
|
+
return new Text("Press Enter", 0, 0);
|
|
178
|
+
}, { overlay: true });
|
|
197
179
|
```
|
|
198
180
|
|
|
199
|
-
|
|
181
|
+
Pi 0.64.0 also added `ctx.ui.setHiddenThinkingLabel(...)` so interactive extensions can customize the collapsed thinking label.
|
|
200
182
|
|
|
201
|
-
|
|
202
|
-
pi.registerCommand("commandname", {
|
|
203
|
-
description: "What this command does", // Shown in autocomplete
|
|
204
|
-
args: "optional args description",
|
|
183
|
+
## Commands, Shortcuts, Flags
|
|
205
184
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
},
|
|
210
|
-
|
|
211
|
-
// Handler receives ExtensionCommandContext (superset of ExtensionContext)
|
|
185
|
+
```typescript
|
|
186
|
+
pi.registerCommand("name", {
|
|
187
|
+
description: "Run a command",
|
|
212
188
|
async handler(args, ctx) {
|
|
213
|
-
await ctx.waitForIdle();
|
|
214
|
-
ctx.
|
|
215
|
-
ctx.fork(entryId); // Fork from entry
|
|
216
|
-
ctx.navigateTree(); // Open tree navigator
|
|
217
|
-
ctx.reload(); // Reload extensions
|
|
189
|
+
await ctx.waitForIdle();
|
|
190
|
+
ctx.reload();
|
|
218
191
|
},
|
|
219
192
|
});
|
|
220
|
-
```
|
|
221
193
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
pi.registerKeybinding({
|
|
226
|
-
key: "ctrl+shift+k",
|
|
227
|
-
description: "Do something",
|
|
228
|
-
handler: async (ctx) => { /* ... */ },
|
|
194
|
+
pi.registerShortcut("ctrl+x", {
|
|
195
|
+
description: "Do something quickly",
|
|
196
|
+
handler: async (ctx) => {},
|
|
229
197
|
});
|
|
230
|
-
```
|
|
231
198
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
```typescript
|
|
235
|
-
pi.registerFlag({
|
|
236
|
-
name: "--my-flag",
|
|
199
|
+
pi.registerFlag("my-flag", {
|
|
237
200
|
description: "Enable something",
|
|
238
|
-
handler: (value) => {
|
|
201
|
+
handler: (value) => {},
|
|
239
202
|
});
|
|
240
203
|
```
|
|
241
204
|
|
|
242
|
-
##
|
|
205
|
+
## Persistence & Process Hooks
|
|
243
206
|
|
|
244
|
-
|
|
207
|
+
Store extension state with custom session entries:
|
|
245
208
|
|
|
246
209
|
```typescript
|
|
247
|
-
// Write state
|
|
248
210
|
pi.appendEntry("my-extension-state", { key: "value" });
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Use lifecycle hooks to restore it:
|
|
249
214
|
|
|
250
|
-
|
|
215
|
+
```typescript
|
|
251
216
|
pi.on("session_start", async (_event, ctx) => {
|
|
252
|
-
const
|
|
253
|
-
for (const entry of branch.reverse()) {
|
|
217
|
+
for (const entry of ctx.sessionManager.getBranch().reverse()) {
|
|
254
218
|
if (entry.type === "custom" && entry.name === "my-extension-state") {
|
|
255
|
-
const state = entry.data;
|
|
256
|
-
// Restore from state
|
|
257
219
|
break;
|
|
258
220
|
}
|
|
259
221
|
}
|
|
260
222
|
});
|
|
261
223
|
```
|
|
262
224
|
|
|
263
|
-
## Provider
|
|
225
|
+
## Provider Integration
|
|
264
226
|
|
|
265
|
-
Extensions can register
|
|
227
|
+
Extensions can register providers:
|
|
266
228
|
|
|
267
229
|
```typescript
|
|
268
230
|
pi.registerProvider("my-provider", {
|
|
269
231
|
baseUrl: "https://api.example.com",
|
|
270
232
|
api: "openai-completions",
|
|
271
|
-
apiKey: "
|
|
272
|
-
models: [
|
|
273
|
-
{ id: "my-model", name: "My Model", contextWindow: 128000, maxTokens: 4096 },
|
|
274
|
-
],
|
|
233
|
+
apiKey: "MY_PROVIDER_KEY",
|
|
234
|
+
models: [{ id: "my-model", name: "My Model", contextWindow: 128000, maxTokens: 4096 }],
|
|
275
235
|
});
|
|
276
236
|
```
|
|
277
237
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
## Bash Execution
|
|
238
|
+
If you need auth for a specific model request, use:
|
|
281
239
|
|
|
282
240
|
```typescript
|
|
283
|
-
const
|
|
241
|
+
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
|
|
284
242
|
```
|
|
285
243
|
|
|
286
|
-
##
|
|
287
|
-
|
|
288
|
-
### Permission Gate
|
|
244
|
+
## Handy Patterns
|
|
289
245
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
if (!ctx.hasUI) return { block: true, reason: "Dangerous command blocked" };
|
|
296
|
-
const ok = await ctx.ui.select(`⚠️ Dangerous: ${cmd}\nAllow?`, ["Yes", "No"]);
|
|
297
|
-
if (ok !== "Yes") return { block: true, reason: "Blocked by user" };
|
|
298
|
-
}
|
|
299
|
-
});
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
### System Prompt Injection
|
|
303
|
-
|
|
304
|
-
```typescript
|
|
305
|
-
pi.on("before_agent_start", async (event, _ctx) => {
|
|
306
|
-
event.systemPrompt += "\n\nAdditional instructions here.";
|
|
307
|
-
});
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
### Git Checkpoint on Every Turn
|
|
311
|
-
|
|
312
|
-
```typescript
|
|
313
|
-
pi.on("turn_start", async () => {
|
|
314
|
-
const { stdout } = await pi.exec("git", ["stash", "create"]);
|
|
315
|
-
if (stdout.trim()) checkpoints.set(currentEntryId, stdout.trim());
|
|
316
|
-
});
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
### Custom Compaction
|
|
320
|
-
|
|
321
|
-
```typescript
|
|
322
|
-
pi.on("session_before_compact", async (event, ctx) => {
|
|
323
|
-
// Return a custom CompactionResult to override default compaction
|
|
324
|
-
const allMessages = [...event.messagesToSummarize, ...event.turnPrefixMessages];
|
|
325
|
-
const summary = await generateCustomSummary(allMessages);
|
|
326
|
-
return { summary, firstKeptEntryId: event.firstKeptEntryId, tokensBefore: event.tokens };
|
|
327
|
-
});
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
### Dynamic Tool Registration
|
|
331
|
-
|
|
332
|
-
```typescript
|
|
333
|
-
pi.on("session_start", async () => {
|
|
334
|
-
pi.registerTool({ name: "echo", /* ... */ });
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
pi.registerCommand("add-tool", {
|
|
338
|
-
async handler(name, ctx) {
|
|
339
|
-
pi.registerTool({ name, /* ... */ });
|
|
340
|
-
ctx.ui.notify(`Registered tool: ${name}`, "info");
|
|
341
|
-
},
|
|
342
|
-
});
|
|
343
|
-
```
|
|
246
|
+
- Permission gates via `tool_call`
|
|
247
|
+
- Prompt injection via `before_agent_start`
|
|
248
|
+
- Branch or compaction customization via `session_before_tree` / `session_before_compact`
|
|
249
|
+
- Provider payload inspection via `before_provider_request`
|
|
250
|
+
- Stateful tools via `details` plus `session_start` reconstruction
|