@mariozechner/pi-coding-agent 0.45.7 → 0.46.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 (106) hide show
  1. package/CHANGELOG.md +30 -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.map +1 -1
  11. package/dist/core/agent-session.js +2 -2
  12. package/dist/core/agent-session.js.map +1 -1
  13. package/dist/core/compaction/compaction.d.ts.map +1 -1
  14. package/dist/core/compaction/compaction.js +6 -5
  15. package/dist/core/compaction/compaction.js.map +1 -1
  16. package/dist/core/keybindings.d.ts +1 -5
  17. package/dist/core/keybindings.d.ts.map +1 -1
  18. package/dist/core/keybindings.js +4 -12
  19. package/dist/core/keybindings.js.map +1 -1
  20. package/dist/core/model-resolver.d.ts.map +1 -1
  21. package/dist/core/model-resolver.js +1 -0
  22. package/dist/core/model-resolver.js.map +1 -1
  23. package/dist/core/tools/edit-diff.d.ts +30 -0
  24. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  25. package/dist/core/tools/edit-diff.js +82 -10
  26. package/dist/core/tools/edit-diff.js.map +1 -1
  27. package/dist/core/tools/edit.d.ts.map +1 -1
  28. package/dist/core/tools/edit.js +16 -13
  29. package/dist/core/tools/edit.js.map +1 -1
  30. package/dist/index.d.ts +1 -0
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +2 -0
  33. package/dist/index.js.map +1 -1
  34. package/dist/main.d.ts.map +1 -1
  35. package/dist/main.js +38 -9
  36. package/dist/main.js.map +1 -1
  37. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  38. package/dist/modes/interactive/components/bash-execution.js +4 -3
  39. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  40. package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
  41. package/dist/modes/interactive/components/bordered-loader.js +2 -1
  42. package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
  43. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  44. package/dist/modes/interactive/components/branch-summary-message.js +4 -1
  45. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  46. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  47. package/dist/modes/interactive/components/compaction-summary-message.js +4 -1
  48. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  49. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  50. package/dist/modes/interactive/components/custom-editor.js +3 -3
  51. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  52. package/dist/modes/interactive/components/extension-editor.d.ts +3 -1
  53. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  54. package/dist/modes/interactive/components/extension-editor.js +14 -8
  55. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  56. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  57. package/dist/modes/interactive/components/extension-input.js +2 -1
  58. package/dist/modes/interactive/components/extension-input.js.map +1 -1
  59. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  60. package/dist/modes/interactive/components/extension-selector.js +6 -1
  61. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  62. package/dist/modes/interactive/components/index.d.ts +1 -0
  63. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  64. package/dist/modes/interactive/components/index.js +1 -0
  65. package/dist/modes/interactive/components/index.js.map +1 -1
  66. package/dist/modes/interactive/components/keybinding-hints.d.ts +41 -0
  67. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -0
  68. package/dist/modes/interactive/components/keybinding-hints.js +61 -0
  69. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -0
  70. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  71. package/dist/modes/interactive/components/login-dialog.js +4 -3
  72. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  73. package/dist/modes/interactive/components/session-selector-search.d.ts +21 -0
  74. package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -0
  75. package/dist/modes/interactive/components/session-selector-search.js +146 -0
  76. package/dist/modes/interactive/components/session-selector-search.js.map +1 -0
  77. package/dist/modes/interactive/components/session-selector.d.ts +7 -1
  78. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  79. package/dist/modes/interactive/components/session-selector.js +35 -8
  80. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  81. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  82. package/dist/modes/interactive/components/tool-execution.js +14 -8
  83. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  84. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  85. package/dist/modes/interactive/components/tree-selector.js +5 -2
  86. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  87. package/dist/modes/interactive/interactive-mode.d.ts +4 -4
  88. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  89. package/dist/modes/interactive/interactive-mode.js +63 -101
  90. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  91. package/dist/utils/image-convert.d.ts.map +1 -1
  92. package/dist/utils/image-convert.js +12 -14
  93. package/dist/utils/image-convert.js.map +1 -1
  94. package/dist/utils/image-resize.d.ts +2 -2
  95. package/dist/utils/image-resize.d.ts.map +1 -1
  96. package/dist/utils/image-resize.js +102 -122
  97. package/dist/utils/image-resize.js.map +1 -1
  98. package/examples/extensions/plan-mode/README.md +1 -1
  99. package/examples/extensions/plan-mode/index.ts +2 -2
  100. package/examples/extensions/with-deps/package-lock.json +2 -2
  101. package/examples/extensions/with-deps/package.json +1 -1
  102. package/package.json +5 -5
  103. package/dist/utils/vips.d.ts +0 -11
  104. package/dist/utils/vips.d.ts.map +0 -1
  105. package/dist/utils/vips.js +0 -35
  106. 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;
