@oh-my-pi/pi-coding-agent 3.20.0 → 3.21.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.
Files changed (95) hide show
  1. package/CHANGELOG.md +78 -8
  2. package/docs/custom-tools.md +3 -3
  3. package/docs/extensions.md +226 -220
  4. package/docs/hooks.md +2 -2
  5. package/docs/sdk.md +3 -3
  6. package/examples/custom-tools/README.md +2 -2
  7. package/examples/custom-tools/subagent/index.ts +1 -1
  8. package/examples/extensions/README.md +76 -74
  9. package/examples/extensions/todo.ts +2 -5
  10. package/examples/hooks/custom-compaction.ts +1 -1
  11. package/examples/hooks/handoff.ts +1 -1
  12. package/examples/hooks/qna.ts +1 -1
  13. package/examples/sdk/02-custom-model.ts +1 -1
  14. package/examples/sdk/12-full-control.ts +1 -1
  15. package/examples/sdk/README.md +1 -1
  16. package/package.json +5 -5
  17. package/src/cli/file-processor.ts +1 -1
  18. package/src/cli/list-models.ts +1 -1
  19. package/src/core/agent-session.ts +13 -2
  20. package/src/core/auth-storage.ts +1 -1
  21. package/src/core/compaction/branch-summarization.ts +2 -2
  22. package/src/core/compaction/compaction.ts +2 -2
  23. package/src/core/compaction/utils.ts +1 -1
  24. package/src/core/custom-tools/types.ts +1 -1
  25. package/src/core/extensions/runner.ts +1 -1
  26. package/src/core/extensions/types.ts +1 -1
  27. package/src/core/extensions/wrapper.ts +1 -1
  28. package/src/core/hooks/runner.ts +2 -2
  29. package/src/core/hooks/types.ts +1 -1
  30. package/src/core/messages.ts +1 -1
  31. package/src/core/model-registry.ts +1 -1
  32. package/src/core/model-resolver.ts +1 -1
  33. package/src/core/sdk.ts +33 -4
  34. package/src/core/session-manager.ts +11 -22
  35. package/src/core/settings-manager.ts +66 -1
  36. package/src/core/slash-commands.ts +12 -5
  37. package/src/core/system-prompt.ts +27 -3
  38. package/src/core/title-generator.ts +2 -2
  39. package/src/core/tools/ask.ts +88 -1
  40. package/src/core/tools/bash-interceptor.ts +7 -0
  41. package/src/core/tools/bash.ts +106 -0
  42. package/src/core/tools/edit-diff.ts +73 -24
  43. package/src/core/tools/edit.ts +214 -20
  44. package/src/core/tools/find.ts +162 -1
  45. package/src/core/tools/gemini-image.ts +279 -56
  46. package/src/core/tools/git.ts +4 -0
  47. package/src/core/tools/grep.ts +191 -0
  48. package/src/core/tools/index.ts +3 -6
  49. package/src/core/tools/ls.ts +142 -2
  50. package/src/core/tools/lsp/render.ts +34 -14
  51. package/src/core/tools/notebook.ts +110 -0
  52. package/src/core/tools/output.ts +179 -7
  53. package/src/core/tools/read.ts +122 -9
  54. package/src/core/tools/render-utils.ts +241 -0
  55. package/src/core/tools/renderers.ts +40 -828
  56. package/src/core/tools/review.ts +26 -7
  57. package/src/core/tools/rulebook.ts +3 -1
  58. package/src/core/tools/task/index.ts +18 -3
  59. package/src/core/tools/task/render.ts +7 -2
  60. package/src/core/tools/task/types.ts +1 -1
  61. package/src/core/tools/truncate.ts +27 -1
  62. package/src/core/tools/web-fetch.ts +23 -15
  63. package/src/core/tools/web-search/index.ts +130 -45
  64. package/src/core/tools/web-search/providers/anthropic.ts +7 -2
  65. package/src/core/tools/web-search/providers/exa.ts +2 -1
  66. package/src/core/tools/web-search/providers/perplexity.ts +6 -1
  67. package/src/core/tools/web-search/render.ts +5 -0
  68. package/src/core/tools/web-search/types.ts +13 -0
  69. package/src/core/tools/write.ts +90 -0
  70. package/src/core/voice.ts +1 -1
  71. package/src/lib/worktree/constants.ts +6 -6
  72. package/src/main.ts +1 -1
  73. package/src/modes/interactive/components/assistant-message.ts +1 -1
  74. package/src/modes/interactive/components/custom-message.ts +1 -1
  75. package/src/modes/interactive/components/extensions/inspector-panel.ts +25 -22
  76. package/src/modes/interactive/components/extensions/state-manager.ts +12 -0
  77. package/src/modes/interactive/components/footer.ts +1 -1
  78. package/src/modes/interactive/components/hook-message.ts +1 -1
  79. package/src/modes/interactive/components/model-selector.ts +1 -1
  80. package/src/modes/interactive/components/oauth-selector.ts +1 -1
  81. package/src/modes/interactive/components/settings-defs.ts +49 -0
  82. package/src/modes/interactive/components/status-line.ts +1 -1
  83. package/src/modes/interactive/components/tool-execution.ts +93 -538
  84. package/src/modes/interactive/interactive-mode.ts +19 -7
  85. package/src/modes/print-mode.ts +1 -1
  86. package/src/modes/rpc/rpc-client.ts +1 -1
  87. package/src/modes/rpc/rpc-types.ts +1 -1
  88. package/src/prompts/system-prompt.md +4 -0
  89. package/src/prompts/tools/gemini-image.md +5 -1
  90. package/src/prompts/tools/output.md +4 -0
  91. package/src/prompts/tools/web-fetch.md +1 -0
  92. package/src/prompts/tools/web-search.md +2 -0
  93. package/src/utils/image-convert.ts +8 -2
  94. package/src/utils/image-magick.ts +247 -0
  95. package/src/utils/image-resize.ts +53 -13
