@mariozechner/pi-coding-agent 0.25.1 → 0.25.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +4 -1
  3. package/dist/cli/args.d.ts +1 -0
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +5 -0
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/core/agent-session.d.ts +4 -1
  8. package/dist/core/agent-session.d.ts.map +1 -1
  9. package/dist/core/agent-session.js +5 -0
  10. package/dist/core/agent-session.js.map +1 -1
  11. package/dist/core/settings-manager.d.ts +9 -0
  12. package/dist/core/settings-manager.d.ts.map +1 -1
  13. package/dist/core/settings-manager.js +13 -0
  14. package/dist/core/settings-manager.js.map +1 -1
  15. package/dist/core/skills.d.ts +2 -1
  16. package/dist/core/skills.d.ts.map +1 -1
  17. package/dist/core/skills.js +41 -14
  18. package/dist/core/skills.js.map +1 -1
  19. package/dist/core/system-prompt.d.ts +2 -1
  20. package/dist/core/system-prompt.d.ts.map +1 -1
  21. package/dist/core/system-prompt.js +5 -5
  22. package/dist/core/system-prompt.js.map +1 -1
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/main.d.ts.map +1 -1
  27. package/dist/main.js +9 -2
  28. package/dist/main.js.map +1 -1
  29. package/dist/modes/interactive/components/custom-editor.d.ts +2 -0
  30. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  31. package/dist/modes/interactive/components/custom-editor.js +13 -1
  32. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  33. package/dist/modes/interactive/components/tool-execution.d.ts +1 -0
  34. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  35. package/dist/modes/interactive/components/tool-execution.js +8 -1
  36. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  37. package/dist/modes/interactive/interactive-mode.d.ts +2 -0
  38. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  39. package/dist/modes/interactive/interactive-mode.js +76 -12
  40. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  41. package/docs/skills.md +53 -0
  42. package/package.json +4 -4
@@ -3,9 +3,10 @@
3
3
  * Handles TUI rendering and user interaction, delegating business logic to AgentSession.
4
4
  */
5
5
  import * as fs from "node:fs";
6
+ import * as os from "node:os";
6
7
  import * as path from "node:path";
7
8
  import { CombinedAutocompleteProvider, Container, getCapabilities, Input, Loader, Markdown, ProcessTerminal, Spacer, Text, TruncatedText, TUI, visibleWidth, } from "@mariozechner/pi-tui";
8
- import { exec } from "child_process";
9
+ import { exec, spawnSync } from "child_process";
9
10
  import { APP_NAME, getDebugLogPath, getOAuthPath } from "../../config.js";
10
11
  import { isBashExecutionMessage } from "../../core/messages.js";
11
12
  import { invalidateOAuthCache } from "../../core/model-config.js";
