pi-ui-extend 0.1.9 → 0.1.13

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 (121) hide show
  1. package/README.md +23 -2
  2. package/dist/app/app.d.ts +4 -0
  3. package/dist/app/app.js +76 -7
  4. package/dist/app/cli/install.d.ts +16 -0
  5. package/dist/app/cli/install.js +34 -7
  6. package/dist/app/cli/startup-info.js +5 -2
  7. package/dist/app/cli/update.d.ts +7 -0
  8. package/dist/app/cli/update.js +11 -3
  9. package/dist/app/commands/command-controller.js +4 -0
  10. package/dist/app/commands/command-host.d.ts +4 -0
  11. package/dist/app/commands/command-model-actions.d.ts +5 -0
  12. package/dist/app/commands/command-model-actions.js +104 -0
  13. package/dist/app/commands/command-navigation-actions.d.ts +6 -1
  14. package/dist/app/commands/command-navigation-actions.js +37 -14
  15. package/dist/app/commands/command-registry.d.ts +4 -0
  16. package/dist/app/commands/command-registry.js +32 -0
  17. package/dist/app/commands/command-session-actions.d.ts +1 -0
  18. package/dist/app/commands/command-session-actions.js +15 -5
  19. package/dist/app/commands/shell-command.d.ts +7 -0
  20. package/dist/app/commands/shell-command.js +12 -4
  21. package/dist/app/commands/shell-controller.d.ts +1 -0
  22. package/dist/app/commands/shell-controller.js +1 -1
  23. package/dist/app/constants.d.ts +1 -1
  24. package/dist/app/constants.js +1 -1
  25. package/dist/app/icons.d.ts +1 -0
  26. package/dist/app/icons.js +3 -1
  27. package/dist/app/input/autocomplete-controller.d.ts +52 -0
  28. package/dist/app/input/autocomplete-controller.js +352 -0
  29. package/dist/app/input/input-action-controller.d.ts +1 -0
  30. package/dist/app/input/input-action-controller.js +21 -0
  31. package/dist/app/input/input-controller.d.ts +1 -0
  32. package/dist/app/input/input-controller.js +2 -0
  33. package/dist/app/input/input-paste-handler.d.ts +1 -0
  34. package/dist/app/input/input-paste-handler.js +22 -18
  35. package/dist/app/input/prompt-enhancer-controller.d.ts +7 -1
  36. package/dist/app/input/prompt-enhancer-controller.js +12 -3
  37. package/dist/app/input/voice-controller.d.ts +51 -1
  38. package/dist/app/input/voice-controller.js +42 -19
  39. package/dist/app/model/model-usage-status.d.ts +9 -0
  40. package/dist/app/model/model-usage-status.js +124 -34
  41. package/dist/app/popup/popup-action-controller.js +1 -1
  42. package/dist/app/process.d.ts +17 -0
  43. package/dist/app/process.js +68 -0
  44. package/dist/app/rendering/conversation-entry-renderer.js +8 -6
  45. package/dist/app/rendering/conversation-tool-renderer.js +3 -2
  46. package/dist/app/rendering/editor-layout-renderer.d.ts +1 -0
  47. package/dist/app/rendering/editor-layout-renderer.js +11 -1
  48. package/dist/app/rendering/message-content.js +65 -7
  49. package/dist/app/rendering/render-controller.js +6 -1
  50. package/dist/app/rendering/render-text.d.ts +3 -0
  51. package/dist/app/rendering/render-text.js +51 -3
  52. package/dist/app/rendering/status-line-renderer.d.ts +5 -1
  53. package/dist/app/rendering/status-line-renderer.js +61 -25
  54. package/dist/app/rendering/toast-renderer.js +10 -13
  55. package/dist/app/rendering/tool-block-renderer.d.ts +1 -0
  56. package/dist/app/rendering/tool-block-renderer.js +16 -33
  57. package/dist/app/runtime.d.ts +6 -1
  58. package/dist/app/runtime.js +35 -2
  59. package/dist/app/screen/clipboard.d.ts +11 -2
  60. package/dist/app/screen/clipboard.js +29 -21
  61. package/dist/app/screen/file-link-opener.d.ts +8 -0
  62. package/dist/app/screen/file-link-opener.js +11 -3
  63. package/dist/app/screen/file-links.js +3 -3
  64. package/dist/app/screen/image-opener.d.ts +12 -0
  65. package/dist/app/screen/image-opener.js +13 -5
  66. package/dist/app/screen/mouse-controller.d.ts +5 -2
  67. package/dist/app/screen/mouse-controller.js +16 -1
  68. package/dist/app/screen/screen-styler.d.ts +4 -1
  69. package/dist/app/screen/screen-styler.js +3 -2
  70. package/dist/app/screen/status-controller.d.ts +3 -0
  71. package/dist/app/screen/status-controller.js +23 -8
  72. package/dist/app/session/queued-message-controller.d.ts +7 -1
  73. package/dist/app/session/queued-message-controller.js +36 -21
  74. package/dist/app/session/resume-session-loader.d.ts +15 -0
  75. package/dist/app/session/resume-session-loader.js +204 -0
  76. package/dist/app/session/session-event-controller.d.ts +5 -1
  77. package/dist/app/session/session-event-controller.js +72 -5
  78. package/dist/app/session/session-history.js +4 -3
  79. package/dist/app/session/session-lifecycle-controller.d.ts +5 -0
  80. package/dist/app/session/session-lifecycle-controller.js +9 -1
  81. package/dist/app/session/tabs-controller.d.ts +10 -1
  82. package/dist/app/session/tabs-controller.js +101 -5
  83. package/dist/app/terminal/nerd-font-controller.d.ts +16 -0
  84. package/dist/app/terminal/nerd-font-controller.js +30 -23
  85. package/dist/app/terminal/terminal-controller.d.ts +1 -0
  86. package/dist/app/terminal/terminal-controller.js +1 -0
  87. package/dist/app/types.d.ts +14 -0
  88. package/dist/app/workspace/workspace-actions-controller.d.ts +1 -1
  89. package/dist/app/workspace/workspace-actions-controller.js +3 -3
  90. package/dist/app/workspace/workspace-undo.d.ts +1 -1
  91. package/dist/app/workspace/workspace-undo.js +22 -20
  92. package/dist/config.d.ts +27 -0
  93. package/dist/config.js +174 -1
  94. package/dist/default-pix-config.js +39 -353
  95. package/dist/input-editor.d.ts +7 -1
  96. package/dist/input-editor.js +47 -6
  97. package/dist/markdown-format.d.ts +1 -0
  98. package/dist/markdown-format.js +26 -1
  99. package/dist/schemas/index.d.ts +5 -0
  100. package/dist/schemas/index.js +5 -0
  101. package/dist/schemas/pi-tools-suite-schema.d.ts +177 -0
  102. package/dist/schemas/pi-tools-suite-schema.js +218 -0
  103. package/dist/schemas/pix-schema.d.ts +65 -0
  104. package/dist/schemas/pix-schema.js +91 -0
  105. package/dist/terminal-width.js +73 -56
  106. package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +3 -0
  107. package/external/pi-tools-suite/src/dcp/compression-blocks.ts +1 -0
  108. package/external/pi-tools-suite/src/dcp/prompts.ts +1 -0
  109. package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +46 -195
  110. package/external/pi-tools-suite/src/lib/lsp.ts +2 -1
  111. package/external/pi-tools-suite/src/lsp/_shared/output.ts +8 -7
  112. package/external/pi-tools-suite/src/lsp/manager.ts +4 -4
  113. package/external/pi-tools-suite/src/repo-discovery/index.ts +49 -2
  114. package/external/pi-tools-suite/src/todo/index.ts +4 -2
  115. package/external/pi-tools-suite/src/todo/state/selectors.ts +4 -0
  116. package/external/pi-tools-suite/src/todo/todo.ts +2 -6
  117. package/external/pi-tools-suite/src/todo/tool/response-envelope.ts +9 -1
  118. package/external/pi-tools-suite/src/tool-descriptions.ts +1 -1
  119. package/package.json +12 -3
  120. package/schemas/pi-tools-suite.json +881 -0
  121. package/schemas/pix.json +298 -0
