@oh-my-pi/pi-coding-agent 3.15.0 → 3.20.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 (193) hide show
  1. package/CHANGELOG.md +61 -1
  2. package/docs/extensions.md +1055 -0
  3. package/docs/rpc.md +69 -13
  4. package/docs/session-tree-plan.md +1 -1
  5. package/examples/extensions/README.md +141 -0
  6. package/examples/extensions/api-demo.ts +87 -0
  7. package/examples/extensions/chalk-logger.ts +26 -0
  8. package/examples/extensions/hello.ts +33 -0
  9. package/examples/extensions/pirate.ts +44 -0
  10. package/examples/extensions/plan-mode.ts +551 -0
  11. package/examples/extensions/subagent/agents/reviewer.md +35 -0
  12. package/examples/extensions/todo.ts +299 -0
  13. package/examples/extensions/tools.ts +145 -0
  14. package/examples/extensions/with-deps/index.ts +36 -0
  15. package/examples/extensions/with-deps/package-lock.json +31 -0
  16. package/examples/extensions/with-deps/package.json +16 -0
  17. package/examples/sdk/02-custom-model.ts +3 -3
  18. package/examples/sdk/05-tools.ts +7 -3
  19. package/examples/sdk/06-extensions.ts +81 -0
  20. package/examples/sdk/06-hooks.ts +14 -13
  21. package/examples/sdk/08-prompt-templates.ts +42 -0
  22. package/examples/sdk/08-slash-commands.ts +17 -12
  23. package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
  24. package/examples/sdk/12-full-control.ts +6 -6
  25. package/package.json +11 -7
  26. package/src/capability/extension-module.ts +34 -0
  27. package/src/cli/args.ts +22 -7
  28. package/src/cli/file-processor.ts +38 -67
  29. package/src/cli/list-models.ts +1 -1
  30. package/src/config.ts +25 -14
  31. package/src/core/agent-session.ts +505 -242
  32. package/src/core/auth-storage.ts +33 -21
  33. package/src/core/compaction/branch-summarization.ts +4 -4
  34. package/src/core/compaction/compaction.ts +3 -3
  35. package/src/core/custom-commands/bundled/wt/index.ts +430 -0
  36. package/src/core/custom-commands/loader.ts +9 -0
  37. package/src/core/custom-tools/wrapper.ts +5 -0
  38. package/src/core/event-bus.ts +59 -0
  39. package/src/core/export-html/vendor/highlight.min.js +1213 -0
  40. package/src/core/export-html/vendor/marked.min.js +6 -0
  41. package/src/core/extensions/index.ts +100 -0
  42. package/src/core/extensions/loader.ts +501 -0
  43. package/src/core/extensions/runner.ts +477 -0
  44. package/src/core/extensions/types.ts +712 -0
  45. package/src/core/extensions/wrapper.ts +147 -0
  46. package/src/core/hooks/types.ts +2 -2
  47. package/src/core/index.ts +10 -21
  48. package/src/core/keybindings.ts +199 -0
  49. package/src/core/messages.ts +26 -7
  50. package/src/core/model-registry.ts +123 -46
  51. package/src/core/model-resolver.ts +7 -5
  52. package/src/core/prompt-templates.ts +242 -0
  53. package/src/core/sdk.ts +378 -295
  54. package/src/core/session-manager.ts +72 -58
  55. package/src/core/settings-manager.ts +118 -22
  56. package/src/core/system-prompt.ts +24 -1
  57. package/src/core/terminal-notify.ts +37 -0
  58. package/src/core/tools/context.ts +4 -4
  59. package/src/core/tools/exa/mcp-client.ts +5 -4
  60. package/src/core/tools/exa/render.ts +176 -131
  61. package/src/core/tools/gemini-image.ts +361 -0
  62. package/src/core/tools/git.ts +216 -0
  63. package/src/core/tools/index.ts +28 -15
  64. package/src/core/tools/lsp/config.ts +5 -4
  65. package/src/core/tools/lsp/index.ts +17 -12
  66. package/src/core/tools/lsp/render.ts +39 -47
  67. package/src/core/tools/read.ts +66 -29
  68. package/src/core/tools/render-utils.ts +268 -0
  69. package/src/core/tools/renderers.ts +243 -225
  70. package/src/core/tools/task/discovery.ts +2 -2
  71. package/src/core/tools/task/executor.ts +66 -58
  72. package/src/core/tools/task/index.ts +29 -10
  73. package/src/core/tools/task/model-resolver.ts +8 -13
  74. package/src/core/tools/task/omp-command.ts +24 -0
  75. package/src/core/tools/task/render.ts +35 -60
  76. package/src/core/tools/task/types.ts +3 -0
  77. package/src/core/tools/web-fetch.ts +29 -28
  78. package/src/core/tools/web-search/index.ts +6 -5
  79. package/src/core/tools/web-search/providers/exa.ts +6 -5
  80. package/src/core/tools/web-search/render.ts +66 -111
  81. package/src/core/voice-controller.ts +135 -0
  82. package/src/core/voice-supervisor.ts +1003 -0
  83. package/src/core/voice.ts +308 -0
  84. package/src/discovery/builtin.ts +75 -1
  85. package/src/discovery/claude.ts +47 -1
  86. package/src/discovery/codex.ts +54 -2
  87. package/src/discovery/gemini.ts +55 -2
  88. package/src/discovery/helpers.ts +100 -1
  89. package/src/discovery/index.ts +2 -0
  90. package/src/index.ts +14 -9
  91. package/src/lib/worktree/collapse.ts +179 -0
  92. package/src/lib/worktree/constants.ts +14 -0
  93. package/src/lib/worktree/errors.ts +23 -0
  94. package/src/lib/worktree/git.ts +110 -0
  95. package/src/lib/worktree/index.ts +23 -0
  96. package/src/lib/worktree/operations.ts +216 -0
  97. package/src/lib/worktree/session.ts +114 -0
  98. package/src/lib/worktree/stats.ts +67 -0
  99. package/src/main.ts +61 -37
  100. package/src/migrations.ts +37 -7
  101. package/src/modes/interactive/components/bash-execution.ts +6 -4
  102. package/src/modes/interactive/components/custom-editor.ts +55 -0
  103. package/src/modes/interactive/components/custom-message.ts +95 -0
  104. package/src/modes/interactive/components/extensions/extension-list.ts +5 -0
  105. package/src/modes/interactive/components/extensions/inspector-panel.ts +18 -12
  106. package/src/modes/interactive/components/extensions/state-manager.ts +12 -0
  107. package/src/modes/interactive/components/extensions/types.ts +1 -0
  108. package/src/modes/interactive/components/footer.ts +324 -0
  109. package/src/modes/interactive/components/hook-editor.ts +1 -0
  110. package/src/modes/interactive/components/hook-selector.ts +3 -3
  111. package/src/modes/interactive/components/model-selector.ts +7 -6
  112. package/src/modes/interactive/components/oauth-selector.ts +3 -3
  113. package/src/modes/interactive/components/settings-defs.ts +55 -6
  114. package/src/modes/interactive/components/status-line/separators.ts +4 -4
  115. package/src/modes/interactive/components/status-line.ts +45 -35
  116. package/src/modes/interactive/components/tool-execution.ts +95 -23
  117. package/src/modes/interactive/interactive-mode.ts +644 -113
  118. package/src/modes/interactive/theme/defaults/alabaster.json +99 -0
  119. package/src/modes/interactive/theme/defaults/amethyst.json +103 -0
  120. package/src/modes/interactive/theme/defaults/anthracite.json +100 -0
  121. package/src/modes/interactive/theme/defaults/basalt.json +90 -0
  122. package/src/modes/interactive/theme/defaults/birch.json +101 -0
  123. package/src/modes/interactive/theme/defaults/dark-abyss.json +97 -0
  124. package/src/modes/interactive/theme/defaults/dark-aurora.json +94 -0
  125. package/src/modes/interactive/theme/defaults/dark-cavern.json +97 -0
  126. package/src/modes/interactive/theme/defaults/dark-copper.json +94 -0
  127. package/src/modes/interactive/theme/defaults/dark-cosmos.json +96 -0
  128. package/src/modes/interactive/theme/defaults/dark-eclipse.json +97 -0
  129. package/src/modes/interactive/theme/defaults/dark-ember.json +94 -0
  130. package/src/modes/interactive/theme/defaults/dark-equinox.json +96 -0
  131. package/src/modes/interactive/theme/defaults/dark-lavender.json +94 -0
  132. package/src/modes/interactive/theme/defaults/dark-lunar.json +95 -0
  133. package/src/modes/interactive/theme/defaults/dark-midnight.json +94 -0
  134. package/src/modes/interactive/theme/defaults/dark-nebula.json +96 -0
  135. package/src/modes/interactive/theme/defaults/dark-rainforest.json +97 -0
  136. package/src/modes/interactive/theme/defaults/dark-reef.json +97 -0
  137. package/src/modes/interactive/theme/defaults/dark-sakura.json +94 -0
  138. package/src/modes/interactive/theme/defaults/dark-slate.json +94 -0
  139. package/src/modes/interactive/theme/defaults/dark-solstice.json +96 -0
  140. package/src/modes/interactive/theme/defaults/dark-starfall.json +97 -0
  141. package/src/modes/interactive/theme/defaults/dark-swamp.json +96 -0
  142. package/src/modes/interactive/theme/defaults/dark-taiga.json +97 -0
  143. package/src/modes/interactive/theme/defaults/dark-terminal.json +94 -0
  144. package/src/modes/interactive/theme/defaults/dark-tundra.json +97 -0
  145. package/src/modes/interactive/theme/defaults/dark-twilight.json +97 -0
  146. package/src/modes/interactive/theme/defaults/dark-volcanic.json +97 -0
  147. package/src/modes/interactive/theme/defaults/graphite.json +99 -0
  148. package/src/modes/interactive/theme/defaults/index.ts +128 -0
  149. package/src/modes/interactive/theme/defaults/light-aurora-day.json +97 -0
  150. package/src/modes/interactive/theme/defaults/light-canyon.json +97 -0
  151. package/src/modes/interactive/theme/defaults/light-cirrus.json +96 -0
  152. package/src/modes/interactive/theme/defaults/light-coral.json +94 -0
  153. package/src/modes/interactive/theme/defaults/light-dawn.json +96 -0
  154. package/src/modes/interactive/theme/defaults/light-dunes.json +97 -0
  155. package/src/modes/interactive/theme/defaults/light-eucalyptus.json +94 -0
  156. package/src/modes/interactive/theme/defaults/light-frost.json +94 -0
  157. package/src/modes/interactive/theme/defaults/light-glacier.json +97 -0
  158. package/src/modes/interactive/theme/defaults/light-haze.json +96 -0
  159. package/src/modes/interactive/theme/defaults/light-honeycomb.json +94 -0
  160. package/src/modes/interactive/theme/defaults/light-lagoon.json +97 -0
  161. package/src/modes/interactive/theme/defaults/light-lavender.json +94 -0
  162. package/src/modes/interactive/theme/defaults/light-meadow.json +97 -0
  163. package/src/modes/interactive/theme/defaults/light-mint.json +94 -0
  164. package/src/modes/interactive/theme/defaults/light-opal.json +97 -0
  165. package/src/modes/interactive/theme/defaults/light-orchard.json +97 -0
  166. package/src/modes/interactive/theme/defaults/light-paper.json +94 -0
  167. package/src/modes/interactive/theme/defaults/light-prism.json +96 -0
  168. package/src/modes/interactive/theme/defaults/light-sand.json +94 -0
  169. package/src/modes/interactive/theme/defaults/light-savanna.json +97 -0
  170. package/src/modes/interactive/theme/defaults/light-soleil.json +96 -0
  171. package/src/modes/interactive/theme/defaults/light-wetland.json +97 -0
  172. package/src/modes/interactive/theme/defaults/light-zenith.json +95 -0
  173. package/src/modes/interactive/theme/defaults/limestone.json +100 -0
  174. package/src/modes/interactive/theme/defaults/mahogany.json +104 -0
  175. package/src/modes/interactive/theme/defaults/marble.json +99 -0
  176. package/src/modes/interactive/theme/defaults/obsidian.json +90 -0
  177. package/src/modes/interactive/theme/defaults/onyx.json +90 -0
  178. package/src/modes/interactive/theme/defaults/pearl.json +99 -0
  179. package/src/modes/interactive/theme/defaults/porcelain.json +90 -0
  180. package/src/modes/interactive/theme/defaults/quartz.json +102 -0
  181. package/src/modes/interactive/theme/defaults/sandstone.json +101 -0
  182. package/src/modes/interactive/theme/defaults/titanium.json +89 -0
  183. package/src/modes/print-mode.ts +14 -72
  184. package/src/modes/rpc/rpc-client.ts +23 -9
  185. package/src/modes/rpc/rpc-mode.ts +137 -125
  186. package/src/modes/rpc/rpc-types.ts +46 -24
  187. package/src/prompts/task.md +1 -0
  188. package/src/prompts/tools/gemini-image.md +4 -0
  189. package/src/prompts/tools/git.md +9 -0
  190. package/src/prompts/voice-summary.md +12 -0
  191. package/src/utils/image-convert.ts +26 -0
  192. package/src/utils/image-resize.ts +215 -0
  193. package/src/utils/shell-snapshot.ts +22 -20