@@ -5,6 +5,7 @@
5
5
  Extensions are TypeScript modules that extend pi's behavior. They can subscribe to lifecycle events, register custom tools callable by the LLM, add commands, and more.
6
6
 
7
7
  **Key capabilities:**
8
+
8
9
  - **Custom tools** - Register tools the LLM can call via `pi.registerTool()`
9
10
  - **Event interception** - Block or modify tool calls, inject context, customize compaction
10
11
  - **User interaction** - Prompt users via `ctx.ui` (select, confirm, input, notify)
@@ -14,6 +15,7 @@ Extensions are TypeScript modules that extend pi's behavior. They can subscribe
14
15
  - **Custom rendering** - Control how tool calls/results and messages appear in TUI
15
16
 
16
17
  **Example use cases:**
18
+
17
19
  - Permission gates (confirm before `rm -rf`, `sudo`, etc.)
18
20
  - Git checkpointing (stash at each turn, restore on branch)
19
21
  - Path protection (block writes to `.env`, `node_modules/`)
@@ -55,41 +57,41 @@ import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
55
57
  import { Type } from "@sinclair/typebox";
56
58
 
57
59
  export default function (pi: ExtensionAPI) {
58
- // React to events
59
- pi.on("session_start", async (_event, ctx) => {
60
- ctx.ui.notify("Extension loaded!", "info");
61
- });
62
-
63
- pi.on("tool_call", async (event, ctx) => {
64
- if (event.toolName === "bash" && event.input.command?.includes("rm -rf")) {
65
- const ok = await ctx.ui.confirm("Dangerous!", "Allow rm -rf?");
66
- if (!ok) return { block: true, reason: "Blocked by user" };
67
- }
68
- });
69
-
70
- // Register a custom tool
71
- pi.registerTool({
72
- name: "greet",
73
- label: "Greet",
74
- description: "Greet someone by name",
75
- parameters: Type.Object({
76
- name: Type.String({ description: "Name to greet" }),
77
- }),
78
- async execute(toolCallId, params, onUpdate, ctx, signal) {
79
- return {
80
- content: [{ type: "text", text: `Hello, ${params.name}!` }],
81
- details: {},
82
- };
83
- },
84
- });
85
-
86
- // Register a command
87
- pi.registerCommand("hello", {
88
- description: "Say hello",
89
- handler: async (args, ctx) => {
90
- ctx.ui.notify(`Hello ${args || "world"}!`, "info");
91
- },
92
- });
60
+ // React to events
61
+ pi.on("session_start", async (_event, ctx) => {
62
+ ctx.ui.notify("Extension loaded!", "info");
63
+ });
64
+
65
+ pi.on("tool_call", async (event, ctx) => {
66
+ if (event.toolName === "bash" && event.input.command?.includes("rm -rf")) {
67
+ const ok = await ctx.ui.confirm("Dangerous!", "Allow rm -rf?");
68
+ if (!ok) return { block: true, reason: "Blocked by user" };
69
+ }
70
+ });
71
+
72
+ // Register a custom tool
73
+ pi.registerTool({
74
+ name: "greet",
75
+ label: "Greet",
76
+ description: "Greet someone by name",
77
+ parameters: Type.Object({
78
+ name: Type.String({ description: "Name to greet" }),
79
+ }),
80
+ async execute(toolCallId, params, onUpdate, ctx, signal) {
81
+ return {
82
+ content: [{ type: "text", text: `Hello, ${params.name}!` }],
83
+ details: {},
84
+ };
85
+ },
86
+ });
87
+
88
+ // Register a command
89
+ pi.registerCommand("hello", {
90
+ description: "Say hello",
91
+ handler: async (args, ctx) => {
92
+ ctx.ui.notify(`Hello ${args || "world"}!`, "info");
93
+ },
94
+ });
93
95
  }