package/README.md CHANGED
@@ -14,6 +14,23 @@ The npm package is currently named `pi-ui-extend` and installs the `pix` CLI.
14
14
  - Pix extension UI helpers for toasts, menus, and rows above the input editor.
15
15
  - Bundled `pi-tools-suite` payload. On startup, Pix links it into Pi's standard user extension directory.
16
16
 
17
+ ## Why use Pix instead of bare Pi?
18
+
19
+ Pix keeps the same Pi agent engine and SDK, but replaces the default terminal experience with a faster, more interactive coding cockpit:
20
+
21
+ - **Tabs for real work.** Keep several workspace-scoped Pi sessions open in one terminal, switch between tasks instantly, and restore tab state after restart.
22
+ - **A readable tool-call UI.** Tool output is grouped into clickable rows, so long reads, patches, shell logs, and errors can be collapsed or expanded instead of flooding the conversation.
23
+ - **Local shell without leaving the chat.** Prefix a command with `!` to run it in an ephemeral in-chat shell block, or use `!!` for raw TTY programs.
24
+ - **Voice prompts.** Dictate prompts locally in Russian or English with Vosk; no cloud speech service is required.
25
+ - **Prompt drafting assistance.** Inline AI autocomplete suggests the next words while you type, and `/enhance` rewrites a rough draft into a clearer agent prompt.
26
+ - **Workspace safety net.** Pix tracks supported file mutations and lets you roll back the latest workspace change with `/undo`.
27
+ - **Clickable files and images.** File paths in the conversation become clickable links that open in Zed, including line and column when available; image labels can be opened in the system viewer.
28
+ - **Session power tools.** Search across saved sessions, fork from an earlier message, clone the current branch, rename sessions, export/import JSONL, or share a private gist.
29
+ - **Queued follow-ups.** Add the next prompt while the agent is still running; Pix sends it automatically when the current turn finishes.
30
+ - **Live status panels.** Built-in panels surface todo state, async sub-agent activity, model/context usage, and compression stats above the editor.
31
+ - **Renderer-aware extensions.** Pix extensions can draw rows above/below the input, show stackable toasts, open searchable popup menus, and react through an isolated event bus.
32
+ - **Nicer terminal ergonomics.** Mouse scrolling, PageUp/PageDown scrollback, tab attention, status lines, terminal-output buffering, Nerd Font icons, and fallback icon mode are handled by the Pix renderer.
33
+
17
34
  ## Requirements
18
35
 
19
36
  - Node.js `>=22.19.0 <25` (`24.16.0` is pinned for development).
@@ -43,6 +60,12 @@ pix install --check
43
60
 
44
61
  `pix install` checks the icon font, `pi` CLI availability, and clipboard helpers. The `pix` launcher also prepends Pix's bundled dependency bin directory to `PATH`, so a package-manager install can use the bundled Pi CLI even when a separate global `pi` command is not present.
45
62
 
63
+ After the setup check, review the user config files:
64
+
65
+ - `~/.config/pi/pix.jsonc`: choose the voice-input language with `dictation.language` and keep only the `dictation.languages` models you want enabled.
66
+ - `~/.config/pi/pi-tools-suite.jsonc`: enable/configure your LSP servers under `lsp.servers` and adjust pi-tools-suite modules or sub-agent presets if needed.
67
+ - In Pix, run `/opencode-import` to import opencode accounts. For Antigravity OAuth accounts, run `/antigravity-import` or add another account with `/antigravity-add-account`.
68
+
46
69
  On startup, Pix ensures the bundled suite is available at:
