@oh-my-pi/pi-coding-agent 11.8.2 → 11.9.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 (141) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/docs/tui.md +9 -9
  3. package/package.json +7 -7
  4. package/src/capability/mcp.ts +9 -0
  5. package/src/cli/file-processor.ts +8 -13
  6. package/src/cli/oclif-help.ts +1 -1
  7. package/src/cli.ts +14 -0
  8. package/src/commit/git/index.ts +16 -16
  9. package/src/config/file-lock.ts +1 -1
  10. package/src/config/keybindings.ts +11 -11
  11. package/src/config/model-registry.ts +31 -66
  12. package/src/config/settings.ts +88 -95
  13. package/src/config.ts +2 -2
  14. package/src/cursor.ts +4 -4
  15. package/src/debug/index.ts +28 -28
  16. package/src/discovery/builtin.ts +48 -0
  17. package/src/discovery/codex.ts +5 -13
  18. package/src/discovery/cursor.ts +2 -7
  19. package/src/discovery/mcp-json.ts +33 -0
  20. package/src/exa/mcp-client.ts +2 -2
  21. package/src/exa/websets.ts +2 -2
  22. package/src/export/html/index.ts +3 -3
  23. package/src/export/ttsr.ts +27 -27
  24. package/src/extensibility/custom-tools/loader.ts +9 -9
  25. package/src/extensibility/extensions/runner.ts +64 -64
  26. package/src/extensibility/hooks/runner.ts +46 -46
  27. package/src/extensibility/plugins/manager.ts +49 -49
  28. package/src/extensibility/slash-commands.ts +1 -0
  29. package/src/index.ts +0 -3
  30. package/src/internal-urls/router.ts +5 -5
  31. package/src/ipy/kernel.ts +61 -57
  32. package/src/lsp/client.ts +1 -1
  33. package/src/lsp/clients/biome-client.ts +2 -2
  34. package/src/lsp/clients/lsp-linter-client.ts +7 -7
  35. package/src/lsp/index.ts +9 -9
  36. package/src/mcp/config-writer.ts +194 -0
  37. package/src/mcp/config.ts +20 -6
  38. package/src/mcp/index.ts +4 -0
  39. package/src/mcp/loader.ts +6 -0
  40. package/src/mcp/manager.ts +139 -50
  41. package/src/mcp/oauth-discovery.ts +274 -0
  42. package/src/mcp/oauth-flow.ts +229 -0
  43. package/src/mcp/tool-bridge.ts +20 -20
  44. package/src/mcp/transports/http.ts +107 -66
  45. package/src/mcp/transports/stdio.ts +74 -59
  46. package/src/mcp/types.ts +15 -1
  47. package/src/modes/components/assistant-message.ts +25 -25
  48. package/src/modes/components/bash-execution.ts +51 -51
  49. package/src/modes/components/bordered-loader.ts +7 -7
  50. package/src/modes/components/branch-summary-message.ts +7 -7
  51. package/src/modes/components/compaction-summary-message.ts +7 -7
  52. package/src/modes/components/countdown-timer.ts +15 -15
  53. package/src/modes/components/custom-editor.ts +22 -22
  54. package/src/modes/components/custom-message.ts +21 -21
  55. package/src/modes/components/dynamic-border.ts +3 -3
  56. package/src/modes/components/extensions/extension-dashboard.ts +72 -72
  57. package/src/modes/components/extensions/extension-list.ts +99 -97
  58. package/src/modes/components/extensions/inspector-panel.ts +26 -26
  59. package/src/modes/components/footer.ts +36 -36
  60. package/src/modes/components/history-search.ts +52 -52
  61. package/src/modes/components/hook-editor.ts +20 -20
  62. package/src/modes/components/hook-input.ts +20 -20
  63. package/src/modes/components/hook-message.ts +22 -22
  64. package/src/modes/components/hook-selector.ts +52 -52
  65. package/src/modes/components/index.ts +0 -1
  66. package/src/modes/components/login-dialog.ts +57 -57
  67. package/src/modes/components/mcp-add-wizard.ts +1286 -0
  68. package/src/modes/components/model-selector.ts +173 -173
  69. package/src/modes/components/oauth-selector.ts +45 -45
  70. package/src/modes/components/plugin-settings.ts +52 -52
  71. package/src/modes/components/python-execution.ts +53 -53
  72. package/src/modes/components/queue-mode-selector.ts +7 -7
  73. package/src/modes/components/read-tool-group.ts +23 -23
  74. package/src/modes/components/session-selector.ts +40 -37
  75. package/src/modes/components/settings-selector.ts +80 -80
  76. package/src/modes/components/show-images-selector.ts +7 -7
  77. package/src/modes/components/skill-message.ts +27 -27
  78. package/src/modes/components/status-line-segment-editor.ts +81 -81
  79. package/src/modes/components/status-line.ts +73 -73
  80. package/src/modes/components/theme-selector.ts +11 -11
  81. package/src/modes/components/thinking-selector.ts +7 -7
  82. package/src/modes/components/todo-display.ts +19 -19
  83. package/src/modes/components/todo-reminder.ts +9 -9
  84. package/src/modes/components/tool-execution.ts +212 -216
  85. package/src/modes/components/tree-selector.ts +144 -144
  86. package/src/modes/components/ttsr-notification.ts +17 -17
  87. package/src/modes/components/user-message-selector.ts +18 -18
  88. package/src/modes/components/welcome.ts +10 -10
  89. package/src/modes/controllers/command-controller.ts +0 -7
  90. package/src/modes/controllers/event-controller.ts +23 -23
  91. package/src/modes/controllers/extension-ui-controller.ts +13 -13
  92. package/src/modes/controllers/input-controller.ts +12 -9
  93. package/src/modes/controllers/mcp-command-controller.ts +1223 -0
  94. package/src/modes/interactive-mode.ts +240 -241
  95. package/src/modes/rpc/rpc-client.ts +77 -77
  96. package/src/modes/rpc/rpc-mode.ts +5 -5
  97. package/src/modes/theme/theme.ts +113 -113
  98. package/src/modes/types.ts +1 -1
  99. package/src/patch/index.ts +45 -45
  100. package/src/prompts/tools/task.md +22 -2
  101. package/src/sdk.ts +1 -0
  102. package/src/session/agent-session.ts +512 -476
  103. package/src/session/agent-storage.ts +72 -75
  104. package/src/session/auth-storage.ts +186 -252
  105. package/src/session/history-storage.ts +36 -38
  106. package/src/session/session-manager.ts +300 -299
  107. package/src/session/session-storage.ts +65 -90
  108. package/src/ssh/connection-manager.ts +9 -9
  109. package/src/system-prompt.ts +2 -3
  110. package/src/task/agents.ts +1 -1
  111. package/src/task/executor.ts +28 -40
  112. package/src/task/index.ts +13 -12
  113. package/src/task/subprocess-tool-registry.ts +5 -5
  114. package/src/task/worktree.ts +8 -5
  115. package/src/tools/ask.ts +7 -7
  116. package/src/tools/bash.ts +15 -10
  117. package/src/tools/browser.ts +130 -127
  118. package/src/tools/calculator.ts +46 -46
  119. package/src/tools/context.ts +9 -9
  120. package/src/tools/exit-plan-mode.ts +5 -5
  121. package/src/tools/fetch.ts +5 -5
  122. package/src/tools/find.ts +16 -16
  123. package/src/tools/grep.ts +12 -24
  124. package/src/tools/index.ts +1 -1
  125. package/src/tools/notebook.ts +6 -6
  126. package/src/tools/output-meta.ts +10 -2
  127. package/src/tools/python.ts +12 -11
  128. package/src/tools/read.ts +17 -17
  129. package/src/tools/ssh.ts +9 -9
  130. package/src/tools/submit-result.ts +13 -13
  131. package/src/tools/todo-write.ts +6 -6
  132. package/src/tools/write.ts +10 -10
  133. package/src/tui/output-block.ts +6 -6
  134. package/src/tui/utils.ts +9 -9
  135. package/src/utils/event-bus.ts +13 -11
  136. package/src/utils/frontmatter.ts +1 -1
  137. package/src/utils/ignore-files.ts +1 -1
  138. package/src/web/search/index.ts +5 -5
  139. package/src/web/search/providers/anthropic.ts +7 -2
  140. package/examples/hooks/snake.ts +0 -342
  141. package/src/modes/components/armin.ts +0 -379