@@ -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
  }, () => {
@@ -1508,7 +1469,7 @@ export class InteractiveMode {
1508
1469
  // Show compacting indicator with reason
1509
1470
  this.statusContainer.clear();
1510
1471
  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)`);
1472
+ 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
1473
  this.statusContainer.addChild(this.autoCompactionLoader);
1513
1474
  this.ui.requestRender();
1514
1475
  break;
@@ -1555,7 +1516,7 @@ export class InteractiveMode {
1555
1516
  // Show retry indicator
1556
1517
  this.statusContainer.clear();
1557
1518
  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)`);
1519
+ 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
1520
  this.statusContainer.addChild(this.retryLoader);
1560
1521
  this.ui.requestRender();
1561
1522
  break;
@@ -1971,11 +1932,10 @@ export class InteractiveMode {
1971
1932
  this.ui.requestRender();
1972
1933
  }
1973
1934
  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");
1935
+ const action = isBunBinary
1936
+ ? `Download from: ${theme.fg("accent", "https://github.com/badlogic/pi-mono/releases/latest")}`
1937
+ : `Run: ${theme.fg("accent", `${isBunRuntime ? "bun" : "npm"} install -g @mariozechner/pi-coding-agent`)}`;
1938
+ const updateInstruction = theme.fg("muted", `New version ${newVersion} is available. `) + action;
1979
1939
  this.chatContainer.addChild(new Spacer(1));
1980
1940
  this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
1981
1941
  this.chatContainer.addChild(new Text(`${theme.bold(theme.fg("warning", "Update Available"))}\n${updateInstruction}`, 1, 0));
@@ -2503,7 +2463,7 @@ export class InteractiveMode {
2503
2463
  this.session.abortBranchSummary();
2504
2464
  };
2505
2465
  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)");
2466
+ summaryLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), `Summarizing branch... (${appKey(this.keybindings, "interrupt")} to cancel)`);
2507
2467
  this.statusContainer.addChild(summaryLoader);
2508
2468
  this.ui.requestRender();
2509
2469
  }
@@ -2868,7 +2828,10 @@ export class InteractiveMode {
2868
2828
  const content = fs.readFileSync(skillPath, "utf-8");
2869
2829
  // Strip YAML frontmatter if present
2870
2830
  const body = content.replace(/^---\n[\s\S]*?\n---\n/, "").trim();
2871
- const message = args ? `${body}\n\n---\n\nUser: ${args}` : body;
2831
+ const skillDir = path.dirname(skillPath);
2832
+ const header = `Skill location: ${skillPath}\nReferences are relative to ${skillDir}.`;
2833
+ const skillMessage = `${header}\n\n${body}`;
2834
+ const message = args ? `${skillMessage}\n\n---\n\nUser: ${args}` : skillMessage;
2872
2835
  await this.session.prompt(message);
2873
2836
  }
2874
2837
  catch (err) {
@@ -2893,30 +2856,28 @@ export class InteractiveMode {
2893
2856
  this.ui.requestRender();
2894
2857
  }
2895
2858
  /**
2896
- * Format keybindings for display (e.g., "ctrl+c" -> "Ctrl+C").
2859
+ * Capitalize keybinding for display (e.g., "ctrl+c" -> "Ctrl+C").
2897
2860
  */
2898
- formatKeyDisplay(keys) {
2899
- const keyArray = Array.isArray(keys) ? keys : [keys];
2900
- return keyArray
2901
- .map((key) => key
2861
+ capitalizeKey(key) {
2862
+ return key
2863
+ .split("/")
2864
+ .map((k) => k
2902
2865
  .split("+")
2903
2866
  .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
2904
2867
  .join("+"))
2905
2868
  .join("/");
2906
2869
  }
2907
2870
  /**
2908
- * Get display string for an app keybinding action.
2871
+ * Get capitalized display string for an app keybinding action.
2909
2872
  */
2910
2873
  getAppKeyDisplay(action) {
2911
- const display = this.keybindings.getDisplayString(action);
2912
- return this.formatKeyDisplay(display);
2874
+ return this.capitalizeKey(appKey(this.keybindings, action));
2913
2875
  }
2914
2876
  /**
2915
- * Get display string for an editor keybinding action.
2877
+ * Get capitalized display string for an editor keybinding action.
2916
2878
  */
2917
2879
  getEditorKeyDisplay(action) {
2918
- const keys = getEditorKeybindings().getKeys(action);
2919
- return this.formatKeyDisplay(keys);
2880
+ return this.capitalizeKey(editorKey(action));
2920
2881
  }
2921
2882
  handleHotkeysCommand() {
2922
2883
  // Navigation keybindings
@@ -3146,7 +3107,8 @@ export class InteractiveMode {
3146
3107
  };
3147
3108
  // Show compacting status
3148
3109
  this.chatContainer.addChild(new Spacer(1));
3149
- const label = isAuto ? "Auto-compacting context... (esc to cancel)" : "Compacting context... (esc to cancel)";
3110
+ const cancelHint = `(${appKey(this.keybindings, "interrupt")} to cancel)`;
3111
+ const label = isAuto ? `Auto-compacting context... ${cancelHint}` : `Compacting context... ${cancelHint}`;
3150
3112
  const compactingLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), label);
3151
3113
  this.statusContainer.addChild(compactingLoader);
3152
3114
  this.ui.requestRender();