@mariozechner/pi-coding-agent 0.31.0 → 0.32.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 (116) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +56 -5
  3. package/dist/cli/file-processor.d.ts +5 -1
  4. package/dist/cli/file-processor.d.ts.map +1 -1
  5. package/dist/cli/file-processor.js +28 -8
  6. package/dist/cli/file-processor.js.map +1 -1
  7. package/dist/core/agent-session.d.ts +41 -16
  8. package/dist/core/agent-session.d.ts.map +1 -1
  9. package/dist/core/agent-session.js +90 -41
  10. package/dist/core/agent-session.js.map +1 -1
  11. package/dist/core/auth-storage.d.ts +6 -1
  12. package/dist/core/auth-storage.d.ts.map +1 -1
  13. package/dist/core/auth-storage.js +16 -1
  14. package/dist/core/auth-storage.js.map +1 -1
  15. package/dist/core/custom-tools/types.d.ts +1 -1
  16. package/dist/core/custom-tools/types.d.ts.map +1 -1
  17. package/dist/core/custom-tools/types.js.map +1 -1
  18. package/dist/core/hooks/index.d.ts +1 -1
  19. package/dist/core/hooks/index.d.ts.map +1 -1
  20. package/dist/core/hooks/index.js +1 -0
  21. package/dist/core/hooks/index.js.map +1 -1
  22. package/dist/core/hooks/loader.d.ts +4 -1
  23. package/dist/core/hooks/loader.d.ts.map +1 -1
  24. package/dist/core/hooks/loader.js +2 -2
  25. package/dist/core/hooks/loader.js.map +1 -1
  26. package/dist/core/hooks/runner.d.ts +2 -2
  27. package/dist/core/hooks/runner.d.ts.map +1 -1
  28. package/dist/core/hooks/runner.js +3 -3
  29. package/dist/core/hooks/runner.js.map +1 -1
  30. package/dist/core/hooks/types.d.ts +10 -4
  31. package/dist/core/hooks/types.d.ts.map +1 -1
  32. package/dist/core/hooks/types.js.map +1 -1
  33. package/dist/core/model-registry.d.ts +5 -2
  34. package/dist/core/model-registry.d.ts.map +1 -1
  35. package/dist/core/model-registry.js +85 -49
  36. package/dist/core/model-registry.js.map +1 -1
  37. package/dist/core/model-resolver.d.ts.map +1 -1
  38. package/dist/core/model-resolver.js +1 -0
  39. package/dist/core/model-resolver.js.map +1 -1
  40. package/dist/core/sdk.d.ts.map +1 -1
  41. package/dist/core/sdk.js +9 -6
  42. package/dist/core/sdk.js.map +1 -1
  43. package/dist/core/settings-manager.d.ts +17 -3
  44. package/dist/core/settings-manager.d.ts.map +1 -1
  45. package/dist/core/settings-manager.js +41 -6
  46. package/dist/core/settings-manager.js.map +1 -1
  47. package/dist/core/tools/index.d.ts +9 -4
  48. package/dist/core/tools/index.d.ts.map +1 -1
  49. package/dist/core/tools/index.js +6 -6
  50. package/dist/core/tools/index.js.map +1 -1
  51. package/dist/core/tools/read.d.ts +5 -1
  52. package/dist/core/tools/read.d.ts.map +1 -1
  53. package/dist/core/tools/read.js +22 -5
  54. package/dist/core/tools/read.js.map +1 -1
  55. package/dist/index.d.ts +3 -3
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +4 -2
  58. package/dist/index.js.map +1 -1
  59. package/dist/main.d.ts.map +1 -1
  60. package/dist/main.js +5 -5
  61. package/dist/main.js.map +1 -1
  62. package/dist/modes/interactive/components/custom-editor.d.ts +1 -0
  63. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  64. package/dist/modes/interactive/components/custom-editor.js +7 -1
  65. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  66. package/dist/modes/interactive/components/hook-editor.d.ts.map +1 -1
  67. package/dist/modes/interactive/components/hook-editor.js +3 -3
  68. package/dist/modes/interactive/components/hook-editor.js.map +1 -1
  69. package/dist/modes/interactive/components/hook-input.d.ts.map +1 -1
  70. package/dist/modes/interactive/components/hook-input.js +3 -3
  71. package/dist/modes/interactive/components/hook-input.js.map +1 -1
  72. package/dist/modes/interactive/components/hook-selector.d.ts.map +1 -1
  73. package/dist/modes/interactive/components/hook-selector.js +3 -3
  74. package/dist/modes/interactive/components/hook-selector.js.map +1 -1
  75. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  76. package/dist/modes/interactive/components/model-selector.js +8 -3
  77. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  78. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  79. package/dist/modes/interactive/components/oauth-selector.js +3 -3
  80. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  81. package/dist/modes/interactive/components/settings-selector.d.ts +8 -2
  82. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  83. package/dist/modes/interactive/components/settings-selector.js +37 -6
  84. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  85. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  86. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  87. package/dist/modes/interactive/interactive-mode.js +66 -19
  88. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  89. package/dist/modes/print-mode.d.ts.map +1 -1
  90. package/dist/modes/print-mode.js +3 -3
  91. package/dist/modes/print-mode.js.map +1 -1
  92. package/dist/modes/rpc/rpc-client.d.ts +12 -4
  93. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  94. package/dist/modes/rpc/rpc-client.js +18 -6
  95. package/dist/modes/rpc/rpc-client.js.map +1 -1
  96. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  97. package/dist/modes/rpc/rpc-mode.js +21 -12
  98. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  99. package/dist/modes/rpc/rpc-types.d.ts +25 -6
  100. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  101. package/dist/modes/rpc/rpc-types.js.map +1 -1
  102. package/dist/utils/image-resize.d.ts +29 -0
  103. package/dist/utils/image-resize.d.ts.map +1 -0
  104. package/dist/utils/image-resize.js +111 -0
  105. package/dist/utils/image-resize.js.map +1 -0
  106. package/docs/hooks.md +16 -9
  107. package/examples/README.md +6 -0
  108. package/examples/custom-tools/README.md +2 -0
  109. package/examples/hooks/README.md +1 -0
  110. package/examples/hooks/file-trigger.ts +1 -1
  111. package/examples/hooks/todo/index.ts +134 -0
  112. package/package.json +6 -5
  113. package/dist/modes/interactive/components/queue-mode-selector.d.ts +0 -10
  114. package/dist/modes/interactive/components/queue-mode-selector.d.ts.map +0 -1
  115. package/dist/modes/interactive/components/queue-mode-selector.js +0 -42
  116. package/dist/modes/interactive/components/queue-mode-selector.js.map +0 -1