94
96
  ```
95
97
 
@@ -103,12 +105,12 @@ pi -e ./my-extension.ts
103
105
 
104
106
  Extensions are auto-discovered from:
105
107
 
106
- | Location | Scope |
107
- |----------|-------|
108
- | `~/.omp/agent/extensions/*.ts` | Global (all projects) |
109
- | `~/.omp/agent/extensions/*/index.ts` | Global (subdirectory) |
110
- | `.omp/extensions/*.ts` | Project-local |
111
- | `.omp/extensions/*/index.ts` | Project-local (subdirectory) |
108
+ | Location | Scope |
109
+ | ------------------------------------ | ---------------------------- |
110
+ | `~/.omp/agent/extensions/*.ts` | Global (all projects) |
111
+ | `~/.omp/agent/extensions/*/index.ts` | Global (subdirectory) |
112
+ | `.omp/extensions/*.ts` | Project-local |
113
+ | `.omp/extensions/*/index.ts` | Project-local (subdirectory) |
112
114
 
113
115
  Legacy `.pi` directories are supported as aliases for the `.omp` paths above.
114
116
 
@@ -116,7 +118,7 @@ Additional paths via `settings.json`:
116
118
 
117
119
  ```json
118
120
  {
119
- "extensions": ["/path/to/extension.ts", "/path/to/extension/dir"]
121
+ "extensions": ["/path/to/extension.ts", "/path/to/extension/dir"]
120
122
  }
121
123
  ```
122
124
 
@@ -142,17 +144,18 @@ Additional paths via `settings.json`:
142
144
  ```json
143
145
  // my-extension-pack/package.json
144
146
  {
145
- "name": "my-extension-pack",
146
- "dependencies": {
147
- "zod": "^3.0.0"
148
- },
149
- "omp": {
150
- "extensions": ["./src/safety-gates.ts", "./src/custom-tools.ts"]
151
- }
147
+ "name": "my-extension-pack",
148
+ "dependencies": {
149
+ "zod": "^3.0.0"
150
+ },
151
+ "omp": {
152
+ "extensions": ["./src/safety-gates.ts", "./src/custom-tools.ts"]
153
+ }
152
154
  }
153
155
  ```
154
156
 
155
157
  The `package.json` approach enables:
158
+
156
159
  - Multiple extensions from one package
157
160
  - Third-party npm dependencies (resolved via jiti)
158
161
  - Nested source structure (no depth limit within the package)
@@ -160,12 +163,12 @@ The `package.json` approach enables:
160
163
 
161
164
  ## Available Imports
162
165
 
163
- | Package | Purpose |
164
- |---------|---------|
166
+ | Package | Purpose |
167
+ | --------------------------- | ------------------------------------------------------------ |
165
168
  | `@oh-my-pi/pi-coding-agent` | Extension types (`ExtensionAPI`, `ExtensionContext`, events) |
166
- | `@sinclair/typebox` | Schema definitions for tool parameters |
167
- | `@oh-my-pi/pi-ai` | AI utilities (`StringEnum` for Google-compatible enums) |
168
- | `@oh-my-pi/pi-tui` | TUI components for custom rendering |
169
+ | `@sinclair/typebox` | Schema definitions for tool parameters |
170
+ | `@mariozechner/pi-ai` | AI utilities (`StringEnum` for Google-compatible enums) |
171
+ | `@oh-my-pi/pi-tui` | TUI components for custom rendering |
169
172
 
170
173
  npm dependencies work too. Add a `package.json` next to your extension (or in a parent directory), run `npm install`, and imports from `node_modules/` are resolved automatically.
171
174
 
@@ -232,14 +235,14 @@ Extensions are loaded via [jiti](https://github.com/unjs/jiti), so TypeScript wo
232
235
  ```json
233
236
  // package.json
234
237
  {
235
- "name": "my-extension",
236
- "dependencies": {
237
- "zod": "^3.0.0",
238
- "chalk": "^5.0.0"
239
- },
240
- "pi": {
241
- "extensions": ["./src/index.ts"]
242
- }
238
+ "name": "my-extension",
239
+ "dependencies": {
240
+ "zod": "^3.0.0",
241
+ "chalk": "^5.0.0"
242
+ },
243
+ "pi": {
244
+ "extensions": ["./src/index.ts"]
245
+ }
243
246
  }
244
247
  ```
245
248
 
@@ -304,7 +307,7 @@ Fired on initial session load.
304
307
 
305
308
  ```typescript
