@mariozechner/pi-coding-agent 0.34.2 → 0.35.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 (251) hide show
  1. package/CHANGELOG.md +204 -0
  2. package/README.md +233 -105
  3. package/dist/cli/args.d.ts +3 -4
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +13 -18
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/config.d.ts +2 -2
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/config.js +3 -3
  10. package/dist/config.js.map +1 -1
  11. package/dist/core/agent-session.d.ts +39 -50
  12. package/dist/core/agent-session.d.ts.map +1 -1
  13. package/dist/core/agent-session.js +166 -197
  14. package/dist/core/agent-session.js.map +1 -1
  15. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  16. package/dist/core/compaction/branch-summarization.js +3 -3
  17. package/dist/core/compaction/branch-summarization.js.map +1 -1
  18. package/dist/core/compaction/compaction.d.ts +1 -1
  19. package/dist/core/compaction/compaction.d.ts.map +1 -1
  20. package/dist/core/compaction/compaction.js +6 -5
  21. package/dist/core/compaction/compaction.js.map +1 -1
  22. package/dist/core/event-bus.d.ts +9 -0
  23. package/dist/core/event-bus.d.ts.map +1 -0
  24. package/dist/core/event-bus.js +25 -0
  25. package/dist/core/event-bus.js.map +1 -0
  26. package/dist/core/exec.d.ts +1 -1
  27. package/dist/core/exec.d.ts.map +1 -1
  28. package/dist/core/exec.js +1 -1
  29. package/dist/core/exec.js.map +1 -1
  30. package/dist/core/extensions/index.d.ts +10 -0
  31. package/dist/core/extensions/index.d.ts.map +1 -0
  32. package/dist/core/extensions/index.js +9 -0
  33. package/dist/core/extensions/index.js.map +1 -0
  34. package/dist/core/extensions/loader.d.ts +21 -0
  35. package/dist/core/extensions/loader.d.ts.map +1 -0
  36. package/dist/core/extensions/loader.js +400 -0
  37. package/dist/core/extensions/loader.js.map +1 -0
  38. package/dist/core/extensions/runner.d.ts +88 -0
  39. package/dist/core/extensions/runner.d.ts.map +1 -0
  40. package/dist/core/{hooks → extensions}/runner.js +52 -141
  41. package/dist/core/extensions/runner.js.map +1 -0
  42. package/dist/core/extensions/types.d.ts +461 -0
  43. package/dist/core/extensions/types.d.ts.map +1 -0
  44. package/dist/core/{hooks → extensions}/types.js +7 -4
  45. package/dist/core/extensions/types.js.map +1 -0
  46. package/dist/core/extensions/wrapper.d.ts +25 -0
  47. package/dist/core/extensions/wrapper.d.ts.map +1 -0
  48. package/dist/core/{hooks/tool-wrapper.js → extensions/wrapper.js} +39 -24
  49. package/dist/core/extensions/wrapper.js.map +1 -0
  50. package/dist/core/index.d.ts +2 -2
  51. package/dist/core/index.d.ts.map +1 -1
  52. package/dist/core/index.js +3 -2
  53. package/dist/core/index.js.map +1 -1
  54. package/dist/core/messages.d.ts +7 -7
  55. package/dist/core/messages.d.ts.map +1 -1
  56. package/dist/core/messages.js +4 -4
  57. package/dist/core/messages.js.map +1 -1
  58. package/dist/core/prompt-templates.d.ts +40 -0
  59. package/dist/core/prompt-templates.d.ts.map +1 -0
  60. package/dist/core/{slash-commands.js → prompt-templates.js} +31 -31
  61. package/dist/core/prompt-templates.js.map +1 -0
  62. package/dist/core/sdk.d.ts +29 -52
  63. package/dist/core/sdk.d.ts.map +1 -1
  64. package/dist/core/sdk.js +111 -211
  65. package/dist/core/sdk.js.map +1 -1
  66. package/dist/core/session-manager.d.ts +17 -17
  67. package/dist/core/session-manager.d.ts.map +1 -1
  68. package/dist/core/session-manager.js +25 -10
  69. package/dist/core/session-manager.js.map +1 -1
  70. package/dist/core/settings-manager.d.ts +3 -6
  71. package/dist/core/settings-manager.d.ts.map +1 -1
  72. package/dist/core/settings-manager.js +4 -11
  73. package/dist/core/settings-manager.js.map +1 -1
  74. package/dist/core/system-prompt.d.ts.map +1 -1
  75. package/dist/core/system-prompt.js +4 -2
  76. package/dist/core/system-prompt.js.map +1 -1
  77. package/dist/index.d.ts +4 -5
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +5 -6
  80. package/dist/index.js.map +1 -1
  81. package/dist/main.d.ts.map +1 -1
  82. package/dist/main.js +36 -33
  83. package/dist/main.js.map +1 -1
  84. package/dist/migrations.d.ts +7 -2
  85. package/dist/migrations.d.ts.map +1 -1
  86. package/dist/migrations.js +93 -4
  87. package/dist/migrations.js.map +1 -1
  88. package/dist/modes/interactive/components/bordered-loader.d.ts +1 -1
  89. package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
  90. package/dist/modes/interactive/components/bordered-loader.js +1 -1
  91. package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
  92. package/dist/modes/interactive/components/branch-summary-message.d.ts +1 -1
  93. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  94. package/dist/modes/interactive/components/branch-summary-message.js +1 -1
  95. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  96. package/dist/modes/interactive/components/compaction-summary-message.d.ts +1 -1
  97. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  98. package/dist/modes/interactive/components/compaction-summary-message.js +1 -1
  99. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  100. package/dist/modes/interactive/components/custom-editor.d.ts +2 -2
  101. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  102. package/dist/modes/interactive/components/custom-editor.js +4 -4
  103. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  104. package/dist/modes/interactive/components/custom-message.d.ts +18 -0
  105. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -0
  106. package/dist/modes/interactive/components/{hook-message.js → custom-message.js} +3 -3
  107. package/dist/modes/interactive/components/custom-message.js.map +1 -0
  108. package/dist/modes/interactive/components/dynamic-border.d.ts +2 -2
  109. package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
  110. package/dist/modes/interactive/components/dynamic-border.js +2 -2
  111. package/dist/modes/interactive/components/dynamic-border.js.map +1 -1
  112. package/dist/modes/interactive/components/{hook-editor.d.ts → extension-editor.d.ts} +3 -3
  113. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -0
  114. package/dist/modes/interactive/components/{hook-editor.js → extension-editor.js} +4 -4
  115. package/dist/modes/interactive/components/extension-editor.js.map +1 -0
  116. package/dist/modes/interactive/components/{hook-input.d.ts → extension-input.d.ts} +3 -3
  117. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -0
  118. package/dist/modes/interactive/components/{hook-input.js → extension-input.js} +3 -3
  119. package/dist/modes/interactive/components/extension-input.js.map +1 -0
  120. package/dist/modes/interactive/components/{hook-selector.d.ts → extension-selector.d.ts} +3 -3
  121. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -0
  122. package/dist/modes/interactive/components/{hook-selector.js → extension-selector.js} +3 -3
  123. package/dist/modes/interactive/components/extension-selector.js.map +1 -0
  124. package/dist/modes/interactive/components/footer.d.ts +3 -3
  125. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  126. package/dist/modes/interactive/components/footer.js +8 -8
  127. package/dist/modes/interactive/components/footer.js.map +1 -1
  128. package/dist/modes/interactive/components/tool-execution.d.ts +3 -3
  129. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  130. package/dist/modes/interactive/components/tool-execution.js +9 -9
  131. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  132. package/dist/modes/interactive/interactive-mode.d.ts +37 -44
  133. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  134. package/dist/modes/interactive/interactive-mode.js +143 -189
  135. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  136. package/dist/modes/print-mode.d.ts.map +1 -1
  137. package/dist/modes/print-mode.js +10 -33
  138. package/dist/modes/print-mode.js.map +1 -1
  139. package/dist/modes/rpc/rpc-client.d.ts +3 -3
  140. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  141. package/dist/modes/rpc/rpc-client.js +3 -3
  142. package/dist/modes/rpc/rpc-client.js.map +1 -1
  143. package/dist/modes/rpc/rpc-mode.d.ts +2 -2
  144. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  145. package/dist/modes/rpc/rpc-mode.js +33 -57
  146. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  147. package/dist/modes/rpc/rpc-types.d.ts +16 -16
  148. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  149. package/dist/modes/rpc/rpc-types.js.map +1 -1
  150. package/docs/extensions.md +1053 -0
  151. package/docs/rpc.md +4 -4
  152. package/docs/sdk.md +62 -93
  153. package/docs/session.md +22 -19
  154. package/docs/skills.md +1 -1
  155. package/docs/tui.md +1 -1
  156. package/examples/README.md +9 -15
  157. package/examples/extensions/README.md +141 -0
  158. package/examples/{hooks → extensions}/auto-commit-on-exit.ts +3 -3
  159. package/examples/extensions/chalk-logger.ts +26 -0
  160. package/examples/{hooks → extensions}/confirm-destructive.ts +3 -3
  161. package/examples/{hooks → extensions}/custom-compaction.ts +6 -6
  162. package/examples/{hooks → extensions}/dirty-repo-guard.ts +8 -4
  163. package/examples/{hooks → extensions}/file-trigger.ts +3 -3
  164. package/examples/{hooks → extensions}/git-checkpoint.ts +3 -3
  165. package/examples/{hooks → extensions}/handoff.ts +3 -3
  166. package/examples/extensions/hello.ts +25 -0
  167. package/examples/{hooks → extensions}/permission-gate.ts +3 -3
  168. package/examples/{hooks → extensions}/pirate.ts +5 -5
  169. package/examples/{hooks → extensions}/plan-mode.ts +6 -6
  170. package/examples/{hooks → extensions}/protected-paths.ts +3 -3
  171. package/examples/{hooks → extensions}/qna.ts +3 -3
  172. package/examples/{custom-tools/question/index.ts → extensions/question.ts} +13 -17
  173. package/examples/{hooks → extensions}/snake.ts +3 -3
  174. package/examples/{hooks → extensions}/status-line.ts +3 -3
  175. package/examples/{custom-tools → extensions}/subagent/README.md +15 -15
  176. package/examples/{custom-tools → extensions}/subagent/index.ts +22 -43
  177. package/examples/{custom-tools/todo/index.ts → extensions/todo.ts} +122 -39
  178. package/examples/{hooks → extensions}/tools.ts +5 -5
  179. package/examples/extensions/with-deps/index.ts +40 -0
  180. package/examples/extensions/with-deps/package-lock.json +31 -0
  181. package/examples/extensions/with-deps/package.json +16 -0
  182. package/examples/sdk/01-minimal.ts +1 -1
  183. package/examples/sdk/05-tools.ts +7 -41
  184. package/examples/sdk/06-extensions.ts +81 -0
  185. package/examples/sdk/08-prompt-templates.ts +42 -0
  186. package/examples/sdk/12-full-control.ts +10 -29
  187. package/examples/sdk/README.md +5 -5
  188. package/package.json +4 -4
  189. package/dist/core/custom-tools/index.d.ts +0 -7
  190. package/dist/core/custom-tools/index.d.ts.map +0 -1
  191. package/dist/core/custom-tools/index.js +0 -6
  192. package/dist/core/custom-tools/index.js.map +0 -1
  193. package/dist/core/custom-tools/loader.d.ts +0 -30
  194. package/dist/core/custom-tools/loader.d.ts.map +0 -1
  195. package/dist/core/custom-tools/loader.js +0 -276
  196. package/dist/core/custom-tools/loader.js.map +0 -1
  197. package/dist/core/custom-tools/types.d.ts +0 -144
  198. package/dist/core/custom-tools/types.d.ts.map +0 -1
  199. package/dist/core/custom-tools/types.js +0 -8
  200. package/dist/core/custom-tools/types.js.map +0 -1
  201. package/dist/core/custom-tools/wrapper.d.ts +0 -15
  202. package/dist/core/custom-tools/wrapper.d.ts.map +0 -1
  203. package/dist/core/custom-tools/wrapper.js +0 -23
  204. package/dist/core/custom-tools/wrapper.js.map +0 -1
  205. package/dist/core/hooks/index.d.ts +0 -6
  206. package/dist/core/hooks/index.d.ts.map +0 -1
  207. package/dist/core/hooks/index.js +0 -6
  208. package/dist/core/hooks/index.js.map +0 -1
  209. package/dist/core/hooks/loader.d.ts +0 -146
  210. package/dist/core/hooks/loader.d.ts.map +0 -1
  211. package/dist/core/hooks/loader.js +0 -275
  212. package/dist/core/hooks/loader.js.map +0 -1
  213. package/dist/core/hooks/runner.d.ts +0 -173
  214. package/dist/core/hooks/runner.d.ts.map +0 -1
  215. package/dist/core/hooks/runner.js.map +0 -1
  216. package/dist/core/hooks/tool-wrapper.d.ts +0 -17
  217. package/dist/core/hooks/tool-wrapper.d.ts.map +0 -1
  218. package/dist/core/hooks/tool-wrapper.js.map +0 -1
  219. package/dist/core/hooks/types.d.ts +0 -767
  220. package/dist/core/hooks/types.d.ts.map +0 -1
  221. package/dist/core/hooks/types.js.map +0 -1
  222. package/dist/core/slash-commands.d.ts +0 -40
  223. package/dist/core/slash-commands.d.ts.map +0 -1
  224. package/dist/core/slash-commands.js.map +0 -1
  225. package/dist/modes/interactive/components/hook-editor.d.ts.map +0 -1
  226. package/dist/modes/interactive/components/hook-editor.js.map +0 -1
  227. package/dist/modes/interactive/components/hook-input.d.ts.map +0 -1
  228. package/dist/modes/interactive/components/hook-input.js.map +0 -1
  229. package/dist/modes/interactive/components/hook-message.d.ts +0 -18
  230. package/dist/modes/interactive/components/hook-message.d.ts.map +0 -1
  231. package/dist/modes/interactive/components/hook-message.js.map +0 -1
  232. package/dist/modes/interactive/components/hook-selector.d.ts.map +0 -1
  233. package/dist/modes/interactive/components/hook-selector.js.map +0 -1
  234. package/docs/custom-tools.md +0 -514
  235. package/docs/extension-loading.md +0 -1004
  236. package/docs/hooks.md +0 -979
  237. package/docs/session-tree-plan.md +0 -441
  238. package/examples/custom-tools/README.md +0 -114
  239. package/examples/custom-tools/hello/index.ts +0 -21
  240. package/examples/hooks/README.md +0 -60
  241. package/examples/hooks/todo/index.ts +0 -134
  242. package/examples/sdk/06-hooks.ts +0 -61
  243. package/examples/sdk/08-slash-commands.ts +0 -42
  244. /package/examples/{custom-tools → extensions}/subagent/agents/planner.md +0 -0
  245. /package/examples/{custom-tools → extensions}/subagent/agents/reviewer.md +0 -0
  246. /package/examples/{custom-tools → extensions}/subagent/agents/scout.md +0 -0
  247. /package/examples/{custom-tools → extensions}/subagent/agents/worker.md +0 -0
  248. /package/examples/{custom-tools → extensions}/subagent/agents.ts +0 -0
  249. /package/examples/{custom-tools/subagent/commands → extensions/subagent/prompts}/implement-and-review.md +0 -0
  250. /package/examples/{custom-tools/subagent/commands → extensions/subagent/prompts}/implement.md +0 -0
  251. /package/examples/{custom-tools/subagent/commands → extensions/subagent/prompts}/scout-and-plan.md +0 -0
