@counterposition/skill-pi 0.1.1 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@counterposition/skill-pi",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Pi skill package for pi install",
5
5
  "keywords": [
6
6
  "pi-package"
@@ -7,13 +7,13 @@ compatibility: "Pi-specific guidance for agents that support Agent Skills"
7
7
 
8
8
  # Pi Coding Agent
9
9
 
10
- Pi is a minimal, extensible terminal coding agent. It ships four default tools (read, write, edit, bash) and four execution modes (interactive TUI, print/JSON, RPC, SDK). Everything else — sub-agents, plan mode, permission popups, MCP — is added through extensions.
10
+ Pi is a minimal terminal coding harness. It ships four default tools (`read`, `write`, `edit`, `bash`) and four execution modes (interactive TUI, print, JSON/RPC, SDK). Features like sub-agents, plan mode, permission flows, and MCP are intentionally left to extensions and Pi packages.
11
11
 
12
12
  Pi's philosophy: **adapt Pi to your workflows, not the other way around**. Users extend it via TypeScript extensions, skills, prompt templates, themes, and shareable Pi packages.
13
13
 
14
14
  ## Architecture at a Glance
15
15
 
16
- Pi is a monorepo with four core packages:
16
+ The coding-agent stack centers on four core packages. The monorepo also includes adjacent packages such as `pi-web-ui`, `pi-mom`, and `pi-pods`.
17
17
 
18
18
  | Package | npm | Purpose |
19
19
  |---------|-----|---------|
@@ -76,6 +76,12 @@ Bundles of extensions, skills, prompts, and themes distributed via npm, git, or
76
76
 
77
77
  Read `references/packages.md` for the package structure, `pi` manifest field, installation, and publishing.
78
78
 
79
+ ### Context Files & Prompt Templates
80
+
81
+ Pi loads `AGENTS.md` or `CLAUDE.md` from the global agent dir and from `cwd` up through parent directories. `.pi/SYSTEM.md` replaces the default system prompt, and `APPEND_SYSTEM.md` appends to it. Prompt templates in `prompts/` directories become slash commands.
82
+
83
+ Read `references/settings.md` for discovery rules and config keys, and `references/packages.md` for how prompts/themes ship in packages.
84
+
79
85
  ### SDK
80
86
 
81
87
  Programmatic embedding via `createAgentSession()`. Gives full control over models, tools, extensions, sessions, and event streaming.
@@ -117,7 +123,7 @@ Compaction walks backward from the newest message, keeping `keepRecentTokens` (d
117
123
  | `find` | Find files by glob pattern |
118
124
  | `ls` | List directory contents |
119
125
 
120
- **Tool presets:** `CODING_TOOLS` = [read, bash, edit, write], `READ_ONLY_TOOLS` = [read, grep, find, ls], `ALL_TOOLS` = all seven.
126
+ **Common tool sets:** `codingTools()` = `[read, bash, edit, write]`, `readOnlyTools()` = `[read, grep, find, ls]`. When you provide a custom `cwd` and explicit tools in the SDK, use `createCodingTools(cwd)` / `createReadOnlyTools(cwd)` so path resolution stays correct.
121
127
 
122
128
  ## Execution Modes
123
129
 
@@ -125,34 +131,60 @@ Compaction walks backward from the newest message, keeping `keepRecentTokens` (d
125
131
  |------|-----------|----------|
126
132
  | Interactive | `pi` | Full TUI with editor, streaming, keybindings |
127
133
  | Print | `pi -p "prompt"` | Single-shot text output |
128
- | JSON | `pi --mode json -p "prompt"` | Newline-delimited JSON events |
134
+ | JSON | `pi --mode json "prompt"` | Newline-delimited event stream |
129
135
  | RPC | `pi --mode rpc` | JSON-RPC over stdin/stdout for IDE integration |
130
136
  | SDK | `createAgentSession()` | Embed in Node.js applications |
131
137
 
132
138
  ## Quick Reference: CLI Flags
133
139
 
134
140
  ```text
135
- pi [options] [message]
136
-
137
- --model, -m <model> Model (e.g., "claude-sonnet-4-20250514")
138
- --provider <name> Provider name
139
- --api-key <key> API key override
140
- --tools <list> Comma-separated tool names
141
- --extensions, -e <path> Load extension from path
142
- --skills <path> Load skill from path
143
- --system-prompt <text> Override system prompt
144
- --thinking-level <level> off | minimal | low | medium | high | xhigh
145
- --new Start new session
146
- --resume <id> Resume session by ID
147
- --fork <id> Fork from session
148
- --continue-recent Continue most recent session
149
- --no-session Don't persist session
150
- --mode <mode> json | rpc
151
- -p Print mode (non-interactive)
152
- install <source> Install package (npm:pkg, git:url, path)
153
- remove <pkg> Remove package
154
- update Update packages
155
- list List installed packages
141
+ pi [options] [@files...] [messages...]
142
+
143
+ # package commands
144
+ install <source> [-l]
145
+ remove <source> [-l]
146
+ uninstall <source> [-l]
147
+ update [source]
148
+ list
149
+ config
150
+
151
+ # modes and output
152
+ -p, --print
153
+ --mode json|rpc
154
+ --export <in> [out]
155
+
156
+ # model selection
157
+ --provider <name>
158
+ --model <pattern>
159
+ --api-key <key>
160
+ --thinking <level>
161
+ --models <patterns>
162
+ --list-models [search]
163
+
164
+ # sessions
165
+ -c, --continue
166
+ -r, --resume
167
+ --session <path|id>
168
+ --fork <path|id>
169
+ --session-dir <dir>
170
+ --no-session
171
+
172
+ # tools and resources
173
+ --tools <list>
174
+ --no-tools
175
+ -e, --extension <source>
176
+ --skill <path>
177
+ --prompt-template <path>
178
+ --theme <path>
179
+ --no-extensions
180
+ --no-skills
181
+ --no-prompt-templates
182
+ --no-themes
183
+
184
+ # prompts and misc
185
+ --system-prompt <text>
186
+ --append-system-prompt <text>
187
+ --verbose
156
188
  ```
157
189
 
158
190
  ## How to Use This Skill
@@ -164,6 +196,7 @@ When working with Pi, read the appropriate reference file for the task at hand:
164
196
  | Writing an extension (tools, commands, events, UI) | `references/extensions.md` |
165
197
  | Creating a skill (SKILL.md authoring) | `references/skills.md` |
166
198
  | Configuring Pi (settings.json options) | `references/settings.md` |
199
+ | Context files, prompt templates, themes, and resource discovery | `references/settings.md` and `references/packages.md` |
167
200
  | Building a shareable package | `references/packages.md` |
168
201
  | Embedding Pi programmatically | `references/sdk.md` |
169
202
  | Adding LLM providers or models | `references/providers.md` |
@@ -1,30 +1,42 @@
1
1
  # Extensions
2
2
 
3
- Extensions are TypeScript modules that customize Pi's behavior. They have **full system permissions** — they can read/write files, run commands, make network requests, and access anything the user can.
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 | Auto-discovered |
8
- |----------|-------|-----------------|
9
- | `~/.pi/agent/extensions/*.ts` | Global (all projects) | Yes |
10
- | `.pi/extensions/*.ts` | Project-local | Yes |
11
- | `settings.json` → `extensions` | Configured | Yes |
12
- | `pi -e ./path.ts` | CLI one-off | No |
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
- Subdirectories with an `index.ts` or a `package.json` containing a `"pi"` field are also discovered. Reload with `/reload`.
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
- ## Basic Structure
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!", "info");
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, _signal, _onUpdate, _ctx) {
36
- const { name } = params as { name: string };
47
+ async execute(_toolCallId, params) {
37
48
  return {
38
- content: [{ type: "text", text: `Hello, ${name}!` }],
39
- details: { greeted: name },
49
+ content: [{ type: "text", text: `Hello, ${params.name}!` }],
50
+ details: {},
40
51
  };
41
52
  },
42
53
  });
43
54
 
44
- // Register a slash command
45
- pi.registerCommand("mycommand", {
46
- description: "Do something",
55
+ pi.registerCommand("hello", {
56
+ description: "Say hello",
47
57
  async handler(args, ctx) {
48
- ctx.ui.notify(`You said: ${args}`, "info");
58
+ if (ctx.hasUI) ctx.ui.notify(`Hello ${args || "world"}!`, "info");
49
59
  },
50
60
  });
51
61
  }
52
62
  ```
53
63
 
54
- **Import note:** Use `Type` from `@mariozechner/pi-ai` (re-exports TypeBox) and `StringEnum` from the same package for string enums (required for Google API compatibility — do not use `Type.Union` with `Type.Literal` for string enums).
55
-
56
- ## Lifecycle Events
57
-
58
- ### Session Events
59
-
60
- | Event | When | Payload |
61
- |-------|------|---------|
62
- | `session_start` | Session begins | `{ sessionId }` |
63
- | `session_switch` | User switches session | `{ sessionId }` |
64
- | `session_fork` | Session forked | `{ entryId }` |
65
- | `session_before_fork` | Before fork executes | `{ entryId }` |
66
- | `session_shutdown` | Session closing | `{}` |
67
- | `session_tree` | After `/tree` navigation | `{ entryId }` |
68
- | `session_before_tree` | Before `/tree` navigation | `{ entryId, messages }` |
69
- | `session_before_compact` | Before compaction | `{ messages, settings, ... }` |
70
- | `session_compact` | After compaction | `{ summary }` |
71
- | `resources_discover` | Resource discovery phase | `{ extensions, skills, prompts, themes }` |
72
-
73
- ### Agent Events
74
-
75
- | Event | When | Payload |
76
- |-------|------|---------|
77
- | `before_agent_start` | Before LLM call (modify system prompt) | `{ systemPrompt }` |
78
- | `agent_start` | Agent begins processing | `{}` |
79
- | `agent_end` | Agent finishes | `{}` |
80
- | `turn_start` | New LLM turn begins | `{}` |
81
- | `turn_end` | LLM turn finishes | `{}` |
82
-
83
- ### Tool Events
84
-
85
- | Event | When | Payload |
86
- |-------|------|---------|
87
- | `tool_call` | Before tool executes | `{ toolName, input }` — return `{ block, reason }` to prevent |
88
- | `tool_result` | After tool executes | `{ toolName, result }` |
89
- | `tool_execution_start` | Tool execution begins | `{ toolCallId, toolName }` |
90
- | `tool_execution_end` | Tool execution ends | `{ toolCallId, toolName }` |
91
-
92
- ### Other Events
93
-
94
- | Event | When | Payload |
95
- |-------|------|---------|
96
- | `input` | User submits input | `{ text }` — return modified text or `{ block }` |
97
- | `model_select` | Model selector opened | `{ models }` — return filtered list |
98
-
99
- ## Event Handler Signature
100
-
101
- ```typescript
102
- pi.on("event_name", async (event, ctx) => {
103
- // event: event-specific payload
104
- // ctx: ExtensionContext
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", // Unique identifier
130
- label: "Tool Name", // Display name
131
- description: "What this tool does", // Shown to LLM
132
- parameters: Type.Object({ // TypeBox schema
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
- // Execute the tool (called by LLM)
138
- async execute(toolCallId, params, signal, onUpdate, ctx) {
139
- // params: parsed and validated parameters
140
- // signal: AbortSignal for cancellation — check signal?.aborted
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: { anyMetadata: true }, // Stored in session, available on branch restore
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
- **Critical rules for tools:**
144
+ Important rules:
163
145
 
164
- 1. **Truncate output** — Default limits are 50KB or 2000 lines. Use `truncateHead()` or `truncateTail()` from the tools module.
165
- 2. **Handle cancellation** — Check `signal?.aborted` in long-running operations.
166
- 3. **Throw on error** — Throwing signals failure to the LLM. Returning always means success.
167
- 4. **Use `withFileMutationQueue()`** — If your tool modifies files, wrap the operation to participate in the mutation queue.
168
- 5. **Store state in `details`** — Tool result `details` persist in session history and survive branching. Reconstruct state from session entries on `session_start`.
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
- All UI methods are on `ctx.ui`. **Always check `ctx.hasUI` first** — non-interactive modes (print, JSON, RPC) may not support UI.
154
+ Check `ctx.hasUI` first. Non-interactive modes may not support UI.
173
155
 
174
156
  ```typescript
175
- // Dialogs (blocking — waits for user response)
176
- const choice = await ctx.ui.select("Pick one:", ["Option A", "Option B"]);
177
- const confirmed = await ctx.ui.confirm("Are you sure?", "Details here");
178
- const text = await ctx.ui.input("Enter a value:", "default");
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
- // Notifications (non-blocking)
182
- ctx.ui.notify("Something happened", "info"); // "info" | "warning" | "error"
162
+ ctx.ui.notify("Done", "info");
183
163
 
184
- // Status indicators
185
- ctx.ui.setStatus("Processing..."); // Footer status text
186
- ctx.ui.setWidget("above", component); // Widget above/below editor
187
- ctx.ui.setFooter(component); // Custom footer component
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.pasteToEditor("appended text");
171
+ const current = ctx.ui.getEditorText();
172
+ ctx.ui.pasteToEditor("extra text");
192
173
 
193
- // Custom components (advanced)
194
- const handle = ctx.ui.custom(component, { overlay: true });
195
- handle.requestRender();
196
- handle.close();
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
- ## Command Registration
181
+ Pi 0.64.0 also added `ctx.ui.setHiddenThinkingLabel(...)` so interactive extensions can customize the collapsed thinking label.
200
182
 
201
- ```typescript
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
- // Argument autocompletion
207
- complete(partial) {
208
- return ["suggestion1", "suggestion2"].filter(s => s.startsWith(partial));
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(); // Wait for agent to finish
214
- ctx.newSession(); // Create new session
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
- ## Keybinding Registration
223
-
224
- ```typescript
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
- ## CLI Flag Registration
233
-
234
- ```typescript
235
- pi.registerFlag({
236
- name: "--my-flag",
199
+ pi.registerFlag("my-flag", {
237
200
  description: "Enable something",
238
- handler: (value) => { /* value is the flag argument */ },
201
+ handler: (value) => {},
239
202
  });
240
203
  ```
241
204
 
242
- ## State Persistence
205
+ ## Persistence & Process Hooks
243
206
 
244
- Extensions persist state via custom session entries:
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
- // Read state on session restore
215
+ ```typescript
251
216
  pi.on("session_start", async (_event, ctx) => {
252
- const branch = ctx.sessionManager.getBranch();
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 Registration
225
+ ## Provider Integration
264
226
 
265
- Extensions can register custom LLM providers:
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: "sk-...",
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
- For full custom streaming, implement `streamSimple` on the provider config. See `references/providers.md`.
279
-
280
- ## Bash Execution
238
+ If you need auth for a specific model request, use:
281
239
 
282
240
  ```typescript
283
- const { stdout, stderr, exitCode } = await pi.exec("git", ["status"]);
241
+ const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
284
242
  ```
285
243
 
286
- ## Practical Examples
287
-
288
- ### Permission Gate
244
+ ## Handy Patterns
289
245
 
290
- ```typescript
291
- pi.on("tool_call", async (event, ctx) => {
292
- if (event.toolName !== "bash") return;
293
- const cmd = event.input.command as string;
294
- if (/\brm\s+(-rf?|--recursive)/i.test(cmd)) {
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