@mariozechner/pi-coding-agent 0.46.0 → 0.47.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 (90) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/core/agent-session.d.ts +11 -3
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +81 -14
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/export-html/ansi-to-html.d.ts +22 -0
  7. package/dist/core/export-html/ansi-to-html.d.ts.map +1 -0
  8. package/dist/core/export-html/ansi-to-html.js +249 -0
  9. package/dist/core/export-html/ansi-to-html.js.map +1 -0
  10. package/dist/core/export-html/index.d.ts +17 -0
  11. package/dist/core/export-html/index.d.ts.map +1 -1
  12. package/dist/core/export-html/index.js +52 -23
  13. package/dist/core/export-html/index.js.map +1 -1
  14. package/dist/core/export-html/template.css +0 -33
  15. package/dist/core/export-html/template.js +171 -18
  16. package/dist/core/export-html/tool-renderer.d.ts +35 -0
  17. package/dist/core/export-html/tool-renderer.d.ts.map +1 -0
  18. package/dist/core/export-html/tool-renderer.js +57 -0
  19. package/dist/core/export-html/tool-renderer.js.map +1 -0
  20. package/dist/core/extensions/index.d.ts +1 -1
  21. package/dist/core/extensions/index.d.ts.map +1 -1
  22. package/dist/core/extensions/index.js.map +1 -1
  23. package/dist/core/extensions/runner.d.ts +5 -1
  24. package/dist/core/extensions/runner.d.ts.map +1 -1
  25. package/dist/core/extensions/runner.js +41 -0
  26. package/dist/core/extensions/runner.js.map +1 -1
  27. package/dist/core/extensions/types.d.ts +24 -1
  28. package/dist/core/extensions/types.d.ts.map +1 -1
  29. package/dist/core/extensions/types.js.map +1 -1
  30. package/dist/core/prompt-templates.d.ts.map +1 -1
  31. package/dist/core/prompt-templates.js +4 -27
  32. package/dist/core/prompt-templates.js.map +1 -1
  33. package/dist/core/skills.d.ts.map +1 -1
  34. package/dist/core/skills.js +6 -37
  35. package/dist/core/skills.js.map +1 -1
  36. package/dist/core/system-prompt.d.ts.map +1 -1
  37. package/dist/core/system-prompt.js +19 -14
  38. package/dist/core/system-prompt.js.map +1 -1
  39. package/dist/core/tools/path-utils.d.ts +1 -0
  40. package/dist/core/tools/path-utils.d.ts.map +1 -1
  41. package/dist/core/tools/path-utils.js +7 -0
  42. package/dist/core/tools/path-utils.js.map +1 -1
  43. package/dist/core/tools/read.d.ts.map +1 -1
  44. package/dist/core/tools/read.js +13 -2
  45. package/dist/core/tools/read.js.map +1 -1
  46. package/dist/index.d.ts +2 -1
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +1 -0
  49. package/dist/index.js.map +1 -1
  50. package/dist/main.d.ts.map +1 -1
  51. package/dist/main.js +32 -0
  52. package/dist/main.js.map +1 -1
  53. package/dist/modes/interactive/components/custom-editor.d.ts +2 -2
  54. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  55. package/dist/modes/interactive/components/custom-editor.js +2 -2
  56. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  57. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  58. package/dist/modes/interactive/components/extension-editor.js +1 -1
  59. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  60. package/dist/modes/interactive/components/tree-selector.d.ts +7 -0
  61. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  62. package/dist/modes/interactive/components/tree-selector.js +140 -4
  63. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  64. package/dist/modes/interactive/interactive-mode.d.ts +0 -1
  65. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  66. package/dist/modes/interactive/interactive-mode.js +4 -29
  67. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  68. package/dist/modes/print-mode.d.ts.map +1 -1
  69. package/dist/modes/print-mode.js +1 -1
  70. package/dist/modes/print-mode.js.map +1 -1
  71. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  72. package/dist/modes/rpc/rpc-mode.js +1 -0
  73. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  74. package/dist/utils/frontmatter.d.ts +8 -0
  75. package/dist/utils/frontmatter.d.ts.map +1 -0
  76. package/dist/utils/frontmatter.js +26 -0
  77. package/dist/utils/frontmatter.js.map +1 -0
  78. package/docs/extensions.md +51 -0
  79. package/docs/rpc.md +15 -15
  80. package/docs/tui.md +26 -0
  81. package/examples/extensions/input-transform.ts +43 -0
  82. package/examples/extensions/modal-editor.ts +1 -1
  83. package/examples/extensions/overlay-test.ts +8 -3
  84. package/examples/extensions/question.ts +1 -1
  85. package/examples/extensions/questionnaire.ts +1 -1
  86. package/examples/extensions/rainbow-editor.ts +1 -8
  87. package/examples/extensions/subagent/agents.ts +3 -32
  88. package/examples/extensions/with-deps/package-lock.json +2 -2
  89. package/examples/extensions/with-deps/package.json +1 -1
  90. package/package.json +6 -5