@@ -6,7 +6,7 @@ import { DynamicBorder } from "./dynamic-border";
6
6
  * Component that renders a queue mode selector with borders
7
7
  */
8
8
  export class QueueModeSelectorComponent extends Container {
9
- private selectList: SelectList;
9
+ #selectList: SelectList;
10
10
 
11
11
  constructor(
12
12
  currentMode: "all" | "one-at-a-time",
@@ -28,29 +28,29 @@ export class QueueModeSelectorComponent extends Container {
28
28
  this.addChild(new DynamicBorder());
29
29
 
30
30
  // Create selector
31
- this.selectList = new SelectList(queueModes, 2, getSelectListTheme());
31
+ this.#selectList = new SelectList(queueModes, 2, getSelectListTheme());
32
32
 
33
33
  // Preselect current mode
34
34
  const currentIndex = queueModes.findIndex(item => item.value === currentMode);
35
35
  if (currentIndex !== -1) {
36
- this.selectList.setSelectedIndex(currentIndex);
36
+ this.#selectList.setSelectedIndex(currentIndex);
37
37
  }
38
38
 
39
- this.selectList.onSelect = item => {
39
+ this.#selectList.onSelect = item => {
40
40
  onSelect(item.value as "all" | "one-at-a-time");
41
41
  };
42
42
 
43
- this.selectList.onCancel = () => {
43
+ this.#selectList.onCancel = () => {
44
44
  onCancel();
45
45
  };
46
46
 
47
- this.addChild(this.selectList);
47
+ this.addChild(this.#selectList);
48
48
 
49
49
  // Add bottom border
50
50
  this.addChild(new DynamicBorder());
51
51
  }
52
52
 
53
53
  getSelectList(): SelectList {
54
- return this.selectList;
54
+ return this.#selectList;
55
55
  }
56
56
  }
@@ -20,20 +20,20 @@ type ReadEntry = {
20
20
  };
21
21
 
22
22
  export class ReadToolGroupComponent extends Container implements ToolExecutionHandle {
23
- private entries = new Map<string, ReadEntry>();
24
- private text: Text;
23
+ #entries = new Map<string, ReadEntry>();
24
+ #text: Text;
25
25
 
26
26
  constructor() {
27
27
  super();
28
- this.text = new Text("", 0, 0);
29
- this.addChild(this.text);
30
- this.updateDisplay();
28
+ this.#text = new Text("", 0, 0);
29
+ this.addChild(this.#text);
30
+ this.#updateDisplay();
31
31
  }
32
32
 
33
33
  updateArgs(args: ReadRenderArgs, toolCallId?: string): void {
34
34
  if (!toolCallId) return;
35
35
  const rawPath = args.file_path || args.path || "";
36
- const entry: ReadEntry = this.entries.get(toolCallId) ?? {
36
+ const entry: ReadEntry = this.#entries.get(toolCallId) ?? {
37
37
  toolCallId,
38
38
  path: rawPath,
39
39
  offset: args.offset,
@@ -43,8 +43,8 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
43
43
  entry.path = rawPath;
44
44
  entry.offset = args.offset;
45
45
  entry.limit = args.limit;
46
- this.entries.set(toolCallId, entry);
47
- this.updateDisplay();
46
+ this.#entries.set(toolCallId, entry);
47
+ this.#updateDisplay();
48
48
  }
49
49
 
50
50
  updateResult(
@@ -53,38 +53,38 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
53
53
  toolCallId?: string,
54
54
  ): void {
55
55
  if (!toolCallId) return;
56
- const entry = this.entries.get(toolCallId);
56
+ const entry = this.#entries.get(toolCallId);
57
57
  if (!entry) return;
58
58
  if (isPartial) return;
59
59
  entry.status = result.isError ? "error" : "success";
60
- this.updateDisplay();
60
+ this.#updateDisplay();
61
61
  }
62
62
 
63
63
  setArgsComplete(_toolCallId?: string): void {
64
- this.updateDisplay();
64
+ this.#updateDisplay();
65
65
  }
66
66
 
67
67
  setExpanded(_expanded: boolean): void {
68
- this.updateDisplay();
68
+ this.#updateDisplay();
69
69
  }
70
70
 
71
71
  getComponent(): Component {
72
72
  return this;
73
73
  }
74
74
 
75
- private updateDisplay(): void {
76
- const entries = [...this.entries.values()];
75
+ #updateDisplay(): void {
76
+ const entries = [...this.#entries.values()];
77
77
 
78
78
  if (entries.length === 0) {
79
- this.text.setText(` ${theme.format.bullet} ${theme.fg("toolTitle", theme.bold("Read"))}`);
79
+ this.#text.setText(` ${theme.format.bullet} ${theme.fg("toolTitle", theme.bold("Read"))}`);
80
80
  return;
81
81
  }
82
82
 
83
83
  if (entries.length === 1) {
84
84
  const entry = entries[0];
85
- const statusSymbol = this.formatStatus(entry.status);
86
- const pathDisplay = this.formatPath(entry);
87
- this.text.setText(
85
+ const statusSymbol = this.#formatStatus(entry.status);
86
+ const pathDisplay = this.#formatPath(entry);
87
+ this.#text.setText(
88
88
  ` ${theme.format.bullet} ${theme.fg("toolTitle", theme.bold("Read"))} ${pathDisplay} ${statusSymbol}`.trimEnd(),
89
89
  );
90
90
  return;
@@ -95,15 +95,15 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
95
95
  const total = entries.length;
96
96
  for (const [index, entry] of entries.entries()) {
97
97
  const connector = index === total - 1 ? theme.tree.last : theme.tree.branch;
98
- const statusSymbol = this.formatStatus(entry.status);
99
- const pathDisplay = this.formatPath(entry);
98
+ const statusSymbol = this.#formatStatus(entry.status);
99
+ const pathDisplay = this.#formatPath(entry);
100
100
  lines.push(` ${theme.fg("dim", connector)} ${statusSymbol} ${pathDisplay}`.trimEnd());
101
101
  }
102
102
 
103
- this.text.setText(lines.join("\n"));
103
+ this.#text.setText(lines.join("\n"));
104
104
  }
105
105
 
106
- private formatPath(entry: ReadEntry): string {
106
+ #formatPath(entry: ReadEntry): string {
107
107
  const filePath = shortenPath(entry.path);
108
108
  let pathDisplay = filePath ? theme.fg("accent", filePath) : theme.fg("toolOutput", "…");
109
109
  if (entry.offset !== undefined || entry.limit !== undefined) {
@@ -114,7 +114,7 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
114
114
  return pathDisplay;
115
115
  }
116
116
 
117
- private formatStatus(status: ReadEntry["status"]): string {
117
+ #formatStatus(status: ReadEntry["status"]): string {
118
118
  if (status === "success") {
119
119
  return theme.fg("success", theme.status.success);
120
120
  }
@@ -18,25 +18,25 @@ import { DynamicBorder } from "./dynamic-border";
18
18
  * Custom session list component with multi-line items and search
19
19
  */
20
20
  class SessionList implements Component {
21
- private filteredSessions: SessionInfo[] = [];
22
- private selectedIndex: number = 0;
23
- private readonly searchInput: Input;
24
- public onSelect?: (sessionPath: string) => void;
25
- public onCancel?: () => void;
26
- public onExit: () => void = () => {};
27
- private maxVisible: number = 5; // Max sessions visible (each session is 3 lines: msg + metadata + blank)
21
+ #filteredSessions: SessionInfo[] = [];
22
+ #selectedIndex: number = 0;
23
+ readonly #searchInput: Input;
24
+ onSelect?: (sessionPath: string) => void;
25
+ onCancel?: () => void;
26
+ onExit: () => void = () => {};
27
+ #maxVisible: number = 5; // Max sessions visible (each session is 3 lines: msg + metadata + blank)
28
28
 
29
29
  constructor(
30
30
  private readonly allSessions: SessionInfo[],
31
31
  private readonly showCwd = false,
32
32
  ) {
33
- this.filteredSessions = allSessions;
34
- this.searchInput = new Input();
33
+ this.#filteredSessions = allSessions;
34
+ this.#searchInput = new Input();
35
35
 
36
36
  // Handle Enter in search input - select current item
37
- this.searchInput.onSubmit = () => {
38
- if (this.filteredSessions[this.selectedIndex]) {
39
- const selected = this.filteredSessions[this.selectedIndex];
37
+ this.#searchInput.onSubmit = () => {
38
+ if (this.#filteredSessions[this.#selectedIndex]) {
39
+ const selected = this.#filteredSessions[this.#selectedIndex];
40
40
  if (this.onSelect) {
41
41
  this.onSelect(selected.path);
42
42
  }
@@ -44,8 +44,8 @@ class SessionList implements Component {
44
44
  };
45
45
  }
46
46
 
47
- private filterSessions(query: string): void {
48
- this.filteredSessions = fuzzyFilter(this.allSessions, query, session => {
47
+ #filterSessions(query: string): void {
48
+ this.#filteredSessions = fuzzyFilter(this.allSessions, query, session => {
49
49
  const parts = [
50
50
  session.id,
51
51
  session.title ?? "",
@@ -56,7 +56,7 @@ class SessionList implements Component {
56
56
  ];
57
57
  return parts.filter(Boolean).join(" ");
58
58
  });
59
- this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredSessions.length - 1));
59
+ this.#selectedIndex = Math.min(this.#selectedIndex, Math.max(0, this.#filteredSessions.length - 1));
60
60
  }
61
61
 
62
62
  invalidate(): void {
@@ -67,10 +67,10 @@ class SessionList implements Component {
67
67
  const lines: string[] = [];
68
68
 
69
69
  // Render search input
70
- lines.push(...this.searchInput.render(width));
70
+ lines.push(...this.#searchInput.render(width));
71
71
  lines.push(""); // Blank line after search
72
72
 
73
- if (this.filteredSessions.length === 0) {
73
+ if (this.#filteredSessions.length === 0) {
74
74
  if (this.showCwd) {
75
75
  // "All" scope - no sessions anywhere that match filter
76
76
  lines.push(truncateToWidth(theme.fg("muted", " No sessions found"), width));
@@ -103,14 +103,17 @@ class SessionList implements Component {
103
103
  // Calculate visible range with scrolling
104
104
  const startIndex = Math.max(
105
105
  0,
106
- Math.min(this.selectedIndex - Math.floor(this.maxVisible / 2), this.filteredSessions.length - this.maxVisible),
106
+ Math.min(
107
+ this.#selectedIndex - Math.floor(this.#maxVisible / 2),
108
+ this.#filteredSessions.length - this.#maxVisible,
109
+ ),
107
110
  );
108
- const endIndex = Math.min(startIndex + this.maxVisible, this.filteredSessions.length);
111
+ const endIndex = Math.min(startIndex + this.#maxVisible, this.#filteredSessions.length);
109
112
 
110
113
  // Render visible sessions (2-3 lines per session + blank line)
111
114
  for (let i = startIndex; i < endIndex; i++) {
112
- const session = this.filteredSessions[i];
113
- const isSelected = i === this.selectedIndex;
115
+ const session = this.#filteredSessions[i];
116
+ const isSelected = i === this.#selectedIndex;
114
117
 
115
118
  // Normalize first message to single line
116
119
  const normalizedMessage = session.firstMessage.replace(/\n/g, " ").trim();
@@ -148,8 +151,8 @@ class SessionList implements Component {
148
151
  }
149
152
 
150
153
  // Add scroll indicator if needed
151
- if (startIndex > 0 || endIndex < this.filteredSessions.length) {
152
- const scrollText = ` (${this.selectedIndex + 1}/${this.filteredSessions.length})`;
154
+ if (startIndex > 0 || endIndex < this.#filteredSessions.length) {
155
+ const scrollText = ` (${this.#selectedIndex + 1}/${this.#filteredSessions.length})`;
153
156
  const scrollInfo = theme.fg("muted", truncateToWidth(scrollText, width));
154
157
  lines.push(scrollInfo);
155
158
  }
@@ -160,23 +163,23 @@ class SessionList implements Component {
160
163
  handleInput(keyData: string): void {
161
164
  // Up arrow
162
165
  if (matchesKey(keyData, "up")) {
163
- this.selectedIndex = Math.max(0, this.selectedIndex - 1);
166
+ this.#selectedIndex = Math.max(0, this.#selectedIndex - 1);
164
167
  }
165
168
  // Down arrow
166
169
  else if (matchesKey(keyData, "down")) {
167
- this.selectedIndex = Math.min(this.filteredSessions.length - 1, this.selectedIndex + 1);
170
+ this.#selectedIndex = Math.min(this.#filteredSessions.length - 1, this.#selectedIndex + 1);
168
171
  }
169
172
  // Page up - jump up by maxVisible items
170
173
  else if (matchesKey(keyData, "pageUp")) {
171
- this.selectedIndex = Math.max(0, this.selectedIndex - this.maxVisible);
174
+ this.#selectedIndex = Math.max(0, this.#selectedIndex - this.#maxVisible);
172
175
  }
173
176
  // Page down - jump down by maxVisible items
174
177
  else if (matchesKey(keyData, "pageDown")) {
175
- this.selectedIndex = Math.min(this.filteredSessions.length - 1, this.selectedIndex + this.maxVisible);
178
+ this.#selectedIndex = Math.min(this.#filteredSessions.length - 1, this.#selectedIndex + this.#maxVisible);
176
179
  }
177
180
  // Enter
178
181
  else if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
179
- const selected = this.filteredSessions[this.selectedIndex];
182
+ const selected = this.#filteredSessions[this.#selectedIndex];
180
183
  if (selected && this.onSelect) {
181
184
  this.onSelect(selected.path);
182
185
  }
@@ -193,8 +196,8 @@ class SessionList implements Component {
193
196
  }
194
197
  // Pass everything else to search input
195
198
  else {
196
- this.searchInput.handleInput(keyData);
197
- this.filterSessions(this.searchInput.getValue());
199
+ this.#searchInput.handleInput(keyData);
200
+ this.#filterSessions(this.#searchInput.getValue());
198
201
  }
199
202
  }
200
203
  }
@@ -203,7 +206,7 @@ class SessionList implements Component {
203
206
  * Component that renders a session selector
204
207
  */
205
208
  export class SessionSelectorComponent extends Container {
206
- private sessionList: SessionList;
209
+ #sessionList: SessionList;
207
210
 
208
211
  constructor(
209
212
  sessions: SessionInfo[],
@@ -221,12 +224,12 @@ export class SessionSelectorComponent extends Container {
221
224
  this.addChild(new Spacer(1));
222
225
 
223
226
  // Create session list
224
- this.sessionList = new SessionList(sessions);
225
- this.sessionList.onSelect = onSelect;
226
- this.sessionList.onCancel = onCancel;
227
- this.sessionList.onExit = onExit;
227
+ this.#sessionList = new SessionList(sessions);
228
+ this.#sessionList.onSelect = onSelect;
229
+ this.#sessionList.onCancel = onCancel;
230
+ this.#sessionList.onExit = onExit;
228
231
 
229
- this.addChild(this.sessionList);
232
+ this.addChild(this.#sessionList);
230
233
 
231
234
  // Add bottom border
232
235
  this.addChild(new Spacer(1));
@@ -234,6 +237,6 @@ export class SessionSelectorComponent extends Container {
234
237
  }
235
238
 
236
239
  getSessionList(): SessionList {
237
- return this.sessionList;
240
+ return this.#sessionList;
238
241
  }
239
242
  }