306
309
  pi.on("session_start", async (_event, ctx) => {
307
- ctx.ui.notify(`Session: ${ctx.sessionManager.getSessionFile() ?? "ephemeral"}`, "info");
310
+ ctx.ui.notify(`Session: ${ctx.sessionManager.getSessionFile() ?? "ephemeral"}`, "info");
308
311
  });
309
312
  ```
310
313
 
@@ -314,18 +317,18 @@ Fired when starting a new session (`/new`) or switching sessions (`/resume`).
314
317
 
315
318
  ```typescript
316
319
  pi.on("session_before_switch", async (event, ctx) => {
317
- // event.reason - "new" or "resume"
318
- // event.targetSessionFile - session we're switching to (only for "resume")
320
+ // event.reason - "new" or "resume"
321
+ // event.targetSessionFile - session we're switching to (only for "resume")
319
322
 
320
- if (event.reason === "new") {
321
- const ok = await ctx.ui.confirm("Clear?", "Delete all messages?");
322
- if (!ok) return { cancel: true };
323
- }
323
+ if (event.reason === "new") {
324
+ const ok = await ctx.ui.confirm("Clear?", "Delete all messages?");
325
+ if (!ok) return { cancel: true };
326
+ }
324
327
  });
325
328
 
326
329
  pi.on("session_switch", async (event, ctx) => {
327
- // event.reason - "new" or "resume"
328
- // event.previousSessionFile - session we came from
330
+ // event.reason - "new" or "resume"
331
+ // event.previousSessionFile - session we came from
329
332
  });
330
333
  ```
331
334
 
@@ -335,14 +338,14 @@ Fired when branching via `/branch`.
335
338
 
336
339
  ```typescript
337
340
  pi.on("session_before_branch", async (event, ctx) => {
338
- // event.entryId - ID of the entry being branched from
339
- return { cancel: true }; // Cancel branch
340
- // OR
341
- return { skipConversationRestore: true }; // Branch but don't rewind messages
341
+ // event.entryId - ID of the entry being branched from
342
+ return { cancel: true }; // Cancel branch
343
+ // OR
344
+ return { skipConversationRestore: true }; // Branch but don't rewind messages
342
345
  });
343
346
 
344
347
  pi.on("session_branch", async (event, ctx) => {
345
- // event.previousSessionFile - previous session file
348
+ // event.previousSessionFile - previous session file
346
349
  });
347
350
  ```
348
351
 
@@ -352,24 +355,24 @@ Fired on compaction. See [compaction.md](compaction.md) for details.
352
355
 
353
356
  ```typescript
354
357
  pi.on("session_before_compact", async (event, ctx) => {
355
- const { preparation, branchEntries, customInstructions, signal } = event;
356
-
357
- // Cancel:
358
- return { cancel: true };
359
-
360
- // Custom summary:
361
- return {
362
- compaction: {
363
- summary: "...",
364
- firstKeptEntryId: preparation.firstKeptEntryId,
365
- tokensBefore: preparation.tokensBefore,
366
- }
367
- };
358
+ const { preparation, branchEntries, customInstructions, signal } = event;
359
+
360
+ // Cancel:
361
+ return { cancel: true };
362
+
363
+ // Custom summary:
364
+ return {
365
+ compaction: {
366
+ summary: "...",
367
+ firstKeptEntryId: preparation.firstKeptEntryId,
368
+ tokensBefore: preparation.tokensBefore,
369
+ },
370
+ };
368
371
  });
369
372
 
370
373
  pi.on("session_compact", async (event, ctx) => {
371
- // event.compactionEntry - the saved compaction
372
- // event.fromExtension - whether extension provided it
374
+ // event.compactionEntry - the saved compaction
375
+ // event.fromExtension - whether extension provided it
373
376
  });
374
377
  ```
375
378
 
@@ -379,14 +382,14 @@ Fired on `/tree` navigation.
379
382
 
380
383
  ```typescript
381
384
  pi.on("session_before_tree", async (event, ctx) => {
382
- const { preparation, signal } = event;
383
- return { cancel: true };
384
- // OR provide custom summary:
385
- return { summary: { summary: "...", details: {} } };
385
+ const { preparation, signal } = event;
386
+ return { cancel: true };
387
+ // OR provide custom summary:
388
+ return { summary: { summary: "...", details: {} } };
386
389
  });
387
390
 
388
391
  pi.on("session_tree", async (event, ctx) => {
389
- // event.newLeafId, oldLeafId, summaryEntry, fromExtension
392
+ // event.newLeafId, oldLeafId, summaryEntry, fromExtension
390
393
  });
391
394
  ```
392
395
 
@@ -396,7 +399,7 @@ Fired on exit (Ctrl+C, Ctrl+D, SIGTERM).
396
399
 
397
400
  ```typescript
398
401
  pi.on("session_shutdown", async (_event, ctx) => {
399
- // Cleanup, save state, etc.
402
+ // Cleanup, save state, etc.
400
403
  });
