@oh-my-pi/pi-coding-agent 11.8.2 → 11.8.3

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 (122) hide show
  1. package/docs/tui.md +9 -9
  2. package/package.json +7 -7
  3. package/src/cli/file-processor.ts +8 -13
  4. package/src/cli/oclif-help.ts +1 -1
  5. package/src/cli.ts +14 -0
  6. package/src/commit/git/index.ts +16 -16
  7. package/src/config/keybindings.ts +11 -11
  8. package/src/config/model-registry.ts +31 -66
  9. package/src/config/settings.ts +88 -95
  10. package/src/config.ts +2 -2
  11. package/src/cursor.ts +4 -4
  12. package/src/debug/index.ts +28 -28
  13. package/src/discovery/codex.ts +5 -13
  14. package/src/discovery/cursor.ts +2 -7
  15. package/src/exa/mcp-client.ts +2 -2
  16. package/src/exa/websets.ts +2 -2
  17. package/src/export/html/index.ts +3 -3
  18. package/src/export/ttsr.ts +27 -27
  19. package/src/extensibility/custom-tools/loader.ts +9 -9
  20. package/src/extensibility/extensions/runner.ts +64 -64
  21. package/src/extensibility/hooks/runner.ts +46 -46
  22. package/src/extensibility/plugins/manager.ts +49 -49
  23. package/src/index.ts +0 -1
  24. package/src/internal-urls/router.ts +5 -5
  25. package/src/ipy/kernel.ts +61 -57
  26. package/src/lsp/client.ts +1 -1
  27. package/src/lsp/clients/biome-client.ts +2 -2
  28. package/src/lsp/clients/lsp-linter-client.ts +7 -7
  29. package/src/lsp/index.ts +9 -9
  30. package/src/mcp/manager.ts +47 -47
  31. package/src/mcp/tool-bridge.ts +12 -12
  32. package/src/mcp/transports/http.ts +34 -34
  33. package/src/mcp/transports/stdio.ts +47 -47
  34. package/src/modes/components/assistant-message.ts +25 -25
  35. package/src/modes/components/bash-execution.ts +51 -51
  36. package/src/modes/components/bordered-loader.ts +7 -7
  37. package/src/modes/components/branch-summary-message.ts +7 -7
  38. package/src/modes/components/compaction-summary-message.ts +7 -7
  39. package/src/modes/components/countdown-timer.ts +15 -15
  40. package/src/modes/components/custom-editor.ts +22 -22
  41. package/src/modes/components/custom-message.ts +21 -21
  42. package/src/modes/components/dynamic-border.ts +3 -3
  43. package/src/modes/components/extensions/extension-dashboard.ts +72 -72
  44. package/src/modes/components/extensions/extension-list.ts +99 -97
  45. package/src/modes/components/extensions/inspector-panel.ts +26 -26
  46. package/src/modes/components/footer.ts +36 -36
  47. package/src/modes/components/history-search.ts +52 -52
  48. package/src/modes/components/hook-editor.ts +20 -20
  49. package/src/modes/components/hook-input.ts +20 -20
  50. package/src/modes/components/hook-message.ts +22 -22
  51. package/src/modes/components/hook-selector.ts +52 -52
  52. package/src/modes/components/index.ts +0 -1
  53. package/src/modes/components/login-dialog.ts +57 -57
  54. package/src/modes/components/model-selector.ts +173 -173
  55. package/src/modes/components/oauth-selector.ts +45 -45
  56. package/src/modes/components/plugin-settings.ts +52 -52
  57. package/src/modes/components/python-execution.ts +53 -53
  58. package/src/modes/components/queue-mode-selector.ts +7 -7
  59. package/src/modes/components/read-tool-group.ts +23 -23
  60. package/src/modes/components/session-selector.ts +40 -37
  61. package/src/modes/components/settings-selector.ts +80 -80
  62. package/src/modes/components/show-images-selector.ts +7 -7
  63. package/src/modes/components/skill-message.ts +27 -27
  64. package/src/modes/components/status-line-segment-editor.ts +81 -81
  65. package/src/modes/components/status-line.ts +73 -73
  66. package/src/modes/components/theme-selector.ts +11 -11
  67. package/src/modes/components/thinking-selector.ts +7 -7
  68. package/src/modes/components/todo-display.ts +19 -19
  69. package/src/modes/components/todo-reminder.ts +9 -9
  70. package/src/modes/components/tool-execution.ts +204 -196
  71. package/src/modes/components/tree-selector.ts +144 -144
  72. package/src/modes/components/ttsr-notification.ts +17 -17
  73. package/src/modes/components/user-message-selector.ts +18 -18
  74. package/src/modes/components/welcome.ts +10 -10
  75. package/src/modes/controllers/command-controller.ts +0 -7
  76. package/src/modes/controllers/event-controller.ts +23 -23
  77. package/src/modes/controllers/extension-ui-controller.ts +13 -13
  78. package/src/modes/controllers/input-controller.ts +4 -9
  79. package/src/modes/interactive-mode.ts +234 -241
  80. package/src/modes/rpc/rpc-client.ts +77 -77
  81. package/src/modes/rpc/rpc-mode.ts +5 -5
  82. package/src/modes/theme/theme.ts +113 -113
  83. package/src/modes/types.ts +0 -1
  84. package/src/patch/index.ts +45 -45
  85. package/src/prompts/tools/task.md +22 -2
  86. package/src/session/agent-session.ts +463 -476
  87. package/src/session/agent-storage.ts +72 -75
  88. package/src/session/auth-storage.ts +186 -252
  89. package/src/session/history-storage.ts +36 -38
  90. package/src/session/session-manager.ts +300 -299
  91. package/src/session/session-storage.ts +65 -90
  92. package/src/ssh/connection-manager.ts +9 -9
  93. package/src/task/agents.ts +1 -1
  94. package/src/task/executor.ts +2 -2
  95. package/src/task/index.ts +13 -12
  96. package/src/task/subprocess-tool-registry.ts +5 -5
  97. package/src/tools/ask.ts +7 -7
  98. package/src/tools/bash.ts +8 -7
  99. package/src/tools/browser.ts +123 -123
  100. package/src/tools/calculator.ts +46 -46
  101. package/src/tools/context.ts +9 -9
  102. package/src/tools/exit-plan-mode.ts +5 -5
  103. package/src/tools/fetch.ts +5 -5
  104. package/src/tools/find.ts +16 -16
  105. package/src/tools/grep.ts +10 -10
  106. package/src/tools/notebook.ts +6 -6
  107. package/src/tools/output-meta.ts +10 -2
  108. package/src/tools/python.ts +12 -11
  109. package/src/tools/read.ts +17 -17
  110. package/src/tools/ssh.ts +9 -9
  111. package/src/tools/submit-result.ts +13 -13
  112. package/src/tools/todo-write.ts +6 -6
  113. package/src/tools/write.ts +10 -10
  114. package/src/tui/output-block.ts +6 -6
  115. package/src/tui/utils.ts +9 -9
  116. package/src/utils/event-bus.ts +10 -10
  117. package/src/utils/frontmatter.ts +1 -1
  118. package/src/utils/ignore-files.ts +1 -1
  119. package/src/web/search/index.ts +5 -5
  120. package/src/web/search/providers/anthropic.ts +7 -2
  121. package/examples/hooks/snake.ts +0 -342
  122. package/src/modes/components/armin.ts +0 -379