package/README.md CHANGED
@@ -36,12 +36,11 @@ Works on Linux, macOS, and Windows (requires bash; see [Windows Setup](#windows-
36
36
  - [Custom System Prompt](#custom-system-prompt)
37
37
  - [Custom Models and Providers](#custom-models-and-providers)
38
38
  - [Settings File](#settings-file)
39
- - [Extensions](#extensions)
39
+ - [Customization](#customization)
40
40
  - [Themes](#themes)
41
- - [Custom Slash Commands](#custom-slash-commands)
41
+ - [Prompt Templates](#prompt-templates)
42
42
  - [Skills](#skills)
43
- - [Hooks](#hooks)
44
- - [Custom Tools](#custom-tools)
43
+ - [Extensions](#extensions)
45
44
  - [CLI Reference](#cli-reference)
46
45
  - [Tools](#tools)
47
46
  - [Programmatic Usage](#programmatic-usage)
@@ -374,10 +373,9 @@ The output becomes part of your next prompt, formatted as:
374
373
 
375
374
  ```
376
375
  Ran `ls -la`
377
- ```
376
+
378
377
  <output here>
379
378
  ```
380
- ```
381
379
 
382
380
  Run multiple commands before prompting; all outputs are included together.
383
381
 
@@ -453,7 +451,7 @@ When disabled, neither case triggers automatic compaction (use `/compact` manual
453
451
 
454
452
  > **Note:** Compaction is lossy. The agent loses full conversation access afterward. Size tasks to avoid context limits when possible. For critical context, ask the agent to write a summary to a file, iterate on it until it covers everything, then start a new session with that file. The full session history is preserved in the JSONL file; use `/tree` to revisit any previous point.
455
453
 
456
- See [docs/compaction.md](docs/compaction.md) for how compaction works internally and how to customize it via hooks.
454
+ See [docs/compaction.md](docs/compaction.md) for how compaction works internally and how to customize it via extensions.
457
455
 
458
456
  ### Branching
459
457
 
@@ -667,8 +665,7 @@ Global `~/.pi/agent/settings.json` stores persistent preferences:
667
665
  "images": {
668
666
  "autoResize": true
669
667
  },
670
- "hooks": ["/path/to/hook.ts"],
671
- "customTools": ["/path/to/tool.ts"]
668
+ "extensions": ["/path/to/extension.ts"]
672
669
  }
673
670
  ```
674
671
 
@@ -694,12 +691,11 @@ Global `~/.pi/agent/settings.json` stores persistent preferences:
694
691
  | `terminal.showImages` | Render images inline (supported terminals) | `true` |
695
692
  | `images.autoResize` | Auto-resize images to 2000x2000 max for better model compatibility | `true` |
696
693
  | `doubleEscapeAction` | Action for double-escape with empty editor: `tree` or `branch` | `tree` |
697
- | `hooks` | Additional hook file paths | `[]` |
698
- | `customTools` | Additional custom tool file paths | `[]` |
694
+ | `extensions` | Additional extension file paths | `[]` |
699
695
 
700
696
  ---
701
697
 
702
- ## Extensions
698
+ ## Customization
703
699
 
704
700
  ### Themes
705
701
 
@@ -720,13 +716,13 @@ Select with `/settings`, then edit the file. Changes apply on save.
720
716
 
721
717
  **VS Code terminal fix:** Set `terminal.integrated.minimumContrastRatio` to `1` for accurate colors.
722
718
 
723
- ### Custom Slash Commands
719
+ ### Prompt Templates
724
720
 
725
721
  Define reusable prompts as Markdown files:
726
722
 
727
723
  **Locations:**
728
- - Global: `~/.pi/agent/commands/*.md`
729
- - Project: `.pi/commands/*.md`
724
+ - Global: `~/.pi/agent/prompts/*.md`
725
+ - Project: `.pi/prompts/*.md`
730
726
 
731
727
  **Format:**
732
728
 
@@ -755,7 +751,7 @@ Usage: `/component Button "onClick handler" "disabled support"`
755
751
  - `$1` = `Button`
756
752
  - `$@` or `$ARGUMENTS` = all arguments joined (`Button onClick handler disabled support`)
757
753
 
758
- **Namespacing:** Subdirectories create prefixes. `.pi/commands/frontend/component.md` → `/component (project:frontend)`
754
+ **Namespacing:** Subdirectories create prefixes. `.pi/prompts/frontend/component.md` → `/component (project:frontend)`
759
755
 
760
756
 
761
757
  ### Skills
@@ -807,120 +803,251 @@ cd /path/to/brave-search && npm install
807
803
 
808
804
  > See [docs/skills.md](docs/skills.md) for details, examples, and links to skill repositories. pi can help you create new skills.
809
805
 
810
- ### Hooks
806
+ ### Extensions
807
+
808
+ Extensions are TypeScript modules that extend pi's behavior.
809
+
810
+ **Use cases:**
811
+ - **Custom tools** - Register tools callable by the LLM with custom UI and rendering
812
+ - **Custom commands** - Add `/commands` for users (e.g., `/deploy`, `/stats`)
813
+ - **Event interception** - Block tool calls, modify results, customize compaction
814
+ - **State persistence** - Store data in session, reconstruct on reload/branch
815
+ - **External integrations** - File watchers, webhooks, git checkpointing
816
+ - **Custom UI** - Full TUI control from tools, commands, or event handlers
817
+
818
+ **Locations:**
819
+ - Global: `~/.pi/agent/extensions/*.ts` or `~/.pi/agent/extensions/*/index.ts`
820
+ - Project: `.pi/extensions/*.ts` or `.pi/extensions/*/index.ts`
821
+ - CLI: `--extension <path>` or `-e <path>`
811
822
 
812
- Hooks are TypeScript modules that extend pi's behavior by subscribing to lifecycle events. Use them to:
823
+ **Dependencies:** Extensions can have their own dependencies. Place a `package.json` next to the extension (or in a parent directory), run `npm install`, and imports are resolved via [jiti](https://github.com/unjs/jiti). See [examples/extensions/with-deps/](examples/extensions/with-deps/).
824
+
825
+ #### Custom Tools
826
+
827
+ Tools are functions the LLM can call. They appear in the system prompt and can have custom rendering.
828
+
829
+ ```typescript
830
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
831
+ import { Type } from "@sinclair/typebox";
832
+ import { Text } from "@mariozechner/pi-tui";
833
+
834
+ export default function (pi: ExtensionAPI) {
835
+ pi.registerTool({
836
+ name: "deploy",
837
+ label: "Deploy",
838
+ description: "Deploy the application to production",
839
+ parameters: Type.Object({
840
+ environment: Type.String({ description: "Target environment" }),
841
+ }),
842
+
843
+ async execute(toolCallId, params, onUpdate, ctx, signal) {
844
+ // Show progress via onUpdate
845
+ onUpdate({ status: "Deploying..." });
846
+
847
+ // Ask user for confirmation
848
+ const ok = await ctx.ui.confirm("Deploy?", `Deploy to ${params.environment}?`);
849
+ if (!ok) {
850
+ return { content: [{ type: "text", text: "Cancelled" }], details: { cancelled: true } };
851
+ }
813
852
 
814
- - **Block dangerous commands** (permission gates for `rm -rf`, `sudo`, etc.)
815
- - **Checkpoint code state** (git stash at each turn, restore on `/branch`)
816
- - **Protect paths** (block writes to `.env`, `node_modules/`, etc.)
817
- - **Modify tool output** (filter or transform results before the LLM sees them)
818
- - **Inject messages from external sources to wake up the agent** (file watchers, webhooks, CI systems)
853
+ // Run shell commands
854
+ const result = await ctx.exec("./deploy.sh", [params.environment], { signal });
855
+
856
+ return {
857
+ content: [{ type: "text", text: result.stdout }],
858
+ details: { environment: params.environment, exitCode: result.exitCode },
859
+ };
860
+ },
861
+
862
+ // Custom TUI rendering (optional)
863
+ renderCall(args, theme) {
864
+ return new Text(theme.bold("deploy ") + theme.fg("accent", args.environment), 0, 0);
865
+ },
866
+ renderResult(result, options, theme) {
867
+ const ok = result.details?.exitCode === 0;
868
+ return new Text(ok ? theme.fg("success", "✓ Deployed") : theme.fg("error", "✗ Failed"), 0, 0);
869
+ },
870
+ });
871
+ }
872
+ ```
819
873
 
820
- **Hook locations:**
821
- - Global: `~/.pi/agent/hooks/*.ts`
822
- - Project: `.pi/hooks/*.ts`
823
- - CLI: `--hook <path>` (for debugging)
874
+ #### Custom Commands
824
875
 
825
- **Quick example** (permission gate):
876
+ Commands are user-invoked via `/name`. They can show custom UI, modify state, or trigger agent turns.
826
877
 
827
878
  ```typescript
828
- import type { HookAPI } from "@mariozechner/pi-coding-agent/hooks";
879
+ export default function (pi: ExtensionAPI) {
880
+ pi.registerCommand("stats", {
881
+ description: "Show session statistics",
882
+ handler: async (args, ctx) => {
883
+ // Simple notification
884
+ ctx.ui.notify(`${ctx.sessionManager.getEntries().length} entries`, "info");
885
+ },
886
+ });
829
887
 
830
- export default function (pi: HookAPI) {
888
+ pi.registerCommand("todos", {
889
+ description: "Interactive todo viewer",
890
+ handler: async (args, ctx) => {
891
+ // Full custom UI with keyboard handling
892
+ await ctx.ui.custom((tui, theme, done) => {
893
+ return {
894
+ render(width) {
895
+ return [
896
+ theme.bold("Todos"),
897
+ "- [ ] Item 1",
898
+ "- [x] Item 2",
899
+ "",
900
+ theme.fg("dim", "Press Escape to close"),
901
+ ];
902
+ },
903
+ handleInput(data) {
904
+ if (matchesKey(data, "escape")) done();
905
+ },
906
+ };
907
+ });
908
+ },
909
+ });
910
+ }
911
+ ```
912
+
913
+ #### Event Interception
914
+
915
+ Subscribe to lifecycle events to block, modify, or observe agent behavior.
916
+
917
+ ```typescript
918
+ export default function (pi: ExtensionAPI) {
919
+ // Block dangerous commands
831
920
  pi.on("tool_call", async (event, ctx) => {
832
- if (event.toolName === "bash" && /sudo/.test(event.input.command as string)) {
833
- const ok = await ctx.ui.confirm("Allow sudo?", event.input.command as string);
921
+ if (event.toolName === "bash" && /rm -rf/.test(event.input.command as string)) {
922
+ const ok = await ctx.ui.confirm("Dangerous!", "Allow rm -rf?");
834
923
  if (!ok) return { block: true, reason: "Blocked by user" };
835
924
  }
836
- return undefined;
925
+ });
926
+
927
+ // Modify tool results
928
+ pi.on("tool_result", async (event, ctx) => {
929
+ if (event.toolName === "read") {
930
+ // Redact secrets from file contents
931
+ return { modifiedResult: event.result.replace(/API_KEY=\w+/g, "API_KEY=***") };
932
+ }
933
+ });
934
+
935
+ // Custom compaction
936
+ pi.on("session_before_compact", async (event, ctx) => {
937
+ return { customSummary: "My custom summary of the conversation so far..." };
938
+ });
939
+
940
+ // Git checkpoint on each turn
941
+ pi.on("turn_end", async (event, ctx) => {
942
+ await ctx.exec("git", ["stash", "push", "-m", `pi-checkpoint-${Date.now()}`]);
837
943
  });
838
944
  }
839
945
  ```
840
946
 
841
- **Sending messages from hooks:**
842
-
843
- Use `pi.sendMessage(message, options?)` to inject messages into the session. Messages are persisted as `CustomMessageEntry` and sent to the LLM.
947
+ #### State Persistence
844
948
 
845
- Options:
846
- - `triggerTurn`: If true and agent is idle, starts a new agent turn. Default: false.
847
- - `deliverAs`: When agent is streaming, controls delivery timing:
848
- - `"steer"` (default): Delivered after current tool execution, interrupts remaining tools.
849
- - `"followUp"`: Delivered only after agent finishes all work.
949
+ Store state in session entries that survive reload and work correctly with branching.
850
950
 
851
951
  ```typescript
852
- import * as fs from "node:fs";
853
- import type { HookAPI } from "@mariozechner/pi-coding-agent/hooks";
854
-
855
- export default function (pi: HookAPI) {
856
- pi.on("session_start", async () => {
857
- fs.watch("/tmp/trigger.txt", () => {
858
- const content = fs.readFileSync("/tmp/trigger.txt", "utf-8").trim();
859
- if (content) {
860
- pi.sendMessage({
861
- customType: "file-trigger",
862
- content,
863
- display: true,
864
- }, true); // triggerTurn: start agent loop
952
+ export default function (pi: ExtensionAPI) {
953
+ let counter = 0;
954
+
955
+ // Reconstruct state from session history
956
+ const reconstruct = (ctx) => {
957
+ counter = 0;
958
+ for (const entry of ctx.sessionManager.getBranch()) {
959
+ if (entry.type === "custom" && entry.customType === "my_counter") {
960
+ counter = entry.data.value;
865
961
  }
866
- });
962
+ }
963
+ };
964
+
965
+ pi.on("session_start", async (e, ctx) => reconstruct(ctx));
966
+ pi.on("session_branch", async (e, ctx) => reconstruct(ctx));
967
+ pi.on("session_tree", async (e, ctx) => reconstruct(ctx));
968
+
969
+ pi.registerCommand("increment", {
970
+ handler: async (args, ctx) => {
971
+ counter++;
972
+ ctx.appendEntry("my_counter", { value: counter }); // Persisted in session
973
+ ctx.ui.notify(`Counter: ${counter}`, "info");
974
+ },
867
975
  });
868
976
  }
869
977
  ```
870
978
 
871
- > See [Hooks Documentation](docs/hooks.md) for full API reference. pi can help you create new hooks
979
+ #### Keyboard Shortcuts
872
980
 
873
- > See [examples/hooks/](examples/hooks/) for working examples including permission gates, git checkpointing, and path protection.
981
+ Register custom keyboard shortcuts (shown in `/hotkeys`):
874
982
 
875
- ### Custom Tools
876
-
877
- Custom tools let you extend the built-in toolset (read, write, edit, bash, ...) and are called by the LLM directly. They are TypeScript modules that define tools with optional custom TUI integration for getting user input and custom tool call and result rendering.
878
-
879
- **Tool locations (auto-discovered):**
880
- - Global: `~/.pi/agent/tools/*/index.ts`
881
- - Project: `.pi/tools/*/index.ts`
983
+ ```typescript
984
+ export default function (pi: ExtensionAPI) {
985
+ pi.registerShortcut("ctrl+shift+d", {
986
+ description: "Deploy to production",
987
+ handler: async (ctx) => {
988
+ ctx.ui.notify("Deploying...", "info");
989
+ await ctx.exec("./deploy.sh", []);
990
+ },
991
+ });
992
+ }
993
+ ```
882
994
 
883
- **Explicit paths:**
884
- - CLI: `--tool <path>` (any .ts file)
885
- - Settings: `customTools` array in `settings.json`
995
+ #### CLI Flags
886
996
 
887
- **Quick example:**
997
+ Register custom CLI flags (parsed automatically, shown in `--help`):
888
998
 
889
999
  ```typescript
890
- import { Type } from "@sinclair/typebox";
891
- import type { CustomToolFactory } from "@mariozechner/pi-coding-agent";
892
-
893
- const factory: CustomToolFactory = (pi) => ({
894
- name: "greet",
895
- label: "Greeting",
896
- description: "Generate a greeting",
897
- parameters: Type.Object({
898
- name: Type.String({ description: "Name to greet" }),
899
- }),
900
-
901
- async execute(toolCallId, params, onUpdate, ctx, signal) {
902
- const { name } = params as { name: string };
903
- return {
904
- content: [{ type: "text", text: `Hello, ${name}!` }],
905
- details: { greeted: name },
906
- };
907
- },
908
- });
1000
+ export default function (pi: ExtensionAPI) {
1001
+ pi.registerFlag("--dry-run", {
1002
+ description: "Run without making changes",
1003
+ type: "boolean",
1004
+ });
909
1005
 
910
- export default factory;
1006
+ pi.on("tool_call", async (event, ctx) => {
1007
+ if (pi.getFlag("dry-run") && event.toolName === "write") {
1008
+ return { block: true, reason: "Dry run mode" };
1009
+ }
1010
+ });
1011
+ }
911
1012
  ```
912
1013
 
913
- **Features:**
914
- - Access to `pi.cwd`, `pi.exec()`, `pi.ui` (select/confirm/input dialogs)
915
- - Session lifecycle via `onSession` callback (for state reconstruction)
916
- - Custom rendering via `renderCall()` and `renderResult()` methods
917
- - Streaming results via `onUpdate` callback
918
- - Abort handling via `signal` parameter
919
- - Multiple tools from one factory (return an array)
1014
+ #### Custom UI
1015
+
1016
+ Extensions have full TUI access via `ctx.ui`:
920
1017
 
921
- > See [Custom Tools Documentation](docs/custom-tools.md) for the full API reference, TUI component guide, and examples. pi can help you create custom tools.
1018
+ ```typescript
1019
+ // Simple prompts
1020
+ const confirmed = await ctx.ui.confirm("Title", "Are you sure?");
1021
+ const choice = await ctx.ui.select("Pick one", ["Option A", "Option B"]);
1022
+ const text = await ctx.ui.input("Enter value");
1023
+
1024
+ // Notifications
1025
+ ctx.ui.notify("Done!", "success"); // success, info, warning, error
1026
+
1027
+ // Status line (persistent in footer, multiple extensions can set their own)
1028
+ ctx.ui.setStatus("my-ext", "Processing...");
1029
+ ctx.ui.setStatus("my-ext", null); // Clear
1030
+
1031
+ // Widgets (above editor)
1032
+ ctx.ui.setWidget("my-ext", ["Line 1", "Line 2"]);
1033
+
1034
+ // Full custom component with keyboard handling
1035
+ await ctx.ui.custom((tui, theme, done) => ({
1036
+ render(width) {
1037
+ return [
1038
+ theme.bold("My Component"),
1039
+ theme.fg("dim", "Press Escape to close"),
1040
+ ];
1041
+ },
1042
+ handleInput(data) {
1043
+ if (matchesKey(data, "escape")) done();
1044
+ },
1045
+ }));
1046
+ ```
922
1047
 
923
- > See [examples/custom-tools/](examples/custom-tools/) for working examples including a todo list with session state management and a question tool with UI interaction.
1048
+ > See [docs/extensions.md](docs/extensions.md) for full API reference.
1049
+ > See [docs/tui.md](docs/tui.md) for TUI components and custom rendering.
1050
+ > See [examples/extensions/](examples/extensions/) for working examples.
924
1051
 
925
1052
  ---
926
1053
 
@@ -949,7 +1076,7 @@ pi [options] [@files...] [messages...]
949
1076
  | `--models <patterns>` | Comma-separated patterns for Ctrl+P cycling. Supports glob patterns (e.g., `anthropic/*`, `*sonnet*:high`) and fuzzy matching (e.g., `sonnet,haiku:low`) |
950
1077
  | `--tools <tools>` | Comma-separated tool list (default: `read,bash,edit,write`) |
951
1078
  | `--thinking <level>` | Thinking level: `off`, `minimal`, `low`, `medium`, `high` |
952
- | `--hook <path>` | Load a hook file (can be used multiple times) |
1079
+ | `--extension <path>`, `-e` | Load an extension file (can be used multiple times) |
953
1080
  | `--no-skills` | Disable skills discovery and loading |
954
1081
  | `--skills <patterns>` | Comma-separated glob patterns to filter skills (e.g., `git-*,docker`) |
955
1082
  | `--export <file> [output]` | Export session to HTML |
@@ -1033,7 +1160,7 @@ Available via `--tools` flag:
1033
1160
 
1034
1161
  Example: `--tools read,grep,find,ls` for code review without modification.
1035
1162
 
1036
- For adding new tools, see [Custom Tools](#custom-tools) in the Configuration section.
1163
+ For adding new tools, see [Extensions](#extensions) in the Customization section.
1037
1164
 
1038
1165
  ---
1039
1166
 
@@ -1068,8 +1195,8 @@ The SDK provides full control over:
1068
1195
  - Model selection and thinking level
1069
1196
  - System prompt (replace or modify)
1070
1197
  - Tools (built-in subsets, custom tools)
1071
- - Hooks (inline or discovered)
1072
- - Skills, context files, slash commands
1198
+ - Extensions (discovered or via paths)
1199
+ - Skills, context files, prompt templates
1073
1200
  - Session persistence (`SessionManager`)
1074
1201
  - Settings (`SettingsManager`)
1075
1202
  - API key resolution and OAuth
@@ -1101,7 +1228,7 @@ pi --export session.jsonl # Auto-generated filename
1101
1228
  pi --export session.jsonl output.html # Custom filename
1102
1229
  ```
1103
1230
 
1104
- Works with both session files and streaming event logs from `--mode json`.
1231
+ Works with session files.
1105
1232
 
1106
1233
  ---
1107
1234
 
@@ -1111,13 +1238,13 @@ Pi is opinionated about what it won't do. These are intentional design decisions
1111
1238
 
1112
1239
  **No MCP.** Build CLI tools with READMEs (see [Skills](#skills)). The agent reads them on demand. [Would you like to know more?](https://mariozechner.at/posts/2025-11-02-what-if-you-dont-need-mcp/)
1113
1240
 
1114
- **No sub-agents.** Spawn pi instances via tmux, or [build your own sub-agent tool](examples/custom-tools/subagent/) with [custom tools](#custom-tools). Full observability and steerability.
1241
+ **No sub-agents.** Spawn pi instances via tmux, or [build your own sub-agent tool](examples/extensions/subagent/) with [Extensions](#extensions). Full observability and steerability.
1115
1242
 
1116
- **No permission popups.** Security theater. Run in a container or build your own with [Hooks](#hooks).
1243
+ **No permission popups.** Security theater. Run in a container or build your own with [Extensions](#extensions).
1117
1244
 
1118
1245
  **No plan mode.** Gather context in one session, write plans to file, start fresh for implementation.
1119
1246
 
1120
- **No built-in to-dos.** They confuse models. Use a TODO.md file, or [build your own](examples/custom-tools/todo/) with [custom tools](#custom-tools).
1247
+ **No built-in to-dos.** They confuse models. Use a TODO.md file, or [build your own](examples/extensions/todo.ts) with [Extensions](#extensions).
1121
1248
 
1122
1249
  **No background bash.** Use tmux. Full observability, direct interaction.
1123
1250
 
@@ -1168,3 +1295,4 @@ MIT
1168
1295
 
1169
1296
  - [@mariozechner/pi-ai](https://www.npmjs.com/package/@mariozechner/pi-ai): Core LLM toolkit
1170
1297
  - [@mariozechner/pi-agent](https://www.npmjs.com/package/@mariozechner/pi-agent): Agent framework
1298
+ - [@mariozechner/pi-tui](https://www.npmjs.com/package/@mariozechner/pi-tui): Terminal UI components
@@ -21,8 +21,7 @@ export interface Args {
21
21
  sessionDir?: string;
22
22
  models?: string[];
23
23
  tools?: ToolName[];
24
- hooks?: string[];
25
- customTools?: string[];
24
+ extensions?: string[];
26
25
  print?: boolean;
27
26
  export?: string;
28
27
  noSkills?: boolean;
@@ -30,11 +29,11 @@ export interface Args {
30
29
  listModels?: string | true;
31
30
  messages: string[];
32
31
  fileArgs: string[];
33
- /** Unknown flags (potentially hook flags) - map of flag name to value */
32
+ /** Unknown flags (potentially extension flags) - map of flag name to value */
34
33
  unknownFlags: Map<string, boolean | string>;
35
34
  }
36
35
  export declare function isValidThinkingLevel(level: string): level is ThinkingLevel;
37
- export declare function parseArgs(args: string[], hookFlags?: Map<string, {
36
+ export declare function parseArgs(args: string[], extensionFlags?: Map<string, {
38
37
  type: "boolean" | "string";
39
38
  }>): Args;
40
39
  export declare function printHelp(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAGjE,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAEjE,MAAM,MAAM,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;AAE3C,MAAM,WAAW,IAAI;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,yEAAyE;IACzE,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC,CAAC;CAC5C;AAID,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,aAAa,CAE1E;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,SAAS,GAAG,QAAQ,CAAA;CAAE,CAAC,GAAG,IAAI,CA2GvG;AAED,wBAAgB,SAAS,IAAI,IAAI,CAiGhC","sourcesContent":["/**\n * CLI argument parsing and help display\n */\n\nimport type { ThinkingLevel } from \"@mariozechner/pi-agent-core\";\nimport chalk from \"chalk\";\nimport { APP_NAME, CONFIG_DIR_NAME, ENV_AGENT_DIR } from \"../config.js\";\nimport { allTools, type ToolName } from \"../core/tools/index.js\";\n\nexport type Mode = \"text\" | \"json\" | \"rpc\";\n\nexport interface Args {\n\tprovider?: string;\n\tmodel?: string;\n\tapiKey?: string;\n\tsystemPrompt?: string;\n\tappendSystemPrompt?: string;\n\tthinking?: ThinkingLevel;\n\tcontinue?: boolean;\n\tresume?: boolean;\n\thelp?: boolean;\n\tversion?: boolean;\n\tmode?: Mode;\n\tnoSession?: boolean;\n\tsession?: string;\n\tsessionDir?: string;\n\tmodels?: string[];\n\ttools?: ToolName[];\n\thooks?: string[];\n\tcustomTools?: string[];\n\tprint?: boolean;\n\texport?: string;\n\tnoSkills?: boolean;\n\tskills?: string[];\n\tlistModels?: string | true;\n\tmessages: string[];\n\tfileArgs: string[];\n\t/** Unknown flags (potentially hook flags) - map of flag name to value */\n\tunknownFlags: Map<string, boolean | string>;\n}\n\nconst VALID_THINKING_LEVELS = [\"off\", \"minimal\", \"low\", \"medium\", \"high\", \"xhigh\"] as const;\n\nexport function isValidThinkingLevel(level: string): level is ThinkingLevel {\n\treturn VALID_THINKING_LEVELS.includes(level as ThinkingLevel);\n}\n\nexport function parseArgs(args: string[], hookFlags?: Map<string, { type: \"boolean\" | \"string\" }>): Args {\n\tconst result: Args = {\n\t\tmessages: [],\n\t\tfileArgs: [],\n\t\tunknownFlags: new Map(),\n\t};\n\n\tfor (let i = 0; i < args.length; i++) {\n\t\tconst arg = args[i];\n\n\t\tif (arg === \"--help\" || arg === \"-h\") {\n\t\t\tresult.help = true;\n\t\t} else if (arg === \"--version\" || arg === \"-v\") {\n\t\t\tresult.version = true;\n\t\t} else if (arg === \"--mode\" && i + 1 < args.length) {\n\t\t\tconst mode = args[++i];\n\t\t\tif (mode === \"text\" || mode === \"json\" || mode === \"rpc\") {\n\t\t\t\tresult.mode = mode;\n\t\t\t}\n\t\t} else if (arg === \"--continue\" || arg === \"-c\") {\n\t\t\tresult.continue = true;\n\t\t} else if (arg === \"--resume\" || arg === \"-r\") {\n\t\t\tresult.resume = true;\n\t\t} else if (arg === \"--provider\" && i + 1 < args.length) {\n\t\t\tresult.provider = args[++i];\n\t\t} else if (arg === \"--model\" && i + 1 < args.length) {\n\t\t\tresult.model = args[++i];\n\t\t} else if (arg === \"--api-key\" && i + 1 < args.length) {\n\t\t\tresult.apiKey = args[++i];\n\t\t} else if (arg === \"--system-prompt\" && i + 1 < args.length) {\n\t\t\tresult.systemPrompt = args[++i];\n\t\t} else if (arg === \"--append-system-prompt\" && i + 1 < args.length) {\n\t\t\tresult.appendSystemPrompt = args[++i];\n\t\t} else if (arg === \"--no-session\") {\n\t\t\tresult.noSession = true;\n\t\t} else if (arg === \"--session\" && i + 1 < args.length) {\n\t\t\tresult.session = args[++i];\n\t\t} else if (arg === \"--session-dir\" && i + 1 < args.length) {\n\t\t\tresult.sessionDir = args[++i];\n\t\t} else if (arg === \"--models\" && i + 1 < args.length) {\n\t\t\tresult.models = args[++i].split(\",\").map((s) => s.trim());\n\t\t} else if (arg === \"--tools\" && i + 1 < args.length) {\n\t\t\tconst toolNames = args[++i].split(\",\").map((s) => s.trim());\n\t\t\tconst validTools: ToolName[] = [];\n\t\t\tfor (const name of toolNames) {\n\t\t\t\tif (name in allTools) {\n\t\t\t\t\tvalidTools.push(name as ToolName);\n\t\t\t\t} else {\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\tchalk.yellow(`Warning: Unknown tool \"${name}\". Valid tools: ${Object.keys(allTools).join(\", \")}`),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tresult.tools = validTools;\n\t\t} else if (arg === \"--thinking\" && i + 1 < args.length) {\n\t\t\tconst level = args[++i];\n\t\t\tif (isValidThinkingLevel(level)) {\n\t\t\t\tresult.thinking = level;\n\t\t\t} else {\n\t\t\t\tconsole.error(\n\t\t\t\t\tchalk.yellow(\n\t\t\t\t\t\t`Warning: Invalid thinking level \"${level}\". Valid values: ${VALID_THINKING_LEVELS.join(\", \")}`,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t} else if (arg === \"--print\" || arg === \"-p\") {\n\t\t\tresult.print = true;\n\t\t} else if (arg === \"--export\" && i + 1 < args.length) {\n\t\t\tresult.export = args[++i];\n\t\t} else if (arg === \"--hook\" && i + 1 < args.length) {\n\t\t\tresult.hooks = result.hooks ?? [];\n\t\t\tresult.hooks.push(args[++i]);\n\t\t} else if (arg === \"--tool\" && i + 1 < args.length) {\n\t\t\tresult.customTools = result.customTools ?? [];\n\t\t\tresult.customTools.push(args[++i]);\n\t\t} else if (arg === \"--no-skills\") {\n\t\t\tresult.noSkills = true;\n\t\t} else if (arg === \"--skills\" && i + 1 < args.length) {\n\t\t\t// Comma-separated glob patterns for skill filtering\n\t\t\tresult.skills = args[++i].split(\",\").map((s) => s.trim());\n\t\t} else if (arg === \"--list-models\") {\n\t\t\t// Check if next arg is a search pattern (not a flag or file arg)\n\t\t\tif (i + 1 < args.length && !args[i + 1].startsWith(\"-\") && !args[i + 1].startsWith(\"@\")) {\n\t\t\t\tresult.listModels = args[++i];\n\t\t\t} else {\n\t\t\t\tresult.listModels = true;\n\t\t\t}\n\t\t} else if (arg.startsWith(\"@\")) {\n\t\t\tresult.fileArgs.push(arg.slice(1)); // Remove @ prefix\n\t\t} else if (arg.startsWith(\"--\") && hookFlags) {\n\t\t\t// Check if it's a hook-registered flag\n\t\t\tconst flagName = arg.slice(2);\n\t\t\tconst hookFlag = hookFlags.get(flagName);\n\t\t\tif (hookFlag) {\n\t\t\t\tif (hookFlag.type === \"boolean\") {\n\t\t\t\t\tresult.unknownFlags.set(flagName, true);\n\t\t\t\t} else if (hookFlag.type === \"string\" && i + 1 < args.length) {\n\t\t\t\t\tresult.unknownFlags.set(flagName, args[++i]);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Unknown flags without hookFlags are silently ignored (first pass)\n\t\t} else if (!arg.startsWith(\"-\")) {\n\t\t\tresult.messages.push(arg);\n\t\t}\n\t}\n\n\treturn result;\n}\n\nexport function printHelp(): void {\n\tconsole.log(`${chalk.bold(APP_NAME)} - AI coding assistant with read, bash, edit, write tools\n\n${chalk.bold(\"Usage:\")}\n ${APP_NAME} [options] [@files...] [messages...]\n\n${chalk.bold(\"Options:\")}\n --provider <name> Provider name (default: google)\n --model <id> Model ID (default: gemini-2.5-flash)\n --api-key <key> API key (defaults to env vars)\n --system-prompt <text> System prompt (default: coding assistant prompt)\n --append-system-prompt <text> Append text or file contents to the system prompt\n --mode <mode> Output mode: text (default), json, or rpc\n --print, -p Non-interactive mode: process prompt and exit\n --continue, -c Continue previous session\n --resume, -r Select a session to resume\n --session <path> Use specific session file\n --session-dir <dir> Directory for session storage and lookup\n --no-session Don't save session (ephemeral)\n --models <patterns> Comma-separated model patterns for Ctrl+P cycling\n Supports globs (anthropic/*, *sonnet*) and fuzzy matching\n --tools <tools> Comma-separated list of tools to enable (default: read,bash,edit,write)\n Available: read, bash, edit, write, grep, find, ls\n --thinking <level> Set thinking level: off, minimal, low, medium, high, xhigh\n --hook <path> Load a hook file (can be used multiple times)\n --tool <path> Load a custom tool file (can be used multiple times)\n --no-skills Disable skills discovery and loading\n --skills <patterns> Comma-separated glob patterns to filter skills (e.g., git-*,docker)\n --export <file> Export session file to HTML and exit\n --list-models [search] List available models (with optional fuzzy search)\n --help, -h Show this help\n --version, -v Show version number\n\nHooks can register additional flags (e.g., --plan from plan-mode hook).\n\n${chalk.bold(\"Examples:\")}\n # Interactive mode\n ${APP_NAME}\n\n # Interactive mode with initial prompt\n ${APP_NAME} \"List all .ts files in src/\"\n\n # Include files in initial message\n ${APP_NAME} @prompt.md @image.png \"What color is the sky?\"\n\n # Non-interactive mode (process and exit)\n ${APP_NAME} -p \"List all .ts files in src/\"\n\n # Multiple messages (interactive)\n ${APP_NAME} \"Read package.json\" \"What dependencies do we have?\"\n\n # Continue previous session\n ${APP_NAME} --continue \"What did we discuss?\"\n\n # Use different model\n ${APP_NAME} --provider openai --model gpt-4o-mini \"Help me refactor this code\"\n\n # Limit model cycling to specific models\n ${APP_NAME} --models claude-sonnet,claude-haiku,gpt-4o\n\n # Limit to a specific provider with glob pattern\n ${APP_NAME} --models \"github-copilot/*\"\n\n # Cycle models with fixed thinking levels\n ${APP_NAME} --models sonnet:high,haiku:low\n\n # Start with a specific thinking level\n ${APP_NAME} --thinking high \"Solve this complex problem\"\n\n # Read-only mode (no file modifications possible)\n ${APP_NAME} --tools read,grep,find,ls -p \"Review the code in src/\"\n\n # Export a session file to HTML\n ${APP_NAME} --export ~/${CONFIG_DIR_NAME}/agent/sessions/--path--/session.jsonl\n ${APP_NAME} --export session.jsonl output.html\n\n${chalk.bold(\"Environment Variables:\")}\n ANTHROPIC_API_KEY - Anthropic Claude API key\n ANTHROPIC_OAUTH_TOKEN - Anthropic OAuth token (alternative to API key)\n OPENAI_API_KEY - OpenAI GPT API key\n GEMINI_API_KEY - Google Gemini API key\n GROQ_API_KEY - Groq API key\n CEREBRAS_API_KEY - Cerebras API key\n XAI_API_KEY - xAI Grok API key\n OPENROUTER_API_KEY - OpenRouter API key\n ZAI_API_KEY - ZAI API key\n ${ENV_AGENT_DIR.padEnd(23)} - Session storage directory (default: ~/${CONFIG_DIR_NAME}/agent)\n\n${chalk.bold(\"Available Tools (default: read, bash, edit, write):\")}\n read - Read file contents\n bash - Execute bash commands\n edit - Edit files with find/replace\n write - Write files (creates/overwrites)\n grep - Search file contents (read-only, off by default)\n find - Find files by glob pattern (read-only, off by default)\n ls - List directory contents (read-only, off by default)\n`);\n}\n"]}
1
+ {"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAGjE,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAEjE,MAAM,MAAM,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;AAE3C,MAAM,WAAW,IAAI;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,8EAA8E;IAC9E,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC,CAAC;CAC5C;AAID,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,aAAa,CAE1E;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,cAAc,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,SAAS,GAAG,QAAQ,CAAA;CAAE,CAAC,GAAG,IAAI,CAwG5G;AAED,wBAAgB,SAAS,IAAI,IAAI,CAgGhC","sourcesContent":["/**\n * CLI argument parsing and help display\n */\n\nimport type { ThinkingLevel } from \"@mariozechner/pi-agent-core\";\nimport chalk from \"chalk\";\nimport { APP_NAME, CONFIG_DIR_NAME, ENV_AGENT_DIR } from \"../config.js\";\nimport { allTools, type ToolName } from \"../core/tools/index.js\";\n\nexport type Mode = \"text\" | \"json\" | \"rpc\";\n\nexport interface Args {\n\tprovider?: string;\n\tmodel?: string;\n\tapiKey?: string;\n\tsystemPrompt?: string;\n\tappendSystemPrompt?: string;\n\tthinking?: ThinkingLevel;\n\tcontinue?: boolean;\n\tresume?: boolean;\n\thelp?: boolean;\n\tversion?: boolean;\n\tmode?: Mode;\n\tnoSession?: boolean;\n\tsession?: string;\n\tsessionDir?: string;\n\tmodels?: string[];\n\ttools?: ToolName[];\n\textensions?: string[];\n\tprint?: boolean;\n\texport?: string;\n\tnoSkills?: boolean;\n\tskills?: string[];\n\tlistModels?: string | true;\n\tmessages: string[];\n\tfileArgs: string[];\n\t/** Unknown flags (potentially extension flags) - map of flag name to value */\n\tunknownFlags: Map<string, boolean | string>;\n}\n\nconst VALID_THINKING_LEVELS = [\"off\", \"minimal\", \"low\", \"medium\", \"high\", \"xhigh\"] as const;\n\nexport function isValidThinkingLevel(level: string): level is ThinkingLevel {\n\treturn VALID_THINKING_LEVELS.includes(level as ThinkingLevel);\n}\n\nexport function parseArgs(args: string[], extensionFlags?: Map<string, { type: \"boolean\" | \"string\" }>): Args {\n\tconst result: Args = {\n\t\tmessages: [],\n\t\tfileArgs: [],\n\t\tunknownFlags: new Map(),\n\t};\n\n\tfor (let i = 0; i < args.length; i++) {\n\t\tconst arg = args[i];\n\n\t\tif (arg === \"--help\" || arg === \"-h\") {\n\t\t\tresult.help = true;\n\t\t} else if (arg === \"--version\" || arg === \"-v\") {\n\t\t\tresult.version = true;\n\t\t} else if (arg === \"--mode\" && i + 1 < args.length) {\n\t\t\tconst mode = args[++i];\n\t\t\tif (mode === \"text\" || mode === \"json\" || mode === \"rpc\") {\n\t\t\t\tresult.mode = mode;\n\t\t\t}\n\t\t} else if (arg === \"--continue\" || arg === \"-c\") {\n\t\t\tresult.continue = true;\n\t\t} else if (arg === \"--resume\" || arg === \"-r\") {\n\t\t\tresult.resume = true;\n\t\t} else if (arg === \"--provider\" && i + 1 < args.length) {\n\t\t\tresult.provider = args[++i];\n\t\t} else if (arg === \"--model\" && i + 1 < args.length) {\n\t\t\tresult.model = args[++i];\n\t\t} else if (arg === \"--api-key\" && i + 1 < args.length) {\n\t\t\tresult.apiKey = args[++i];\n\t\t} else if (arg === \"--system-prompt\" && i + 1 < args.length) {\n\t\t\tresult.systemPrompt = args[++i];\n\t\t} else if (arg === \"--append-system-prompt\" && i + 1 < args.length) {\n\t\t\tresult.appendSystemPrompt = args[++i];\n\t\t} else if (arg === \"--no-session\") {\n\t\t\tresult.noSession = true;\n\t\t} else if (arg === \"--session\" && i + 1 < args.length) {\n\t\t\tresult.session = args[++i];\n\t\t} else if (arg === \"--session-dir\" && i + 1 < args.length) {\n\t\t\tresult.sessionDir = args[++i];\n\t\t} else if (arg === \"--models\" && i + 1 < args.length) {\n\t\t\tresult.models = args[++i].split(\",\").map((s) => s.trim());\n\t\t} else if (arg === \"--tools\" && i + 1 < args.length) {\n\t\t\tconst toolNames = args[++i].split(\",\").map((s) => s.trim());\n\t\t\tconst validTools: ToolName[] = [];\n\t\t\tfor (const name of toolNames) {\n\t\t\t\tif (name in allTools) {\n\t\t\t\t\tvalidTools.push(name as ToolName);\n\t\t\t\t} else {\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\tchalk.yellow(`Warning: Unknown tool \"${name}\". Valid tools: ${Object.keys(allTools).join(\", \")}`),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tresult.tools = validTools;\n\t\t} else if (arg === \"--thinking\" && i + 1 < args.length) {\n\t\t\tconst level = args[++i];\n\t\t\tif (isValidThinkingLevel(level)) {\n\t\t\t\tresult.thinking = level;\n\t\t\t} else {\n\t\t\t\tconsole.error(\n\t\t\t\t\tchalk.yellow(\n\t\t\t\t\t\t`Warning: Invalid thinking level \"${level}\". Valid values: ${VALID_THINKING_LEVELS.join(\", \")}`,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t} else if (arg === \"--print\" || arg === \"-p\") {\n\t\t\tresult.print = true;\n\t\t} else if (arg === \"--export\" && i + 1 < args.length) {\n\t\t\tresult.export = args[++i];\n\t\t} else if ((arg === \"--extension\" || arg === \"-e\") && i + 1 < args.length) {\n\t\t\tresult.extensions = result.extensions ?? [];\n\t\t\tresult.extensions.push(args[++i]);\n\t\t} else if (arg === \"--no-skills\") {\n\t\t\tresult.noSkills = true;\n\t\t} else if (arg === \"--skills\" && i + 1 < args.length) {\n\t\t\t// Comma-separated glob patterns for skill filtering\n\t\t\tresult.skills = args[++i].split(\",\").map((s) => s.trim());\n\t\t} else if (arg === \"--list-models\") {\n\t\t\t// Check if next arg is a search pattern (not a flag or file arg)\n\t\t\tif (i + 1 < args.length && !args[i + 1].startsWith(\"-\") && !args[i + 1].startsWith(\"@\")) {\n\t\t\t\tresult.listModels = args[++i];\n\t\t\t} else {\n\t\t\t\tresult.listModels = true;\n\t\t\t}\n\t\t} else if (arg.startsWith(\"@\")) {\n\t\t\tresult.fileArgs.push(arg.slice(1)); // Remove @ prefix\n\t\t} else if (arg.startsWith(\"--\") && extensionFlags) {\n\t\t\t// Check if it's an extension-registered flag\n\t\t\tconst flagName = arg.slice(2);\n\t\t\tconst extFlag = extensionFlags.get(flagName);\n\t\t\tif (extFlag) {\n\t\t\t\tif (extFlag.type === \"boolean\") {\n\t\t\t\t\tresult.unknownFlags.set(flagName, true);\n\t\t\t\t} else if (extFlag.type === \"string\" && i + 1 < args.length) {\n\t\t\t\t\tresult.unknownFlags.set(flagName, args[++i]);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Unknown flags without extensionFlags are silently ignored (first pass)\n\t\t} else if (!arg.startsWith(\"-\")) {\n\t\t\tresult.messages.push(arg);\n\t\t}\n\t}\n\n\treturn result;\n}\n\nexport function printHelp(): void {\n\tconsole.log(`${chalk.bold(APP_NAME)} - AI coding assistant with read, bash, edit, write tools\n\n${chalk.bold(\"Usage:\")}\n ${APP_NAME} [options] [@files...] [messages...]\n\n${chalk.bold(\"Options:\")}\n --provider <name> Provider name (default: google)\n --model <id> Model ID (default: gemini-2.5-flash)\n --api-key <key> API key (defaults to env vars)\n --system-prompt <text> System prompt (default: coding assistant prompt)\n --append-system-prompt <text> Append text or file contents to the system prompt\n --mode <mode> Output mode: text (default), json, or rpc\n --print, -p Non-interactive mode: process prompt and exit\n --continue, -c Continue previous session\n --resume, -r Select a session to resume\n --session <path> Use specific session file\n --session-dir <dir> Directory for session storage and lookup\n --no-session Don't save session (ephemeral)\n --models <patterns> Comma-separated model patterns for Ctrl+P cycling\n Supports globs (anthropic/*, *sonnet*) and fuzzy matching\n --tools <tools> Comma-separated list of tools to enable (default: read,bash,edit,write)\n Available: read, bash, edit, write, grep, find, ls\n --thinking <level> Set thinking level: off, minimal, low, medium, high, xhigh\n --extension, -e <path> Load an extension file (can be used multiple times)\n --no-skills Disable skills discovery and loading\n --skills <patterns> Comma-separated glob patterns to filter skills (e.g., git-*,docker)\n --export <file> Export session file to HTML and exit\n --list-models [search] List available models (with optional fuzzy search)\n --help, -h Show this help\n --version, -v Show version number\n\nExtensions can register additional flags (e.g., --plan from plan-mode extension).\n\n${chalk.bold(\"Examples:\")}\n # Interactive mode\n ${APP_NAME}\n\n # Interactive mode with initial prompt\n ${APP_NAME} \"List all .ts files in src/\"\n\n # Include files in initial message\n ${APP_NAME} @prompt.md @image.png \"What color is the sky?\"\n\n # Non-interactive mode (process and exit)\n ${APP_NAME} -p \"List all .ts files in src/\"\n\n # Multiple messages (interactive)\n ${APP_NAME} \"Read package.json\" \"What dependencies do we have?\"\n\n # Continue previous session\n ${APP_NAME} --continue \"What did we discuss?\"\n\n # Use different model\n ${APP_NAME} --provider openai --model gpt-4o-mini \"Help me refactor this code\"\n\n # Limit model cycling to specific models\n ${APP_NAME} --models claude-sonnet,claude-haiku,gpt-4o\n\n # Limit to a specific provider with glob pattern\n ${APP_NAME} --models \"github-copilot/*\"\n\n # Cycle models with fixed thinking levels\n ${APP_NAME} --models sonnet:high,haiku:low\n\n # Start with a specific thinking level\n ${APP_NAME} --thinking high \"Solve this complex problem\"\n\n # Read-only mode (no file modifications possible)\n ${APP_NAME} --tools read,grep,find,ls -p \"Review the code in src/\"\n\n # Export a session file to HTML\n ${APP_NAME} --export ~/${CONFIG_DIR_NAME}/agent/sessions/--path--/session.jsonl\n ${APP_NAME} --export session.jsonl output.html\n\n${chalk.bold(\"Environment Variables:\")}\n ANTHROPIC_API_KEY - Anthropic Claude API key\n ANTHROPIC_OAUTH_TOKEN - Anthropic OAuth token (alternative to API key)\n OPENAI_API_KEY - OpenAI GPT API key\n GEMINI_API_KEY - Google Gemini API key\n GROQ_API_KEY - Groq API key\n CEREBRAS_API_KEY - Cerebras API key\n XAI_API_KEY - xAI Grok API key\n OPENROUTER_API_KEY - OpenRouter API key\n ZAI_API_KEY - ZAI API key\n ${ENV_AGENT_DIR.padEnd(23)} - Session storage directory (default: ~/${CONFIG_DIR_NAME}/agent)\n\n${chalk.bold(\"Available Tools (default: read, bash, edit, write):\")}\n read - Read file contents\n bash - Execute bash commands\n edit - Edit files with find/replace\n write - Write files (creates/overwrites)\n grep - Search file contents (read-only, off by default)\n find - Find files by glob pattern (read-only, off by default)\n ls - List directory contents (read-only, off by default)\n`);\n}\n"]}
package/dist/cli/args.js CHANGED
@@ -8,7 +8,7 @@ const VALID_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh
8
8
  export function isValidThinkingLevel(level) {
9
9
  return VALID_THINKING_LEVELS.includes(level);
10
10
  }
11
- export function parseArgs(args, hookFlags) {
11
+ export function parseArgs(args, extensionFlags) {
12
12
  const result = {
13
13
  messages: [],
14
14
  fileArgs: [],
@@ -89,13 +89,9 @@ export function parseArgs(args, hookFlags) {
89
89
  else if (arg === "--export" && i + 1 < args.length) {
90
90
  result.export = args[++i];
91
91
  }
92
- else if (arg === "--hook" && i + 1 < args.length) {
93
- result.hooks = result.hooks ?? [];
94
- result.hooks.push(args[++i]);
95
- }
96
- else if (arg === "--tool" && i + 1 < args.length) {
97
- result.customTools = result.customTools ?? [];
98
- result.customTools.push(args[++i]);
92
+ else if ((arg === "--extension" || arg === "-e") && i + 1 < args.length) {
93
+ result.extensions = result.extensions ?? [];
94
+ result.extensions.push(args[++i]);
99
95
  }
100
96
  else if (arg === "--no-skills") {
101
97
  result.noSkills = true;
@@ -116,19 +112,19 @@ export function parseArgs(args, hookFlags) {
116
112
  else if (arg.startsWith("@")) {
117
113
  result.fileArgs.push(arg.slice(1)); // Remove @ prefix
118
114
  }
119
- else if (arg.startsWith("--") && hookFlags) {
120
- // Check if it's a hook-registered flag
115
+ else if (arg.startsWith("--") && extensionFlags) {
116
+ // Check if it's an extension-registered flag
121
117
  const flagName = arg.slice(2);
122
- const hookFlag = hookFlags.get(flagName);
123
- if (hookFlag) {
124
- if (hookFlag.type === "boolean") {
118
+ const extFlag = extensionFlags.get(flagName);
119
+ if (extFlag) {
120
+ if (extFlag.type === "boolean") {
125
121
  result.unknownFlags.set(flagName, true);
126
122
  }
127
- else if (hookFlag.type === "string" && i + 1 < args.length) {
123
+ else if (extFlag.type === "string" && i + 1 < args.length) {
128
124
  result.unknownFlags.set(flagName, args[++i]);
129
125
  }
130
126
  }
131
- // Unknown flags without hookFlags are silently ignored (first pass)
127
+ // Unknown flags without extensionFlags are silently ignored (first pass)
132
128
  }
133
129
  else if (!arg.startsWith("-")) {
134
130
  result.messages.push(arg);
@@ -160,8 +156,7 @@ ${chalk.bold("Options:")}
160
156
  --tools <tools> Comma-separated list of tools to enable (default: read,bash,edit,write)
161
157
  Available: read, bash, edit, write, grep, find, ls
162
158
  --thinking <level> Set thinking level: off, minimal, low, medium, high, xhigh
163
- --hook <path> Load a hook file (can be used multiple times)
164
- --tool <path> Load a custom tool file (can be used multiple times)
159
+ --extension, -e <path> Load an extension file (can be used multiple times)
165
160
  --no-skills Disable skills discovery and loading
166
161
  --skills <patterns> Comma-separated glob patterns to filter skills (e.g., git-*,docker)
167
162
  --export <file> Export session file to HTML and exit
@@ -169,7 +164,7 @@ ${chalk.bold("Options:")}
169
164
  --help, -h Show this help
170
165
  --version, -v Show version number
171
166
 
172
- Hooks can register additional flags (e.g., --plan from plan-mode hook).
167
+ Extensions can register additional flags (e.g., --plan from plan-mode extension).
173
168
 
174
169
  ${chalk.bold("Examples:")}
175
170
  # Interactive mode