401
404
  ```
402
405
 
@@ -408,19 +411,19 @@ Fired after user submits prompt, before agent loop. Can inject a message and/or
408
411
 
409
412
  ```typescript
410
413
  pi.on("before_agent_start", async (event, ctx) => {
411
- // event.prompt - user's prompt text
412
- // event.images - attached images (if any)
413
-
414
- return {
415
- // Inject a persistent message (stored in session, sent to LLM)
416
- message: {
417
- customType: "my-extension",
418
- content: "Additional context for the LLM",
419
- display: true,
420
- },
421
- // Append to system prompt for this turn only
422
- systemPromptAppend: "Extra instructions for this turn...",
423
- };
414
+ // event.prompt - user's prompt text
415
+ // event.images - attached images (if any)
416
+
417
+ return {
418
+ // Inject a persistent message (stored in session, sent to LLM)
419
+ message: {
420
+ customType: "my-extension",
421
+ content: "Additional context for the LLM",
422
+ display: true,
423
+ },
424
+ // Append to system prompt for this turn only
425
+ systemPromptAppend: "Extra instructions for this turn...",
426
+ };
424
427
  });
425
428
  ```
426
429
 
@@ -432,7 +435,7 @@ Fired once per user prompt.
432
435
  pi.on("agent_start", async (_event, ctx) => {});
433
436
 
434
437
  pi.on("agent_end", async (event, ctx) => {
435
- // event.messages - messages from this prompt
438
+ // event.messages - messages from this prompt
436
439
  });
437
440
  ```
438
441
 
@@ -442,11 +445,11 @@ Fired for each turn (one LLM response + tool calls).
442
445
 
443
446
  ```typescript
444
447
  pi.on("turn_start", async (event, ctx) => {
445
- // event.turnIndex, event.timestamp
448
+ // event.turnIndex, event.timestamp
446
449
  });
447
450
 
448
451
  pi.on("turn_end", async (event, ctx) => {
449
- // event.turnIndex, event.message, event.toolResults
452
+ // event.turnIndex, event.message, event.toolResults
450
453
  });
451
454
  ```
452
455
 
@@ -456,9 +459,9 @@ Fired before each LLM call. Modify messages non-destructively.
456
459
 
457
460
  ```typescript
458
461
  pi.on("context", async (event, ctx) => {
459
- // event.messages - deep copy, safe to modify
460
- const filtered = event.messages.filter(m => !shouldPrune(m));
461
- return { messages: filtered };
462
+ // event.messages - deep copy, safe to modify
463
+ const filtered = event.messages.filter((m) => !shouldPrune(m));
464
+ return { messages: filtered };
462
465
  });
463
466
  ```
464
467
 
@@ -470,13 +473,13 @@ Fired before tool executes. **Can block.**
470
473
 
471
474
  ```typescript
472
475
  pi.on("tool_call", async (event, ctx) => {
473
- // event.toolName - "bash", "read", "write", "edit", etc.
474
- // event.toolCallId
475
- // event.input - tool parameters
476
+ // event.toolName - "bash", "read", "write", "edit", etc.
477
+ // event.toolCallId
478
+ // event.input - tool parameters
476
479
 
477
- if (shouldBlock(event)) {
478
- return { block: true, reason: "Not allowed" };
479
- }
480
+ if (shouldBlock(event)) {
481
+ return { block: true, reason: "Not allowed" };
482
+ }
480
483
  });
481
484
  ```
482
485
 
@@ -521,9 +524,9 @@ Current working directory.
521
524
  Read-only access to session state:
522
525
 
523
526
  ```typescript
524
- ctx.sessionManager.getEntries() // All entries
525
- ctx.sessionManager.getBranch() // Current branch
526
- ctx.sessionManager.getLeafId() // Current leaf entry ID
527
+ ctx.sessionManager.getEntries(); // All entries
528
+ ctx.sessionManager.getBranch(); // Current branch
529
+ ctx.sessionManager.getLeafId(); // Current leaf entry ID
527
530
  ```
528
531
 
529
532
  ### ctx.modelRegistry / ctx.model
@@ -544,10 +547,10 @@ Wait for the agent to finish streaming:
544
547
 
545
548
  ```typescript
546
549
  pi.registerCommand("my-cmd", {
547
- handler: async (args, ctx) => {
548
- await ctx.waitForIdle();
549
- // Agent is now idle, safe to modify session
550
- },
550
+ handler: async (args, ctx) => {
551
+ await ctx.waitForIdle();
552
+ // Agent is now idle, safe to modify session
553
+ },
551
554
  });
552
555
  ```
553
556
 
@@ -557,18 +560,18 @@ Create a new session:
557
560
 