package/docs/rpc.md CHANGED
@@ -52,9 +52,9 @@ With images:
52
52
 
53
53
  If the agent is streaming and no `streamingBehavior` is specified, the command returns an error.
54
54
 
55
- **Extension commands**: If the message is a hook command (e.g., `/mycommand`), it executes immediately even during streaming. Extension commands manage their own LLM interaction via `pi.sendMessage()`.
55
+ **Extension commands**: If the message is an extension command (e.g., `/mycommand`), it executes immediately even during streaming. Extension commands manage their own LLM interaction via `pi.sendMessage()`.
56
56
 
57
- **Prompt templates**: File-based prompt templates (from `.md` files) are expanded before sending/queueing.
57
+ **Input expansion**: Skill commands (`/skill:name`) and prompt templates (`/template`) are expanded before sending/queueing.
58
58
 
59
59
  Response:
60
60
  ```json
@@ -65,7 +65,7 @@ The `images` field is optional. Each image uses `ImageContent` format with base6
65
65
 
66
66
  #### steer
67
67
 
68
- Queue a steering message to interrupt the agent mid-run. Delivered after current tool execution, remaining tools are skipped. File-based prompt templates are expanded. Extension commands are not allowed (use `prompt` instead).
68
+ Queue a steering message to interrupt the agent mid-run. Delivered after current tool execution, remaining tools are skipped. Skill commands and prompt templates are expanded. Extension commands are not allowed (use `prompt` instead).
69
69
 
70
70
  ```json
71
71
  {"type": "steer", "message": "Stop and do this instead"}
@@ -80,7 +80,7 @@ See [set_steering_mode](#set_steering_mode) for controlling how steering message
80
80
 
81
81
  #### follow_up
82
82
 
83
- Queue a follow-up message to be processed after the agent finishes. Delivered only when agent has no more tool calls or steering messages. File-based prompt templates are expanded. Extension commands are not allowed (use `prompt` instead).
83
+ Queue a follow-up message to be processed after the agent finishes. Delivered only when agent has no more tool calls or steering messages. Skill commands and prompt templates are expanded. Extension commands are not allowed (use `prompt` instead).
84
84
 
85
85
  ```json
86
86
  {"type": "follow_up", "message": "After you're done, also do this"}
@@ -108,7 +108,7 @@ Response:
108
108
 
109
109
  #### new_session
110
110
 
111
- Start a fresh session. Can be cancelled by a `session_before_switch` hook.
111
+ Start a fresh session. Can be cancelled by a `session_before_switch` extension event handler.
112
112
 
113
113
  ```json
114
114
  {"type": "new_session"}
@@ -124,7 +124,7 @@ Response:
124
124
  {"type": "response", "command": "new_session", "success": true, "data": {"cancelled": false}}
125
125
  ```
126
126
 
127
- If a hook cancelled:
127
+ If an extension cancelled:
128
128
  ```json
129
129
  {"type": "response", "command": "new_session", "success": true, "data": {"cancelled": true}}
130
130
  ```
@@ -525,7 +525,7 @@ Response:
525
525
 
526
526
  #### switch_session
527
527
 
528
- Load a different session file. Can be cancelled by a `before_switch` hook.
528
+ Load a different session file. Can be cancelled by a `session_before_switch` extension event handler.
529
529
 
530
530
  ```json
531
531
  {"type": "switch_session", "sessionPath": "/path/to/session.jsonl"}
@@ -536,14 +536,14 @@ Response:
536
536
  {"type": "response", "command": "switch_session", "success": true, "data": {"cancelled": false}}
537
537
  ```
538
538
 
539
- If a hook cancelled the switch:
539
+ If an extension cancelled the switch:
540
540
  ```json
541
541
  {"type": "response", "command": "switch_session", "success": true, "data": {"cancelled": true}}
542
542
  ```
543
543
 
544
544
  #### fork
545
545
 
546
- Create a new fork from a previous user message. Can be cancelled by a `before_fork` hook. Returns the text of the message being forked from.
546
+ Create a new fork from a previous user message. Can be cancelled by a `session_before_fork` extension event handler. Returns the text of the message being forked from.
547
547
 
548
548
  ```json
549
549
  {"type": "fork", "entryId": "abc123"}
@@ -559,7 +559,7 @@ Response:
559
559
  }
