@mariozechner/pi-coding-agent 0.60.0 → 0.61.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 (181) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +1 -1
  3. package/dist/bun/cli.d.ts.map +1 -1
  4. package/dist/bun/cli.js +1 -0
  5. package/dist/bun/cli.js.map +1 -1
  6. package/dist/bun/register-bedrock.d.ts.map +1 -1
  7. package/dist/bun/register-bedrock.js +3 -84
  8. package/dist/bun/register-bedrock.js.map +1 -1
  9. package/dist/cli/session-picker.d.ts.map +1 -1
  10. package/dist/cli/session-picker.js +2 -1
  11. package/dist/cli/session-picker.js.map +1 -1
  12. package/dist/cli.d.ts.map +1 -1
  13. package/dist/cli.js +1 -0
  14. package/dist/cli.js.map +1 -1
  15. package/dist/core/agent-session.d.ts +17 -2
  16. package/dist/core/agent-session.d.ts.map +1 -1
  17. package/dist/core/agent-session.js +61 -6
  18. package/dist/core/agent-session.js.map +1 -1
  19. package/dist/core/exec.d.ts.map +1 -1
  20. package/dist/core/exec.js +7 -3
  21. package/dist/core/exec.js.map +1 -1
  22. package/dist/core/export-html/template.css +43 -13
  23. package/dist/core/export-html/template.html +1 -0
  24. package/dist/core/export-html/template.js +107 -0
  25. package/dist/core/extensions/index.d.ts +1 -1
  26. package/dist/core/extensions/index.d.ts.map +1 -1
  27. package/dist/core/extensions/index.js.map +1 -1
  28. package/dist/core/extensions/loader.d.ts.map +1 -1
  29. package/dist/core/extensions/loader.js +4 -4
  30. package/dist/core/extensions/loader.js.map +1 -1
  31. package/dist/core/extensions/runner.d.ts +1 -1
  32. package/dist/core/extensions/runner.d.ts.map +1 -1
  33. package/dist/core/extensions/runner.js +45 -33
  34. package/dist/core/extensions/runner.js.map +1 -1
  35. package/dist/core/extensions/types.d.ts +4 -3
  36. package/dist/core/extensions/types.d.ts.map +1 -1
  37. package/dist/core/extensions/types.js.map +1 -1
  38. package/dist/core/footer-data-provider.d.ts +9 -2
  39. package/dist/core/footer-data-provider.d.ts.map +1 -1
  40. package/dist/core/footer-data-provider.js +85 -13
  41. package/dist/core/footer-data-provider.js.map +1 -1
  42. package/dist/core/keybindings.d.ts +268 -51
  43. package/dist/core/keybindings.d.ts.map +1 -1
  44. package/dist/core/keybindings.js +221 -143
  45. package/dist/core/keybindings.js.map +1 -1
  46. package/dist/core/model-registry.d.ts +1 -0
  47. package/dist/core/model-registry.d.ts.map +1 -1
  48. package/dist/core/model-registry.js +23 -14
  49. package/dist/core/model-registry.js.map +1 -1
  50. package/dist/core/sdk.d.ts +2 -2
  51. package/dist/core/sdk.d.ts.map +1 -1
  52. package/dist/core/sdk.js +2 -2
  53. package/dist/core/sdk.js.map +1 -1
  54. package/dist/core/slash-commands.d.ts.map +1 -1
  55. package/dist/core/slash-commands.js +2 -1
  56. package/dist/core/slash-commands.js.map +1 -1
  57. package/dist/core/tools/bash.d.ts.map +1 -1
  58. package/dist/core/tools/bash.js +12 -10
  59. package/dist/core/tools/bash.js.map +1 -1
  60. package/dist/core/tools/edit.d.ts.map +1 -1
  61. package/dist/core/tools/edit.js +3 -2
  62. package/dist/core/tools/edit.js.map +1 -1
  63. package/dist/core/tools/file-mutation-queue.d.ts +6 -0
  64. package/dist/core/tools/file-mutation-queue.d.ts.map +1 -0
  65. package/dist/core/tools/file-mutation-queue.js +37 -0
  66. package/dist/core/tools/file-mutation-queue.js.map +1 -0
  67. package/dist/core/tools/index.d.ts +1 -0
  68. package/dist/core/tools/index.d.ts.map +1 -1
  69. package/dist/core/tools/index.js +1 -0
  70. package/dist/core/tools/index.js.map +1 -1
  71. package/dist/core/tools/write.d.ts.map +1 -1
  72. package/dist/core/tools/write.js +6 -3
  73. package/dist/core/tools/write.js.map +1 -1
  74. package/dist/index.d.ts +3 -3
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +2 -2
  77. package/dist/index.js.map +1 -1
  78. package/dist/main.d.ts.map +1 -1
  79. package/dist/main.js +10 -5
  80. package/dist/main.js.map +1 -1
  81. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  82. package/dist/modes/interactive/components/bash-execution.js +4 -4
  83. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  84. package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
  85. package/dist/modes/interactive/components/bordered-loader.js +1 -1
  86. package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
  87. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  88. package/dist/modes/interactive/components/branch-summary-message.js +2 -2
  89. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  90. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  91. package/dist/modes/interactive/components/compaction-summary-message.js +2 -2
  92. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  93. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  94. package/dist/modes/interactive/components/config-selector.js +8 -8
  95. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  96. package/dist/modes/interactive/components/custom-editor.d.ts +3 -3
  97. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  98. package/dist/modes/interactive/components/custom-editor.js +6 -6
  99. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  100. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  101. package/dist/modes/interactive/components/extension-editor.js +9 -9
  102. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  103. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  104. package/dist/modes/interactive/components/extension-input.js +5 -5
  105. package/dist/modes/interactive/components/extension-input.js.map +1 -1
  106. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  107. package/dist/modes/interactive/components/extension-selector.js +8 -8
  108. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  109. package/dist/modes/interactive/components/index.d.ts +1 -1
  110. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  111. package/dist/modes/interactive/components/index.js +1 -1
  112. package/dist/modes/interactive/components/index.js.map +1 -1
  113. package/dist/modes/interactive/components/keybinding-hints.d.ts +3 -36
  114. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
  115. package/dist/modes/interactive/components/keybinding-hints.js +5 -44
  116. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
  117. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  118. package/dist/modes/interactive/components/login-dialog.js +6 -6
  119. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  120. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  121. package/dist/modes/interactive/components/model-selector.js +12 -8
  122. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  123. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  124. package/dist/modes/interactive/components/oauth-selector.js +6 -6
  125. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  126. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  127. package/dist/modes/interactive/components/scoped-models-selector.js +4 -4
  128. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  129. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  130. package/dist/modes/interactive/components/session-selector.js +32 -35
  131. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  132. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  133. package/dist/modes/interactive/components/skill-invocation-message.js +2 -2
  134. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  135. package/dist/modes/interactive/components/tool-execution.d.ts +7 -0
  136. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  137. package/dist/modes/interactive/components/tool-execution.js +51 -6
  138. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  139. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  140. package/dist/modes/interactive/components/tree-selector.js +15 -15
  141. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  142. package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
  143. package/dist/modes/interactive/components/user-message-selector.js +6 -6
  144. package/dist/modes/interactive/components/user-message-selector.js.map +1 -1
  145. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  146. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  147. package/dist/modes/interactive/interactive-mode.js +140 -87
  148. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  149. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  150. package/dist/modes/interactive/theme/theme.js +49 -37
  151. package/dist/modes/interactive/theme/theme.js.map +1 -1
  152. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  153. package/dist/modes/rpc/rpc-mode.js +4 -1
  154. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  155. package/dist/utils/child-process.d.ts +11 -0
  156. package/dist/utils/child-process.d.ts.map +1 -0
  157. package/dist/utils/child-process.js +78 -0
  158. package/dist/utils/child-process.js.map +1 -0
  159. package/dist/utils/clipboard-native.d.ts +1 -0
  160. package/dist/utils/clipboard-native.d.ts.map +1 -1
  161. package/dist/utils/clipboard-native.js.map +1 -1
  162. package/dist/utils/clipboard.d.ts +1 -1
  163. package/dist/utils/clipboard.d.ts.map +1 -1
  164. package/dist/utils/clipboard.js +11 -1
  165. package/dist/utils/clipboard.js.map +1 -1
  166. package/docs/extensions.md +44 -7
  167. package/docs/keybindings.md +101 -112
  168. package/docs/providers.md +7 -0
  169. package/docs/rpc.md +4 -4
  170. package/docs/sdk.md +2 -2
  171. package/examples/extensions/antigravity-image-gen.ts +5 -3
  172. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  173. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  174. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  175. package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
  176. package/examples/extensions/subagent/index.ts +7 -5
  177. package/examples/extensions/tool-override.ts +9 -7
  178. package/examples/extensions/truncated-tool.ts +6 -3
  179. package/examples/extensions/with-deps/package-lock.json +2 -2
  180. package/examples/extensions/with-deps/package.json +1 -1
  181. package/package.json +4 -4