558
561
  ```typescript
559
562
  const result = await ctx.newSession({
560
- parentSession: ctx.sessionManager.getSessionFile(),
561
- setup: async (sm) => {
562
- sm.appendMessage({
563
- role: "user",
564
- content: [{ type: "text", text: "Context from previous session..." }],
565
- timestamp: Date.now(),
566
- });
567
- },
563
+ parentSession: ctx.sessionManager.getSessionFile(),
564
+ setup: async (sm) => {
565
+ sm.appendMessage({
566
+ role: "user",
567
+ content: [{ type: "text", text: "Context from previous session..." }],
568
+ timestamp: Date.now(),
569
+ });
570
+ },
568
571
  });
569
572
 
570
573
  if (result.cancelled) {
571
- // An extension cancelled the new session
574
+ // An extension cancelled the new session
572
575
  }
573
576
  ```
574
577
 
@@ -579,7 +582,7 @@ Branch from a specific entry:
579
582
  ```typescript
580
583
  const result = await ctx.branch("entry-id-123");
581
584
  if (!result.cancelled) {
582
- // Now in the branched session
585
+ // Now in the branched session
583
586
  }
584
587
  ```
585
588
 
@@ -589,7 +592,7 @@ Navigate to a different point in the session tree:
589
592
 
590
593
  ```typescript
591
594
  const result = await ctx.navigateTree("entry-id-456", {
592
- summarize: true,
595
+ summarize: true,
593
596
  });
594
597
  ```
595
598
 