@@ -157,6 +158,9 @@ export class InteractiveMode {
157
158
  theme.fg("dim", "ctrl+d") +
158
159
  theme.fg("muted", " to exit (empty)") +
159
160
  "\n" +
161
+ theme.fg("dim", "ctrl+z") +
162
+ theme.fg("muted", " to suspend") +
163
+ "\n" +
160
164
  theme.fg("dim", "ctrl+k") +
161
165
  theme.fg("muted", " to delete line") +
162
166
  "\n" +
@@ -244,17 +248,22 @@ export class InteractiveMode {
244
248
  this.chatContainer.addChild(new Spacer(1));
245
249
  }
246
250
  // Show loaded skills
247
- const { skills, warnings: skillWarnings } = loadSkills();
248
- if (skills.length > 0) {
249
- const skillList = skills.map((s) => theme.fg("dim", ` ${s.filePath}`)).join("\n");
250
- this.chatContainer.addChild(new Text(theme.fg("muted", "Loaded skills:\n") + skillList, 0, 0));
251
- this.chatContainer.addChild(new Spacer(1));
252
- }
253
- // Show skill warnings if any
254
- if (skillWarnings.length > 0) {
255
- const warningList = skillWarnings.map((w) => theme.fg("warning", ` ${w.skillPath}: ${w.message}`)).join("\n");
256
- this.chatContainer.addChild(new Text(theme.fg("warning", "Skill warnings:\n") + warningList, 0, 0));
257
- this.chatContainer.addChild(new Spacer(1));
251
+ const skillsSettings = this.session.skillsSettings;
252
+ if (skillsSettings?.enabled !== false) {
253
+ const { skills, warnings: skillWarnings } = loadSkills(skillsSettings ?? {});
254
+ if (skills.length > 0) {
255
+ const skillList = skills.map((s) => theme.fg("dim", ` ${s.filePath}`)).join("\n");
256
+ this.chatContainer.addChild(new Text(theme.fg("muted", "Loaded skills:\n") + skillList, 0, 0));
257
+ this.chatContainer.addChild(new Spacer(1));
258
+ }
259
+ // Show skill warnings if any
260
+ if (skillWarnings.length > 0) {
261
+ const warningList = skillWarnings
262
+ .map((w) => theme.fg("warning", ` ${w.skillPath}: ${w.message}`))
263
+ .join("\n");
264
+ this.chatContainer.addChild(new Text(theme.fg("warning", "Skill warnings:\n") + warningList, 0, 0));
265
+ this.chatContainer.addChild(new Spacer(1));
266
+ }
258
267
  }
259
268
  // Show loaded custom tools
260
269
  if (this.customTools.size > 0) {
@@ -480,10 +489,12 @@ export class InteractiveMode {
480
489
  };
481
490
  this.editor.onCtrlC = () => this.handleCtrlC();
482
491
  this.editor.onCtrlD = () => this.handleCtrlD();
492
+ this.editor.onCtrlZ = () => this.handleCtrlZ();
483
493
  this.editor.onShiftTab = () => this.cycleThinkingLevel();
484
494
  this.editor.onCtrlP = () => this.cycleModel();
485
495
  this.editor.onCtrlO = () => this.toggleToolOutputExpansion();
486
496
  this.editor.onCtrlT = () => this.toggleThinkingBlockVisibility();
497
+ this.editor.onCtrlG = () => this.openExternalEditor();
487
498
  this.editor.onChange = (text) => {
488
499
  const wasBashMode = this.isBashMode;
489
500
  this.isBashMode = text.trimStart().startsWith("!");
@@ -990,6 +1001,17 @@ export class InteractiveMode {
990
1001
  this.stop();
991
1002
  process.exit(0);
992
1003
  }
1004
+ handleCtrlZ() {
1005
+ // Set up handler to restore TUI when resumed
1006
+ process.once("SIGCONT", () => {
1007
+ this.ui.start();
1008
+ this.ui.requestRender(true);
1009
+ });
1010
+ // Stop the TUI (restore terminal to normal mode)
1011
+ this.ui.stop();
1012
+ // Send SIGTSTP to process group (pid=0 means all processes in group)
1013
+ process.kill(0, "SIGTSTP");
1014
+ }
993
1015
  updateEditorBorderColor() {
994
1016
  if (this.isBashMode) {
995
1017
  this.editor.borderColor = theme.getBashModeBorderColor();
@@ -1056,6 +1078,46 @@ export class InteractiveMode {
1056
1078
  this.rebuildChatFromMessages();
1057
1079
  this.showStatus(`Thinking blocks: ${this.hideThinkingBlock ? "hidden" : "visible"}`);
1058
1080
  }
1081
+ openExternalEditor() {
1082
+ // Determine editor (respect $VISUAL, then $EDITOR)
1083
+ const editorCmd = process.env.VISUAL || process.env.EDITOR;
1084
+ if (!editorCmd) {
1085
+ this.showWarning("No editor configured. Set $VISUAL or $EDITOR environment variable.");
1086
+ return;
1087
+ }
1088
+ const currentText = this.editor.getText();
1089
+ const tmpFile = path.join(os.tmpdir(), `pi-editor-${Date.now()}.pi.md`);
1090
+ try {
1091
+ // Write current content to temp file
1092
+ fs.writeFileSync(tmpFile, currentText, "utf-8");
1093
+ // Stop TUI to release terminal
1094
+ this.ui.stop();
1095
+ // Split by space to support editor arguments (e.g., "code --wait")
1096
+ const [editor, ...editorArgs] = editorCmd.split(" ");
1097
+ // Spawn editor synchronously with inherited stdio for interactive editing
1098
+ const result = spawnSync(editor, [...editorArgs, tmpFile], {
1099
+ stdio: "inherit",
1100
+ });
1101
+ // On successful exit (status 0), replace editor content
1102
+ if (result.status === 0) {
1103
+ const newContent = fs.readFileSync(tmpFile, "utf-8").replace(/\n$/, "");
1104
+ this.editor.setText(newContent);
1105
+ }
1106
+ // On non-zero exit, keep original text (no action needed)
1107
+ }
1108
+ finally {
1109
+ // Clean up temp file
1110
+ try {
1111
+ fs.unlinkSync(tmpFile);
1112
+ }
1113
+ catch {
1114
+ // Ignore cleanup errors
1115
+ }
1116
+ // Restart TUI
1117
+ this.ui.start();
1118
+ this.ui.requestRender();
1119
+ }
1120
+ }
1059
1121
  // =========================================================================
1060
1122
  // UI helpers
1061
1123
  // =========================================================================
@@ -1443,10 +1505,12 @@ export class InteractiveMode {
1443
1505
  | \`Escape\` | Cancel autocomplete / abort streaming |
1444
1506
  | \`Ctrl+C\` | Clear editor (first) / exit (second) |
1445
1507
  | \`Ctrl+D\` | Exit (when editor is empty) |
1508
+ | \`Ctrl+Z\` | Suspend to background |
1446
1509
  | \`Shift+Tab\` | Cycle thinking level |
1447
1510
  | \`Ctrl+P\` | Cycle models |
1448
1511
  | \`Ctrl+O\` | Toggle tool output expansion |
1449
1512
  | \`Ctrl+T\` | Toggle thinking block visibility |
1513
+ | \`Ctrl+G\` | Edit message in external editor |
1450
1514
  | \`/\` | Slash commands |
1451
1515
  | \`!\` | Run bash command |
1452
1516
  `;