@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
@@ -7,8 +7,8 @@ import { theme } from "../../modes/theme/theme";
7
7
  * Shows when a rule violation is detected and the stream is being rewound.
8
8
  */
9
9
  export class TtsrNotificationComponent extends Container {
10
- private box: Box;
11
- private _expanded = false;
10
+ #box: Box;
11
+ #expanded = false;
12
12
 
13
13
  constructor(private readonly rules: Rule[]) {
14
14
  super();
@@ -16,25 +16,25 @@ export class TtsrNotificationComponent extends Container {
16
16
  this.addChild(new Spacer(1));
17
17
 
18
18
  // Use inverse warning color for yellow background effect
19
- this.box = new Box(1, 1, t => theme.inverse(theme.fg("warning", t)));
20
- this.addChild(this.box);
19
+ this.#box = new Box(1, 1, t => theme.inverse(theme.fg("warning", t)));
20
+ this.addChild(this.#box);
21
21
 
22
- this.rebuild();
22
+ this.#rebuild();
23
23
  }
24
24
 
25
25
  setExpanded(expanded: boolean): void {
26
- if (this._expanded !== expanded) {
27
- this._expanded = expanded;
28
- this.rebuild();
26
+ if (this.#expanded !== expanded) {
27
+ this.#expanded = expanded;
28
+ this.#rebuild();
29
29
  }
30
30
  }
31
31
 
32
32
  isExpanded(): boolean {
33
- return this._expanded;
33
+ return this.#expanded;
34
34
  }
35
35
 
36
- private rebuild(): void {
37
- this.box.clear();
36
+ #rebuild(): void {
37
+ this.#box.clear();
38
38
 
39
39
  // Build header: warning symbol + rule name + rewind icon
40
40
  const ruleNames = this.rules.map(r => theme.bold(r.name)).join(", ");
@@ -44,16 +44,16 @@ export class TtsrNotificationComponent extends Container {
44
44
  // Create header with rewind icon on the right
45
45
  const rewindIcon = theme.icon.rewind;
46
46
 
47
- this.box.addChild(new Text(`${header} ${rewindIcon}`, 0, 0));
47
+ this.#box.addChild(new Text(`${header} ${rewindIcon}`, 0, 0));
48
48
 
49
49
  // Show description(s) - italic and truncated
50
50
  for (const rule of this.rules) {
51
51
  const desc = rule.description || rule.content;
52
52
  if (desc) {
53
- this.box.addChild(new Spacer(1));
53
+ this.#box.addChild(new Spacer(1));
54
54
 
55
55
  let displayText = desc.trim();
56
- if (!this._expanded) {
56
+ if (!this.#expanded) {
57
57
  // Truncate to first 2 lines
58
58
  const lines = displayText.split("\n");
59
59
  if (lines.length > 2) {
@@ -62,18 +62,18 @@ export class TtsrNotificationComponent extends Container {
62
62
  }
63
63
 
64
64
  // Use italic for subtle distinction (fg colors conflict with inverse)
65
- this.box.addChild(new Text(theme.italic(displayText), 0, 0));
65
+ this.#box.addChild(new Text(theme.italic(displayText), 0, 0));
66
66
  }
67
67
  }
68
68
 
69
69
  // Show expand hint if collapsed and there's more content
70
- if (!this._expanded) {
70
+ if (!this.#expanded) {
71
71
  const hasMoreContent = this.rules.some(r => {
72
72
  const desc = r.description || r.content;
73
73
  return desc && desc.split("\n").length > 2;
74
74
  });
75
75
  if (hasMoreContent) {
76
- this.box.addChild(new Text(theme.italic(" (ctrl+o to expand)"), 0, 0));
76
+ this.#box.addChild(new Text(theme.italic(" (ctrl+o to expand)"), 0, 0));
77
77
  }
78
78
  }
79
79
  }
@@ -12,15 +12,15 @@ interface UserMessageItem {
12
12
  * Custom user message list component with selection
13
13
  */
14
14
  class UserMessageList implements Component {
15
- private selectedIndex: number = 0;
16
- public onSelect?: (entryId: string) => void;
17
- public onCancel?: () => void;
18
- private maxVisible: number = 10; // Max messages visible
15
+ #selectedIndex: number = 0;
16
+ onSelect?: (entryId: string) => void;
17
+ onCancel?: () => void;
18
+ #maxVisible: number = 10; // Max messages visible
19
19
 
20
20
  constructor(private readonly messages: UserMessageItem[]) {
21
21
  // Store messages in chronological order (oldest to newest)
22
22
  // Start with the last (most recent) message selected
23
- this.selectedIndex = Math.max(0, this.messages.length - 1);
23
+ this.#selectedIndex = Math.max(0, this.messages.length - 1);
24
24
  }
25
25
 
26
26
  invalidate(): void {
@@ -38,14 +38,14 @@ class UserMessageList implements Component {
38
38
  // Calculate visible range with scrolling
39
39
  const startIndex = Math.max(
40
40
  0,
41
- Math.min(this.selectedIndex - Math.floor(this.maxVisible / 2), this.messages.length - this.maxVisible),
41
+ Math.min(this.#selectedIndex - Math.floor(this.#maxVisible / 2), this.messages.length - this.#maxVisible),
42
42
  );
43
- const endIndex = Math.min(startIndex + this.maxVisible, this.messages.length);
43
+ const endIndex = Math.min(startIndex + this.#maxVisible, this.messages.length);
44
44
 
45
45
  // Render visible messages (2 lines per message + blank line)
46
46
  for (let i = startIndex; i < endIndex; i++) {
47
47
  const message = this.messages[i];
48
- const isSelected = i === this.selectedIndex;
48
+ const isSelected = i === this.#selectedIndex;
49
49
 
50
50
  // Normalize message to single line
51
51
  const normalizedMessage = message.text.replace(/\n/g, " ").trim();
@@ -68,7 +68,7 @@ class UserMessageList implements Component {
68
68
 
69
69
  // Add scroll indicator if needed
70
70
  if (startIndex > 0 || endIndex < this.messages.length) {
71
- const scrollInfo = theme.fg("muted", ` (${this.selectedIndex + 1}/${this.messages.length})`);
71
+ const scrollInfo = theme.fg("muted", ` (${this.#selectedIndex + 1}/${this.messages.length})`);
72
72
  lines.push(scrollInfo);
73
73
  }
74
74
 
@@ -78,15 +78,15 @@ class UserMessageList implements Component {
78
78
  handleInput(keyData: string): void {
79
79
  // Up arrow - go to previous (older) message, wrap to bottom when at top
80
80
  if (matchesKey(keyData, "up")) {
81
- this.selectedIndex = this.selectedIndex === 0 ? this.messages.length - 1 : this.selectedIndex - 1;
81
+ this.#selectedIndex = this.#selectedIndex === 0 ? this.messages.length - 1 : this.#selectedIndex - 1;
82
82
  }
83
83
  // Down arrow - go to next (newer) message, wrap to top when at bottom
84
84
  else if (matchesKey(keyData, "down")) {
85
- this.selectedIndex = this.selectedIndex === this.messages.length - 1 ? 0 : this.selectedIndex + 1;
85
+ this.#selectedIndex = this.#selectedIndex === this.messages.length - 1 ? 0 : this.#selectedIndex + 1;
86
86
  }
87
87
  // Enter - select message and branch
88
88
  else if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
89
- const selected = this.messages[this.selectedIndex];
89
+ const selected = this.messages[this.#selectedIndex];
90
90
  if (selected && this.onSelect) {
91
91
  this.onSelect(selected.id);
92
92
  }
@@ -110,7 +110,7 @@ class UserMessageList implements Component {
110
110
  * Component that renders a user message selector for branching
111
111
  */
112
112
  export class UserMessageSelectorComponent extends Container {
113
- private messageList: UserMessageList;
113
+ #messageList: UserMessageList;
114
114
 
115
115
  constructor(messages: UserMessageItem[], onSelect: (entryId: string) => void, onCancel: () => void) {
116
116
  super();
@@ -124,11 +124,11 @@ export class UserMessageSelectorComponent extends Container {
124
124
  this.addChild(new Spacer(1));
125
125
 
126
126
  // Create message list
127
- this.messageList = new UserMessageList(messages);
128
- this.messageList.onSelect = onSelect;
129
- this.messageList.onCancel = onCancel;
127
+ this.#messageList = new UserMessageList(messages);
128
+ this.#messageList.onSelect = onSelect;
129
+ this.#messageList.onCancel = onCancel;
130
130
 
131
- this.addChild(this.messageList);
131
+ this.addChild(this.#messageList);
132
132
 
133
133
  // Add bottom border
134
134
  this.addChild(new Spacer(1));
@@ -141,6 +141,6 @@ export class UserMessageSelectorComponent extends Container {
141
141
  }
142
142
 
143
143
  getMessageList(): UserMessageList {
144
- return this.messageList;
144
+ return this.#messageList;
145
145
  }
146
146
  }
@@ -53,17 +53,17 @@ export class WelcomeComponent implements Component {
53
53
  const piLogo = ["▀████████████▀", " ╘███ ███ ", " ███ ███ ", " ███ ███ ", " ▄███▄ ▄███▄ "];
54
54
 
55
55
  // Apply gradient to logo
56
- const logoColored = piLogo.map(line => this.gradientLine(line));
56
+ const logoColored = piLogo.map(line => this.#gradientLine(line));
57
57
 
58
58
  // Left column - centered content
59
59
  const leftLines = [
60
60
  "",
61
- this.centerText(theme.bold("Welcome back!"), leftCol),
61
+ this.#centerText(theme.bold("Welcome back!"), leftCol),
62
62
  "",
63
- ...logoColored.map(l => this.centerText(l, leftCol)),
63
+ ...logoColored.map(l => this.#centerText(l, leftCol)),
64
64
  "",
65
- this.centerText(theme.fg("muted", this.modelName), leftCol),
66
- this.centerText(theme.fg("borderMuted", this.providerName), leftCol),
65
+ this.#centerText(theme.fg("muted", this.modelName), leftCol),
66
+ this.#centerText(theme.fg("borderMuted", this.providerName), leftCol),
67
67
  ];
68
68
 
69
69
  // Right column separator
@@ -138,8 +138,8 @@ export class WelcomeComponent implements Component {
138
138
  // Content rows
139
139
  const maxRows = Math.max(leftLines.length, rightLines.length);
140
140
  for (let i = 0; i < maxRows; i++) {
141
- const left = this.fitToWidth(leftLines[i] ?? "", leftCol);
142
- const right = this.fitToWidth(rightLines[i] ?? "", rightCol);
141
+ const left = this.#fitToWidth(leftLines[i] ?? "", leftCol);
142
+ const right = this.#fitToWidth(rightLines[i] ?? "", rightCol);
143
143
  lines.push(v + left + v + right + v);
144
144
  }
145
145
 
@@ -150,7 +150,7 @@ export class WelcomeComponent implements Component {
150
150
  }
151
151
 
152
152
  /** Center text within a given width */
153
- private centerText(text: string, width: number): string {
153
+ #centerText(text: string, width: number): string {
154
154
  const visLen = visibleWidth(text);
155
155
  if (visLen >= width) {
156
156
  return truncateToWidth(text, width);
@@ -161,7 +161,7 @@ export class WelcomeComponent implements Component {
161
161
  }
162
162
 
163
163
  /** Apply magenta→cyan gradient to a string */
164
- private gradientLine(line: string): string {
164
+ #gradientLine(line: string): string {
165
165
  const colors = [
166
166
  "\x1b[38;5;199m", // bright magenta
167
167
  "\x1b[38;5;171m", // magenta-purple
@@ -191,7 +191,7 @@ export class WelcomeComponent implements Component {
191
191
  }
192
192
 
193
193
  /** Fit string to exact width with ANSI-aware truncation/padding */
194
- private fitToWidth(str: string, width: number): string {
194
+ #fitToWidth(str: string, width: number): string {
195
195
  const visLen = visibleWidth(str);
196
196
  if (visLen > width) {
197
197
  const ellipsis = "…";
@@ -9,7 +9,6 @@ import { $ } from "bun";
9
9
  import { loadCustomShare } from "../../export/custom-share";
10
10
  import type { CompactOptions } from "../../extensibility/extensions/types";
11
11
  import { getGatewayStatus } from "../../ipy/gateway-coordinator";
12
- import { ArminComponent } from "../../modes/components/armin";
13
12
  import { BashExecutionComponent } from "../../modes/components/bash-execution";
14
13
  import { BorderedLoader } from "../../modes/components/bordered-loader";
15
14
  import { DynamicBorder } from "../../modes/components/dynamic-border";
@@ -448,12 +447,6 @@ export class CommandController {
448
447
  this.ctx.ui.requestRender();
449
448
  }
450
449
 
451
- handleArminSaysHi(): void {
452
- this.ctx.chatContainer.addChild(new Spacer(1));
453
- this.ctx.chatContainer.addChild(new ArminComponent(this.ctx.ui));
454
- this.ctx.ui.requestRender();
455
- }
456
-
457
450
  async handleBashCommand(command: string, excludeFromContext = false): Promise<void> {
458
451
  const isDeferred = this.ctx.session.isStreaming;
459
452
  this.ctx.bashComponent = new BashExecutionComponent(command, this.ctx.ui, excludeFromContext);
@@ -11,25 +11,25 @@ import type { AgentSessionEvent } from "../../session/agent-session";
11
11
  import type { ExitPlanModeDetails } from "../../tools";
12
12
 
13
13
  export class EventController {
14
- private lastReadGroup: ReadToolGroupComponent | undefined = undefined;
15
- private lastThinkingCount = 0;
16
- private renderedCustomMessages = new Set<string>();
14
+ #lastReadGroup: ReadToolGroupComponent | undefined = undefined;
15
+ #lastThinkingCount = 0;
16
+ #renderedCustomMessages = new Set<string>();
17
17
 
18
18
  constructor(private ctx: InteractiveModeContext) {}
19
19
 
20
- private resetReadGroup(): void {
21
- this.lastReadGroup = undefined;
20
+ #resetReadGroup(): void {
21
+ this.#lastReadGroup = undefined;
22
22
  }
23
23
 
24
- private getReadGroup(): ReadToolGroupComponent {
25
- if (!this.lastReadGroup) {
24
+ #getReadGroup(): ReadToolGroupComponent {
25
+ if (!this.#lastReadGroup) {
26
26
  this.ctx.chatContainer.addChild(new Text("", 0, 0));
27
27
  const group = new ReadToolGroupComponent();
28
28
  group.setExpanded(this.ctx.toolOutputExpanded);
29
29
  this.ctx.chatContainer.addChild(group);
30
- this.lastReadGroup = group;
30
+ this.#lastReadGroup = group;
31
31
  }
32
- return this.lastReadGroup;
32
+ return this.#lastReadGroup;
33
33
  }
34
34
 
35
35
  subscribeToAgent(): void {
@@ -76,15 +76,15 @@ export class EventController {
76
76
  case "message_start":
77
77
  if (event.message.role === "hookMessage" || event.message.role === "custom") {
78
78
  const signature = `${event.message.role}:${event.message.customType}:${event.message.timestamp}`;
79
- if (this.renderedCustomMessages.has(signature)) {
79
+ if (this.#renderedCustomMessages.has(signature)) {
80
80
  break;
81
81
  }
82
- this.renderedCustomMessages.add(signature);
83
- this.resetReadGroup();
82
+ this.#renderedCustomMessages.add(signature);
83
+ this.#resetReadGroup();
84
84
  this.ctx.addMessageToChat(event.message);
85
85
  this.ctx.ui.requestRender();
86
86
  } else if (event.message.role === "user") {
87
- this.resetReadGroup();
87
+ this.#resetReadGroup();
88
88
  this.ctx.addMessageToChat(event.message);
89
89
  if (!event.message.synthetic) {
90
90
  this.ctx.editor.setText("");
@@ -92,12 +92,12 @@ export class EventController {
92
92
  }
93
93
  this.ctx.ui.requestRender();
94
94
  } else if (event.message.role === "fileMention") {
95
- this.resetReadGroup();
95
+ this.#resetReadGroup();
96
96
  this.ctx.addMessageToChat(event.message);
97
97
  this.ctx.ui.requestRender();
98
98
  } else if (event.message.role === "assistant") {
99
- this.lastThinkingCount = 0;
100
- this.resetReadGroup();
99
+ this.#lastThinkingCount = 0;
100
+ this.#resetReadGroup();
101
101
  this.ctx.streamingComponent = new AssistantMessageComponent(undefined, this.ctx.hideThinkingBlock);
102
102
  this.ctx.streamingMessage = event.message;
103
103
  this.ctx.chatContainer.addChild(this.ctx.streamingComponent);
@@ -114,9 +114,9 @@ export class EventController {
114
114
  const thinkingCount = this.ctx.streamingMessage.content.filter(
115
115
  content => content.type === "thinking" && content.thinking.trim(),
116
116
  ).length;
117
- if (thinkingCount > this.lastThinkingCount) {
118
- this.resetReadGroup();
119
- this.lastThinkingCount = thinkingCount;
117
+ if (thinkingCount > this.#lastThinkingCount) {
118
+ this.#resetReadGroup();
119
+ this.#lastThinkingCount = thinkingCount;
120
120
  }
121
121
 
122
122
  for (const content of this.ctx.streamingMessage.content) {
@@ -124,13 +124,13 @@ export class EventController {
124
124
 
125
125
  if (!this.ctx.pendingTools.has(content.id)) {
126
126
  if (content.name === "read") {
127
- const group = this.getReadGroup();
127
+ const group = this.#getReadGroup();
128
128
  group.updateArgs(content.arguments, content.id);
129
129
  this.ctx.pendingTools.set(content.id, group);
130
130
  continue;
131
131
  }
132
132
 
133
- this.resetReadGroup();
133
+ this.#resetReadGroup();
134
134
  this.ctx.chatContainer.addChild(new Text("", 0, 0));
135
135
  const tool = this.ctx.session.getToolByName(content.name);
136
136
  const component = new ToolExecutionComponent(
@@ -198,14 +198,14 @@ export class EventController {
198
198
  case "tool_execution_start": {
199
199
  if (!this.ctx.pendingTools.has(event.toolCallId)) {
200
200
  if (event.toolName === "read") {
201
- const group = this.getReadGroup();
201
+ const group = this.#getReadGroup();
202
202
  group.updateArgs(event.args, event.toolCallId);
203
203
  this.ctx.pendingTools.set(event.toolCallId, group);
204
204
  this.ctx.ui.requestRender();
205
205
  break;
206
206
  }
207
207
 
208
- this.resetReadGroup();
208
+ this.#resetReadGroup();
209
209
  const tool = this.ctx.session.getToolByName(event.toolName);
210
210
  const component = new ToolExecutionComponent(
211
211
  event.toolName,
@@ -18,10 +18,10 @@ import type { InteractiveModeContext } from "../../modes/types";
18
18
  import { setTerminalTitle } from "../../utils/title-generator";
19
19
 
20
20
  export class ExtensionUiController {
21
- private hookSelectorOverlay: OverlayHandle | undefined;
22
- private hookInputOverlay: OverlayHandle | undefined;
21
+ #hookSelectorOverlay: OverlayHandle | undefined;
22
+ #hookInputOverlay: OverlayHandle | undefined;
23
23
 
24
- private readonly dialogOverlayOptions = {
24
+ readonly #dialogOverlayOptions = {
25
25
  anchor: "bottom-center",
26
26
  width: "80%",
27
27
  minWidth: 40,
@@ -555,8 +555,8 @@ export class ExtensionUiController {
555
555
  dialogOptions?: ExtensionUIDialogOptions,
556
556
  ): Promise<string | undefined> {
557
557
  const { promise, resolve } = Promise.withResolvers<string | undefined>();
558
- this.hookSelectorOverlay?.hide();
559
- this.hookSelectorOverlay = undefined;
558
+ this.#hookSelectorOverlay?.hide();
559
+ this.#hookSelectorOverlay = undefined;
560
560
  const maxVisible = Math.max(4, Math.min(15, this.ctx.ui.terminal.rows - 12));
561
561
  this.ctx.hookSelector = new HookSelectorComponent(
562
562
  title,
@@ -577,7 +577,7 @@ export class ExtensionUiController {
577
577
  maxVisible,
578
578
  },
579
579
  );
580
- this.hookSelectorOverlay = this.ctx.ui.showOverlay(this.ctx.hookSelector, this.dialogOverlayOptions);
580
+ this.#hookSelectorOverlay = this.ctx.ui.showOverlay(this.ctx.hookSelector, this.#dialogOverlayOptions);
581
581
  return promise;
582
582
  }
583
583
 
@@ -586,8 +586,8 @@ export class ExtensionUiController {
586
586
  */
587
587
  hideHookSelector(): void {
588
588
  this.ctx.hookSelector?.dispose();
589
- this.hookSelectorOverlay?.hide();
590
- this.hookSelectorOverlay = undefined;
589
+ this.#hookSelectorOverlay?.hide();
590
+ this.#hookSelectorOverlay = undefined;
591
591
  this.ctx.hookSelector = undefined;
592
592
  this.ctx.ui.setFocus(this.ctx.editor);
593
593
  this.ctx.ui.requestRender();
@@ -606,8 +606,8 @@ export class ExtensionUiController {
606
606
  */
607
607
  showHookInput(title: string, placeholder?: string): Promise<string | undefined> {
608
608
  const { promise, resolve } = Promise.withResolvers<string | undefined>();
609
- this.hookInputOverlay?.hide();
610
- this.hookInputOverlay = undefined;
609
+ this.#hookInputOverlay?.hide();
610
+ this.#hookInputOverlay = undefined;
611
611
  this.ctx.hookInput = new HookInputComponent(
612
612
  title,
613
613
  placeholder,
@@ -620,7 +620,7 @@ export class ExtensionUiController {
620
620
  resolve(undefined);
621
621
  },
622
622
  );
623
- this.hookInputOverlay = this.ctx.ui.showOverlay(this.ctx.hookInput, this.dialogOverlayOptions);
623
+ this.#hookInputOverlay = this.ctx.ui.showOverlay(this.ctx.hookInput, this.#dialogOverlayOptions);
624
624
  return promise;
625
625
  }
626
626
 
@@ -629,8 +629,8 @@ export class ExtensionUiController {
629
629
  */
630
630
  hideHookInput(): void {
631
631
  this.ctx.hookInput?.dispose();
632
- this.hookInputOverlay?.hide();
633
- this.hookInputOverlay = undefined;
632
+ this.#hookInputOverlay?.hide();
633
+ this.#hookInputOverlay = undefined;
634
634
  this.ctx.hookInput = undefined;
635
635
  this.ctx.ui.setFocus(this.ctx.editor);
636
636
  this.ctx.ui.requestRender();
@@ -322,11 +322,6 @@ export class InputController {
322
322
  this.ctx.editor.setText("");
323
323
  return;
324
324
  }
325
- if (text === "/arminsayshi") {
326
- this.ctx.handleArminSaysHi();
327
- this.ctx.editor.setText("");
328
- return;
329
- }
330
325
  if (text === "/resume") {
331
326
  this.ctx.showSessionSelector();
332
327
  this.ctx.editor.setText("");
@@ -338,6 +333,14 @@ export class InputController {
338
333
  return;
339
334
  }
340
335
 
336
+ // Handle MCP server management commands
337
+ if (text === "/mcp" || text.startsWith("/mcp ")) {
338
+ this.ctx.editor.addToHistory(text);
339
+ this.ctx.editor.setText("");
340
+ await this.ctx.handleMCPCommand(text);
341
+ return;
342
+ }
343
+
341
344
  // Handle skill commands (/skill:name [args])
342
345
  if (text.startsWith("/skill:")) {
343
346
  const spaceIndex = text.indexOf(" ");
@@ -722,15 +725,15 @@ export class InputController {
722
725
  this.ctx.showStatus(`Thinking blocks: ${this.ctx.hideThinkingBlock ? "hidden" : "visible"}`);
723
726
  }
724
727
 
725
- private getEditorTerminalPath(): string | null {
728
+ #getEditorTerminalPath(): string | null {
726
729
  if (process.platform === "win32") {
727
730
  return null;
728
731
  }
729
732
  return "/dev/tty";
730
733
  }
731
734
 
732
- private async openEditorTerminalHandle(): Promise<fs.FileHandle | null> {
733
- const terminalPath = this.getEditorTerminalPath();
735
+ async #openEditorTerminalHandle(): Promise<fs.FileHandle | null> {
736
+ const terminalPath = this.#getEditorTerminalPath();
734
737
  if (!terminalPath) {
735
738
  return null;
736
739
  }
@@ -752,7 +755,7 @@ export class InputController {
752
755
 
753
756
  let ttyHandle: fs.FileHandle | null = null;
754
757
  try {
755
- ttyHandle = await this.openEditorTerminalHandle();
758
+ ttyHandle = await this.#openEditorTerminalHandle();
756
759
  this.ctx.ui.stop();
757
760
 
758
761
  const stdio: [number | "inherit", number | "inherit", number | "inherit"] = ttyHandle