@@ -6,7 +6,7 @@ import * as crypto from "node:crypto";
6
6
  import * as fs from "node:fs";
7
7
  import * as os from "node:os";
8
8
  import * as path from "node:path";
9
- import { CombinedAutocompleteProvider, Container, fuzzyFilter, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, Text, TruncatedText, TUI, visibleWidth, } from "@mariozechner/pi-tui";
9
+ import { CombinedAutocompleteProvider, Container, fuzzyFilter, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, visibleWidth, } from "@mariozechner/pi-tui";
10
10
  import { spawn, spawnSync } from "child_process";
11
11
  import { APP_NAME, getAgentDir, getAuthPath, getDebugLogPath, getShareViewerUrl, getUpdateInstruction, VERSION, } from "../../config.js";
12
12
  import { parseSkillBlock } from "../../core/agent-session.js";
@@ -35,7 +35,7 @@ import { ExtensionEditorComponent } from "./components/extension-editor.js";
35
35
  import { ExtensionInputComponent } from "./components/extension-input.js";
36
36
  import { ExtensionSelectorComponent } from "./components/extension-selector.js";
37
37
  import { FooterComponent } from "./components/footer.js";
38
- import { appKey, appKeyHint, editorKey, keyHint, rawKeyHint } from "./components/keybinding-hints.js";
38
+ import { keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
39
39
  import { LoginDialogComponent } from "./components/login-dialog.js";
40
40
  import { ModelSelectorComponent } from "./components/model-selector.js";
41
41
  import { OAuthSelectorComponent } from "./components/oauth-selector.js";
@@ -65,6 +65,7 @@ export class InteractiveMode {
65
65
  editorContainer;
66
66
  footer;
67
67
  footerDataProvider;
68
+ // Stored so the same manager can be injected into custom editors, selectors, and extension UI.
68
69
  keybindings;
69
70
  version;
70
71
  isInitialized = false;
@@ -148,6 +149,7 @@ export class InteractiveMode {
148
149
  this.widgetContainerAbove = new Container();
149
150
  this.widgetContainerBelow = new Container();
150
151
  this.keybindings = KeybindingsManager.create();
152
+ setKeybindings(this.keybindings);
151
153
  const editorPaddingX = this.settingsManager.getEditorPaddingX();
152
154
  const autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();
153
155
  this.defaultEditor = new CustomEditor(this.ui, getEditorTheme(), this.keybindings, {
@@ -242,27 +244,26 @@ export class InteractiveMode {
242
244
  if (this.options.verbose || !this.settingsManager.getQuietStartup()) {
243
245
  const logo = theme.bold(theme.fg("accent", APP_NAME)) + theme.fg("dim", ` v${this.version}`);
244
246
  // Build startup instructions using keybinding hint helpers
245
- const kb = this.keybindings;
246
- const hint = (action, desc) => appKeyHint(kb, action, desc);
247
+ const hint = (keybinding, description) => keyHint(keybinding, description);
247
248
  const instructions = [
248
- hint("interrupt", "to interrupt"),
249
- hint("clear", "to clear"),
250
- rawKeyHint(`${appKey(kb, "clear")} twice`, "to exit"),
251
- hint("exit", "to exit (empty)"),
252
- hint("suspend", "to suspend"),
253
- keyHint("deleteToLineEnd", "to delete to end"),
254
- hint("cycleThinkingLevel", "to cycle thinking level"),
255
- rawKeyHint(`${appKey(kb, "cycleModelForward")}/${appKey(kb, "cycleModelBackward")}`, "to cycle models"),
256
- hint("selectModel", "to select model"),
257
- hint("expandTools", "to expand tools"),
258
- hint("toggleThinking", "to expand thinking"),
259
- hint("externalEditor", "for external editor"),
249
+ hint("app.interrupt", "to interrupt"),
250
+ hint("app.clear", "to clear"),
251
+ rawKeyHint(`${keyText("app.clear")} twice`, "to exit"),
252
+ hint("app.exit", "to exit (empty)"),
253
+ hint("app.suspend", "to suspend"),
254
+ keyHint("tui.editor.deleteToLineEnd", "to delete to end"),
255
+ hint("app.thinking.cycle", "to cycle thinking level"),
256
+ rawKeyHint(`${keyText("app.model.cycleForward")}/${keyText("app.model.cycleBackward")}`, "to cycle models"),
257
+ hint("app.model.select", "to select model"),
258
+ hint("app.tools.expand", "to expand tools"),
259
+ hint("app.thinking.toggle", "to expand thinking"),
260
+ hint("app.editor.external", "for external editor"),
260
261
  rawKeyHint("/", "for commands"),
261
262
  rawKeyHint("!", "to run bash"),
262
263
  rawKeyHint("!!", "to run bash (no context)"),
263
- hint("followUp", "to queue follow-up"),
264
- hint("dequeue", "to edit all queued messages"),
265
- hint("pasteImage", "to paste image"),
264
+ hint("app.message.followUp", "to queue follow-up"),
265
+ hint("app.message.dequeue", "to edit all queued messages"),
266
+ hint("app.clipboard.pasteImage", "to paste image"),
266
267
  rawKeyHint("drop files", "to attach"),
267
268
  ].join("\n");
268
269
  this.builtInHeader = new Text(`${logo}\n${instructions}`, 1, 0);
@@ -1049,7 +1050,7 @@ export class InteractiveMode {
1049
1050
  this.defaultEditor.onExtensionShortcut = undefined;
1050
1051
  this.updateTerminalTitle();
1051
1052
  if (this.loadingAnimation) {
1052
- this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${appKey(this.keybindings, "interrupt")} to interrupt)`);
1053
+ this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${keyText("app.interrupt")} to interrupt)`);
1053
1054
  }
1054
1055
  }
1055
1056
  // Maximum total widget lines to prevent viewport overflow
@@ -1172,7 +1173,7 @@ export class InteractiveMode {
1172
1173
  this.loadingAnimation.setMessage(message);
1173
1174
  }
1174
1175
  else {
1175
- this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${appKey(this.keybindings, "interrupt")} to interrupt)`);
1176
+ this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${keyText("app.interrupt")} to interrupt)`);
1176
1177
  }
1177
1178
  }
1178
1179
  else {
@@ -1530,24 +1531,24 @@ export class InteractiveMode {
1530
1531
  }
1531
1532
  };
1532
1533
  // Register app action handlers
1533
- this.defaultEditor.onAction("clear", () => this.handleCtrlC());
1534
+ this.defaultEditor.onAction("app.clear", () => this.handleCtrlC());
1534
1535
  this.defaultEditor.onCtrlD = () => this.handleCtrlD();
1535
- this.defaultEditor.onAction("suspend", () => this.handleCtrlZ());
1536
- this.defaultEditor.onAction("cycleThinkingLevel", () => this.cycleThinkingLevel());
1537
- this.defaultEditor.onAction("cycleModelForward", () => this.cycleModel("forward"));
1538
- this.defaultEditor.onAction("cycleModelBackward", () => this.cycleModel("backward"));
1536
+ this.defaultEditor.onAction("app.suspend", () => this.handleCtrlZ());
1537
+ this.defaultEditor.onAction("app.thinking.cycle", () => this.cycleThinkingLevel());
1538
+ this.defaultEditor.onAction("app.model.cycleForward", () => this.cycleModel("forward"));
1539
+ this.defaultEditor.onAction("app.model.cycleBackward", () => this.cycleModel("backward"));
1539
1540
  // Global debug handler on TUI (works regardless of focus)
1540
1541
  this.ui.onDebug = () => this.handleDebugCommand();
1541
- this.defaultEditor.onAction("selectModel", () => this.showModelSelector());
1542
- this.defaultEditor.onAction("expandTools", () => this.toggleToolOutputExpansion());
1543
- this.defaultEditor.onAction("toggleThinking", () => this.toggleThinkingBlockVisibility());
1544
- this.defaultEditor.onAction("externalEditor", () => this.openExternalEditor());
1545
- this.defaultEditor.onAction("followUp", () => this.handleFollowUp());
1546
- this.defaultEditor.onAction("dequeue", () => this.handleDequeue());
1547
- this.defaultEditor.onAction("newSession", () => this.handleClearCommand());
1548
- this.defaultEditor.onAction("tree", () => this.showTreeSelector());
1549
- this.defaultEditor.onAction("fork", () => this.showUserMessageSelector());
1550
- this.defaultEditor.onAction("resume", () => this.showSessionSelector());
1542
+ this.defaultEditor.onAction("app.model.select", () => this.showModelSelector());
1543
+ this.defaultEditor.onAction("app.tools.expand", () => this.toggleToolOutputExpansion());
1544
+ this.defaultEditor.onAction("app.thinking.toggle", () => this.toggleThinkingBlockVisibility());
1545
+ this.defaultEditor.onAction("app.editor.external", () => this.openExternalEditor());
1546
+ this.defaultEditor.onAction("app.message.followUp", () => this.handleFollowUp());
1547
+ this.defaultEditor.onAction("app.message.dequeue", () => this.handleDequeue());
1548
+ this.defaultEditor.onAction("app.session.new", () => this.handleClearCommand());
1549
+ this.defaultEditor.onAction("app.session.tree", () => this.showTreeSelector());
1550
+ this.defaultEditor.onAction("app.session.fork", () => this.showUserMessageSelector());
1551
+ this.defaultEditor.onAction("app.session.resume", () => this.showSessionSelector());
1551
1552
  this.defaultEditor.onChange = (text) => {
1552
1553
  const wasBashMode = this.isBashMode;
1553
1554
  this.isBashMode = text.trimStart().startsWith("!");
@@ -1607,13 +1608,18 @@ export class InteractiveMode {
1607
1608
  this.editor.setText("");
1608
1609
  return;
1609
1610
  }
1611
+ if (text.startsWith("/import")) {
1612
+ await this.handleImportCommand(text);
1613
+ this.editor.setText("");
1614
+ return;
1615
+ }
1610
1616
  if (text === "/share") {
1611
1617
  await this.handleShareCommand();
1612
1618
  this.editor.setText("");
1613
1619
  return;
1614
1620
  }
1615
1621
  if (text === "/copy") {
1616
- this.handleCopyCommand();
1622
+ await this.handleCopyCommand();
1617
1623
  this.editor.setText("");
1618
1624
  return;
1619
1625
  }
@@ -1861,15 +1867,17 @@ export class InteractiveMode {
1861
1867
  this.ui.requestRender();
1862
1868
  break;
1863
1869
  case "tool_execution_start": {
1864
- if (!this.pendingTools.has(event.toolCallId)) {
1865
- const component = new ToolExecutionComponent(event.toolName, event.args, {
1870
+ let component = this.pendingTools.get(event.toolCallId);
1871
+ if (!component) {
1872
+ component = new ToolExecutionComponent(event.toolName, event.args, {
1866
1873
  showImages: this.settingsManager.getShowImages(),
1867
1874
  }, this.getRegisteredToolDefinition(event.toolName), this.ui);
1868
1875
  component.setExpanded(this.toolOutputExpanded);
1869
1876
  this.chatContainer.addChild(component);
1870
1877
  this.pendingTools.set(event.toolCallId, component);
1871
- this.ui.requestRender();
1872
1878
  }
1879
+ component.markExecutionStarted();
1880
+ this.ui.requestRender();
1873
1881
  break;
1874
1882
  }
1875
1883
  case "tool_execution_update": {
@@ -1914,7 +1922,7 @@ export class InteractiveMode {
1914
1922
  // Show compacting indicator with reason
1915
1923
  this.statusContainer.clear();
1916
1924
  const reasonText = event.reason === "overflow" ? "Context overflow detected, " : "";
1917
- 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)`);
1925
+ this.autoCompactionLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), `${reasonText}Auto-compacting... (${keyText("app.interrupt")} to cancel)`);
1918
1926
  this.statusContainer.addChild(this.autoCompactionLoader);
1919
1927
  this.ui.requestRender();
1920
1928
  break;
@@ -1966,7 +1974,7 @@ export class InteractiveMode {
1966
1974
  // Show retry indicator
1967
1975
  this.statusContainer.clear();
1968
1976
  const delaySeconds = Math.round(event.delayMs / 1000);
1969
- 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)`);
1977
+ this.retryLoader = new Loader(this.ui, (spinner) => theme.fg("warning", spinner), (text) => theme.fg("muted", text), `Retrying (${event.attempt}/${event.maxAttempts}) in ${delaySeconds}s... (${keyText("app.interrupt")} to cancel)`);
1970
1978
  this.statusContainer.addChild(this.retryLoader);
1971
1979
  this.ui.requestRender();
1972
1980
  break;
@@ -2481,7 +2489,7 @@ export class InteractiveMode {
2481
2489
  const text = theme.fg("dim", `Follow-up: ${message}`);
2482
2490
  this.pendingMessagesContainer.addChild(new TruncatedText(text, 1, 0));
2483
2491
  }
2484
- const dequeueHint = this.getAppKeyDisplay("dequeue");
2492
+ const dequeueHint = this.getAppKeyDisplay("app.message.dequeue");
2485
2493
  const hintText = theme.fg("dim", `↳ ${dequeueHint} to edit all queued messages`);
2486
2494
  this.pendingMessagesContainer.addChild(new TruncatedText(hintText, 1, 0));
2487
2495
  }
@@ -3003,7 +3011,7 @@ export class InteractiveMode {
3003
3011
  this.session.abortBranchSummary();
3004
3012
  };
3005
3013
  this.chatContainer.addChild(new Spacer(1));
3006
- summaryLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), `Summarizing branch... (${appKey(this.keybindings, "interrupt")} to cancel)`);
3014
+ summaryLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), `Summarizing branch... (${keyText("app.interrupt")} to cancel)`);
3007
3015
  this.statusContainer.addChild(summaryLoader);
3008
3016
  this.ui.requestRender();
3009
3017
  }
@@ -3285,13 +3293,58 @@ export class InteractiveMode {
3285
3293
  const parts = text.split(/\s+/);
3286
3294
  const outputPath = parts.length > 1 ? parts[1] : undefined;
3287
3295
  try {
3288
- const filePath = await this.session.exportToHtml(outputPath);
3289
- this.showStatus(`Session exported to: ${filePath}`);
3296
+ if (outputPath?.endsWith(".jsonl")) {
3297
+ const filePath = this.session.exportToJsonl(outputPath);
3298
+ this.showStatus(`Session exported to: ${filePath}`);
3299
+ }
3300
+ else {
3301
+ const filePath = await this.session.exportToHtml(outputPath);
3302
+ this.showStatus(`Session exported to: ${filePath}`);
3303
+ }
3290
3304
  }
3291
3305
  catch (error) {
3292
3306
  this.showError(`Failed to export session: ${error instanceof Error ? error.message : "Unknown error"}`);
3293
3307
  }
3294
3308
  }
3309
+ async handleImportCommand(text) {
3310
+ const parts = text.split(/\s+/);
3311
+ if (parts.length < 2 || !parts[1]) {
3312
+ this.showError("Usage: /import <path.jsonl>");
3313
+ return;
3314
+ }
3315
+ const inputPath = parts[1];
3316
+ const confirmed = await this.showExtensionConfirm("Import session", `Replace current session with ${inputPath}?`);
3317
+ if (!confirmed) {
3318
+ this.showStatus("Import cancelled");
3319
+ return;
3320
+ }
3321
+ try {
3322
+ // Stop loading animation
3323
+ if (this.loadingAnimation) {
3324
+ this.loadingAnimation.stop();
3325
+ this.loadingAnimation = undefined;
3326
+ }
3327
+ this.statusContainer.clear();
3328
+ // Clear UI state
3329
+ this.pendingMessagesContainer.clear();
3330
+ this.compactionQueuedMessages = [];
3331
+ this.streamingComponent = undefined;
3332
+ this.streamingMessage = undefined;
3333
+ this.pendingTools.clear();
3334
+ const success = await this.session.importFromJsonl(inputPath);
3335
+ if (!success) {
3336
+ this.showWarning("Import cancelled");
3337
+ return;
3338
+ }
3339
+ // Clear and re-render the chat
3340
+ this.chatContainer.clear();
3341
+ this.renderInitialMessages();
3342
+ this.showStatus(`Session imported from: ${inputPath}`);
3343
+ }
3344
+ catch (error) {
3345
+ this.showError(`Failed to import session: ${error instanceof Error ? error.message : "Unknown error"}`);
3346
+ }
3347
+ }
3295
3348
  async handleShareCommand() {
3296
3349
  // Check if gh is available and logged in
3297
3350
  try {
@@ -3379,14 +3432,14 @@ export class InteractiveMode {
3379
3432
  }
3380
3433
  }
3381
3434
  }
3382
- handleCopyCommand() {
3435
+ async handleCopyCommand() {
3383
3436
  const text = this.session.getLastAssistantText();
3384
3437
  if (!text) {
3385
3438
  this.showError("No agent messages to copy yet.");
3386
3439
  return;
3387
3440
  }
3388
3441
  try {
3389
- copyToClipboard(text);
3442
+ await copyToClipboard(text);
3390
3443
  this.showStatus("Copied last agent message to clipboard");
3391
3444
  }
3392
3445
  catch (error) {
@@ -3479,54 +3532,54 @@ export class InteractiveMode {
3479
3532
  * Get capitalized display string for an app keybinding action.
3480
3533
  */
3481
3534
  getAppKeyDisplay(action) {
3482
- return this.capitalizeKey(appKey(this.keybindings, action));
3535
+ return this.capitalizeKey(keyText(action));
3483
3536
  }
3484
3537
  /**
3485
3538
  * Get capitalized display string for an editor keybinding action.
3486
3539
  */
3487
3540
  getEditorKeyDisplay(action) {
3488
- return this.capitalizeKey(editorKey(action));
3541
+ return this.capitalizeKey(keyText(action));
3489
3542
  }
3490
3543
  handleHotkeysCommand() {
3491
3544
  // Navigation keybindings
3492
- const cursorUp = this.getEditorKeyDisplay("cursorUp");
3493
- const cursorDown = this.getEditorKeyDisplay("cursorDown");
3494
- const cursorLeft = this.getEditorKeyDisplay("cursorLeft");
3495
- const cursorRight = this.getEditorKeyDisplay("cursorRight");
3496
- const cursorWordLeft = this.getEditorKeyDisplay("cursorWordLeft");
3497
- const cursorWordRight = this.getEditorKeyDisplay("cursorWordRight");
3498
- const cursorLineStart = this.getEditorKeyDisplay("cursorLineStart");
3499
- const cursorLineEnd = this.getEditorKeyDisplay("cursorLineEnd");
3500
- const jumpForward = this.getEditorKeyDisplay("jumpForward");
3501
- const jumpBackward = this.getEditorKeyDisplay("jumpBackward");
3502
- const pageUp = this.getEditorKeyDisplay("pageUp");
3503
- const pageDown = this.getEditorKeyDisplay("pageDown");
3545
+ const cursorUp = this.getEditorKeyDisplay("tui.editor.cursorUp");
3546
+ const cursorDown = this.getEditorKeyDisplay("tui.editor.cursorDown");
3547
+ const cursorLeft = this.getEditorKeyDisplay("tui.editor.cursorLeft");
3548
+ const cursorRight = this.getEditorKeyDisplay("tui.editor.cursorRight");
3549
+ const cursorWordLeft = this.getEditorKeyDisplay("tui.editor.cursorWordLeft");
3550
+ const cursorWordRight = this.getEditorKeyDisplay("tui.editor.cursorWordRight");
3551
+ const cursorLineStart = this.getEditorKeyDisplay("tui.editor.cursorLineStart");
3552
+ const cursorLineEnd = this.getEditorKeyDisplay("tui.editor.cursorLineEnd");
3553
+ const jumpForward = this.getEditorKeyDisplay("tui.editor.jumpForward");
3554
+ const jumpBackward = this.getEditorKeyDisplay("tui.editor.jumpBackward");
3555
+ const pageUp = this.getEditorKeyDisplay("tui.editor.pageUp");
3556
+ const pageDown = this.getEditorKeyDisplay("tui.editor.pageDown");
3504
3557
  // Editing keybindings
3505
- const submit = this.getEditorKeyDisplay("submit");
3506
- const newLine = this.getEditorKeyDisplay("newLine");
3507
- const deleteWordBackward = this.getEditorKeyDisplay("deleteWordBackward");
3508
- const deleteWordForward = this.getEditorKeyDisplay("deleteWordForward");
3509
- const deleteToLineStart = this.getEditorKeyDisplay("deleteToLineStart");
3510
- const deleteToLineEnd = this.getEditorKeyDisplay("deleteToLineEnd");
3511
- const yank = this.getEditorKeyDisplay("yank");
3512
- const yankPop = this.getEditorKeyDisplay("yankPop");
3513
- const undo = this.getEditorKeyDisplay("undo");
3514
- const tab = this.getEditorKeyDisplay("tab");
3558
+ const submit = this.getEditorKeyDisplay("tui.input.submit");
3559
+ const newLine = this.getEditorKeyDisplay("tui.input.newLine");
3560
+ const deleteWordBackward = this.getEditorKeyDisplay("tui.editor.deleteWordBackward");
3561
+ const deleteWordForward = this.getEditorKeyDisplay("tui.editor.deleteWordForward");
3562
+ const deleteToLineStart = this.getEditorKeyDisplay("tui.editor.deleteToLineStart");
3563
+ const deleteToLineEnd = this.getEditorKeyDisplay("tui.editor.deleteToLineEnd");
3564
+ const yank = this.getEditorKeyDisplay("tui.editor.yank");
3565
+ const yankPop = this.getEditorKeyDisplay("tui.editor.yankPop");
3566
+ const undo = this.getEditorKeyDisplay("tui.editor.undo");
3567
+ const tab = this.getEditorKeyDisplay("tui.input.tab");
3515
3568
  // App keybindings
3516
- const interrupt = this.getAppKeyDisplay("interrupt");
3517
- const clear = this.getAppKeyDisplay("clear");
3518
- const exit = this.getAppKeyDisplay("exit");
3519
- const suspend = this.getAppKeyDisplay("suspend");
3520
- const cycleThinkingLevel = this.getAppKeyDisplay("cycleThinkingLevel");
3521
- const cycleModelForward = this.getAppKeyDisplay("cycleModelForward");
3522
- const selectModel = this.getAppKeyDisplay("selectModel");
3523
- const expandTools = this.getAppKeyDisplay("expandTools");
3524
- const toggleThinking = this.getAppKeyDisplay("toggleThinking");
3525
- const externalEditor = this.getAppKeyDisplay("externalEditor");
3526
- const cycleModelBackward = this.getAppKeyDisplay("cycleModelBackward");
3527
- const followUp = this.getAppKeyDisplay("followUp");
3528
- const dequeue = this.getAppKeyDisplay("dequeue");
3529
- const pasteImage = this.getAppKeyDisplay("pasteImage");
3569
+ const interrupt = this.getAppKeyDisplay("app.interrupt");
3570
+ const clear = this.getAppKeyDisplay("app.clear");
3571
+ const exit = this.getAppKeyDisplay("app.exit");
3572
+ const suspend = this.getAppKeyDisplay("app.suspend");
3573
+ const cycleThinkingLevel = this.getAppKeyDisplay("app.thinking.cycle");
3574
+ const cycleModelForward = this.getAppKeyDisplay("app.model.cycleForward");
3575
+ const selectModel = this.getAppKeyDisplay("app.model.select");
3576
+ const expandTools = this.getAppKeyDisplay("app.tools.expand");
3577
+ const toggleThinking = this.getAppKeyDisplay("app.thinking.toggle");
3578
+ const externalEditor = this.getAppKeyDisplay("app.editor.external");
3579
+ const cycleModelBackward = this.getAppKeyDisplay("app.model.cycleBackward");
3580
+ const followUp = this.getAppKeyDisplay("app.message.followUp");
3581
+ const dequeue = this.getAppKeyDisplay("app.message.dequeue");
3582
+ const pasteImage = this.getAppKeyDisplay("app.clipboard.pasteImage");
3530
3583
  let hotkeys = `
3531
3584
  **Navigation**
3532
3585
  | Key | Action |
@@ -3751,7 +3804,7 @@ export class InteractiveMode {
3751
3804
  };
3752
3805
  // Show compacting status
3753
3806
  this.chatContainer.addChild(new Spacer(1));
3754
- const cancelHint = `(${appKey(this.keybindings, "interrupt")} to cancel)`;
3807
+ const cancelHint = `(${keyText("app.interrupt")} to cancel)`;
3755
3808
  const label = isAuto ? `Auto-compacting context... ${cancelHint}` : `Compacting context... ${cancelHint}`;
3756
3809
  const compactingLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), label);
3757
3810
  this.statusContainer.addChild(compactingLoader);