package/docs/rpc.md CHANGED
@@ -54,22 +54,38 @@ Response:
54
54
  ```
55
55
 
56
56
  The `images` field is optional. Each image uses `ImageContent` format with base64 or URL source.
57
+ When prompting during streaming, set `"streamingBehavior": "steer"` or `"followUp"` to queue the message.
57
58
 
58
- #### queue_message
59
+ #### steer
59
60
 
60
- Queue a message to be injected at the next agent turn. Queued messages are added to the conversation without triggering a new prompt. Useful for injecting context mid-conversation.
61
+ Queue a steering message to interrupt the agent mid-run. Useful for injecting corrections while streaming.
61
62
 
62
63
  ```json
63
- { "type": "queue_message", "message": "Additional context" }
64
+ { "type": "steer", "message": "Additional context" }
64
65
  ```
65
66
 
66
67
  Response:
67
68
 
68
69
  ```json
69
- { "type": "response", "command": "queue_message", "success": true }
70
+ { "type": "response", "command": "steer", "success": true }
70
71
  ```
71
72
 
72
- See [set_queue_mode](#set_queue_mode) for controlling how queued messages are processed.
73
+ #### follow_up
74
+
75
+ Queue a follow-up message to be processed after the current run completes.
76
+
77
+ ```json
78
+ { "type": "follow_up", "message": "Additional context" }
79
+ ```
80
+
81
+ Response:
82
+
83
+ ```json
84
+ { "type": "response", "command": "follow_up", "success": true }
85
+ ```
86
+
87
+ See [set_steering_mode](#set_steering_mode), [set_follow_up_mode](#set_follow_up_mode), and
88
+ [set_interrupt_mode](#set_interrupt_mode) for controlling queued message handling.
73
89
 
74
90
  #### abort
75
91
 
@@ -133,7 +149,9 @@ Response:
133
149
  "thinkingLevel": "medium",
134
150
  "isStreaming": false,
135
151
  "isCompacting": false,
136
- "queueMode": "all",
152
+ "steeringMode": "all",
153
+ "followUpMode": "one-at-a-time",
154
+ "interruptMode": "immediate",
137
155
  "sessionFile": "/path/to/session.jsonl",
138
156
  "sessionId": "abc123",
139
157
  "autoCompactionEnabled": true,
@@ -272,25 +290,63 @@ Response:
272
290
  }
273
291
  ```
