@mariozechner/pi-coding-agent 0.45.7 → 0.47.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 (169) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +24 -3
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +1 -0
  5. package/dist/cli.js.map +1 -1
  6. package/dist/config.d.ts +2 -0
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/config.js +2 -0
  9. package/dist/config.js.map +1 -1
  10. package/dist/core/agent-session.d.ts +11 -3
  11. package/dist/core/agent-session.d.ts.map +1 -1
  12. package/dist/core/agent-session.js +81 -14
  13. package/dist/core/agent-session.js.map +1 -1
  14. package/dist/core/compaction/compaction.d.ts.map +1 -1
  15. package/dist/core/compaction/compaction.js +6 -5
  16. package/dist/core/compaction/compaction.js.map +1 -1
  17. package/dist/core/export-html/ansi-to-html.d.ts +22 -0
  18. package/dist/core/export-html/ansi-to-html.d.ts.map +1 -0
  19. package/dist/core/export-html/ansi-to-html.js +249 -0
  20. package/dist/core/export-html/ansi-to-html.js.map +1 -0
  21. package/dist/core/export-html/index.d.ts +17 -0
  22. package/dist/core/export-html/index.d.ts.map +1 -1
  23. package/dist/core/export-html/index.js +52 -23
  24. package/dist/core/export-html/index.js.map +1 -1
  25. package/dist/core/export-html/template.css +0 -33
  26. package/dist/core/export-html/template.js +171 -18
  27. package/dist/core/export-html/tool-renderer.d.ts +35 -0
  28. package/dist/core/export-html/tool-renderer.d.ts.map +1 -0
  29. package/dist/core/export-html/tool-renderer.js +57 -0
  30. package/dist/core/export-html/tool-renderer.js.map +1 -0
  31. package/dist/core/extensions/index.d.ts +1 -1
  32. package/dist/core/extensions/index.d.ts.map +1 -1
  33. package/dist/core/extensions/index.js.map +1 -1
  34. package/dist/core/extensions/runner.d.ts +5 -1
  35. package/dist/core/extensions/runner.d.ts.map +1 -1
  36. package/dist/core/extensions/runner.js +41 -0
  37. package/dist/core/extensions/runner.js.map +1 -1
  38. package/dist/core/extensions/types.d.ts +24 -1
  39. package/dist/core/extensions/types.d.ts.map +1 -1
  40. package/dist/core/extensions/types.js.map +1 -1
  41. package/dist/core/keybindings.d.ts +1 -5
  42. package/dist/core/keybindings.d.ts.map +1 -1
  43. package/dist/core/keybindings.js +4 -12
  44. package/dist/core/keybindings.js.map +1 -1
  45. package/dist/core/model-resolver.d.ts.map +1 -1
  46. package/dist/core/model-resolver.js +1 -0
  47. package/dist/core/model-resolver.js.map +1 -1
  48. package/dist/core/prompt-templates.d.ts.map +1 -1
  49. package/dist/core/prompt-templates.js +4 -27
  50. package/dist/core/prompt-templates.js.map +1 -1
  51. package/dist/core/skills.d.ts.map +1 -1
  52. package/dist/core/skills.js +6 -37
  53. package/dist/core/skills.js.map +1 -1
  54. package/dist/core/system-prompt.d.ts.map +1 -1
  55. package/dist/core/system-prompt.js +19 -14
  56. package/dist/core/system-prompt.js.map +1 -1
  57. package/dist/core/tools/edit-diff.d.ts +30 -0
  58. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  59. package/dist/core/tools/edit-diff.js +82 -10
  60. package/dist/core/tools/edit-diff.js.map +1 -1
  61. package/dist/core/tools/edit.d.ts.map +1 -1
  62. package/dist/core/tools/edit.js +16 -13
  63. package/dist/core/tools/edit.js.map +1 -1
  64. package/dist/core/tools/path-utils.d.ts +1 -0
  65. package/dist/core/tools/path-utils.d.ts.map +1 -1
  66. package/dist/core/tools/path-utils.js +7 -0
  67. package/dist/core/tools/path-utils.js.map +1 -1
  68. package/dist/core/tools/read.d.ts.map +1 -1
  69. package/dist/core/tools/read.js +13 -2
  70. package/dist/core/tools/read.js.map +1 -1
  71. package/dist/index.d.ts +3 -1
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/index.js +3 -0
  74. package/dist/index.js.map +1 -1
  75. package/dist/main.d.ts.map +1 -1
  76. package/dist/main.js +70 -9
  77. package/dist/main.js.map +1 -1
  78. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  79. package/dist/modes/interactive/components/bash-execution.js +4 -3
  80. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  81. package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
  82. package/dist/modes/interactive/components/bordered-loader.js +2 -1
  83. package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
  84. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  85. package/dist/modes/interactive/components/branch-summary-message.js +4 -1
  86. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  87. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  88. package/dist/modes/interactive/components/compaction-summary-message.js +4 -1
  89. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  90. package/dist/modes/interactive/components/custom-editor.d.ts +2 -2
  91. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  92. package/dist/modes/interactive/components/custom-editor.js +5 -5
  93. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  94. package/dist/modes/interactive/components/extension-editor.d.ts +3 -1
  95. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  96. package/dist/modes/interactive/components/extension-editor.js +15 -9
  97. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  98. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  99. package/dist/modes/interactive/components/extension-input.js +2 -1
  100. package/dist/modes/interactive/components/extension-input.js.map +1 -1
  101. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  102. package/dist/modes/interactive/components/extension-selector.js +6 -1
  103. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  104. package/dist/modes/interactive/components/index.d.ts +1 -0
  105. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  106. package/dist/modes/interactive/components/index.js +1 -0
  107. package/dist/modes/interactive/components/index.js.map +1 -1
  108. package/dist/modes/interactive/components/keybinding-hints.d.ts +41 -0
  109. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -0
  110. package/dist/modes/interactive/components/keybinding-hints.js +61 -0
  111. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -0
  112. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  113. package/dist/modes/interactive/components/login-dialog.js +4 -3
  114. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  115. package/dist/modes/interactive/components/session-selector-search.d.ts +21 -0
  116. package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -0
  117. package/dist/modes/interactive/components/session-selector-search.js +146 -0
  118. package/dist/modes/interactive/components/session-selector-search.js.map +1 -0
  119. package/dist/modes/interactive/components/session-selector.d.ts +7 -1
  120. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  121. package/dist/modes/interactive/components/session-selector.js +35 -8
  122. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  123. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  124. package/dist/modes/interactive/components/tool-execution.js +14 -8
  125. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  126. package/dist/modes/interactive/components/tree-selector.d.ts +7 -0
  127. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  128. package/dist/modes/interactive/components/tree-selector.js +143 -4
  129. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  130. package/dist/modes/interactive/interactive-mode.d.ts +4 -5
  131. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  132. package/dist/modes/interactive/interactive-mode.js +63 -126
  133. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  134. package/dist/modes/print-mode.d.ts.map +1 -1
  135. package/dist/modes/print-mode.js +1 -1
  136. package/dist/modes/print-mode.js.map +1 -1
  137. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  138. package/dist/modes/rpc/rpc-mode.js +1 -0
  139. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  140. package/dist/utils/frontmatter.d.ts +8 -0
  141. package/dist/utils/frontmatter.d.ts.map +1 -0
  142. package/dist/utils/frontmatter.js +26 -0
  143. package/dist/utils/frontmatter.js.map +1 -0
  144. package/dist/utils/image-convert.d.ts.map +1 -1
  145. package/dist/utils/image-convert.js +12 -14
  146. package/dist/utils/image-convert.js.map +1 -1
  147. package/dist/utils/image-resize.d.ts +2 -2
  148. package/dist/utils/image-resize.d.ts.map +1 -1
  149. package/dist/utils/image-resize.js +102 -122
  150. package/dist/utils/image-resize.js.map +1 -1
  151. package/docs/extensions.md +51 -0
  152. package/docs/rpc.md +15 -15
  153. package/docs/tui.md +26 -0
  154. package/examples/extensions/input-transform.ts +43 -0
  155. package/examples/extensions/modal-editor.ts +1 -1
  156. package/examples/extensions/overlay-test.ts +8 -3
  157. package/examples/extensions/plan-mode/README.md +1 -1
  158. package/examples/extensions/plan-mode/index.ts +2 -2
  159. package/examples/extensions/question.ts +1 -1
  160. package/examples/extensions/questionnaire.ts +1 -1
  161. package/examples/extensions/rainbow-editor.ts +1 -8
  162. package/examples/extensions/subagent/agents.ts +3 -32
  163. package/examples/extensions/with-deps/package-lock.json +2 -2
  164. package/examples/extensions/with-deps/package.json +1 -1
  165. package/package.json +6 -5
  166. package/dist/utils/vips.d.ts +0 -11
  167. package/dist/utils/vips.d.ts.map +0 -1
  168. package/dist/utils/vips.js +0 -35
  169. package/dist/utils/vips.js.map +0 -1