@@ -605,7 +608,7 @@ Register a custom tool callable by the LLM. See [Custom Tools](#custom-tools) fo
605
608
 
606
609
  ```typescript
607
610
  import { Type } from "@sinclair/typebox";
608
- import { StringEnum } from "@oh-my-pi/pi-ai";
611
+ import { StringEnum } from "@mariozechner/pi-ai";
609
612
 
610
613
  pi.registerTool({
611
614
  name: "my_tool",
@@ -649,6 +652,7 @@ pi.sendMessage({
649
652
  ```
650
653
 
651
654
  **Options:**
655
+
652
656
  - `deliverAs` - Delivery mode:
653
657
  - `"steer"` (default) - Interrupts streaming. Delivered after current tool finishes, remaining tools skipped.
654
658
  - `"followUp"` - Waits for agent to finish. Delivered only when agent has no more tool calls.
@@ -664,11 +668,11 @@ pi.appendEntry("my-state", { count: 42 });
664
668
 
665
669
  // Restore on reload
666
670
  pi.on("session_start", async (_event, ctx) => {
667
- for (const entry of ctx.sessionManager.getEntries()) {
668
- if (entry.type === "custom" && entry.customType === "my-state") {
669
- // Reconstruct from entry.data
670
- }
671
- }
671
+ for (const entry of ctx.sessionManager.getEntries()) {
672
+ if (entry.type === "custom" && entry.customType === "my-state") {
673
+ // Reconstruct from entry.data
674
+ }
675
+ }
672
676
  });
673
677
  ```
674
678
 
@@ -678,11 +682,11 @@ Register a command:
678
682
 
679
683
  ```typescript
680
684
  pi.registerCommand("stats", {
681
- description: "Show session statistics",
682
- handler: async (args, ctx) => {
683
- const count = ctx.sessionManager.getEntries().length;
684
- ctx.ui.notify(`${count} entries`, "info");
685
- }
685
+ description: "Show session statistics",
686
+ handler: async (args, ctx) => {
687
+ const count = ctx.sessionManager.getEntries().length;
688
+ ctx.ui.notify(`${count} entries`, "info");
689
+ },
686
690
  });
687
691
  ```
688
692
 
@@ -696,10 +700,10 @@ Register a keyboard shortcut:
696
700
 
697
701
  ```typescript
698
702
  pi.registerShortcut("ctrl+shift+p", {
699
- description: "Toggle plan mode",
700
- handler: async (ctx) => {
701
- ctx.ui.notify("Toggled!");
702
- },
703
+ description: "Toggle plan mode",
704
+ handler: async (ctx) => {
705
+ ctx.ui.notify("Toggled!");
706
+ },
703
707
  });
704
708
  ```
705
709
 
@@ -709,14 +713,14 @@ Register a CLI flag:
709
713
 
710
714
  ```typescript
711
715
  pi.registerFlag("--plan", {
712
- description: "Start in plan mode",
713
- type: "boolean",
714
- default: false,
716
+ description: "Start in plan mode",
717
+ type: "boolean",
718
+ default: false,
715
719
  });
716
720
 
717
721
  // Check value
718
722
  if (pi.getFlag("--plan")) {
719
- // Plan mode enabled
723
+ // Plan mode enabled
720
724
  }
721
725
  ```
722
726
 
@@ -734,7 +738,7 @@ const result = await pi.exec("git", ["status"], { signal, timeout: 5000 });
734
738
  Manage active tools:
735
739
 
736
740
  ```typescript
737
- const active = pi.getActiveTools(); // ["read", "bash", "edit", "write"]
741
+ const active = pi.getActiveTools(); // ["read", "bash", "edit", "write"]
738
742
  pi.setActiveTools(["read", "bash"]); // Switch to read-only
739
743
  ```
740
744
 
@@ -753,31 +757,31 @@ Extensions with state should store it in tool result `details` for proper branch
753
757
 
754
758
  ```typescript
755
759
  export default function (pi: ExtensionAPI) {
756
- let items: string[] = [];
757
-
758
- // Reconstruct state from session
759
- pi.on("session_start", async (_event, ctx) => {
760
- items = [];
761
- for (const entry of ctx.sessionManager.getBranch()) {
762
- if (entry.type === "message" && entry.message.role === "toolResult") {
763
- if (entry.message.toolName === "my_tool") {
764
- items = entry.message.details?.items ?? [];
765
- }
766
- }
767
- }
768
- });
769
-
770
- pi.registerTool({
771
- name: "my_tool",
772
- // ...
773
- async execute(toolCallId, params, onUpdate, ctx, signal) {
774
- items.push("new item");
775
- return {
776
- content: [{ type: "text", text: "Added" }],
777
- details: { items: [...items] }, // Store for reconstruction
778
- };
779
- },
780
- });
760
+ let items: string[] = [];
761
+
762
+ // Reconstruct state from session
763
+ pi.on("session_start", async (_event, ctx) => {
764
+ items = [];
765
+ for (const entry of ctx.sessionManager.getBranch()) {
766
+ if (entry.type === "message" && entry.message.role === "toolResult") {
767
+ if (entry.message.toolName === "my_tool") {
768
+ items = entry.message.details?.items ?? [];
769
+ }
770
+ }
771
+ }
772
+ });
773
+
774
+ pi.registerTool({
775
+ name: "my_tool",
776
+ // ...
777
+ async execute(toolCallId, params, onUpdate, ctx, signal) {
778
+ items.push("new item");
779
+ return {
780
+ content: [{ type: "text", text: "Added" }],
781
+ details: { items: [...items] }, // Store for reconstruction
782
+ };
783
+ },
784
+ });
781
785
  }
782
786
  ```
783
787
 
@@ -789,7 +793,7 @@ Register tools the LLM can call via `pi.registerTool()`. Tools appear in the sys
789
793
 
790
794
  ```typescript
791
795
  import { Type } from "@sinclair/typebox";
792
- import { StringEnum } from "@oh-my-pi/pi-ai";
796
+ import { StringEnum } from "@mariozechner/pi-ai";
793
797
  import { Text } from "@oh-my-pi/pi-tui";
794
798
 
795
799
  pi.registerTool({
@@ -829,7 +833,7 @@ pi.registerTool({
829
833
  });
830
834
  ```
831
835
 
832
- **Important:** Use `StringEnum` from `@oh-my-pi/pi-ai` for string enums. `Type.Union`/`Type.Literal` doesn't work with Google's API.
836
+ **Important:** Use `StringEnum` from `@mariozechner/pi-ai` for string enums. `Type.Union`/`Type.Literal` doesn't work with Google's API.
833
837
 
834
838
  ### Multiple Tools
835
839
 
@@ -910,6 +914,7 @@ renderResult(result, { expanded, isPartial }, theme) {
910
914
  #### Fallback
911
915
 
912
916
  If `renderCall`/`renderResult` is not defined or throws:
917
+
913
918
  - `renderCall`: Shows tool name
914
919
  - `renderResult`: Shows raw text from `content`
915
920
 
@@ -933,7 +938,7 @@ const name = await ctx.ui.input("Name:", "placeholder");
933
938
  const text = await ctx.ui.editor("Edit:", "prefilled text");
934
939
 
935
940
  // Notification (non-blocking)
936
- ctx.ui.notify("Done!", "info"); // "info" | "warning" | "error"
941
+ ctx.ui.notify("Done!", "info"); // "info" | "warning" | "error"
937
942
  ```
938
943
 
939
944
  ### Widgets and Status
@@ -941,12 +946,12 @@ ctx.ui.notify("Done!", "info"); // "info" | "warning" | "error"
941
946
  ```typescript
942
947
  // Status in footer (persistent until cleared)
943
948
  ctx.ui.setStatus("my-ext", "Processing...");
944
- ctx.ui.setStatus("my-ext", undefined); // Clear
949
+ ctx.ui.setStatus("my-ext", undefined); // Clear
945
950
 
946
951
  // Widget above editor (string array or factory function)
947
952
  ctx.ui.setWidget("my-widget", ["Line 1", "Line 2"]);
948
953
  ctx.ui.setWidget("my-widget", (tui, theme) => new Text(theme.fg("accent", "Custom"), 0, 0));
949
- ctx.ui.setWidget("my-widget", undefined); // Clear
954
+ ctx.ui.setWidget("my-widget", undefined); // Clear
950
955
 
951
956
  // Terminal title
952
957
  ctx.ui.setTitle("pi - my-project");
@@ -964,23 +969,24 @@ For complex UI, use `ctx.ui.custom()`. This temporarily replaces the editor with
964
969
  import { Text, Component } from "@oh-my-pi/pi-tui";
965
970
 
966
971
  const result = await ctx.ui.custom<boolean>((tui, theme, done) => {
967
- const text = new Text("Press Enter to confirm, Escape to cancel", 1, 1);
972
+ const text = new Text("Press Enter to confirm, Escape to cancel", 1, 1);
968
973
 
969
- text.onKey = (key) => {
970
- if (key === "return") done(true);
971
- if (key === "escape") done(false);
972
- return true;
973
- };
974
+ text.onKey = (key) => {
975
+ if (key === "return") done(true);
976
+ if (key === "escape") done(false);
977
+ return true;
978
+ };
974
979
 
975
- return text;
980
+ return text;
976
981
  });
977
982
 
978
983
  if (result) {
979
- // User pressed Enter
984
+ // User pressed Enter
980
985
  }
981
986
  ```
982
987
 
983
988
  The callback receives:
989
+
984
990
  - `tui` - TUI instance (for screen dimensions, focus management)
985
991
  - `theme` - Current theme for styling
986
992
  - `done(value)` - Call to close component and return value
@@ -995,15 +1001,15 @@ Register a custom renderer for messages with your `customType`:
995
1001
  import { Text } from "@oh-my-pi/pi-tui";
996
1002
 
997
1003
  pi.registerMessageRenderer("my-extension", (message, options, theme) => {
998
- const { expanded } = options;
999
- let text = theme.fg("accent", `[${message.customType}] `);
1000
- text += message.content;
1004
+ const { expanded } = options;
1005
+ let text = theme.fg("accent", `[${message.customType}] `);
1006
+ text += message.content;
1001
1007
 
1002
- if (expanded && message.details) {
1003
- text += "\n" + theme.fg("dim", JSON.stringify(message.details, null, 2));
1004
- }
1008
+ if (expanded && message.details) {
1009
+ text += "\n" + theme.fg("dim", JSON.stringify(message.details, null, 2));
1010
+ }
1005
1011
 
1006
- return new Text(text, 0, 0);
1012
+ return new Text(text, 0, 0);
1007
1013
  });
1008
1014
  ```
1009
1015
 
@@ -1024,18 +1030,18 @@ All render functions receive a `theme` object:
1024
1030
 
1025
1031
  ```typescript
1026
1032
  // Foreground colors
1027
- theme.fg("toolTitle", text) // Tool names
1028
- theme.fg("accent", text) // Highlights
1029
- theme.fg("success", text) // Success (green)
1030
- theme.fg("error", text) // Errors (red)
1031
- theme.fg("warning", text) // Warnings (yellow)
1032
- theme.fg("muted", text) // Secondary text
1033
- theme.fg("dim", text) // Tertiary text
1033
+ theme.fg("toolTitle", text); // Tool names
1034
+ theme.fg("accent", text); // Highlights
1035
+ theme.fg("success", text); // Success (green)
1036
+ theme.fg("error", text); // Errors (red)
1037
+ theme.fg("warning", text); // Warnings (yellow)
1038
+ theme.fg("muted", text); // Secondary text
1039
+ theme.fg("dim", text); // Tertiary text
1034
1040
 
1035
1041
  // Text styles
1036
- theme.bold(text)
1037
- theme.italic(text)
1038
- theme.strikethrough(text)
1042
+ theme.bold(text);
1043
+ theme.italic(text);
1044
+ theme.strikethrough(text);
1039
1045
  ```
1040
1046
 
1041
1047
  ## Error Handling
@@ -1046,10 +1052,10 @@ theme.strikethrough(text)
1046
1052
 
1047
1053
  ## Mode Behavior
1048
1054
 
1049
- | Mode | UI Methods | Notes |
1050
- |------|-----------|-------|
1051
- | Interactive | Full TUI | Normal operation |
1052
- | RPC | JSON protocol | Host handles UI |
1053
- | Print (`-p`) | No-op | Extensions run but can't prompt |
1055
+ | Mode | UI Methods | Notes |
1056
+ | ------------ | ------------- | ------------------------------- |
1057
+ | Interactive | Full TUI | Normal operation |
1058
+ | RPC | JSON protocol | Host handles UI |
1059
+ | Print (`-p`) | No-op | Extensions run but can't prompt |
1054
1060
 
1055
1061
  In print mode, check `ctx.hasUI` before using UI methods.