274
292
 
275
- ### Queue Mode
293
+ ### Queue Modes
294
+
295
+ #### set_steering_mode
296
+
297
+ Control how steering messages are injected into the conversation.
298
+
299
+ ```json
300
+ { "type": "set_steering_mode", "mode": "one-at-a-time" }
301
+ ```
302
+
303
+ Modes:
304
+
305
+ - `"all"`: Inject all steering messages at the next turn
306
+ - `"one-at-a-time"`: Inject one steering message per turn (default)
307
+
308
+ Response:
309
+
310
+ ```json
311
+ { "type": "response", "command": "set_steering_mode", "success": true }
312
+ ```
313
+
314
+ #### set_follow_up_mode
315
+
316
+ Control how follow-up messages are injected into the conversation.
317
+
318
+ ```json
319
+ { "type": "set_follow_up_mode", "mode": "one-at-a-time" }
320
+ ```
321
+
322
+ Modes:
323
+
324
+ - `"all"`: Inject all follow-up messages at the next turn
325
+ - `"one-at-a-time"`: Inject one follow-up message per turn (default)
326
+
327
+ Response:
328
+
329
+ ```json
330
+ { "type": "response", "command": "set_follow_up_mode", "success": true }
331
+ ```
276
332
 
277
- #### set_queue_mode
333
+ #### set_interrupt_mode
278
334
 