@@ -25,7 +25,7 @@ export default function (pi: HookAPI) {
25
25
  content: `External trigger: ${content}`,
26
26
  display: true,
27
27
  },
28
- true, // triggerTurn - get LLM to respond
28
+ { triggerTurn: true }, // triggerTurn - get LLM to respond
29
29
  );
30
30
  fs.writeFileSync(triggerFile, ""); // Clear after reading
31
31
  }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Todo Hook - Companion to the todo custom tool
3
+ *
4
+ * Registers a /todos command that displays all todos on the current branch
5
+ * with a nice custom UI.
6
+ */
7
+
8
+ import type { HookAPI, Theme } from "@mariozechner/pi-coding-agent";
9
+ import { isCtrlC, isEscape, truncateToWidth } from "@mariozechner/pi-tui";
10
+
11
+ interface Todo {
12
+ id: number;
13
+ text: string;
14
+ done: boolean;
15
+ }
16
+
17
+ interface TodoDetails {
18
+ action: "list" | "add" | "toggle" | "clear";
19
+ todos: Todo[];
20
+ nextId: number;
21
+ error?: string;
22
+ }
23
+
24
+ class TodoListComponent {
25
+ private todos: Todo[];
26
+ private theme: Theme;
27
+ private onClose: () => void;
28
+ private cachedWidth?: number;
29
+ private cachedLines?: string[];
30
+
31
+ constructor(todos: Todo[], theme: Theme, onClose: () => void) {
32
+ this.todos = todos;
33
+ this.theme = theme;
34
+ this.onClose = onClose;
35
+ }
36
+
37
+ handleInput(data: string): void {
38
+ if (isEscape(data) || isCtrlC(data)) {
39
+ this.onClose();
40
+ }
41
+ }
42
+
43
+ render(width: number): string[] {
44
+ if (this.cachedLines && this.cachedWidth === width) {
45
+ return this.cachedLines;
46
+ }
47
+
48
+ const lines: string[] = [];
49
+ const th = this.theme;
50
+
51
+ // Header
52
+ lines.push("");
53
+ const title = th.fg("accent", " Todos ");
54
+ const headerLine =
55
+ th.fg("borderMuted", "─".repeat(3)) + title + th.fg("borderMuted", "─".repeat(Math.max(0, width - 10)));
56
+ lines.push(truncateToWidth(headerLine, width));
57
+ lines.push("");
58
+
59
+ if (this.todos.length === 0) {
60
+ lines.push(truncateToWidth(` ${th.fg("dim", "No todos yet. Ask the agent to add some!")}`, width));
61
+ } else {
62
+ // Stats
63
+ const done = this.todos.filter((t) => t.done).length;
64
+ const total = this.todos.length;
65
+ const statsText = ` ${th.fg("muted", `${done}/${total} completed`)}`;
66
+ lines.push(truncateToWidth(statsText, width));
67
+ lines.push("");
68
+
69
+ // Todo items
70
+ for (const todo of this.todos) {
71
+ const check = todo.done ? th.fg("success", "✓") : th.fg("dim", "○");
72
+ const id = th.fg("accent", `#${todo.id}`);
73
+ const text = todo.done ? th.fg("dim", todo.text) : th.fg("text", todo.text);
74
+ const line = ` ${check} ${id} ${text}`;
75
+ lines.push(truncateToWidth(line, width));
76
+ }
77
+ }
78
+
79
+ lines.push("");
80
+ lines.push(truncateToWidth(` ${th.fg("dim", "Press Escape to close")}`, width));
81
+ lines.push("");
82
+
83
+ this.cachedWidth = width;
84
+ this.cachedLines = lines;
85
+ return lines;
86
+ }
87
+
88
+ invalidate(): void {
89
+ this.cachedWidth = undefined;
90
+ this.cachedLines = undefined;
91
+ }
92
+ }
93
+
94
+ export default function (pi: HookAPI) {
95
+ /**
96
+ * Reconstruct todos from session entries on the current branch.
97
+ */
98
+ function getTodos(ctx: {
99
+ sessionManager: {
100
+ getBranch: () => Array<{ type: string; message?: { role?: string; toolName?: string; details?: unknown } }>;
101
+ };
102
+ }): Todo[] {
103
+ let todos: Todo[] = [];
104
+
105
+ for (const entry of ctx.sessionManager.getBranch()) {
106
+ if (entry.type !== "message") continue;
107
+ const msg = entry.message;
108
+ if (!msg || msg.role !== "toolResult" || msg.toolName !== "todo") continue;
109
+
110
+ const details = msg.details as TodoDetails | undefined;
111
+ if (details) {
112
+ todos = details.todos;
113
+ }
114
+ }
115
+
116
+ return todos;
117
+ }
118
+
119
+ pi.registerCommand("todos", {
120
+ description: "Show all todos on the current branch",
121
+ handler: async (_args, ctx) => {
122
+ if (!ctx.hasUI) {
123
+ ctx.ui.notify("/todos requires interactive mode", "error");
124
+ return;
125
+ }
126
+
127
+ const todos = getTodos(ctx);
128
+
129
+ await ctx.ui.custom<void>((_tui, theme, done) => {
130
+ return new TodoListComponent(todos, theme, () => done());
131
+ });
132
+ },
133
+ });
134
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mariozechner/pi-coding-agent",
3
- "version": "0.31.0",
3
+ "version": "0.32.0",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "piConfig": {
@@ -38,16 +38,17 @@
38
38
  "prepublishOnly": "npm run clean && npm run build"
39
39
  },
40
40
  "dependencies": {
41
- "@mariozechner/pi-agent-core": "^0.31.0",
42
- "@mariozechner/pi-ai": "^0.31.0",
43
- "@mariozechner/pi-tui": "^0.31.0",
41
+ "@mariozechner/pi-agent-core": "^0.32.0",
42
+ "@mariozechner/pi-ai": "^0.32.0",
43
+ "@mariozechner/pi-tui": "^0.32.0",
44
44
  "chalk": "^5.5.0",
45
45
  "cli-highlight": "^2.1.11",
46
46
  "diff": "^8.0.2",
47
47
  "file-type": "^21.1.1",
48
48
  "glob": "^11.0.3",
49
49
  "jiti": "^2.6.1",
50
- "marked": "^15.0.12"
50
+ "marked": "^15.0.12",
51
+ "sharp": "^0.34.2"
51
52
  },
52
53
  "devDependencies": {
53
54
  "@types/diff": "^7.0.2",
@@ -1,10 +0,0 @@
1
- import { Container, SelectList } from "@mariozechner/pi-tui";
2
- /**
3
- * Component that renders a queue mode selector with borders
4
- */
5
- export declare class QueueModeSelectorComponent extends Container {
6
- private selectList;
7
- constructor(currentMode: "all" | "one-at-a-time", onSelect: (mode: "all" | "one-at-a-time") => void, onCancel: () => void);
8
- getSelectList(): SelectList;
9
- }
10
- //# sourceMappingURL=queue-mode-selector.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"queue-mode-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/queue-mode-selector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAmB,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAI9E;;GAEG;AACH,qBAAa,0BAA2B,SAAQ,SAAS;IACxD,OAAO,CAAC,UAAU,CAAa;IAE/B,YACC,WAAW,EAAE,KAAK,GAAG,eAAe,EACpC,QAAQ,EAAE,CAAC,IAAI,EAAE,KAAK,GAAG,eAAe,KAAK,IAAI,EACjD,QAAQ,EAAE,MAAM,IAAI,EAqCpB;IAED,aAAa,IAAI,UAAU,CAE1B;CACD","sourcesContent":["import { Container, type SelectItem, SelectList } from \"@mariozechner/pi-tui\";\nimport { getSelectListTheme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\n\n/**\n * Component that renders a queue mode selector with borders\n */\nexport class QueueModeSelectorComponent extends Container {\n\tprivate selectList: SelectList;\n\n\tconstructor(\n\t\tcurrentMode: \"all\" | \"one-at-a-time\",\n\t\tonSelect: (mode: \"all\" | \"one-at-a-time\") => void,\n\t\tonCancel: () => void,\n\t) {\n\t\tsuper();\n\n\t\tconst queueModes: SelectItem[] = [\n\t\t\t{\n\t\t\t\tvalue: \"one-at-a-time\",\n\t\t\t\tlabel: \"one-at-a-time\",\n\t\t\t\tdescription: \"Process queued messages one by one (recommended)\",\n\t\t\t},\n\t\t\t{ value: \"all\", label: \"all\", description: \"Process all queued messages at once\" },\n\t\t];\n\n\t\t// Add top border\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Create selector\n\t\tthis.selectList = new SelectList(queueModes, 2, getSelectListTheme());\n\n\t\t// Preselect current mode\n\t\tconst currentIndex = queueModes.findIndex((item) => item.value === currentMode);\n\t\tif (currentIndex !== -1) {\n\t\t\tthis.selectList.setSelectedIndex(currentIndex);\n\t\t}\n\n\t\tthis.selectList.onSelect = (item) => {\n\t\t\tonSelect(item.value as \"all\" | \"one-at-a-time\");\n\t\t};\n\n\t\tthis.selectList.onCancel = () => {\n\t\t\tonCancel();\n\t\t};\n\n\t\tthis.addChild(this.selectList);\n\n\t\t// Add bottom border\n\t\tthis.addChild(new DynamicBorder());\n\t}\n\n\tgetSelectList(): SelectList {\n\t\treturn this.selectList;\n\t}\n}\n"]}
@@ -1,42 +0,0 @@
1
- import { Container, SelectList } from "@mariozechner/pi-tui";
2
- import { getSelectListTheme } from "../theme/theme.js";
3
- import { DynamicBorder } from "./dynamic-border.js";
4
- /**
5
- * Component that renders a queue mode selector with borders
6
- */
7
- export class QueueModeSelectorComponent extends Container {
8
- selectList;
9
- constructor(currentMode, onSelect, onCancel) {
10
- super();
11
- const queueModes = [
12
- {
13
- value: "one-at-a-time",
14
- label: "one-at-a-time",
15
- description: "Process queued messages one by one (recommended)",
16
- },
17
- { value: "all", label: "all", description: "Process all queued messages at once" },
18
- ];
19
- // Add top border
20
- this.addChild(new DynamicBorder());
21
- // Create selector
22
- this.selectList = new SelectList(queueModes, 2, getSelectListTheme());
23
- // Preselect current mode
24
- const currentIndex = queueModes.findIndex((item) => item.value === currentMode);
25
- if (currentIndex !== -1) {
26
- this.selectList.setSelectedIndex(currentIndex);
27
- }
28
- this.selectList.onSelect = (item) => {
29
- onSelect(item.value);
30
- };
31
- this.selectList.onCancel = () => {
32
- onCancel();
33
- };
34
- this.addChild(this.selectList);
35
- // Add bottom border
36
- this.addChild(new DynamicBorder());
37
- }
38
- getSelectList() {
39
- return this.selectList;
40
- }
41
- }
42
- //# sourceMappingURL=queue-mode-selector.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"queue-mode-selector.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/queue-mode-selector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAmB,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAC9E,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD;;GAEG;AACH,MAAM,OAAO,0BAA2B,SAAQ,SAAS;IAChD,UAAU,CAAa;IAE/B,YACC,WAAoC,EACpC,QAAiD,EACjD,QAAoB,EACnB;QACD,KAAK,EAAE,CAAC;QAER,MAAM,UAAU,GAAiB;YAChC;gBACC,KAAK,EAAE,eAAe;gBACtB,KAAK,EAAE,eAAe;gBACtB,WAAW,EAAE,kDAAkD;aAC/D;YACD,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,qCAAqC,EAAE;SAClF,CAAC;QAEF,iBAAiB;QACjB,IAAI,CAAC,QAAQ,CAAC,IAAI,aAAa,EAAE,CAAC,CAAC;QAEnC,kBAAkB;QAClB,IAAI,CAAC,UAAU,GAAG,IAAI,UAAU,CAAC,UAAU,EAAE,CAAC,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAEtE,yBAAyB;QACzB,MAAM,YAAY,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,KAAK,WAAW,CAAC,CAAC;QAChF,IAAI,YAAY,KAAK,CAAC,CAAC,EAAE,CAAC;YACzB,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,YAAY,CAAC,CAAC;QAChD,CAAC;QAED,IAAI,CAAC,UAAU,CAAC,QAAQ,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC;YACpC,QAAQ,CAAC,IAAI,CAAC,KAAgC,CAAC,CAAC;QAAA,CAChD,CAAC;QAEF,IAAI,CAAC,UAAU,CAAC,QAAQ,GAAG,GAAG,EAAE,CAAC;YAChC,QAAQ,EAAE,CAAC;QAAA,CACX,CAAC;QAEF,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAE/B,oBAAoB;QACpB,IAAI,CAAC,QAAQ,CAAC,IAAI,aAAa,EAAE,CAAC,CAAC;IAAA,CACnC;IAED,aAAa,GAAe;QAC3B,OAAO,IAAI,CAAC,UAAU,CAAC;IAAA,CACvB;CACD","sourcesContent":["import { Container, type SelectItem, SelectList } from \"@mariozechner/pi-tui\";\nimport { getSelectListTheme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\n\n/**\n * Component that renders a queue mode selector with borders\n */\nexport class QueueModeSelectorComponent extends Container {\n\tprivate selectList: SelectList;\n\n\tconstructor(\n\t\tcurrentMode: \"all\" | \"one-at-a-time\",\n\t\tonSelect: (mode: \"all\" | \"one-at-a-time\") => void,\n\t\tonCancel: () => void,\n\t) {\n\t\tsuper();\n\n\t\tconst queueModes: SelectItem[] = [\n\t\t\t{\n\t\t\t\tvalue: \"one-at-a-time\",\n\t\t\t\tlabel: \"one-at-a-time\",\n\t\t\t\tdescription: \"Process queued messages one by one (recommended)\",\n\t\t\t},\n\t\t\t{ value: \"all\", label: \"all\", description: \"Process all queued messages at once\" },\n\t\t];\n\n\t\t// Add top border\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Create selector\n\t\tthis.selectList = new SelectList(queueModes, 2, getSelectListTheme());\n\n\t\t// Preselect current mode\n\t\tconst currentIndex = queueModes.findIndex((item) => item.value === currentMode);\n\t\tif (currentIndex !== -1) {\n\t\t\tthis.selectList.setSelectedIndex(currentIndex);\n\t\t}\n\n\t\tthis.selectList.onSelect = (item) => {\n\t\t\tonSelect(item.value as \"all\" | \"one-at-a-time\");\n\t\t};\n\n\t\tthis.selectList.onCancel = () => {\n\t\t\tonCancel();\n\t\t};\n\n\t\tthis.addChild(this.selectList);\n\n\t\t// Add bottom border\n\t\tthis.addChild(new DynamicBorder());\n\t}\n\n\tgetSelectList(): SelectList {\n\t\treturn this.selectList;\n\t}\n}\n"]}