47
70
 
48
71
  ```text
@@ -153,8 +176,6 @@ Useful environment variables:
153
176
  - `PIX_DISABLE_TERMINAL_OUTPUT_BUFFER=1` or `PIX_TERMINAL_OUTPUT_BUFFER=0`: disable Pix terminal output region buffering.
154
177
  - `PIX_USE_FALLBACK_ICONS=1` or `PIX_ICON_THEME=fallback`: use plain fallback icons when Nerd Font glyphs are unavailable.
155
178
  - `PIX_ICON_THEME=nerdFont`: force the Nerd Font icon theme.
156
- - `PIX_ANTIGRAVITY_GOOGLE_CLIENT_ID` / `ANTIGRAVITY_GOOGLE_CLIENT_ID`: Google OAuth client ID used for Antigravity quota/login integrations.
157
- - `PIX_ANTIGRAVITY_GOOGLE_CLIENT_SECRET` / `ANTIGRAVITY_GOOGLE_CLIENT_SECRET`: Google OAuth client secret used for Antigravity quota/login integrations.
158
179
 
159
180
  Pix user configuration is read from:
160
181
 
package/dist/app/app.d.ts CHANGED
@@ -31,6 +31,7 @@ export declare class PiUiExtendApp {
31
31
  private readonly nerdFontController;
32
32
  private readonly popupActions;
33
33
  private readonly promptEnhancer;
34
+ private readonly autocompleteController;
34
35
  private readonly mouseController;
35
36
  private readonly popupMenus;
36
37
  private readonly voiceController;
@@ -48,6 +49,7 @@ export declare class PiUiExtendApp {
48
49
  private get input();
49
50
  private set input(value);
50
51
  private running;
52
+ private scheduledRenderTimer;
51
53
  private todoPanelExpanded;
52
54
  private subagentsPanelExpanded;
53
55
  private allThinkingExpanded;
@@ -85,6 +87,7 @@ export declare class PiUiExtendApp {
85
87
  private setSessionStatus;
86
88
  private setSessionActivity;
87
89
  private toolDefaultExpanded;
90
+ private toggleSuperCompactTools;
88
91
  private stopBlinking;
89
92
  private stop;
90
93
  private toggleTerminalBellSound;
@@ -92,6 +95,7 @@ export declare class PiUiExtendApp {
92
95
  private showToast;
93
96
  private clearToastTimers;
94
97
  private render;
98
+ private scheduleRender;
95
99
  private renderStatusLine;
96
100
  private terminalColumns;
97
101
  private terminalRows;
package/dist/app/app.js CHANGED
@@ -17,6 +17,7 @@ import { NerdFontController } from "./terminal/nerd-font-controller.js";
17
17
  import { AppPopupActionController } from "./popup/popup-action-controller.js";
18
18
  import { AppPopupMenuController } from "./popup/popup-menu-controller.js";
19
19
  import { AppPromptEnhancerController } from "./input/prompt-enhancer-controller.js";
20
+ import { AppAutocompleteController } from "./input/autocomplete-controller.js";
20
21
  import { AppQueuedMessageController } from "./session/queued-message-controller.js";
21
22
  import { AppRequestHistory } from "./session/request-history.js";
22
23
  import { AppRenderController } from "./rendering/render-controller.js";
@@ -46,6 +47,7 @@ import { setAppIconTheme } from "./icons.js";
46
47
  const TERMINAL_BELL_ATTENTION_EVENT = "pix:terminal-bell:attention";
47
48
  const SUBAGENTS_LIVE_STATE_EVENT = "pi-tools-suite:async-subagents:live-state";
48
49
  const TODO_STATE_EVENT = "pi-tools-suite:todo:state";
50
+ const COALESCED_RENDER_DELAY_MS = 16;
49
51
  export class PiUiExtendApp {
50
52
  entries = [];
51
53
  options;
@@ -78,6 +80,7 @@ export class PiUiExtendApp {
78
80
  nerdFontController;
79
81
  popupActions;
80
82
  promptEnhancer;
83
+ autocompleteController;
81
84
  mouseController;
82
85
  popupMenus;
83
86
  voiceController;
@@ -111,6 +114,7 @@ export class PiUiExtendApp {
111
114
  get input() { return this.inputEditor.text; }
112
115
  set input(value) { this.inputEditor.setText(value); }
113
116
  running = false;
117
+ scheduledRenderTimer;
114
118
  todoPanelExpanded = true;
115
119
  subagentsPanelExpanded = true;
116
120
  allThinkingExpanded = false;
@@ -143,6 +147,7 @@ export class PiUiExtendApp {
143
147
  theme: this.theme,
144
148
  blinkController: this.blinkController,
145
149
  runtimeSession: () => this.runtime?.session,
150
+ render: () => this.scheduleRender(),
146
151
  });
147
152
  this.modelUsageController = new AppModelUsageController({
148
153
  runtimeSession: () => this.runtime?.session,
@@ -170,6 +175,8 @@ export class PiUiExtendApp {
170
175
  syncUserSessionEntryMetadata: () => this.workspaceActions.syncUserSessionEntryMetadata(),
171
176
  captureInputState: () => ({ text: this.inputEditor.text, cursor: this.inputEditor.cursor }),
172
177
  restoreInputState: (state) => this.restoreTabInputState(state.text, state.cursor),
178
+ captureDeferredUserMessages: () => this.queuedMessages.captureDeferredUserMessages(),
179
+ restoreDeferredUserMessages: (messages) => this.queuedMessages.restoreDeferredUserMessages(messages),
173
180
  addEntry: (entry) => this.addEntry(entry),
174
181
  showToast: (message, kind) => this.showToast(message, kind),
175
182
  render: () => this.render(),
@@ -191,6 +198,13 @@ export class PiUiExtendApp {
191
198
  toast: this.toastNotifier,
192
199
  render: () => this.render(),
193
200
  });
201
+ this.autocompleteController = new AppAutocompleteController({
202
+ runtime: () => this.runtime,
203
+ inputEditor: () => this.inputEditor,
204
+ autocompleteConfig: () => this.pixConfig.autocomplete,
205
+ isRunning: () => this.running,
206
+ render: () => this.render(),
207
+ });
194
208
  this.voiceController = new AppVoiceController({
195
209
  insertTranscript: (text) => this.insertVoiceTranscript(text),
196
210
  setPartialTranscript: (text) => this.setVoicePartialTranscript(text),
@@ -251,6 +265,7 @@ export class PiUiExtendApp {
251
265
  terminalBellSoundStatusWidgetEnabled: () => this.terminalBellSoundController.isEnabled(),
252
266
  voiceStatusWidgetText: () => this.voiceController.statusWidgetText(),
253
267
  voiceStatusWidgetActive: () => this.voiceController.statusWidgetActive(),
268
+ queueableInputActive: () => this.inputEditor.promptText.trimEnd().length > 0 || this.inputEditor.images.length > 0,
254
269
  userMessageJumpMenuActive: () => this.popupMenus.directMenu === "user-message-jump",
255
270
  allThinkingExpandedActive: () => this.allThinkingExpanded,
256
271
  superCompactToolsActive: () => this.superCompactTools,
@@ -291,12 +306,12 @@ export class PiUiExtendApp {
291
306
  cwd: this.options.cwd,
292
307
  sessionFile: () => this.runtime?.session.sessionFile,
293
308
  isRunning: () => this.running,
294
- render: () => this.render(),
309
+ render: () => this.scheduleRender(),
295
310
  });
296
311
  this.todoWidgetController = new AppTodoWidgetController({
297
312
  sessionFile: () => this.runtime?.session.sessionFile,
298
313
  isRunning: () => this.running,
299
- render: () => this.render(),
314
+ render: () => this.scheduleRender(),
300
315
  });
301
316
  this.workspaceActions = new AppWorkspaceActionsController({
302
317
  entries: this.entries,
@@ -320,13 +335,11 @@ export class PiUiExtendApp {
320
335
  conversationViewport: () => this.conversationViewport,
321
336
  isRunning: () => this.running,
322
337
  render: () => this.render(),
338
+ scheduleRender: () => this.scheduleRender(),
323
339
  setStatus: (status) => this.setStatus(status),
324
340
  restoreSessionStatus: () => this.restoreSessionStatus(),
325
341
  setSessionStatus: (session) => this.setSessionStatus(session),
326
342
  setSessionActivity: (activity) => this.setSessionActivity(activity),
327
- flushDeferredUserMessages: () => {
328
- void this.queuedMessages.flushDeferredUserMessages();
329
- },
330
343
  updateQueuedMessageStatus: () => this.queuedMessages.updateQueuedMessageStatus(),
331
344
  prepareWorkspaceMutation: (toolName, args) => this.workspaceActions.prepareWorkspaceMutation(toolName, args),
332
345
  workspaceMutationFromToolExecution: (input) => this.workspaceActions.workspaceMutationFromToolExecution(input),
@@ -355,6 +368,7 @@ export class PiUiExtendApp {
355
368
  setInput: (value) => this.inputEditor.setText(value),
356
369
  insertInput: (value) => this.inputEditor.insert(value),
357
370
  attachImage: (data, mimeType) => this.inputEditor.attachImage(data, mimeType),
371
+ onDeferredUserMessagesChanged: () => this.tabsController.persistActiveDeferredUserMessages(),
358
372
  });
359
373
  this.editorLayoutRenderer = new EditorLayoutRenderer({
360
374
  theme: this.theme,
@@ -365,6 +379,7 @@ export class PiUiExtendApp {
365
379
  get subagentsPanelExpanded() { return app.subagentsPanelExpanded; },
366
380
  get subagentsWidgetState() { return app.subagentsWidgetController.widgetState; },
367
381
  get voicePartialText() { return app.voicePartialText; },
382
+ get autocompleteSuggestion() { return app.autocompleteController.suggestionText(); },
368
383
  renderExtensionInputComponent: (width) => this.extensionUiController.renderActiveCustomUi(width),
369
384
  extensionInputUsesEditor: () => this.extensionUiController.activeCustomUiUsesEditor(),
370
385
  widgetTuiHandle: () => this.extensionUiController.widgetTuiHandle(),
@@ -401,6 +416,11 @@ export class PiUiExtendApp {
401
416
  getInput: () => this.input,
402
417
  setInput: (value) => this.setInput(value),
403
418
  promptEnhancerModelRef: () => this.pixConfig.promptEnhancer.modelRef,
419
+ autocompleteModelRef: () => this.pixConfig.autocomplete.modelRef,
420
+ setAutocompleteModelRef: (modelRef) => {
421
+ this.pixConfig.autocomplete.modelRef = modelRef;
422
+ this.autocompleteController.dispose();
423
+ },
404
424
  enhancePrompt: () => this.promptEnhancer.enhancePrompt(),
405
425
  isRunning: () => this.running,
406
426
  stop: () => this.stop(),
@@ -414,6 +434,9 @@ export class PiUiExtendApp {
414
434
  modelRef: (model) => this.menuItems.modelRef(model),
415
435
  getFavoriteScopedModels: () => this.menuItems.getFavoriteScopedModels(),
416
436
  setSessionStatus: (session) => this.setSessionStatus(session),
437
+ queueUserMessage: (text) => {
438
+ this.queuedMessages.deferUserMessage(this.queuedMessages.createSubmittedUserMessage(text, text, []));
439
+ },
417
440
  resetSessionView: () => this.resetSessionView(),
418
441
  loadSessionHistory: () => this.loadSessionHistory(),
419
442
  afterSessionReplacement: (message) => this.afterSessionReplacement(message),
@@ -430,6 +453,7 @@ export class PiUiExtendApp {
430
453
  this.popupMenus.setDirectQuery(query);
431
454
  },
432
455
  getResumeLoading: () => this.resumeLoading,
456
+ getResumeSessions: () => this.resumeSessions,
433
457
  setResumeLoading: (loading) => {
434
458
  this.resumeLoading = loading;
435
459
  },
@@ -499,12 +523,20 @@ export class PiUiExtendApp {
499
523
  showToast: (message, kind, options) => this.showToast(message, kind, options),
500
524
  dismissToast: (toastId) => this.toastController.dismissToast(toastId),
501
525
  refreshModelUsageStatus: () => this.refreshModelUsageStatusFromClick(),
526
+ queueInputFromStatus: () => {
527
+ void this.inputActions.queueInputFromEditor().catch((error) => {
528
+ this.addEntry({ id: createId("error"), kind: "error", text: `Queue input failed: ${error instanceof Error ? error.message : String(error)}` });
529
+ this.showToast("Queue input failed", "error");
530
+ this.setSessionStatus(this.runtime?.session);
531
+ this.render();
532
+ });
533
+ },
502
534
  toggleAllThinkingExpanded: () => {
503
535
  this.allThinkingExpanded = !this.allThinkingExpanded;
504
536
  this.render();
505
537
  },
506
538
  toggleSuperCompactTools: () => {
507
- this.superCompactTools = !this.superCompactTools;
539
+ this.toggleSuperCompactTools();
508
540
  this.render();
509
541
  },
510
542
  toggleTerminalBellSound: () => this.toggleTerminalBellSound(),
@@ -543,6 +575,7 @@ export class PiUiExtendApp {
543
575
  setSessionActivity: (activity) => this.setSessionActivity(activity),
544
576
  restoreSessionStatus: () => this.restoreSessionStatus(),
545
577
  render: () => this.render(),
578
+ scheduleRender: () => this.scheduleRender(),
546
579
  });
547
580
  this.inputActions = new AppInputActionController({
548
581
  runtime: () => this.runtime,
@@ -587,6 +620,7 @@ export class PiUiExtendApp {
587
620
  handleDirectPopupInput: (char) => this.popupMenus.handleDirectPopupInput(char),
588
621
  autocompleteModel: () => this.popupMenus.autocompleteModel(),
589
622
  autocompleteThinking: () => this.popupMenus.autocompleteThinking(),
623
+ acceptAutocompleteSuggestion: () => this.autocompleteController.acceptSuggestion(),
590
624
  autocompleteSlashCommand: () => this.popupMenus.autocompleteSlashCommand(),
591
625
  toggleVoiceRecording: () => {
592
626
  void this.voiceController.toggleRecording();
@@ -609,6 +643,7 @@ export class PiUiExtendApp {
609
643
  stopSubagentsPolling: () => this.subagentsWidgetController.stopPolling(),
610
644
  stopModelUsagePolling: () => this.modelUsageController.stopPolling(),
611
645
  stopVoiceInput: () => this.voiceController.dispose(),
646
+ stopAutocomplete: () => this.autocompleteController.dispose(),
612
647
  stopShellCommand: () => this.shellController.dispose(),
613
648
  unsubscribeSession: () => {
614
649
  this.sessionLifecycle.unsubscribeSession();
@@ -660,6 +695,7 @@ export class PiUiExtendApp {
660
695
  this.mouseController.statusThinkingTarget = undefined;
661
696
  this.mouseController.statusContextTarget = undefined;
662
697
  this.mouseController.statusModelUsageTarget = undefined;
698
+ this.mouseController.statusDraftQueueTarget = undefined;
663
699
  this.mouseController.statusUserJumpTarget = undefined;
664
700
  this.mouseController.statusThinkingExpandTarget = undefined;
665
701
  this.mouseController.statusCompactToolsTarget = undefined;
@@ -672,6 +708,7 @@ export class PiUiExtendApp {
672
708
  },
673
709
  scrollReset: () => this.scrollController.reset(),
674
710
  loadSessionHistoryEntries: () => this.sessionEvents.loadSessionHistory(),
711
+ loadSessionHistoryEntriesAsync: (options) => this.sessionEvents.loadSessionHistoryAsync(options),
675
712
  syncUserSessionEntryMetadata: () => this.workspaceActions.syncUserSessionEntryMetadata(),
676
713
  restoreTabsAfterStartup: () => this.tabsController.restoreAfterStartup(),
677
714
  render: () => this.render(),
@@ -735,15 +772,18 @@ export class PiUiExtendApp {
735
772
  this.popupMenus.resetInputMenuDismissals();
736
773
  }
737
774
  this.input = value;
775
+ this.autocompleteController.dispose();
738
776
  }
739
777
  resetInputAfterProgrammaticEdit() {
740
778
  this.requestHistory.resetNavigation();
741
779
  this.popupMenus.resetInputMenuDismissals();
780
+ this.autocompleteController.dispose();
742
781
  }
743
782
  restoreTabInputState(text, cursor) {
744
783
  this.requestHistory.resetNavigation();
745
784
  this.popupMenus.resetInputMenuDismissals();
746
785
  this.inputEditor.setText(text, cursor);
786
+ this.autocompleteController.dispose();
747
787
  }
748
788
  async clearPersistedInputDraft() {
749
789
  await this.tabsController.setInputStateForTab(this.tabsController.activeInputTabId(), { text: "", cursor: 0 });
@@ -768,7 +808,7 @@ export class PiUiExtendApp {
768
808
  if (this.voicePartialText === text)
769
809
  return;
770
810
  this.voicePartialText = text;
771
- this.render();
811
+ this.scheduleRender();
772
812
  }
773
813
  addVoiceSystemMessage(message) {
774
814
  this.addEntry({ id: createId("system"), kind: "system", text: message });
@@ -828,8 +868,23 @@ export class PiUiExtendApp {
828
868
  this.statusController.setSessionActivity(activity);
829
869
  }
830
870
  toolDefaultExpanded(toolName) {
871
+ if (this.superCompactTools)
872
+ return false;
831
873
  return resolveToolRule(toolName, this.pixConfig.toolRenderer).defaultExpanded === true;
832
874
  }
875
+ toggleSuperCompactTools() {
876
+ this.superCompactTools = !this.superCompactTools;
877
+ for (const entry of this.entries) {
878
+ if (entry.kind !== "tool")
879
+ continue;
880
+ const defaultExpanded = resolveToolRule(entry.toolName, this.pixConfig.toolRenderer).defaultExpanded === true;
881
+ const nextExpanded = this.superCompactTools ? false : defaultExpanded;
882
+ if (entry.expanded === nextExpanded)
883
+ continue;
884
+ entry.expanded = nextExpanded;
885
+ this.touchEntry(entry);
886
+ }
887
+ }
833
888
  stopBlinking() {
834
889
  this.blinkController.dispose();
835
890
  }
@@ -877,8 +932,22 @@ export class PiUiExtendApp {
877
932
  this.toastController.clearToastTimers();
878
933
  }
879
934
  render() {
935
+ if (this.scheduledRenderTimer) {
936
+ clearTimeout(this.scheduledRenderTimer);
937
+ this.scheduledRenderTimer = undefined;
938
+ }
939
+ this.autocompleteController.observeInput();
880
940
  this.renderController.render();
881
941
  }
942
+ scheduleRender() {
943
+ if (!this.running || this.scheduledRenderTimer)
944
+ return;
945
+ this.scheduledRenderTimer = setTimeout(() => {
946
+ this.scheduledRenderTimer = undefined;
947
+ this.renderController.render();
948
+ }, COALESCED_RENDER_DELAY_MS);
949
+ this.scheduledRenderTimer.unref?.();
950
+ }
882
951
  renderStatusLine() {
883
952
  this.renderController.renderStatusLine();
884
953
  }
@@ -1,10 +1,26 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { installJetBrainsNerdFont, isJetBrainsNerdFontInstalled } from "../terminal/nerd-font-controller.js";
4
+ import { clipboardInstallHint, clipboardSupportAvailable } from "../screen/clipboard.js";
5
+ type PixInstallTestDeps = {
6
+ existsSync: typeof existsSync;
7
+ spawn: typeof spawn;
8
+ isJetBrainsNerdFontInstalled: typeof isJetBrainsNerdFontInstalled;
9
+ installJetBrainsNerdFont: typeof installJetBrainsNerdFont;
10
+ clipboardSupportAvailable: typeof clipboardSupportAvailable;
11
+ clipboardInstallHint: typeof clipboardInstallHint;
12
+ };
13
+ export declare function setPixInstallTestDeps(overrides?: Partial<PixInstallTestDeps>): void;
1
14
  export type PixInstallCliOptions = {
2
15
  checkOnly: boolean;
3
16
  help: boolean;
4
17
  };
5
18
  export type PixInstallCliContext = {
6
19
  env?: NodeJS.ProcessEnv;
20
+ homeDir?: string;
7
21
  };
22
+ export declare function formatPixInstallNextSteps(homeDir?: string): string;
8
23
  export declare function pixInstallUsage(): string;
9
24
  export declare function parsePixInstallArgs(argv: readonly string[]): PixInstallCliOptions;
10
25
  export declare function runPixInstallCli(argv?: readonly string[], context?: PixInstallCliContext): Promise<number>;
26
+ export {};
@@ -1,8 +1,34 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { existsSync } from "node:fs";
3
+ import { homedir } from "node:os";
3
4
  import { join } from "node:path";
4
5
  import { FONT_FAMILY_NAME, installJetBrainsNerdFont, isJetBrainsNerdFontInstalled, } from "../terminal/nerd-font-controller.js";
5
6
  import { clipboardInstallHint, clipboardSupportAvailable } from "../screen/clipboard.js";
7
+ import { getPixConfigPath } from "../../config.js";
8
+ const defaultPixInstallDeps = {
9
+ existsSync,
10
+ spawn,
11
+ isJetBrainsNerdFontInstalled,
12
+ installJetBrainsNerdFont,
13
+ clipboardSupportAvailable,
14
+ clipboardInstallHint,
15
+ };
16
+ let pixInstallDeps = defaultPixInstallDeps;
17
+ export function setPixInstallTestDeps(overrides) {
18
+ pixInstallDeps = overrides ? { ...defaultPixInstallDeps, ...overrides } : defaultPixInstallDeps;
19
+ }
20
+ export function formatPixInstallNextSteps(homeDir = homedir()) {
21
+ const pixConfigPath = getPixConfigPath(homeDir);
22
+ const toolsConfigPath = join(homeDir, ".config", "pi", "pi-tools-suite.jsonc");
23
+ return [
24
+ "",
25
+ "Next steps:",
26
+ ` 1. Edit ${pixConfigPath} and set dictation.language / dictation.languages for voice input.`,
27
+ ` 2. Edit ${toolsConfigPath} and enable the LSP servers you use under lsp.servers.`,
28
+ " 3. Start pix, then run /opencode-import to import opencode accounts.",
29
+ " For Antigravity accounts, run /antigravity-import or /antigravity-add-account.",
30
+ ].join("\n");
31
+ }
6
32
  export function pixInstallUsage() {
7
33
  return `Usage: pix install [--check]