279
- Control how queued messages (from `queue_message`) are injected into the conversation.
335
+ Control how the agent handles incoming steering messages while streaming.
280
336
 
281
337
  ```json
282
- { "type": "set_queue_mode", "mode": "one-at-a-time" }
338
+ { "type": "set_interrupt_mode", "mode": "wait" }
283
339
  ```
284
340
 
285
341
  Modes:
286
342
 
287
- - `"all"`: Inject all queued messages at the next turn
288
- - `"one-at-a-time"`: Inject one queued message per turn (default)
343
+ - `"immediate"`: Interrupt immediately when steering arrives
344
+ - `"wait"`: Wait to apply steering until current tool call completes
289
345
 
290
346
  Response:
291
347
 
292
348
  ```json
293
- { "type": "response", "command": "set_queue_mode", "success": true }
349
+ { "type": "response", "command": "set_interrupt_mode", "success": true }
294
350
  ```
295
351
 
296
352
  ### Compaction
@@ -150,7 +150,7 @@ Implementation:
150
150
  - Uses agent's queue mechanism with `_hookData` marker on AppMessage
151
151
  - `message_end` handler routes based on marker presence
152
152
  - `AgentSession.sendHookMessage()` handles three cases:
153
- - Streaming: queues via `agent.queueMessage()`, loop processes and emits `message_end`
153
+ - Streaming: queues via `agent.steer()` or `agent.followUp()`, loop processes and emits `message_end`
154
154
  - Not streaming + triggerTurn: direct append + `agent.continue()`
