@counterposition/skill-pi 0.1.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.
@@ -0,0 +1,38 @@
1
+ # Pi Skill
2
+
3
+ Reference skill for the Pi coding agent.
4
+
5
+ - Use when working on Pi extensions, skills, settings, packages, SDK usage, providers, sessions, or compaction.
6
+ - Good trigger terms: `.pi/`, `SKILL.md`, `models.json`, `pi-ai`, `pi-tui`, `pi-agent-core`, `pi-coding-agent`.
7
+ - Not for Raspberry Pi hardware or the math constant.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ # multi-skill repo
13
+ npx skills add counterposition/pi --skill pi
14
+
15
+ # local checkout
16
+ npx skills add /absolute/path/to/pi --skill pi
17
+
18
+ # pi install from npm
19
+ pi install npm:@counterposition/skill-pi
20
+
21
+ # pi install from local package
22
+ pi install ./packages/skill-pi
23
+ ```
24
+
25
+ ## Covers
26
+
27
+ - Pi architecture and package layout
28
+ - Skill authoring and discovery
29
+ - Extensions, tools, and UI hooks
30
+ - Settings, models, providers, and packages
31
+ - Sessions, forks, and compaction
32
+
33
+ ## Files
34
+
35
+ - `SKILL.md` - trigger description and routing
36
+ - `LICENSE.md` - bundled GPLv3 license text
37
+ - `references/` - task-specific Pi docs
38
+ - `evals/evals.json` - example eval cases
@@ -0,0 +1,170 @@
1
+ ---
2
+ name: pi
3
+ description: "Provides Pi-specific guidance for extensions, skills, `.pi/` config, `settings.json`, `models.json`, packages, providers, themes, SDK/RPC, sessions, and compaction in Pi, the coding agent. Use when the user mentions Pi or Pi-specific terms such as `.pi/`, `SKILL.md`, `createAgentSession()`, `thinkingLevel`, `session_before_compact`, `pi-ai`, `pi-tui`, `pi-agent-core`, or `pi-coding-agent`. Not for Raspberry Pi hardware, the math constant, or unrelated generic tooling."
4
+ license: GPLv3
5
+ compatibility: "Pi-specific guidance for agents that support Agent Skills"
6
+ ---
7
+
8
+ # Pi Coding Agent
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.
11
+
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
+
14
+ ## Architecture at a Glance
15
+
16
+ Pi is a monorepo with four core packages:
17
+
18
+ | Package | npm | Purpose |
19
+ |---------|-----|---------|
20
+ | `pi-ai` | `@mariozechner/pi-ai` | Unified LLM API across 20+ providers |
21
+ | `pi-agent-core` | `@mariozechner/pi-agent-core` | Agent loop, tool execution, state management |
22
+ | `pi-tui` | `@mariozechner/pi-tui` | Terminal UI component library |
23
+ | `pi-coding-agent` | `@mariozechner/pi-coding-agent` | CLI, extensions, skills, sessions, settings |
24
+
25
+ **Source:** [github.com/badlogic/pi-mono](https://github.com/badlogic/pi-mono)
26
+
27
+ ## File System Layout
28
+
29
+ ```text
30
+ ~/.pi/agent/ # Global config directory (PI_CODING_AGENT_DIR overrides)
31
+ ├── settings.json # Global settings
32
+ ├── auth.json # Credentials (0600 perms)
33
+ ├── models.json # Custom provider/model definitions
34
+ ├── keybindings.json # Keyboard shortcuts
35
+ ├── presets.json # Named configurations (via preset extension)
36
+ ├── extensions/ # Global extensions (auto-discovered)
37
+ ├── skills/ # Global skills (auto-discovered)
38
+ ├── prompts/ # Global prompt templates (auto-discovered)
39
+ ├── themes/ # Custom themes
40
+ ├── sessions/ # JSONL session files
41
+ └── bin/ # Tool binaries (fd, ripgrep)
42
+
43
+ <project>/
44
+ ├── .pi/ # Project-local config (overrides global)
45
+ │ ├── settings.json # Project settings (merged over global)
46
+ │ ├── extensions/ # Project extensions (auto-discovered)
47
+ │ ├── skills/ # Project skills
48
+ │ ├── prompts/ # Project prompt templates
49
+ │ ├── themes/ # Project themes
50
+ │ └── agents/ # Agent definitions (for subagent extension)
51
+ ```
52
+
53
+ ## Key Concepts
54
+
55
+ ### Extensions
56
+
57
+ TypeScript modules that hook into Pi's lifecycle. They can register tools, intercept tool calls, add commands, build custom UIs, and persist state. Extensions have **full system access** — they run with the user's permissions.
58
+
59
+ Read `references/extensions.md` for the complete API, lifecycle events, tool registration, UI methods, and working examples.
60
+
61
+ ### Skills
62
+
63
+ Markdown-based capability packages (`SKILL.md` with frontmatter). Pi loads skill names and descriptions into the system prompt; the full skill body is read on demand. Skills follow the Agent Skills standard.
64
+
65
+ Read `references/skills.md` for the authoring guide, frontmatter schema, and directory conventions.
66
+
67
+ ### Settings
68
+
69
+ Hierarchical JSON configuration: project `.pi/settings.json` merges over global `~/.pi/agent/settings.json`. Controls model, thinking level, compaction, retry, shell, transport, and resource loading.
70
+
71
+ Read `references/settings.md` for every setting with its type, default, and effect.
72
+
73
+ ### Packages
74
+
75
+ Bundles of extensions, skills, prompts, and themes distributed via npm, git, or local paths. Installed with `pi install`.
76
+
77
+ Read `references/packages.md` for the package structure, `pi` manifest field, installation, and publishing.
78
+
79
+ ### SDK
80
+
81
+ Programmatic embedding via `createAgentSession()`. Gives full control over models, tools, extensions, sessions, and event streaming.
82
+
83
+ Read `references/sdk.md` for factory options, event types, and usage patterns.
84
+
85
+ ### Custom Providers & Models
86
+
87
+ Add any OpenAI-compatible, Anthropic, Google, or custom LLM endpoint via `models.json` or extension-based `registerProvider()`.
88
+
89
+ Read `references/providers.md` for provider configuration, auth methods, and the model schema.
90
+
91
+ ### Common Patterns
92
+
93
+ Practical recipes for permission gates, presets, sub-agents, git checkpoints, custom compaction, TUI components, and more.
94
+
95
+ Read `references/patterns.md` for copy-paste-ready examples.
96
+
97
+ ## Sessions & Compaction
98
+
99
+ Sessions are append-only JSONL files with a tree structure (entries linked by `id`/`parentId`). This allows branching without modifying history. Key operations:
100
+
101
+ - **`/tree`** — Navigate the session tree, jump to any point
102
+ - **`/compact`** — Manually trigger context compaction
103
+ - **`/fork`** — Branch from a specific point
104
+ - **Auto-compaction** — Triggers when `contextTokens > contextWindow - reserveTokens`
105
+
106
+ Compaction walks backward from the newest message, keeping `keepRecentTokens` (default 20,000) of recent conversation, and summarizes everything older. Extensions can intercept compaction via `session_before_compact`.
107
+
108
+ ## Built-in Tools
109
+
110
+ | Tool | Description |
111
+ |------|-------------|
112
+ | `read` | Read files (with offset/limit support, images, PDFs) |
113
+ | `write` | Create or overwrite files |
114
+ | `edit` | Line-based file editing with diffs |
115
+ | `bash` | Execute shell commands with streaming output |
116
+ | `grep` | Search file contents by pattern |
117
+ | `find` | Find files by glob pattern |
118
+ | `ls` | List directory contents |
119
+
120
+ **Tool presets:** `CODING_TOOLS` = [read, bash, edit, write], `READ_ONLY_TOOLS` = [read, grep, find, ls], `ALL_TOOLS` = all seven.
121
+
122
+ ## Execution Modes
123
+
124
+ | Mode | Start with | Use case |
125
+ |------|-----------|----------|
126
+ | Interactive | `pi` | Full TUI with editor, streaming, keybindings |
127
+ | Print | `pi -p "prompt"` | Single-shot text output |
128
+ | JSON | `pi --mode json -p "prompt"` | Newline-delimited JSON events |
129
+ | RPC | `pi --mode rpc` | JSON-RPC over stdin/stdout for IDE integration |
130
+ | SDK | `createAgentSession()` | Embed in Node.js applications |
131
+
132
+ ## Quick Reference: CLI Flags
133
+
134
+ ```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
156
+ ```
157
+
158
+ ## How to Use This Skill
159
+
160
+ When working with Pi, read the appropriate reference file for the task at hand:
161
+
162
+ | Task | Reference |
163
+ |------|-----------|
164
+ | Writing an extension (tools, commands, events, UI) | `references/extensions.md` |
165
+ | Creating a skill (SKILL.md authoring) | `references/skills.md` |
166
+ | Configuring Pi (settings.json options) | `references/settings.md` |
167
+ | Building a shareable package | `references/packages.md` |
168
+ | Embedding Pi programmatically | `references/sdk.md` |
169
+ | Adding LLM providers or models | `references/providers.md` |
170
+ | Looking for a recipe or pattern | `references/patterns.md` |
@@ -0,0 +1,50 @@
1
+ {
2
+ "skill_name": "pi",
3
+ "evals": [
4
+ {
5
+ "id": 1,
6
+ "prompt": "Write a Pi extension that blocks any bash command containing `curl` unless the user confirms, and logs all blocked commands to a file at ~/.pi/agent/blocked-commands.log",
7
+ "expected_output": "A TypeScript extension file that hooks into tool_call events, checks for curl in bash commands, prompts via ctx.ui.select, and appends blocked commands to a log file. Should check ctx.hasUI and handle non-interactive mode.",
8
+ "files": [],
9
+ "assertions": [
10
+ "Uses correct ExtensionAPI import from @mariozechner/pi-coding-agent",
11
+ "Hooks into tool_call event (the correct event for intercepting tool calls)",
12
+ "Checks event.toolName === 'bash' before inspecting command",
13
+ "Tests for curl in the command string",
14
+ "Checks ctx.hasUI before calling interactive UI methods",
15
+ "Uses ctx.ui.select or ctx.ui.confirm for user confirmation",
16
+ "Returns { block: true, reason: '...' } to prevent execution",
17
+ "Logs blocked commands to ~/.pi/agent/blocked-commands.log",
18
+ "Uses correct export default function (pi: ExtensionAPI) signature"
19
+ ]
20
+ },
21
+ {
22
+ "id": 2,
23
+ "prompt": "I want to set up Pi to use Ollama with llama3.2 locally, with auto-compaction disabled and a custom session directory at ~/pi-sessions. Walk me through the configuration.",
24
+ "expected_output": "Instructions covering models.json with Ollama provider config (baseUrl localhost:11434/v1, api openai-completions, dummy apiKey, model id llama3.2), settings.json with compaction.enabled=false and sessionDir pointing to ~/pi-sessions.",
25
+ "files": [],
26
+ "assertions": [
27
+ "Provides models.json with Ollama baseUrl containing localhost:11434",
28
+ "Uses api type openai-completions (correct for Ollama)",
29
+ "Includes model entry with id llama3.2",
30
+ "Provides settings.json with compaction.enabled set to false",
31
+ "Sets sessionDir to ~/pi-sessions",
32
+ "Correct file paths: ~/.pi/agent/models.json and ~/.pi/agent/settings.json"
33
+ ]
34
+ },
35
+ {
36
+ "id": 3,
37
+ "prompt": "Create a Pi skill that helps with database migrations — it should know about Prisma, Drizzle, and raw SQL approaches. Give me the full SKILL.md and any reference files.",
38
+ "expected_output": "A SKILL.md with proper frontmatter (name, description), a main body routing to reference files, and separate reference docs for Prisma, Drizzle, and raw SQL migration patterns.",
39
+ "files": [],
40
+ "assertions": [
41
+ "SKILL.md has valid YAML frontmatter with name and description fields",
42
+ "Name follows Pi convention: lowercase, hyphens only, max 64 chars",
43
+ "Description mentions trigger contexts (migrations, Prisma, Drizzle, SQL)",
44
+ "Includes separate reference files for Prisma, Drizzle, and raw SQL",
45
+ "SKILL.md uses relative paths to reference files",
46
+ "Body routes to the correct reference doc based on the user's framework choice"
47
+ ]
48
+ }
49
+ ]
50
+ }
@@ -0,0 +1,343 @@
1
+ # Extensions
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.
4
+
5
+ ## Placement & Discovery
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 |
13
+
14
+ Subdirectories with an `index.ts` or a `package.json` containing a `"pi"` field are also discovered. Reload with `/reload`.
15
+
16
+ ## Basic Structure
17
+
18
+ ```typescript
19
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
20
+
21
+ export default function (pi: ExtensionAPI) {
22
+ // Subscribe to lifecycle events
23
+ pi.on("session_start", async (_event, ctx) => {
24
+ ctx.ui.notify("Extension loaded!", "info");
25
+ });
26
+
27
+ // Register a tool the LLM can call
28
+ pi.registerTool({
29
+ name: "greet",
30
+ label: "Greet",
31
+ description: "Greet someone by name",
32
+ parameters: Type.Object({
33
+ name: Type.String({ description: "Name to greet" }),
34
+ }),
35
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
36
+ const { name } = params as { name: string };
37
+ return {
38
+ content: [{ type: "text", text: `Hello, ${name}!` }],
39
+ details: { greeted: name },
40
+ };
41
+ },
42
+ });
43
+
44
+ // Register a slash command
45
+ pi.registerCommand("mycommand", {
46
+ description: "Do something",
47
+ async handler(args, ctx) {
48
+ ctx.ui.notify(`You said: ${args}`, "info");
49
+ },
50
+ });
51
+ }
52
+ ```
53
+
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
+ ```
124
+
125
+ ## Tool Registration
126
+
127
+ ```typescript
128
+ 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
133
+ arg1: Type.String({ description: "..." }),
134
+ arg2: Type.Optional(Type.Number()),
135
+ }),
136
+
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
143
+
144
+ return {
145
+ content: [{ type: "text", text: "result" }],
146
+ details: { anyMetadata: true }, // Stored in session, available on branch restore
147
+ };
148
+ },
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
+ });
160
+ ```
161
+
162
+ **Critical rules for tools:**
163
+
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`.
169
+
170
+ ## UI Methods
171
+
172
+ All UI methods are on `ctx.ui`. **Always check `ctx.hasUI` first** — non-interactive modes (print, JSON, RPC) may not support UI.
173
+
174
+ ```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);
180
+
181
+ // Notifications (non-blocking)
182
+ ctx.ui.notify("Something happened", "info"); // "info" | "warning" | "error"
183
+
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
188
+
189
+ // Editor control
190
+ ctx.ui.setEditorText("prefilled text");
191
+ ctx.ui.pasteToEditor("appended text");
192
+
193
+ // Custom components (advanced)
194
+ const handle = ctx.ui.custom(component, { overlay: true });
195
+ handle.requestRender();
196
+ handle.close();
197
+ ```
198
+
199
+ ## Command Registration
200
+
201
+ ```typescript
202
+ pi.registerCommand("commandname", {
203
+ description: "What this command does", // Shown in autocomplete
204
+ args: "optional args description",
205
+
206
+ // Argument autocompletion
207
+ complete(partial) {
208
+ return ["suggestion1", "suggestion2"].filter(s => s.startsWith(partial));
209
+ },
210
+
211
+ // Handler receives ExtensionCommandContext (superset of ExtensionContext)
212
+ 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
218
+ },
219
+ });
220
+ ```
221
+
222
+ ## Keybinding Registration
223
+
224
+ ```typescript
225
+ pi.registerKeybinding({
226
+ key: "ctrl+shift+k",
227
+ description: "Do something",
228
+ handler: async (ctx) => { /* ... */ },
229
+ });
230
+ ```
231
+
232
+ ## CLI Flag Registration
233
+
234
+ ```typescript
235
+ pi.registerFlag({
236
+ name: "--my-flag",
237
+ description: "Enable something",
238
+ handler: (value) => { /* value is the flag argument */ },
239
+ });
240
+ ```
241
+
242
+ ## State Persistence
243
+
244
+ Extensions persist state via custom session entries:
245
+
246
+ ```typescript
247
+ // Write state
248
+ pi.appendEntry("my-extension-state", { key: "value" });
249
+
250
+ // Read state on session restore
251
+ pi.on("session_start", async (_event, ctx) => {
252
+ const branch = ctx.sessionManager.getBranch();
253
+ for (const entry of branch.reverse()) {
254
+ if (entry.type === "custom" && entry.name === "my-extension-state") {
255
+ const state = entry.data;
256
+ // Restore from state
257
+ break;
258
+ }
259
+ }
260
+ });
261
+ ```
262
+
263
+ ## Provider Registration
264
+
265
+ Extensions can register custom LLM providers:
266
+
267
+ ```typescript
268
+ pi.registerProvider("my-provider", {
269
+ baseUrl: "https://api.example.com",
270
+ api: "openai-completions",
271
+ apiKey: "sk-...",
272
+ models: [
273
+ { id: "my-model", name: "My Model", contextWindow: 128000, maxTokens: 4096 },
274
+ ],
275
+ });
276
+ ```
277
+
278
+ For full custom streaming, implement `streamSimple` on the provider config. See `references/providers.md`.
279
+
280
+ ## Bash Execution
281
+
282
+ ```typescript
283
+ const { stdout, stderr, exitCode } = await pi.exec("git", ["status"]);
284
+ ```
285
+
286
+ ## Practical Examples
287
+
288
+ ### Permission Gate
289
+
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
+ ```