8
34
  pix setup [--check]
@@ -50,7 +76,7 @@ export async function runPixInstallCli(argv = process.argv.slice(2), context = {
50
76
  const env = context.env ?? process.env;
51
77
  let failures = 0;
52
78
  console.log("Pix install checks");
53
- if (await isJetBrainsNerdFontInstalled()) {
79
+ if (await pixInstallDeps.isJetBrainsNerdFontInstalled()) {
54
80
  console.log(`✓ ${FONT_FAMILY_NAME} is installed`);
55
81
  }
56
82
  else if (options.checkOnly) {
@@ -59,7 +85,7 @@ export async function runPixInstallCli(argv = process.argv.slice(2), context = {
59
85
  }
60
86
  else {
61
87
  try {
62
- await installJetBrainsNerdFont();
88
+ await pixInstallDeps.installJetBrainsNerdFont();
63
89
  console.log(`✓ Installed ${FONT_FAMILY_NAME}`);
64
90
  }
65
91
  catch (error) {
@@ -86,19 +112,20 @@ export async function runPixInstallCli(argv = process.argv.slice(2), context = {
86
112
  failures += 1;
87
113
  }
88
114
  }
89
- if (clipboardSupportAvailable(env)) {
115
+ if (await pixInstallDeps.clipboardSupportAvailable(env)) {
90
116
  console.log("✓ Clipboard support is available");
91
117
  }
92
118
  else {
93
- console.log(`! Clipboard support is missing. ${clipboardInstallHint()}`);
119
+ console.log(`! Clipboard support is missing. ${pixInstallDeps.clipboardInstallHint()}`);
94
120
  if (process.platform === "linux")
95
121
  failures += 1;
96
122
  }
123
+ console.log(formatPixInstallNextSteps(context.homeDir));
97
124
  return failures === 0 ? 0 : 1;
98
125
  }
99
126
  async function resolvePiCliStatus(env) {
100
127
  const bundledBin = env.PIX_BUNDLED_PI_BIN;
101
- if (bundledBin && (existsSync(join(bundledBin, process.platform === "win32" ? "pi.cmd" : "pi")) || existsSync(join(bundledBin, "pi")))) {
128
+ if (bundledBin && (pixInstallDeps.existsSync(join(bundledBin, process.platform === "win32" ? "pi.cmd" : "pi")) || pixInstallDeps.existsSync(join(bundledBin, "pi")))) {
102
129
  return { available: true, detail: "bundled with Pix" };
103
130
  }
104
131
  if (commandExists("pi", env))
@@ -112,11 +139,11 @@ function commandExists(command, env = process.env) {
112
139
  const pathValue = env.PATH ?? "";
113
140
  const dirs = pathValue.split(process.platform === "win32" ? ";" : ":").filter(Boolean);
114
141
  const names = process.platform === "win32" ? [command, `${command}.cmd`, `${command}.exe`, `${command}.bat`] : [command];
115
- return dirs.some((dir) => names.some((name) => existsSync(join(dir, name))));
142
+ return dirs.some((dir) => names.some((name) => pixInstallDeps.existsSync(join(dir, name))));
116
143
  }
117
144
  async function runRequired(command, args) {
118
145
  await new Promise((resolve, reject) => {
119
- const child = spawn(command, args, { stdio: ["ignore", "ignore", "pipe"] });
146
+ const child = pixInstallDeps.spawn(command, args, { stdio: ["ignore", "ignore", "pipe"] });
120
147
  let stderr = "";
121
148
  child.stderr.on("data", (chunk) => {
122
149
  stderr = `${stderr}${chunk.toString("utf8")}`.slice(-800);
@@ -158,9 +158,12 @@ function displayPath(pathValue, cwd) {
158
158
  }
159
159
  function formatPath(pathValue, cwd) {
160
160
  if (!isAbsolute(pathValue))
161
- return pathValue;
161
+ return displayPathSeparators(pathValue);
162
162
  const rel = relative(cwd, pathValue);
163
- return rel && !rel.startsWith("..") && !isAbsolute(rel) ? rel : basename(pathValue);
163
+ return displayPathSeparators(rel && !rel.startsWith("..") && !isAbsolute(rel) ? rel : basename(pathValue));
164
+ }
165
+ function displayPathSeparators(pathValue) {
166
+ return pathValue.replace(/\\/g, "/");
164
167
  }
165
168
  function unique(...groups) {
166
169
  const seen = new Set();
@@ -1,3 +1,8 @@
1
+ type PixUpdateTestDeps = {
2
+ checkPixUpdate: typeof checkPixUpdate;
3
+ runCommand: typeof runCommand;
4
+ };
5
+ export declare function setPixUpdateTestDeps(overrides?: Partial<PixUpdateTestDeps>): void;
1
6
  export type PixUpdateCliOptions = {
2
7
  checkOnly: boolean;
3
8
  force: boolean;
@@ -36,3 +41,5 @@ export declare function formatPixUpdateCheck(result: PixUpdateCheckResult): stri
36
41
  export declare function formatPixStartupUpdateDialog(result: PixUpdateCheckResult): string;
37
42
  export declare function getPixSelfUpdateCommand(packageName: string, latestVersion?: string, packageRoot?: string): PixSelfUpdateCommand | undefined;
38
43
  export declare function runPixUpdateCli(argv?: readonly string[]): Promise<number>;
44
+ declare function runCommand(command: PixSelfUpdateCommand): Promise<void>;
45
+ export {};
@@ -5,6 +5,14 @@ import { fileURLToPath } from "node:url";
5
5
  import { getAgentDir, SettingsManager } from "@earendil-works/pi-coding-agent";
6
6
  const DEFAULT_UPDATE_TIMEOUT_MS = 10_000;
7
7
  const NPM_REGISTRY_URL = "https://registry.npmjs.org";
8
+ const defaultPixUpdateDeps = {
9
+ checkPixUpdate,
10
+ runCommand,
11
+ };
12
+ let pixUpdateDeps = defaultPixUpdateDeps;
13
+ export function setPixUpdateTestDeps(overrides) {
14
+ pixUpdateDeps = overrides ? { ...defaultPixUpdateDeps, ...overrides } : defaultPixUpdateDeps;
15
+ }
8
16
  export function pixUpdateUsage() {
9
17
  return `Usage: pix update [--check] [--force]
10
18
 
@@ -157,7 +165,7 @@ export async function runPixUpdateCli(argv = process.argv.slice(2)) {
157
165
  console.log(pixUpdateUsage());
158
166
  return 0;
159
167
  }
160
- const check = await checkPixUpdate();
168
+ const check = await pixUpdateDeps.checkPixUpdate();
161
169
  console.log(formatPixUpdateCheck(check));
162
170
  if (options.checkOnly)
163
171
  return check.status === "unavailable" ? 1 : 0;
@@ -165,14 +173,14 @@ export async function runPixUpdateCli(argv = process.argv.slice(2)) {
165
173
  return 0;
166
174
  if ((check.status === "skipped" || check.status === "unavailable") && !options.force)
167
175
  return 1;
168
- const command = getPixSelfUpdateCommand(check.packageName, check.latestVersion);
176
+ const command = getPixSelfUpdateCommand(check.packageName, check.latestVersion, check.packageRoot);
169
177
  if (!command) {
170
178
  console.error(`pix cannot self-update this installation. ${sourceCheckoutUpdateHint()}`);
171
179
  return 1;
172
180
  }
173
181
  console.log(`Updating Pix with ${command.display}...`);
174
182
  try {
175
- await runCommand(command);
183
+ await pixUpdateDeps.runCommand(command);
176
184
  console.log("Updated Pix. Restart any running pix sessions.");
177
185
  return 0;
178
186
  }
@@ -28,13 +28,17 @@ export class AppCommandController {
28
28
  return {
29
29
  runSettingsCommand: () => this.modelActions.runSettingsCommand(),
30
30
  runModelSlashCommand: (argumentsText) => this.modelActions.runModelSlashCommand(argumentsText),
31
+ runDefaultModelSlashCommand: (argumentsText) => this.modelActions.runDefaultModelSlashCommand(argumentsText),
32
+ runAutocompleteSlashCommand: (argumentsText) => this.modelActions.runAutocompleteSlashCommand(argumentsText),
31
33
  runScopedModelsCommand: (argumentsText) => this.modelActions.runScopedModelsCommand(argumentsText),
32
34
  runThinkingSlashCommand: (argumentsText) => this.modelActions.runThinkingSlashCommand(argumentsText),
35
+ runDefaultThinkingSlashCommand: (argumentsText) => this.modelActions.runDefaultThinkingSlashCommand(argumentsText),
33
36
  runEnhanceCommand: () => this.host.enhancePrompt(),
34
37
  runExportCommand: (argumentsText) => this.sessionActions.runExportCommand(argumentsText),
35
38
  runImportCommand: (argumentsText) => this.sessionActions.runImportCommand(argumentsText),
36
39
  runShareCommand: () => this.sessionActions.runShareCommand(),
37
40
  runCopyCommand: () => this.sessionActions.runCopyCommand(),
41
+ runQueueCommand: (argumentsText) => this.sessionActions.runQueueCommand(argumentsText),
38
42
  runNameCommand: (argumentsText) => this.sessionActions.runNameCommand(argumentsText),
39
43
  runSessionInfoCommand: () => this.sessionActions.runSessionInfoCommand(),
40
44
  runUsageCommand: () => this.sessionActions.runUsageCommand(),
@@ -9,6 +9,8 @@ export type CommandControllerHost = {
9
9
  getInput(): string;
10
10
  setInput(value: string): void;
11
11
  promptEnhancerModelRef(): string;
12
+ autocompleteModelRef(): string;
13
+ setAutocompleteModelRef(modelRef: string): void;
12
14
  enhancePrompt(): Promise<void>;
13
15
  isRunning(): boolean;
14
16
  stop(): void | Promise<void>;
@@ -22,6 +24,7 @@ export type CommandControllerHost = {
22
24
  modelRef(model: SessionModel): string;
23
25
  getFavoriteScopedModels(): ScopedSessionModel[];
24
26
  setSessionStatus(session: AgentSession | undefined): void;
27
+ queueUserMessage(text: string): void;
25
28
  resetSessionView(): void;
26
29
  loadSessionHistory(): void;
27
30
  afterSessionReplacement(message?: string): void;
@@ -35,6 +38,7 @@ export type CommandControllerHost = {
35
38
  getDirectPopupMenuQuery(): string;
36
39
  setDirectPopupMenuQuery(query: string): void;
37
40
  getResumeLoading(): boolean;
41
+ getResumeSessions(): readonly SessionInfo[];
38
42
  setResumeLoading(loading: boolean): void;
39
43
  setResumeSessions(sessions: SessionInfo[]): void;
40
44
  openResumeMenuWithQuery(query: string): void;
@@ -5,8 +5,13 @@ export declare class ModelCommandActions {
5
5
  constructor(host: CommandControllerHost);
6
6
  runSettingsCommand(): Promise<void>;
7
7
  runModelSlashCommand(argumentsText: string): Promise<void>;
8
+ runDefaultModelSlashCommand(argumentsText: string): Promise<void>;
9
+ runAutocompleteSlashCommand(argumentsText: string): Promise<void>;
8
10
  runScopedModelsCommand(argumentsText: string): Promise<void>;
9
11
  runThinkingSlashCommand(argumentsText: string): Promise<void>;
12
+ runDefaultThinkingSlashCommand(argumentsText: string): Promise<void>;
10
13
  runModelCommand(model: SessionModel): Promise<void>;
11
14
  runThinkingCommand(level: ThinkingLevel): Promise<void>;
15
+ private saveDefaultModel;
16
+ private saveDefaultThinking;
12
17
  }