@@ -7,9 +7,9 @@ import * as fs from "node:fs";
7
7
  import * as os from "node:os";
8
8
  import * as path from "node:path";
9
9
  import { getOAuthProviders, } from "@mariozechner/pi-ai";
10
- import { CombinedAutocompleteProvider, Container, fuzzyFilter, getEditorKeybindings, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, Text, TruncatedText, TUI, visibleWidth, } from "@mariozechner/pi-tui";
10
+ import { CombinedAutocompleteProvider, Container, fuzzyFilter, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, Text, TruncatedText, TUI, visibleWidth, } from "@mariozechner/pi-tui";
11
11
  import { spawn, spawnSync } from "child_process";
12
- import { APP_NAME, getAuthPath, getDebugLogPath, isBunBinary, VERSION } from "../../config.js";
12
+ import { APP_NAME, getAuthPath, getDebugLogPath, isBunBinary, isBunRuntime, VERSION } from "../../config.js";
13
13
  import { FooterDataProvider } from "../../core/footer-data-provider.js";
14
14
  import { KeybindingsManager } from "../../core/keybindings.js";
15
15
  import { createCompactionSummaryMessage } from "../../core/messages.js";
@@ -33,6 +33,7 @@ import { ExtensionEditorComponent } from "./components/extension-editor.js";
33
33
  import { ExtensionInputComponent } from "./components/extension-input.js";