560
560
  ```
561
561
 
562
- If a hook cancelled the fork:
562
+ If an extension cancelled the fork:
563
563
  ```json
564
564
  {
565
565
  "type": "response",
@@ -634,7 +634,7 @@ Events are streamed to stdout as JSON lines during agent operation. Events do NO
634
634
  | `auto_compaction_end` | Auto-compaction completes |
635
635
  | `auto_retry_start` | Auto-retry begins (after transient error) |
636
636
  | `auto_retry_end` | Auto-retry completes (success or final failure) |
637
- | `hook_error` | Hook threw an error |
637
+ | `extension_error` | Extension threw an error |
638
638
 
639
639
  ### agent_start
640
640
 
@@ -827,14 +827,14 @@ On final failure (max retries exceeded):
827
827
  }
828
828
  ```
829
829
 
830
- ### hook_error
830
+ ### extension_error
831
831
 
832
- Emitted when a hook throws an error.
832
+ Emitted when an extension throws an error.
833
833
 
834
834
  ```json
835
835
  {
836
- "type": "hook_error",
837
- "hookPath": "/path/to/hook.ts",
836
+ "type": "extension_error",
837
+ "extensionPath": "/path/to/extension.ts",
838
838
  "event": "tool_call",
839
839
  "error": "Error message..."
840
840
  }