@@ -8,14 +8,14 @@ import { DynamicBorder } from "./dynamic-border";
8
8
  * Component that renders an OAuth provider selector
9
9
  */
10
10
  export class OAuthSelectorComponent extends Container {
11
- private listContainer: Container;
12
- private allProviders: OAuthProviderInfo[] = [];
13
- private selectedIndex: number = 0;
14
- private mode: "login" | "logout";
15
- private authStorage: AuthStorage;
16
- private onSelectCallback: (providerId: string) => void;
17
- private onCancelCallback: () => void;
18
- private statusMessage: string | undefined;
11
+ #listContainer: Container;
12
+ #allProviders: OAuthProviderInfo[] = [];
13
+ #selectedIndex: number = 0;
14
+ #mode: "login" | "logout";
15
+ #authStorage: AuthStorage;
16
+ #onSelectCallback: (providerId: string) => void;
17
+ #onCancelCallback: () => void;
18
+ #statusMessage: string | undefined;
19
19
 
20
20
  constructor(
21
21
  mode: "login" | "logout",
@@ -25,13 +25,13 @@ export class OAuthSelectorComponent extends Container {
25
25
  ) {
26
26
  super();
27
27
 
28
- this.mode = mode;
29
- this.authStorage = authStorage;
30
- this.onSelectCallback = onSelect;
31
- this.onCancelCallback = onCancel;
28
+ this.#mode = mode;
29
+ this.#authStorage = authStorage;
30
+ this.#onSelectCallback = onSelect;
31
+ this.#onCancelCallback = onCancel;
32
32
 
33
33
  // Load all OAuth providers
34
- this.loadProviders();
34
+ this.#loadProviders();
35
35
 
36
36
  // Add top border
37
37
  this.addChild(new DynamicBorder());
@@ -43,8 +43,8 @@ export class OAuthSelectorComponent extends Container {
43
43
  this.addChild(new Spacer(1));
44
44
 
45
45
  // Create list container
46
- this.listContainer = new Container();
47
- this.addChild(this.listContainer);
46
+ this.#listContainer = new Container();
47
+ this.addChild(this.#listContainer);
48
48
 
49
49
  this.addChild(new Spacer(1));
50
50
 
@@ -52,25 +52,25 @@ export class OAuthSelectorComponent extends Container {
52
52
  this.addChild(new DynamicBorder());
53
53
 
54
54
  // Initial render
55
- this.updateList();
55
+ this.#updateList();
56
56
  }
57
57
 
58
- private loadProviders(): void {
59
- this.allProviders = getOAuthProviders();
58
+ #loadProviders(): void {
59
+ this.#allProviders = getOAuthProviders();
60
60
  }
61
61
 
62
- private updateList(): void {
63
- this.listContainer.clear();
62
+ #updateList(): void {
63
+ this.#listContainer.clear();
64
64
 
65
- for (let i = 0; i < this.allProviders.length; i++) {
66
- const provider = this.allProviders[i];
65
+ for (let i = 0; i < this.#allProviders.length; i++) {
66
+ const provider = this.#allProviders[i];
67
67
  if (!provider) continue;
68
68
 
69
- const isSelected = i === this.selectedIndex;
69
+ const isSelected = i === this.#selectedIndex;
70
70
  const isAvailable = provider.available;
71
71
 
72
72
  // Check if user is logged in for this provider
73
- const isLoggedIn = this.authStorage.hasOAuth(provider.id);
73
+ const isLoggedIn = this.#authStorage.hasOAuth(provider.id);
74
74
  const statusIndicator = isLoggedIn ? theme.fg("success", ` ${theme.status.success} logged in`) : "";
75
75
 
76
76
  let line = "";
@@ -83,53 +83,53 @@ export class OAuthSelectorComponent extends Container {
83
83
  line = text + statusIndicator;
84
84
  }
85
85
 
86
- this.listContainer.addChild(new TruncatedText(line, 0, 0));
86
+ this.#listContainer.addChild(new TruncatedText(line, 0, 0));
87
87
  }
88
88
 
89
89
  // Show "no providers" if empty
90
- if (this.allProviders.length === 0) {
90
+ if (this.#allProviders.length === 0) {
91
91
  const message =
92
- this.mode === "login" ? "No OAuth providers available" : "No OAuth providers logged in. Use /login first.";
93
- this.listContainer.addChild(new TruncatedText(theme.fg("muted", ` ${message}`), 0, 0));
92
+ this.#mode === "login" ? "No OAuth providers available" : "No OAuth providers logged in. Use /login first.";
93
+ this.#listContainer.addChild(new TruncatedText(theme.fg("muted", ` ${message}`), 0, 0));
94
94
  }
95
95
 
96
- if (this.statusMessage) {
97
- this.listContainer.addChild(new Spacer(1));
98
- this.listContainer.addChild(new TruncatedText(theme.fg("warning", ` ${this.statusMessage}`), 0, 0));
96
+ if (this.#statusMessage) {
97
+ this.#listContainer.addChild(new Spacer(1));
98
+ this.#listContainer.addChild(new TruncatedText(theme.fg("warning", ` ${this.#statusMessage}`), 0, 0));
99
99
  }
100
100
  }
101
101
 
102
102
  handleInput(keyData: string): void {
103
103
  // Up arrow
104
104
  if (matchesKey(keyData, "up")) {
105
- if (this.allProviders.length > 0) {
106
- this.selectedIndex = this.selectedIndex === 0 ? this.allProviders.length - 1 : this.selectedIndex - 1;
105
+ if (this.#allProviders.length > 0) {
106
+ this.#selectedIndex = this.#selectedIndex === 0 ? this.#allProviders.length - 1 : this.#selectedIndex - 1;
107
107
  }
108
- this.statusMessage = undefined;
109
- this.updateList();
108
+ this.#statusMessage = undefined;
109
+ this.#updateList();
110
110
  }
111
111
  // Down arrow
112
112
  else if (matchesKey(keyData, "down")) {
113
- if (this.allProviders.length > 0) {
114
- this.selectedIndex = this.selectedIndex === this.allProviders.length - 1 ? 0 : this.selectedIndex + 1;
113
+ if (this.#allProviders.length > 0) {
114
+ this.#selectedIndex = this.#selectedIndex === this.#allProviders.length - 1 ? 0 : this.#selectedIndex + 1;
115
115
  }
116
- this.statusMessage = undefined;
117
- this.updateList();
116
+ this.#statusMessage = undefined;
117
+ this.#updateList();
118
118
  }
119
119
  // Enter
120
120
  else if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
121
- const selectedProvider = this.allProviders[this.selectedIndex];
121
+ const selectedProvider = this.#allProviders[this.#selectedIndex];
122
122
  if (selectedProvider?.available) {
123
- this.statusMessage = undefined;
124
- this.onSelectCallback(selectedProvider.id);
123
+ this.#statusMessage = undefined;
124
+ this.#onSelectCallback(selectedProvider.id);
125
125
  } else if (selectedProvider) {
126
- this.statusMessage = "Provider unavailable in this environment.";
127
- this.updateList();
126
+ this.#statusMessage = "Provider unavailable in this environment.";
127
+ this.#updateList();
128
128
  }
129
129
  }
130
130
  // Escape or Ctrl+C
131
131
  else if (matchesKey(keyData, "escape") || matchesKey(keyData, "esc") || matchesKey(keyData, "ctrl+c")) {
132
- this.onCancelCallback();
132
+ this.#onCancelCallback();
133
133
  }
134
134
  }
135
135
  }
@@ -36,7 +36,7 @@ export interface PluginListCallbacks {
36
36
  * Selecting a plugin opens its detail view.
37
37
  */
38
38
  export class PluginListComponent extends Container {
39
- private readonly selectList: SelectList;
39
+ readonly #selectList: SelectList;
40
40
 
41
41
  constructor(
42
42
  private readonly plugins: InstalledPlugin[],
@@ -57,8 +57,8 @@ export class PluginListComponent extends Container {
57
57
  this.addChild(new DynamicBorder());
58
58
 
59
59
  // Create empty list that just handles escape
60
- this.selectList = new SelectList([], 1, getSelectListTheme());
61
- this.selectList.onCancel = callbacks.onCancel;
60
+ this.#selectList = new SelectList([], 1, getSelectListTheme());
61
+ this.#selectList.onCancel = callbacks.onCancel;
62
62
  return;
63
63
  }
64
64
 
@@ -81,25 +81,25 @@ export class PluginListComponent extends Container {
81
81
  };
82
82
  });
83
83
 
84
- this.selectList = new SelectList(items, Math.min(items.length, 8), getSelectListTheme());
84
+ this.#selectList = new SelectList(items, Math.min(items.length, 8), getSelectListTheme());
85
85
 
86
- this.selectList.onSelect = item => {
86
+ this.#selectList.onSelect = item => {
87
87
  const plugin = this.plugins.find(p => p.name === item.value);
88
88
  if (plugin) {
89
89
  callbacks.onPluginSelect(plugin);
90
90
  }
91
91
  };
92
92
 
93
- this.selectList.onCancel = callbacks.onCancel;
93
+ this.#selectList.onCancel = callbacks.onCancel;
94
94
 
95
- this.addChild(this.selectList);
95
+ this.addChild(this.#selectList);
96
96
  this.addChild(new Spacer(1));
97
97
  this.addChild(new Text(theme.fg("dim", " Enter to configure · Esc to go back"), 0, 0));
98
98
  this.addChild(new DynamicBorder());
99
99
  }
100
100
 
101
101
  handleInput(data: string): void {
102
- this.selectList.handleInput(data);
102
+ this.#selectList.handleInput(data);
103
103
  }
104
104
  }
105
105
 
@@ -121,7 +121,7 @@ export interface PluginDetailCallbacks {
121
121
  * - Config settings
122
122
  */
123
123
  export class PluginDetailComponent extends Container {
124
- private settingsList!: SettingsList;
124
+ #settingsList!: SettingsList;
125
125
 
126
126
  constructor(
127
127
  private plugin: InstalledPlugin,
@@ -130,10 +130,10 @@ export class PluginDetailComponent extends Container {
130
130
  ) {
131
131
  super();
132
132
 
133
- void this.rebuild();
133
+ void this.#rebuild();
134
134
  }
135
135
 
136
- private async rebuild(): Promise<void> {
136
+ async #rebuild(): Promise<void> {
137
137
  this.clear();
138
138
 
139
139
  const plugin = this.plugin;
@@ -239,7 +239,7 @@ export class PluginDetailComponent extends Container {
239
239
  }
240
240
  }
241
241
 
242
- this.settingsList = new SettingsList(
242
+ this.#settingsList = new SettingsList(
243
243
  items,
244
244
  Math.min(items.length, 10),
245
245
  getSettingsListTheme(),
@@ -269,15 +269,15 @@ export class PluginDetailComponent extends Container {
269
269
  this.callbacks.onBack,
270
270
  );
271
271
 
272
- this.addChild(this.settingsList);
272
+ this.addChild(this.#settingsList);
273
273
  this.addChild(new Spacer(1));
274
274
  this.addChild(new Text(theme.fg("dim", " Enter to edit · Esc to go back"), 0, 0));
275
275
  this.addChild(new DynamicBorder());
276
276
  }
277
277
 
278
278
  handleInput(data: string): void {
279
- if (!this.settingsList) return;
280
- this.settingsList.handleInput(data);
279
+ if (!this.#settingsList) return;
280
+ this.#settingsList.handleInput(data);
281
281
  }
282
282
  }
283
283
 
@@ -289,7 +289,7 @@ export class PluginDetailComponent extends Container {
289
289
  * Submenu for enum config values.
290
290
  */
291
291
  class ConfigEnumSubmenu extends Container {
292
- private selectList: SelectList;
292
+ #selectList: SelectList;
293
293
 
294
294
  constructor(
295
295
  key: string,
@@ -309,23 +309,23 @@ class ConfigEnumSubmenu extends Container {
309
309
  this.addChild(new Spacer(1));
310
310
 
311
311
  const items: SelectItem[] = values.map(v => ({ value: v, label: v }));
312
- this.selectList = new SelectList(items, Math.min(items.length, 8), getSelectListTheme());
312
+ this.#selectList = new SelectList(items, Math.min(items.length, 8), getSelectListTheme());
313
313
 
314
314
  const currentIndex = values.indexOf(currentValue);
315
315
  if (currentIndex !== -1) {
316
- this.selectList.setSelectedIndex(currentIndex);
316
+ this.#selectList.setSelectedIndex(currentIndex);
317
317
  }
318
318
 
319
- this.selectList.onSelect = item => onSelect(item.value);
320
- this.selectList.onCancel = onCancel;
319
+ this.#selectList.onSelect = item => onSelect(item.value);
320
+ this.#selectList.onCancel = onCancel;
321
321
 
322
- this.addChild(this.selectList);
322
+ this.addChild(this.#selectList);
323
323
  this.addChild(new Spacer(1));
324
324
  this.addChild(new Text(theme.fg("dim", " Enter to select · Esc to cancel"), 0, 0));
325
325
  }
326
326
 
327
327
  handleInput(data: string): void {
328
- this.selectList.handleInput(data);
328
+ this.#selectList.handleInput(data);
329
329
  }
330
330
  }
331
331
 
@@ -333,7 +333,7 @@ class ConfigEnumSubmenu extends Container {
333
333
  * Submenu for string/number config values with text input.
334
334
  */
335
335
  class ConfigInputSubmenu extends Container {
336
- private input: Input;
336
+ #input: Input;
337
337
 
338
338
  constructor(
339
339
  key: string,
@@ -364,12 +364,12 @@ class ConfigInputSubmenu extends Container {
364
364
  this.addChild(new Spacer(1));
365
365
 
366
366
  // Input field
367
- this.input = new Input();
367
+ this.#input = new Input();
368
368
  if (!schema.secret && currentValue) {
369
- this.input.setValue(currentValue);
369
+ this.#input.setValue(currentValue);
370
370
  }
371
371
 
372
- this.input.onSubmit = value => {
372
+ this.#input.onSubmit = value => {
373
373
  if (value.trim()) {
374
374
  this.onSubmit(value);
375
375
  } else {
@@ -377,7 +377,7 @@ class ConfigInputSubmenu extends Container {
377
377
  }
378
378
  };
379
379
 
380
- this.addChild(this.input);
380
+ this.addChild(this.#input);
381
381
  this.addChild(new Spacer(1));
382
382
  this.addChild(new Text(theme.fg("dim", " Enter to save · Esc to cancel"), 0, 0));
383
383
  }
@@ -387,7 +387,7 @@ class ConfigInputSubmenu extends Container {
387
387
  this.onCancel();
388
388
  return;
389
389
  }
390
- this.input.handleInput(data);
390
+ this.#input.handleInput(data);
391
391
  }
392
392
  }
393
393
 
@@ -410,68 +410,68 @@ interface InputHandler {
410
410
  * Manages navigation between plugin list and plugin detail views.
411
411
  */
412
412
  export class PluginSettingsComponent extends Container {
413
- private manager: PluginManager;
414
- private viewComponent: (Container & InputHandler) | null = null;
413
+ #manager: PluginManager;
414
+ #viewComponent: (Container & InputHandler) | null = null;
415
415
  // biome-ignore lint/correctness/noUnusedPrivateClassMembers: state tracking for view management
416
- private currentView: "list" | "detail" = "list";
416
+ #currentView: "list" | "detail" = "list";
417
417
  // biome-ignore lint/correctness/noUnusedPrivateClassMembers: state tracking for view management
418
- private currentPlugin: InstalledPlugin | null = null;
418
+ #currentPlugin: InstalledPlugin | null = null;
419
419
 
420
420
  constructor(
421
421
  cwd: string,
422
422
  private readonly callbacks: PluginSettingsCallbacks,
423
423
  ) {
424
424
  super();
425
- this.manager = new PluginManager(cwd);
426
- this.showPluginList();
425
+ this.#manager = new PluginManager(cwd);
426
+ this.#showPluginList();
427
427
  }
428
428
 
429
- private async showPluginList(): Promise<void> {
430
- this.currentView = "list";
431
- this.currentPlugin = null;
429
+ async #showPluginList(): Promise<void> {
430
+ this.#currentView = "list";
431
+ this.#currentPlugin = null;
432
432
  this.clear();
433
433
 
434
- const plugins = await this.manager.list();
434
+ const plugins = await this.#manager.list();
435
435
 
436
- this.viewComponent = new PluginListComponent(plugins, {
437
- onPluginSelect: plugin => this.showPluginDetail(plugin),
436
+ this.#viewComponent = new PluginListComponent(plugins, {
437
+ onPluginSelect: plugin => this.#showPluginDetail(plugin),
438
438
  onCancel: () => this.callbacks.onClose(),
439
439
  });
440
440
 
441
- this.addChild(this.viewComponent);
441
+ this.addChild(this.#viewComponent);
442
442
  }
443
443
 
444
- private showPluginDetail(plugin: InstalledPlugin): void {
445
- this.currentView = "detail";
446
- this.currentPlugin = plugin;
444
+ #showPluginDetail(plugin: InstalledPlugin): void {
445
+ this.#currentView = "detail";
446
+ this.#currentPlugin = plugin;
447
447
  this.clear();
448
448
 
449
- this.viewComponent = new PluginDetailComponent(plugin, this.manager, {
449
+ this.#viewComponent = new PluginDetailComponent(plugin, this.#manager, {
450
450
  onEnabledChange: async enabled => {
451
- await this.manager.setEnabled(plugin.name, enabled);
451
+ await this.#manager.setEnabled(plugin.name, enabled);
452
452
  this.callbacks.onPluginChanged();
453
453
  },
454
454
  onFeatureChange: async (feature, enabled) => {
455
- const current = new Set((await this.manager.getEnabledFeatures(plugin.name)) ?? []);
455
+ const current = new Set((await this.#manager.getEnabledFeatures(plugin.name)) ?? []);
456
456
  if (enabled) {
457
457
  current.add(feature);
458
458
  } else {
459
459
  current.delete(feature);
460
460
  }
461
- await this.manager.setEnabledFeatures(plugin.name, [...current]);
461
+ await this.#manager.setEnabledFeatures(plugin.name, [...current]);
462
462
  this.callbacks.onPluginChanged();
463
463
  },
464
464
  onConfigChange: async (key, value) => {
465
- await this.manager.setPluginSetting(plugin.name, key, value);
465
+ await this.#manager.setPluginSetting(plugin.name, key, value);
466
466
  this.callbacks.onPluginChanged();
467
467
  },
468
- onBack: () => this.showPluginList(),
468
+ onBack: () => this.#showPluginList(),
469
469
  });
470
470
 
471
- this.addChild(this.viewComponent);
471
+ this.addChild(this.#viewComponent);
472
472
  }
473
473
 
474
474
  handleInput(data: string): void {
475
- this.viewComponent?.handleInput(data);
475
+ this.#viewComponent?.handleInput(data);
476
476
  }
477
477
  }
@@ -12,15 +12,15 @@ import { truncateToVisualLines } from "./visual-truncate";
12
12
  const PREVIEW_LINES = 20;
13
13
 
14
14
  export class PythonExecutionComponent extends Container {
15
- private outputLines: string[] = [];
16
- private status: "running" | "complete" | "cancelled" | "error" = "running";
17
- private exitCode: number | undefined = undefined;
18
- private loader: Loader;
19
- private truncation?: TruncationMeta;
20
- private expanded = false;
21
- private contentContainer: Container;
22
-
23
- private formatHeader(colorKey: "dim" | "pythonMode"): Text {
15
+ #outputLines: string[] = [];
16
+ #status: "running" | "complete" | "cancelled" | "error" = "running";
17
+ #exitCode: number | undefined = undefined;
18
+ #loader: Loader;
19
+ #truncation?: TruncationMeta;
20
+ #expanded = false;
21
+ #contentContainer: Container;
22
+
23
+ #formatHeader(colorKey: "dim" | "pythonMode"): Text {
24
24
  const prompt = theme.fg(colorKey, theme.bold(">>>"));
25
25
  const continuation = theme.fg(colorKey, " ");
26
26
  const codeLines = highlightCode(this.code, "python");
@@ -43,44 +43,44 @@ export class PythonExecutionComponent extends Container {
43
43
  this.addChild(new Spacer(1));
44
44
  this.addChild(new DynamicBorder(borderColor));
45
45
 
46
- this.contentContainer = new Container();
47
- this.addChild(this.contentContainer);
48
- this.contentContainer.addChild(this.formatHeader(colorKey));
46
+ this.#contentContainer = new Container();
47
+ this.addChild(this.#contentContainer);
48
+ this.#contentContainer.addChild(this.#formatHeader(colorKey));
49
49
 
50
- this.loader = new Loader(
50
+ this.#loader = new Loader(
51
51
  ui,
52
52
  spinner => theme.fg(colorKey, spinner),
53
53
  text => theme.fg("muted", text),
54
54
  `Running… (esc to cancel)`,
55
55
  getSymbolTheme().spinnerFrames,
56
56
  );
57
- this.contentContainer.addChild(this.loader);
57
+ this.#contentContainer.addChild(this.#loader);
58
58
 
59
59
  this.addChild(new DynamicBorder(borderColor));
60
60
  }
61
61
 
62
62
  setExpanded(expanded: boolean): void {
63
- this.expanded = expanded;
64
- this.updateDisplay();
63
+ this.#expanded = expanded;
64
+ this.#updateDisplay();
65
65
  }
66
66
 
67
67
  override invalidate(): void {
68
68
  super.invalidate();
69
- this.updateDisplay();
69
+ this.#updateDisplay();
70
70
  }
71
71
 
72
72
  appendOutput(chunk: string): void {
73
- const clean = this.normalizeOutput(chunk);
73
+ const clean = this.#normalizeOutput(chunk);
74
74
 
75
75
  const newLines = clean.split("\n");
76
- if (this.outputLines.length > 0 && newLines.length > 0) {
77
- this.outputLines[this.outputLines.length - 1] += newLines[0];
78
- this.outputLines.push(...newLines.slice(1));
76
+ if (this.#outputLines.length > 0 && newLines.length > 0) {
77
+ this.#outputLines[this.#outputLines.length - 1] += newLines[0];
78
+ this.#outputLines.push(...newLines.slice(1));
79
79
  } else {
80
- this.outputLines.push(...newLines);
80
+ this.#outputLines.push(...newLines);
81
81
  }
82
82
 
83
- this.updateDisplay();
83
+ this.#updateDisplay();
84
84
  }
85
85
 
86
86
  setComplete(
@@ -88,39 +88,39 @@ export class PythonExecutionComponent extends Container {
88
88
  cancelled: boolean,
89
89
  options?: { output?: string; truncation?: TruncationMeta },
90
90
  ): void {
91
- this.exitCode = exitCode;
92
- this.status = cancelled
91
+ this.#exitCode = exitCode;
92
+ this.#status = cancelled
93
93
  ? "cancelled"
94
94
  : exitCode !== 0 && exitCode !== undefined && exitCode !== null
95
95
  ? "error"
96
96
  : "complete";
97
- this.truncation = options?.truncation;
97
+ this.#truncation = options?.truncation;
98
98
  if (options?.output !== undefined) {
99
- this.setOutput(options.output);
99
+ this.#setOutput(options.output);
100
100
  }
101
101
 
102
- this.loader.stop();
103
- this.updateDisplay();
102
+ this.#loader.stop();
103
+ this.#updateDisplay();
104
104
  }
105
105
 
106
- private updateDisplay(): void {
107
- const availableLines = this.outputLines;
106
+ #updateDisplay(): void {
107
+ const availableLines = this.#outputLines;
108
108
  const previewLogicalLines = availableLines.slice(-PREVIEW_LINES);
109
109
  const hiddenLineCount = availableLines.length - previewLogicalLines.length;
110
110
 
111
- this.contentContainer.clear();
111
+ this.#contentContainer.clear();
112
112
 
113
113
  const colorKey = this.excludeFromContext ? "dim" : "pythonMode";
114
- this.contentContainer.addChild(this.formatHeader(colorKey));
114
+ this.#contentContainer.addChild(this.#formatHeader(colorKey));
115
115
 
116
116
  if (availableLines.length > 0) {
117
- if (this.expanded) {
117
+ if (this.#expanded) {
118
118
  const displayText = availableLines.map(line => theme.fg("muted", line)).join("\n");
119
- this.contentContainer.addChild(new Text(`\n${displayText}`, 1, 0));
119
+ this.#contentContainer.addChild(new Text(`\n${displayText}`, 1, 0));
120
120
  } else {
121
121
  const styledOutput = previewLogicalLines.map(line => theme.fg("muted", line)).join("\n");
122
122
  const previewText = `\n${styledOutput}`;
123
- this.contentContainer.addChild({
123
+ this.#contentContainer.addChild({
124
124
  render: (width: number) => {
125
125
  const { visualLines } = truncateToVisualLines(previewText, PREVIEW_LINES, width, 1);
126
126
  return visualLines;
@@ -130,8 +130,8 @@ export class PythonExecutionComponent extends Container {
130
130
  }
131
131
  }
132
132
 
133
- if (this.status === "running") {
134
- this.contentContainer.addChild(this.loader);
133
+ if (this.#status === "running") {
134
+ this.#contentContainer.addChild(this.#loader);
135
135
  } else {
136
136
  const statusParts: string[] = [];
137
137
 
@@ -139,46 +139,46 @@ export class PythonExecutionComponent extends Container {
139
139
  statusParts.push(theme.fg("dim", `… ${hiddenLineCount} more lines (ctrl+o to expand)`));
140
140
  }
141
141
 
142
- if (this.status === "cancelled") {
142
+ if (this.#status === "cancelled") {
143
143
  statusParts.push(theme.fg("warning", "(cancelled)"));
144
- } else if (this.status === "error") {
145
- statusParts.push(theme.fg("error", `(exit ${this.exitCode})`));
144
+ } else if (this.#status === "error") {
145
+ statusParts.push(theme.fg("error", `(exit ${this.#exitCode})`));
146
146
  }
147
147
 
148
- if (this.truncation) {
148
+ if (this.#truncation) {
149
149
  const warnings: string[] = [];
150
- if (this.truncation.artifactId) {
151
- warnings.push(`Full output: artifact://${this.truncation.artifactId}`);
150
+ if (this.#truncation.artifactId) {
151
+ warnings.push(`Full output: artifact://${this.#truncation.artifactId}`);
152
152
  }
153
- if (this.truncation.truncatedBy === "lines") {
153
+ if (this.#truncation.truncatedBy === "lines") {
154
154
  warnings.push(
155
- `Truncated: showing ${this.truncation.outputLines} of ${this.truncation.totalLines} lines`,
155
+ `Truncated: showing ${this.#truncation.outputLines} of ${this.#truncation.totalLines} lines`,
156
156
  );
157
157
  } else {
158
158
  warnings.push(
159
- `Truncated: ${this.truncation.outputLines} lines shown (${formatSize(this.truncation.outputBytes)} limit)`,
159
+ `Truncated: ${this.#truncation.outputLines} lines shown (${formatSize(this.#truncation.outputBytes)} limit)`,
160
160
  );
161
161
  }
162
162
  statusParts.push(theme.fg("warning", warnings.join(". ")));
163
163
  }
164
164
 
165
165
  if (statusParts.length > 0) {
166
- this.contentContainer.addChild(new Text(`\n${statusParts.join("\n")}`, 1, 0));
166
+ this.#contentContainer.addChild(new Text(`\n${statusParts.join("\n")}`, 1, 0));
167
167
  }
168
168
  }
169
169
  }
170
170
 
171
- private normalizeOutput(text: string): string {
171
+ #normalizeOutput(text: string): string {
172
172
  return Bun.stripANSI(text).replace(/\r\n/g, "\n").replace(/\r/g, "\n");
173
173
  }
174
174
 
175
- private setOutput(output: string): void {
176
- const clean = this.normalizeOutput(output);
177
- this.outputLines = clean ? clean.split("\n") : [];
175
+ #setOutput(output: string): void {
176
+ const clean = this.#normalizeOutput(output);
177
+ this.#outputLines = clean ? clean.split("\n") : [];
178
178
  }
179
179
 
180
180
  getOutput(): string {
181
- return this.outputLines.join("\n");
181
+ return this.#outputLines.join("\n");
182
182
  }
183
183
 
184
184
  getCode(): string {