34
34
  import { ExtensionSelectorComponent } from "./components/extension-selector.js";
35
35
  import { FooterComponent } from "./components/footer.js";
36
+ import { appKey, appKeyHint, editorKey, keyHint, rawKeyHint } from "./components/keybinding-hints.js";
36
37
  import { LoginDialogComponent } from "./components/login-dialog.js";
37
38
  import { ModelSelectorComponent } from "./components/model-selector.js";
38
39
  import { OAuthSelectorComponent } from "./components/oauth-selector.js";
@@ -66,7 +67,7 @@ export class InteractiveMode {
66
67
  isInitialized = false;
67
68
  onInputCallback;
68
69
  loadingAnimation = undefined;
69
- defaultWorkingMessage = "Working... (esc to interrupt)";
70
+ defaultWorkingMessage = "Working...";
70
71
  lastSigintTime = 0;
71
72
  lastEscapeTime = 0;
72
73
  changelogMarkdown = undefined;
@@ -135,7 +136,7 @@ export class InteractiveMode {
135
136
  this.statusContainer = new Container();
136
137
  this.widgetContainer = new Container();
137
138
  this.keybindings = KeybindingsManager.create();
138
- this.defaultEditor = new CustomEditor(getEditorTheme(), this.keybindings);
139
+ this.defaultEditor = new CustomEditor(this.ui, getEditorTheme(), this.keybindings);
139
140
  this.editor = this.defaultEditor;
140
141
  this.editorContainer = new Container();
141
142
  this.editorContainer.addChild(this.editor);
@@ -231,82 +232,30 @@ export class InteractiveMode {
231
232
  this.setupAutocomplete(this.fdPath);
232
233
  // Add header with keybindings from config
233
234
  const logo = theme.bold(theme.fg("accent", APP_NAME)) + theme.fg("dim", ` v${this.version}`);
234
- // Format keybinding for startup display (lowercase, compact)
235
- const formatStartupKey = (keys) => {
236
- const keyArray = Array.isArray(keys) ? keys : [keys];
237
- return keyArray.join("/");
238
- };
235
+ // Build startup instructions using keybinding hint helpers
239
236
  const kb = this.keybindings;
240
- const interrupt = formatStartupKey(kb.getKeys("interrupt"));
241
- const clear = formatStartupKey(kb.getKeys("clear"));
242
- const exit = formatStartupKey(kb.getKeys("exit"));
243
- const suspend = formatStartupKey(kb.getKeys("suspend"));
244
- const deleteToLineEnd = formatStartupKey(getEditorKeybindings().getKeys("deleteToLineEnd"));
245
- const cycleThinkingLevel = formatStartupKey(kb.getKeys("cycleThinkingLevel"));
246
- const cycleModelForward = formatStartupKey(kb.getKeys("cycleModelForward"));
247
- const cycleModelBackward = formatStartupKey(kb.getKeys("cycleModelBackward"));
248
- const selectModel = formatStartupKey(kb.getKeys("selectModel"));
249
- const expandTools = formatStartupKey(kb.getKeys("expandTools"));
250
- const toggleThinking = formatStartupKey(kb.getKeys("toggleThinking"));
251
- const externalEditor = formatStartupKey(kb.getKeys("externalEditor"));
252
- const followUp = formatStartupKey(kb.getKeys("followUp"));
253
- const dequeue = formatStartupKey(kb.getKeys("dequeue"));
254
- const instructions = theme.fg("dim", interrupt) +
255
- theme.fg("muted", " to interrupt") +
256
- "\n" +
257
- theme.fg("dim", clear) +
258
- theme.fg("muted", " to clear") +
259
- "\n" +
260
- theme.fg("dim", `${clear} twice`) +
261
- theme.fg("muted", " to exit") +
262
- "\n" +
263
- theme.fg("dim", exit) +
264
- theme.fg("muted", " to exit (empty)") +
265
- "\n" +
266
- theme.fg("dim", suspend) +
267
- theme.fg("muted", " to suspend") +
268
- "\n" +
269
- theme.fg("dim", deleteToLineEnd) +
270
- theme.fg("muted", " to delete to end") +
271
- "\n" +
272
- theme.fg("dim", cycleThinkingLevel) +
273
- theme.fg("muted", " to cycle thinking") +
274
- "\n" +
275
- theme.fg("dim", `${cycleModelForward}/${cycleModelBackward}`) +
276
- theme.fg("muted", " to cycle models") +
277
- "\n" +
278
- theme.fg("dim", selectModel) +
279
- theme.fg("muted", " to select model") +
280
- "\n" +
281
- theme.fg("dim", expandTools) +
282
- theme.fg("muted", " to expand tools") +
283
- "\n" +
284
- theme.fg("dim", toggleThinking) +
285
- theme.fg("muted", " to toggle thinking") +
286
- "\n" +
287
- theme.fg("dim", externalEditor) +
288
- theme.fg("muted", " for external editor") +
289
- "\n" +
290
- theme.fg("dim", "/") +
291
- theme.fg("muted", " for commands") +
292
- "\n" +
293
- theme.fg("dim", "!") +
294
- theme.fg("muted", " to run bash") +
295
- "\n" +
296
- theme.fg("dim", "!!") +
297
- theme.fg("muted", " to run bash (no context)") +
298
- "\n" +
299
- theme.fg("dim", followUp) +
300
- theme.fg("muted", " to queue follow-up") +
301
- "\n" +
302
- theme.fg("dim", dequeue) +
303
- theme.fg("muted", " to edit all queued messages") +
304
- "\n" +
305
- theme.fg("dim", "ctrl+v") +
306
- theme.fg("muted", " to paste image") +
307
- "\n" +
308
- theme.fg("dim", "drop files") +
309
- theme.fg("muted", " to attach");
237
+ const hint = (action, desc) => appKeyHint(kb, action, desc);
238
+ const instructions = [
239
+ hint("interrupt", "to interrupt"),
240
+ hint("clear", "to clear"),
241
+ rawKeyHint(`${appKey(kb, "clear")} twice`, "to exit"),
242
+ hint("exit", "to exit (empty)"),
243
+ hint("suspend", "to suspend"),
244
+ keyHint("deleteToLineEnd", "to delete to end"),
245
+ hint("cycleThinkingLevel", "to cycle thinking"),
246
+ rawKeyHint(`${appKey(kb, "cycleModelForward")}/${appKey(kb, "cycleModelBackward")}`, "to cycle models"),
247
+ hint("selectModel", "to select model"),
248
+ hint("expandTools", "to expand tools"),
249
+ hint("toggleThinking", "to toggle thinking"),
250
+ hint("externalEditor", "for external editor"),
251
+ rawKeyHint("/", "for commands"),
252
+ rawKeyHint("!", "to run bash"),
253
+ rawKeyHint("!!", "to run bash (no context)"),
254
+ hint("followUp", "to queue follow-up"),
255
+ hint("dequeue", "to edit all queued messages"),
256
+ hint("pasteImage", "to paste image"),
257
+ rawKeyHint("drop files", "to attach"),
258
+ ].join("\n");
310
259
  this.builtInHeader = new Text(`${logo}\n${instructions}`, 1, 0);
311
260
  // Setup UI layout
312
261
  this.ui.addChild(new Spacer(1));
@@ -493,6 +442,13 @@ export class InteractiveMode {
493
442
  this.chatContainer.addChild(new Text(theme.fg("warning", "Skill warnings:\n") + warningList, 0, 0));
494
443
  this.chatContainer.addChild(new Spacer(1));
495
444
  }
445
+ // Show loaded prompt templates
446
+ const templates = this.session.promptTemplates;
447
+ if (templates.length > 0) {
448
+ const templateList = templates.map((t) => theme.fg("dim", ` /${t.name} ${t.source}`)).join("\n");
449
+ this.chatContainer.addChild(new Text(theme.fg("muted", "Loaded prompt templates:\n") + templateList, 0, 0));
450
+ this.chatContainer.addChild(new Spacer(1));
451
+ }
496
452
  const extensionRunner = this.session.extensionRunner;
497
453
  if (!extensionRunner) {
498
454
  return; // No extensions loaded
@@ -793,7 +749,12 @@ export class InteractiveMode {
793
749
  setStatus: (key, text) => this.setExtensionStatus(key, text),
794
750
  setWorkingMessage: (message) => {
795
751
  if (this.loadingAnimation) {
796
- this.loadingAnimation.setMessage(message ?? this.defaultWorkingMessage);
752
+ if (message) {
753
+ this.loadingAnimation.setMessage(message);
754
+ }
755
+ else {
756
+ this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${appKey(this.keybindings, "interrupt")} to interrupt)`);
757
+ }
797
758
  }
798
759
  },
799
760
  setWidget: (key, content) => this.setExtensionWidget(key, content),
@@ -916,7 +877,7 @@ export class InteractiveMode {
916
877
  */
917
878
  showExtensionEditor(title, prefill) {
918
879
  return new Promise((resolve) => {
919
- this.extensionEditor = new ExtensionEditorComponent(this.ui, title, prefill, (value) => {
880
+ this.extensionEditor = new ExtensionEditorComponent(this.ui, this.keybindings, title, prefill, (value) => {
920
881
  this.hideExtensionEditor();
921
882
  resolve(value);
922
883
  }, () => {
@@ -1280,19 +1241,6 @@ export class InteractiveMode {
1280
1241
  await this.shutdown();
1281
1242
  return;
1282
1243
  }
1283
- // Handle skill commands (/skill:name [args])
1284
- if (text.startsWith("/skill:")) {
1285
- const spaceIndex = text.indexOf(" ");
1286
- const commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);
1287
- const args = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1).trim();
1288
- const skillPath = this.skillCommands.get(commandName);
1289
- if (skillPath) {
1290
- this.editor.addToHistory?.(text);
1291
- this.editor.setText("");
1292
- await this.handleSkillCommand(skillPath, args);
1293
- return;
1294
- }
1295
- }
1296
1244
  // Handle bash command (! for normal, !! for excluded from context)
1297
1245
  if (text.startsWith("!")) {
1298
1246
  const isExcluded = text.startsWith("!!");
@@ -1508,7 +1456,7 @@ export class InteractiveMode {
1508
1456
  // Show compacting indicator with reason
1509
1457
  this.statusContainer.clear();
1510
1458
  const reasonText = event.reason === "overflow" ? "Context overflow detected, " : "";
1511
- this.autoCompactionLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), `${reasonText}Auto-compacting... (esc to cancel)`);
1459
+ this.autoCompactionLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), `${reasonText}Auto-compacting... (${appKey(this.keybindings, "interrupt")} to cancel)`);
1512
1460
  this.statusContainer.addChild(this.autoCompactionLoader);
1513
1461
  this.ui.requestRender();
1514
1462
  break;
@@ -1555,7 +1503,7 @@ export class InteractiveMode {
1555
1503
  // Show retry indicator
1556
1504
  this.statusContainer.clear();
1557
1505
  const delaySeconds = Math.round(event.delayMs / 1000);
1558
- this.retryLoader = new Loader(this.ui, (spinner) => theme.fg("warning", spinner), (text) => theme.fg("muted", text), `Retrying (${event.attempt}/${event.maxAttempts}) in ${delaySeconds}s... (esc to cancel)`);
1506
+ this.retryLoader = new Loader(this.ui, (spinner) => theme.fg("warning", spinner), (text) => theme.fg("muted", text), `Retrying (${event.attempt}/${event.maxAttempts}) in ${delaySeconds}s... (${appKey(this.keybindings, "interrupt")} to cancel)`);
1559
1507
  this.statusContainer.addChild(this.retryLoader);
1560
1508
  this.ui.requestRender();
1561
1509
  break;
@@ -1789,6 +1737,9 @@ export class InteractiveMode {
1789
1737
  type: "session_shutdown",
1790
1738
  });
1791
1739
  }
1740
+ // Wait for any pending renders to complete
1741
+ // requestRender() uses process.nextTick(), so we wait one tick
1742
+ await new Promise((resolve) => process.nextTick(resolve));
1792
1743
  this.stop();
1793
1744
  process.exit(0);
1794
1745
  }
@@ -1971,11 +1922,10 @@ export class InteractiveMode {
1971
1922
  this.ui.requestRender();
1972
1923
  }
1973
1924
  showNewVersionNotification(newVersion) {
1974
- const updateInstruction = isBunBinary
1975
- ? theme.fg("muted", `New version ${newVersion} is available. Download from: `) +
1976
- theme.fg("accent", "https://github.com/badlogic/pi-mono/releases/latest")
1977
- : theme.fg("muted", `New version ${newVersion} is available. Run: `) +
1978
- theme.fg("accent", "npm install -g @mariozechner/pi-coding-agent");
1925
+ const action = isBunBinary
1926
+ ? `Download from: ${theme.fg("accent", "https://github.com/badlogic/pi-mono/releases/latest")}`
1927
+ : `Run: ${theme.fg("accent", `${isBunRuntime ? "bun" : "npm"} install -g @mariozechner/pi-coding-agent`)}`;
1928
+ const updateInstruction = theme.fg("muted", `New version ${newVersion} is available. `) + action;
1979
1929
  this.chatContainer.addChild(new Spacer(1));
1980
1930
  this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
1981
1931
  this.chatContainer.addChild(new Text(`${theme.bold(theme.fg("warning", "Update Available"))}\n${updateInstruction}`, 1, 0));
@@ -2503,7 +2453,7 @@ export class InteractiveMode {
2503
2453
  this.session.abortBranchSummary();
2504
2454
  };
2505
2455
  this.chatContainer.addChild(new Spacer(1));
2506
- summaryLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), "Summarizing branch... (esc to cancel)");
2456
+ summaryLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), `Summarizing branch... (${appKey(this.keybindings, "interrupt")} to cancel)`);
2507
2457
  this.statusContainer.addChild(summaryLoader);
2508
2458
  this.ui.requestRender();
2509
2459
  }
@@ -2863,18 +2813,6 @@ export class InteractiveMode {
2863
2813
  this.chatContainer.addChild(new Text(info, 1, 0));
2864
2814
  this.ui.requestRender();
2865
2815
  }
2866
- async handleSkillCommand(skillPath, args) {
2867
- try {
2868
- const content = fs.readFileSync(skillPath, "utf-8");
2869
- // Strip YAML frontmatter if present
2870
- const body = content.replace(/^---\n[\s\S]*?\n---\n/, "").trim();
2871
- const message = args ? `${body}\n\n---\n\nUser: ${args}` : body;
2872
- await this.session.prompt(message);
2873
- }
2874
- catch (err) {
2875
- this.showError(`Failed to load skill: ${err instanceof Error ? err.message : String(err)}`);
2876
- }
2877
- }
2878
2816
  handleChangelogCommand() {
2879
2817
  const changelogPath = getChangelogPath();
2880
2818
  const allEntries = parseChangelog(changelogPath);
@@ -2893,30 +2831,28 @@ export class InteractiveMode {
2893
2831
  this.ui.requestRender();
2894
2832
  }
2895
2833
  /**
2896
- * Format keybindings for display (e.g., "ctrl+c" -> "Ctrl+C").
2834
+ * Capitalize keybinding for display (e.g., "ctrl+c" -> "Ctrl+C").
2897
2835
  */
2898
- formatKeyDisplay(keys) {
2899
- const keyArray = Array.isArray(keys) ? keys : [keys];
2900
- return keyArray
2901
- .map((key) => key
2836
+ capitalizeKey(key) {
2837
+ return key
2838
+ .split("/")
2839
+ .map((k) => k
2902
2840
  .split("+")
2903
2841
  .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
2904
2842
  .join("+"))
2905
2843
  .join("/");
2906
2844
  }
2907
2845
  /**
2908
- * Get display string for an app keybinding action.
2846
+ * Get capitalized display string for an app keybinding action.
2909
2847
  */
2910
2848
  getAppKeyDisplay(action) {
2911
- const display = this.keybindings.getDisplayString(action);
2912
- return this.formatKeyDisplay(display);
2849
+ return this.capitalizeKey(appKey(this.keybindings, action));
2913
2850
  }
2914
2851
  /**
2915
- * Get display string for an editor keybinding action.
2852
+ * Get capitalized display string for an editor keybinding action.
2916
2853
  */
2917
2854
  getEditorKeyDisplay(action) {
2918
- const keys = getEditorKeybindings().getKeys(action);
2919
- return this.formatKeyDisplay(keys);
2855
+ return this.capitalizeKey(editorKey(action));
2920
2856
  }
2921
2857
  handleHotkeysCommand() {
2922
2858
  // Navigation keybindings
@@ -3146,7 +3082,8 @@ export class InteractiveMode {
3146
3082
  };
3147
3083
  // Show compacting status
3148
3084
  this.chatContainer.addChild(new Spacer(1));
3149
- const label = isAuto ? "Auto-compacting context... (esc to cancel)" : "Compacting context... (esc to cancel)";
3085
+ const cancelHint = `(${appKey(this.keybindings, "interrupt")} to cancel)`;
3086
+ const label = isAuto ? `Auto-compacting context... ${cancelHint}` : `Compacting context... ${cancelHint}`;
3150
3087
  const compactingLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), label);
3151
3088
  this.statusContainer.addChild(compactingLoader);
3152
3089
  this.ui.requestRender();