package/docs/tui.md CHANGED
@@ -26,6 +26,32 @@ interface Component {
26
26
 
27
27
  The TUI appends a full SGR reset and OSC 8 reset at the end of each rendered line. Styles do not carry across lines. If you emit multi-line text with styling, reapply styles per line or use `wrapTextWithAnsi()` so styles are preserved for each wrapped line.
28
28
 
29
+ ## Focusable Interface (IME Support)
30
+
31
+ Components that display a text cursor and need IME (Input Method Editor) support should implement the `Focusable` interface:
32
+
33
+ ```typescript
34
+ import { CURSOR_MARKER, type Component, type Focusable } from "@mariozechner/pi-tui";
35
+
36
+ class MyInput implements Component, Focusable {
37
+ focused: boolean = false; // Set by TUI when focus changes
38
+
39
+ render(width: number): string[] {
40
+ const marker = this.focused ? CURSOR_MARKER : "";
41
+ // Emit marker right before the fake cursor
42
+ return [`> ${beforeCursor}${marker}\x1b[7m${atCursor}\x1b[27m${afterCursor}`];
43
+ }
44
+ }
45
+ ```
46
+
47
+ When a `Focusable` component has focus, TUI:
48
+ 1. Sets `focused = true` on the component
49
+ 2. Scans rendered output for `CURSOR_MARKER` (a zero-width APC escape sequence)
50
+ 3. Positions the hardware terminal cursor at that location
51
+ 4. Shows the hardware cursor
52
+
53
+ This enables IME candidate windows to appear at the correct position for CJK input methods. The `Editor` and `Input` built-in components already implement this interface.
54
+
29
55
  ## Using Components
30
56
 
31
57
  **In hooks** via `ctx.ui.custom()`:
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Input Transform Example - demonstrates the `input` event for intercepting user input.
3
+ *
4
+ * Start pi with this extension:
5
+ * pi -e ./examples/extensions/input-transform.ts
6
+ *
7
+ * Then type these inside pi:
8
+ * ?quick What is TypeScript? → "Respond briefly: What is TypeScript?"
9
+ * ping → "pong" (instant, no LLM)
10
+ * time → current time (instant, no LLM)
11
+ */
12
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
13
+
14
+ export default function (pi: ExtensionAPI) {
15
+ pi.on("input", async (event, ctx) => {
16
+ // Source-based logic: skip processing for extension-injected messages
17
+ if (event.source === "extension") {
18
+ return { action: "continue" };
19
+ }
20
+
21
+ // Transform: ?quick prefix for brief responses
22
+ if (event.text.startsWith("?quick ")) {
23
+ const query = event.text.slice(7).trim();
24
+ if (!query) {
25
+ ctx.ui.notify("Usage: ?quick <question>", "warning");
26
+ return { action: "handled" };
27
+ }
28
+ return { action: "transform", text: `Respond briefly in 1-2 sentences: ${query}` };
29
+ }
30
+
31
+ // Handle: instant responses without LLM (extension shows its own feedback)
32
+ if (event.text.toLowerCase() === "ping") {
33
+ ctx.ui.notify("pong", "info");
34
+ return { action: "handled" };
35
+ }
36
+ if (event.text.toLowerCase() === "time") {
37
+ ctx.ui.notify(new Date().toLocaleString(), "info");
38
+ return { action: "handled" };
39
+ }
40
+
41
+ return { action: "continue" };
42
+ });
43
+ }
@@ -80,6 +80,6 @@ class ModalEditor extends CustomEditor {
80
80
 
81
81
  export default function (pi: ExtensionAPI) {
82
82
  pi.on("session_start", (_event, ctx) => {
83
- ctx.ui.setEditorComponent((_tui, theme, kb) => new ModalEditor(theme, kb));
83
+ ctx.ui.setEditorComponent((tui, theme, kb) => new ModalEditor(tui, theme, kb));
84
84
  });
85
85
  }
@@ -9,7 +9,7 @@
9
9
  */
10
10
 
11
11
  import type { ExtensionAPI, ExtensionCommandContext, Theme } from "@mariozechner/pi-coding-agent";
12
- import { matchesKey, visibleWidth } from "@mariozechner/pi-tui";
12
+ import { CURSOR_MARKER, type Focusable, matchesKey, visibleWidth } from "@mariozechner/pi-tui";
13
13
 
14
14
  export default function (pi: ExtensionAPI) {
15
15
  pi.registerCommand("overlay-test", {
@@ -28,9 +28,12 @@ export default function (pi: ExtensionAPI) {
28
28
  });
29
29
  }
30
30
 
31
- class OverlayTestComponent {
31
+ class OverlayTestComponent implements Focusable {
32
32
  readonly width = 70;
33
33
 
34
+ /** Focusable interface - set by TUI when focus changes */
35
+ focused = false;
36
+
34
37
  private selected = 0;
35
38
  private items = [
36
39
  { label: "Search", hasInput: true, text: "", cursor: 0 },
@@ -123,7 +126,9 @@ class OverlayTestComponent {
123
126
  const before = inputDisplay.slice(0, item.cursor);
124
127
  const cursorChar = item.cursor < inputDisplay.length ? inputDisplay[item.cursor] : " ";
125
128
  const after = inputDisplay.slice(item.cursor + 1);
126
- inputDisplay = `${before}\x1b[7m${cursorChar}\x1b[27m${after}`;
129
+ // Emit hardware cursor marker for IME support when focused
130
+ const marker = this.focused ? CURSOR_MARKER : "";
131
+ inputDisplay = `${before}${marker}\x1b[7m${cursorChar}\x1b[27m${after}`;
127
132
  }
128
133
  content = `${prefix + label} ${inputDisplay}`;
129
134
  } else {
@@ -90,7 +90,7 @@ export default function question(pi: ExtensionAPI) {
90
90
  noMatch: (t) => theme.fg("warning", t),
91
91
  },
92
92
  };
93
- const editor = new Editor(editorTheme);
93
+ const editor = new Editor(tui, editorTheme);
94
94
 
95
95
  editor.onSubmit = (value) => {
96
96
  const trimmed = value.trim();
@@ -119,7 +119,7 @@ export default function questionnaire(pi: ExtensionAPI) {
119
119
  noMatch: (t) => theme.fg("warning", t),
120
120
  },
121
121
  };
122
- const editor = new Editor(editorTheme);
122
+ const editor = new Editor(tui, editorTheme);
123
123
 
124
124
  // Helpers
125
125
  function refresh() {
@@ -4,8 +4,7 @@
4
4
  * Usage: pi --extension ./examples/extensions/rainbow-editor.ts
5
5
  */
6
6
 
7
- import { CustomEditor, type ExtensionAPI, type KeybindingsManager } from "@mariozechner/pi-coding-agent";
8
- import type { EditorTheme, TUI } from "@mariozechner/pi-tui";
7
+ import { CustomEditor, type ExtensionAPI } from "@mariozechner/pi-coding-agent";
9
8
 
10
9
  // Base colors (coral → yellow → green → teal → blue → purple → pink)
11
10
  const COLORS: [number, number, number][] = [
@@ -44,14 +43,8 @@ function colorize(text: string, shinePos: number): string {
44
43
 
45
44
  class RainbowEditor extends CustomEditor {
46
45
  private animationTimer?: ReturnType<typeof setInterval>;
47
- private tui: TUI;
48
46
  private frame = 0;
49
47
 
50
- constructor(tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager) {
51
- super(theme, keybindings);
52
- this.tui = tui;
53
- }
54
-
55
48
  private hasUltrathink(): boolean {
56
49
  return /ultrathink/i.test(this.getText());
57
50
  }
@@ -5,6 +5,7 @@
5
5
  import * as fs from "node:fs";
6
6
  import * as os from "node:os";
7
7
  import * as path from "node:path";
8
+ import { parseFrontmatter } from "@mariozechner/pi-coding-agent";
8
9
 
9
10
  export type AgentScope = "user" | "project" | "both";
10
11
 
@@ -23,36 +24,6 @@ export interface AgentDiscoveryResult {
23
24
  projectAgentsDir: string | null;
24
25
  }
25
26
 
26
- function parseFrontmatter(content: string): { frontmatter: Record<string, string>; body: string } {
27
- const frontmatter: Record<string, string> = {};
28
- const normalized = content.replace(/\r\n/g, "\n");
29
-
30
- if (!normalized.startsWith("---")) {
31
- return { frontmatter, body: normalized };
32
- }
33
-
34
- const endIndex = normalized.indexOf("\n---", 3);
35
- if (endIndex === -1) {
36
- return { frontmatter, body: normalized };
37
- }
38
-
39
- const frontmatterBlock = normalized.slice(4, endIndex);
40
- const body = normalized.slice(endIndex + 4).trim();
41
-
42
- for (const line of frontmatterBlock.split("\n")) {
43
- const match = line.match(/^([\w-]+):\s*(.*)$/);
44
- if (match) {
45
- let value = match[2].trim();
46
- if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
47
- value = value.slice(1, -1);
48
- }
49
- frontmatter[match[1]] = value;
50
- }
51
- }
52
-
53
- return { frontmatter, body };
54
- }
55
-
56
27
  function loadAgentsFromDir(dir: string, source: "user" | "project"): AgentConfig[] {
57
28
  const agents: AgentConfig[] = [];
58
29
 
@@ -79,7 +50,7 @@ function loadAgentsFromDir(dir: string, source: "user" | "project"): AgentConfig
79
50
  continue;
80
51
  }
81
52
 
82
- const { frontmatter, body } = parseFrontmatter(content);
53
+ const { frontmatter, body } = parseFrontmatter<Record<string, string>>(content);
83
54
 
84
55
  if (!frontmatter.name || !frontmatter.description) {
85
56
  continue;
@@ -87,7 +58,7 @@ function loadAgentsFromDir(dir: string, source: "user" | "project"): AgentConfig
87
58
 
88
59
  const tools = frontmatter.tools
89
60
  ?.split(",")
90
- .map((t) => t.trim())
61
+ .map((t: string) => t.trim())
91
62
  .filter(Boolean);
92
63
 
93
64
  agents.push({
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-with-deps",
3
- "version": "1.10.0",
3
+ "version": "1.11.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-with-deps",
9
- "version": "1.10.0",
9
+ "version": "1.11.0",
10
10
  "dependencies": {
11
11
  "ms": "^2.1.3"
12
12
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-with-deps",
3
3
  "private": true,
4
- "version": "1.10.0",
4
+ "version": "1.11.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mariozechner/pi-coding-agent",
3
- "version": "0.46.0",
3
+ "version": "0.47.0",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "piConfig": {
@@ -40,9 +40,10 @@
40
40
  "dependencies": {
41
41
  "@mariozechner/clipboard": "^0.3.0",
42
42
  "@mariozechner/jiti": "^2.6.2",
43
- "@mariozechner/pi-agent-core": "^0.46.0",
44
- "@mariozechner/pi-ai": "^0.46.0",
45
- "@mariozechner/pi-tui": "^0.46.0",
43
+ "@mariozechner/pi-agent-core": "^0.47.0",
44
+ "@mariozechner/pi-ai": "^0.47.0",
45
+ "@mariozechner/pi-tui": "^0.47.0",
46
+ "@silvia-odwyer/photon-node": "^0.3.4",
46
47
  "chalk": "^5.5.0",
47
48
  "cli-highlight": "^2.1.11",
48
49
  "diff": "^8.0.2",
@@ -51,7 +52,7 @@
51
52
  "marked": "^15.0.12",
52
53
  "minimatch": "^10.1.1",
53
54
  "proper-lockfile": "^4.1.2",
54
- "@silvia-odwyer/photon-node": "^0.3.4"
55
+ "yaml": "^2.8.2"
55
56
  },
56
57
  "devDependencies": {
57
58
  "@types/diff": "^7.0.2",