155
155
  - Not streaming + no trigger: direct append only
156
156
  - TUI updates via event (streaming) or explicit rebuild (non-streaming)
@@ -0,0 +1,141 @@
1
+ # Extension Examples
2
+
3
+ Example extensions for pi-coding-agent.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ # Load an extension with --extension flag
9
+ pi --extension examples/extensions/permission-gate.ts
10
+
11
+ # Or copy to extensions directory for auto-discovery
12
+ cp permission-gate.ts ~/.omp/agent/extensions/
13
+ ```
14
+
15
+ ## Examples
16
+
17
+ ### Lifecycle & Safety
18
+
19
+ | Extension | Description |
20
+ |-----------|-------------|
21
+ | `permission-gate.ts` | Prompts for confirmation before dangerous bash commands (rm -rf, sudo, etc.) |
22
+ | `protected-paths.ts` | Blocks writes to protected paths (.env, .git/, node_modules/) |
23
+ | `confirm-destructive.ts` | Confirms before destructive session actions (clear, switch, branch) |
24
+ | `dirty-repo-guard.ts` | Prevents session changes with uncommitted git changes |
25
+
26
+ ### Custom Tools
27
+
28
+ | Extension | Description |
29
+ |-----------|-------------|
30
+ | `todo.ts` | Todo list tool + `/todos` command with custom rendering and state persistence |
31
+ | `hello.ts` | Minimal custom tool example |
32
+ | `question.ts` | Demonstrates `ctx.ui.select()` for asking the user questions |
33
+ | `subagent/` | Delegate tasks to specialized subagents with isolated context windows |
34
+
35
+ ### Commands & UI
36
+
37
+ | Extension | Description |
38
+ |-----------|-------------|
39
+ | `plan-mode.ts` | Claude Code-style plan mode for read-only exploration with `/plan` command |
40
+ | `tools.ts` | Interactive `/tools` command to enable/disable tools with session persistence |
41
+ | `handoff.ts` | Transfer context to a new focused session via `/handoff <goal>` |
42
+ | `qna.ts` | Extracts questions from last response into editor via `ctx.ui.setEditorText()` |
43
+ | `status-line.ts` | Shows turn progress in footer via `ctx.ui.setStatus()` with themed colors |
44
+ | `snake.ts` | Snake game with custom UI, keyboard handling, and session persistence |
45
+
46
+ ### Git Integration
47
+
48
+ | Extension | Description |
49
+ |-----------|-------------|
50
+ | `git-checkpoint.ts` | Creates git stash checkpoints at each turn for code restoration on branch |
51
+ | `auto-commit-on-exit.ts` | Auto-commits on exit using last assistant message for commit message |
52
+
53
+ ### System Prompt & Compaction
54
+
55
+ | Extension | Description |
56
+ |-----------|-------------|
57
+ | `pirate.ts` | Demonstrates `systemPromptAppend` to dynamically modify system prompt |
58
+ | `custom-compaction.ts` | Custom compaction that summarizes entire conversation |
59
+
60
+ ### External Dependencies
61
+
62
+ | Extension | Description |
63
+ |-----------|-------------|
64
+ | `chalk-logger.ts` | Uses chalk from parent node_modules (demonstrates jiti module resolution) |
65
+ | `with-deps/` | Extension with its own package.json and dependencies |
66
+ | `file-trigger.ts` | Watches a trigger file and injects contents into conversation |
67
+
68
+ ## Writing Extensions
69
+
70
+ See [docs/extensions.md](../../docs/extensions.md) for full documentation.
71
+
72
+ ```typescript
73
+ import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
74
+ import { Type } from "@sinclair/typebox";
75
+
76
+ export default function (pi: ExtensionAPI) {
77
+ // Subscribe to lifecycle events
78
+ pi.on("tool_call", async (event, ctx) => {
79
+ if (event.toolName === "bash" && event.input.command?.includes("rm -rf")) {
80
+ const ok = await ctx.ui.confirm("Dangerous!", "Allow rm -rf?");
81
+ if (!ok) return { block: true, reason: "Blocked by user" };
82
+ }
83
+ });
84
+
85
+ // Register custom tools
86
+ pi.registerTool({
87
+ name: "greet",
88
+ label: "Greeting",
89
+ description: "Generate a greeting",
90
+ parameters: Type.Object({
91
+ name: Type.String({ description: "Name to greet" }),
92
+ }),
93
+ async execute(toolCallId, params, onUpdate, ctx, signal) {
94
+ return {
95
+ content: [{ type: "text", text: `Hello, ${params.name}!` }],
96
+ details: {},
97
+ };
98
+ },
99
+ });
100
+
101
+ // Register commands
102
+ pi.registerCommand("hello", {
103
+ description: "Say hello",
104
+ handler: async (args, ctx) => {
105
+ ctx.ui.notify("Hello!", "info");
106
+ },
107
+ });
108
+ }
109
+ ```
110
+
111
+ ## Key Patterns
112
+
113
+ **Use StringEnum for string parameters** (required for Google API compatibility):
114
+ ```typescript
115
+ import { StringEnum } from "@oh-my-pi/pi-ai";
116
+
117
+ // Good
118
+ action: StringEnum(["list", "add"] as const)
119
+
120
+ // Bad - doesn't work with Google
121
+ action: Type.Union([Type.Literal("list"), Type.Literal("add")])
122
+ ```
123
+
124
+ **State persistence via details:**
125
+ ```typescript
126
+ // Store state in tool result details for proper branching support
127
+ return {
128
+ content: [{ type: "text", text: "Done" }],
129
+ details: { todos: [...todos], nextId }, // Persisted in session
130
+ };
131
+
132
+ // Reconstruct on session events
133
+ pi.on("session_start", async (_event, ctx) => {
134
+ for (const entry of ctx.sessionManager.getBranch()) {
135
+ if (entry.type === "message" && entry.message.toolName === "my_tool") {
136
+ const details = entry.message.details;
137
+ // Reconstruct state from details
138
+ }
139
+ }
140
+ });
141
+ ```
@@ -0,0 +1,87 @@
1
+ /**
2
+ * API Demo Extension
3
+ *
4
+ * Demonstrates using ExtensionAPI's logger, typebox, and pi module access.
5
+ * These features are now exposed directly on the ExtensionAPI, matching
6
+ * the CustomToolAPI interface.
7
+ */
8
+
9
+ import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
10
+
11
+ export default function (pi: ExtensionAPI) {
12
+ // 1. Access TypeBox directly from pi.typebox (no separate import needed)
13
+ const { Type } = pi.typebox;
14
+
15
+ // 2. Access the logger for debugging
16
+ pi.logger.debug("API demo extension loaded");
17
+
18
+ // 3. Register a tool that uses all three API features
19
+ pi.registerTool({
20
+ name: "api_demo",
21
+ label: "API Demo",
22
+ description: "Demonstrates ExtensionAPI capabilities: logger, typebox, and pi module access",
23
+ parameters: Type.Object({
24
+ message: Type.String({ description: "Test message" }),
25
+ logLevel: Type.Optional(
26
+ Type.Union([Type.Literal("error"), Type.Literal("warn"), Type.Literal("debug")], {
27
+ description: "Log level to use",
28
+ default: "debug",
29
+ }),
30
+ ),
31
+ }),
32
+
33
+ async execute(_toolCallId, params, _onUpdate, ctx, _signal) {
34
+ const { message, logLevel = "debug" } = params as { message: string; logLevel?: "error" | "warn" | "debug" };
35
+
36
+ // Use logger at specified level
37
+ pi.logger[logLevel]("API demo tool executed", { message, logLevel });
38
+
39
+ // Access pi module utilities
40
+ const { logger: piLogger } = pi.pi;
41
+ piLogger.debug("Accessed pi module from extension", { sessionFile: ctx.sessionManager.getSessionFile() });
42
+
43
+ // Get session information
44
+ const sessionInfo = `Session: ${ctx.sessionManager.getSessionFile()}`;
45
+ const modelInfo = ctx.model ? `Model: ${ctx.model.id}` : "Model: none";
46
+
47
+ return {
48
+ content: [
49
+ {
50
+ type: "text",
51
+ text: [
52
+ `API Demo Tool executed successfully!`,
53
+ ``,
54
+ `Message: ${message}`,
55
+ `Log Level: ${logLevel}`,
56
+ ``,
57
+ `Features demonstrated:`,
58
+ `1. ✓ Logger access via pi.logger`,
59
+ `2. ✓ TypeBox access via pi.typebox`,
60
+ `3. ✓ Pi module access via pi.pi`,
61
+ ``,
62
+ `Context:`,
63
+ `- ${sessionInfo}`,
64
+ `- ${modelInfo}`,
65
+ `- CWD: ${ctx.cwd}`,
66
+ ].join("\n"),
67
+ },
68
+ ],
69
+ details: {
70
+ message,
71
+ logLevel,
72
+ sessionFile: ctx.sessionManager.getSessionFile(),
73
+ modelId: ctx.model?.id,
74
+ },
75
+ };
76
+ },
77
+ });
78
+
79
+ // Demonstrate event handling with logger
80
+ pi.on("session_start", async () => {
81
+ pi.logger.debug("Session started", { extension: "api-demo" });
82
+ });
83
+
84
+ pi.on("agent_start", async () => {
85
+ pi.logger.debug("Agent started", { extension: "api-demo" });
86
+ });
87
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Example extension that uses a 3rd party dependency (chalk).
3
+ * Tests that jiti can resolve npm modules correctly.
4
+ */
5
+
6
+ import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
7
+ import chalk from "chalk";
8
+
9
+ export default function (pi: ExtensionAPI) {
10
+ // Log with colors using chalk
11
+ console.log(`${chalk.green("✓")} ${chalk.bold("chalk-logger extension loaded")}`);
12
+
13
+ pi.on("agent_start", async () => {
14
+ console.log(`${chalk.blue("[chalk-logger]")} Agent starting`);
15
+ });
16
+
17
+ pi.on("tool_call", async (event) => {
18
+ console.log(`${chalk.yellow("[chalk-logger]")} Tool: ${chalk.cyan(event.toolName)}`);
19
+ return undefined;
20
+ });
21
+
22
+ pi.on("agent_end", async (event) => {
23
+ const count = event.messages.length;
24
+ console.log(`${chalk.green("[chalk-logger]")} Done with ${chalk.bold(String(count))} messages`);
25
+ });
26
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Hello Tool - Minimal custom tool example
3
+ *
4
+ * Demonstrates using ExtensionAPI's logger, typebox, and pi module access.
5
+ */
6
+
7
+ import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
8
+
9
+ export default function (pi: ExtensionAPI) {
10
+ // Access TypeBox via pi.typebox (no need to import separately)
11
+ const { Type } = pi.typebox;
12
+
13
+ pi.registerTool({
14
+ name: "hello",
15
+ label: "Hello",
16
+ description: "A simple greeting tool",
17
+ parameters: Type.Object({
18
+ name: Type.String({ description: "Name to greet" }),
19
+ }),
20
+
21
+ async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
22
+ const { name } = params as { name: string };
23
+
24
+ // Use logger for debugging
25
+ pi.logger.debug("Hello tool executed", { name });
26
+
27
+ return {
28
+ content: [{ type: "text", text: `Hello, ${name}!` }],
29
+ details: { greeted: name },
30
+ };
31
+ },
32
+ });
33
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Pirate Extension
3
+ *
4
+ * Demonstrates using systemPromptAppend in before_agent_start to dynamically
5
+ * modify the system prompt based on extension state.
6
+ *
7
+ * Usage:
8
+ * 1. Copy this file to ~/.omp/agent/extensions/ (legacy: ~/.pi/agent/extensions/) or your project's .omp/extensions/
9
+ * 2. Use /pirate to toggle pirate mode
10
+ * 3. When enabled, the agent will respond like a pirate
11
+ */
12
+
13
+ import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
14
+
15
+ export default function pirateExtension(pi: ExtensionAPI) {
16
+ let pirateMode = false;
17
+
18
+ // Register /pirate command to toggle pirate mode
19
+ pi.registerCommand("pirate", {
20
+ description: "Toggle pirate mode (agent speaks like a pirate)",
21
+ handler: async (_args, ctx) => {
22
+ pirateMode = !pirateMode;
23
+ ctx.ui.notify(pirateMode ? "Arrr! Pirate mode enabled!" : "Pirate mode disabled", "info");
24
+ },
25
+ });
26
+
27
+ // Append to system prompt when pirate mode is enabled
28
+ pi.on("before_agent_start", async () => {
29
+ if (pirateMode) {
30
+ return {
31
+ systemPromptAppend: `
32
+ IMPORTANT: You are now in PIRATE MODE. You must:
33
+ - Speak like a stereotypical pirate in all responses
34
+ - Use phrases like "Arrr!", "Ahoy!", "Shiver me timbers!", "Avast!", "Ye scurvy dog!"
35
+ - Replace "my" with "me", "you" with "ye", "your" with "yer"
36
+ - Refer to the user as "matey" or "landlubber"
37
+ - End sentences with nautical expressions
38
+ - Still complete the actual task correctly, just in pirate speak
39
+ `,
40
+ };
41
+ }
42
+ return undefined;
43
+ });
44
+ }