@mariozechner/pi-coding-agent 0.29.0 → 0.30.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 (43) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +30 -13
  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 +4 -0
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/core/agent-session.d.ts +3 -2
  8. package/dist/core/agent-session.d.ts.map +1 -1
  9. package/dist/core/agent-session.js +11 -8
  10. package/dist/core/agent-session.js.map +1 -1
  11. package/dist/core/custom-tools/loader.d.ts +5 -0
  12. package/dist/core/custom-tools/loader.d.ts.map +1 -1
  13. package/dist/core/custom-tools/loader.js +58 -3
  14. package/dist/core/custom-tools/loader.js.map +1 -1
  15. package/dist/core/hooks/loader.d.ts.map +1 -1
  16. package/dist/core/hooks/loader.js +8 -1
  17. package/dist/core/hooks/loader.js.map +1 -1
  18. package/dist/core/session-manager.d.ts +26 -9
  19. package/dist/core/session-manager.d.ts.map +1 -1
  20. package/dist/core/session-manager.js +75 -37
  21. package/dist/core/session-manager.js.map +1 -1
  22. package/dist/main.d.ts.map +1 -1
  23. package/dist/main.js +26 -5
  24. package/dist/main.js.map +1 -1
  25. package/dist/modes/interactive/components/custom-editor.d.ts +2 -0
  26. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  27. package/dist/modes/interactive/components/custom-editor.js +13 -1
  28. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  29. package/dist/modes/interactive/components/settings-selector.d.ts +33 -0
  30. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -0
  31. package/dist/modes/interactive/components/settings-selector.js +164 -0
  32. package/dist/modes/interactive/components/settings-selector.js.map +1 -0
  33. package/dist/modes/interactive/interactive-mode.d.ts +1 -5
  34. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  35. package/dist/modes/interactive/interactive-mode.js +80 -121
  36. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  37. package/dist/modes/interactive/theme/theme.d.ts +1 -0
  38. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  39. package/dist/modes/interactive/theme/theme.js +9 -0
  40. package/dist/modes/interactive/theme/theme.js.map +1 -1
  41. package/docs/sdk.md +6 -3
  42. package/examples/sdk/11-sessions.ts +5 -3
  43. package/package.json +4 -4
@@ -5,7 +5,7 @@
5
5
  import * as fs from "node:fs";
6
6
  import * as os from "node:os";
7
7
  import * as path from "node:path";
8
- import { CombinedAutocompleteProvider, Container, getCapabilities, Input, Loader, Markdown, ProcessTerminal, Spacer, Text, TruncatedText, TUI, visibleWidth, } from "@mariozechner/pi-tui";
8
+ import { CombinedAutocompleteProvider, Container, Input, Loader, Markdown, ProcessTerminal, Spacer, Text, TruncatedText, TUI, visibleWidth, } from "@mariozechner/pi-tui";
9
9
  import { exec, spawnSync } from "child_process";
10
10
  import { APP_NAME, getAuthPath, getDebugLogPath } from "../../config.js";
11
11
  import { isBashExecutionMessage } from "../../core/messages.js";
@@ -25,15 +25,12 @@ import { HookInputComponent } from "./components/hook-input.js";
25
25
  import { HookSelectorComponent } from "./components/hook-selector.js";
26
26
  import { ModelSelectorComponent } from "./components/model-selector.js";
27
27
  import { OAuthSelectorComponent } from "./components/oauth-selector.js";
28
- import { QueueModeSelectorComponent } from "./components/queue-mode-selector.js";
29
28
  import { SessionSelectorComponent } from "./components/session-selector.js";
30
- import { ShowImagesSelectorComponent } from "./components/show-images-selector.js";
31
- import { ThemeSelectorComponent } from "./components/theme-selector.js";
32
- import { ThinkingSelectorComponent } from "./components/thinking-selector.js";
29
+ import { SettingsSelectorComponent } from "./components/settings-selector.js";
33
30
  import { ToolExecutionComponent } from "./components/tool-execution.js";
34
31
  import { UserMessageComponent } from "./components/user-message.js";
35
32
  import { UserMessageSelectorComponent } from "./components/user-message-selector.js";
36
- import { getEditorTheme, getMarkdownTheme, onThemeChange, setTheme, theme } from "./theme/theme.js";
33
+ import { getAvailableThemes, getEditorTheme, getMarkdownTheme, onThemeChange, setTheme, theme } from "./theme/theme.js";
37
34
  export class InteractiveMode {
38
35
  setToolUIContext;
39
36
  session;
@@ -107,7 +104,7 @@ export class InteractiveMode {
107
104
  this.footer.setAutoCompactEnabled(session.autoCompactionEnabled);
108
105
  // Define slash commands for autocomplete
109
106
  const slashCommands = [
110
- { name: "thinking", description: "Select reasoning level (opens selector UI)" },
107
+ { name: "settings", description: "Open settings menu" },
111
108
  { name: "model", description: "Select model (opens selector UI)" },
112
109
  { name: "export", description: "Export session to HTML file" },
113
110
  { name: "copy", description: "Copy last agent message to clipboard" },
@@ -117,17 +114,10 @@ export class InteractiveMode {
117
114
  { name: "branch", description: "Create a new branch from a previous message" },
118
115
  { name: "login", description: "Login with OAuth provider" },
119
116
  { name: "logout", description: "Logout from OAuth provider" },
120
- { name: "queue", description: "Select message queue mode (opens selector UI)" },
121
- { name: "theme", description: "Select color theme (opens selector UI)" },
122
117
  { name: "new", description: "Start a new session" },
123
118
  { name: "compact", description: "Manually compact the session context" },
124
- { name: "autocompact", description: "Toggle automatic context compaction" },
125
119
  { name: "resume", description: "Resume a different session" },
126
120
  ];
127
- // Add image toggle command only if terminal supports images
128
- if (getCapabilities().images) {
129
- slashCommands.push({ name: "show-images", description: "Toggle inline image display" });
130
- }
131
121
  // Load hide thinking block setting
132
122
  this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
133
123
  // Convert file commands to SlashCommand format
@@ -165,9 +155,12 @@ export class InteractiveMode {
165
155
  theme.fg("dim", "shift+tab") +
166
156
  theme.fg("muted", " to cycle thinking") +
167
157
  "\n" +
168
- theme.fg("dim", "ctrl+p") +
158
+ theme.fg("dim", "ctrl+p/shift+ctrl+p") +
169
159
  theme.fg("muted", " to cycle models") +
170
160
  "\n" +
161
+ theme.fg("dim", "ctrl+l") +
162
+ theme.fg("muted", " to select model") +
163
+ "\n" +
171
164
  theme.fg("dim", "ctrl+o") +
172
165
  theme.fg("muted", " to expand tools") +
173
166
  "\n" +
@@ -492,7 +485,9 @@ export class InteractiveMode {
492
485
  this.editor.onCtrlD = () => this.handleCtrlD();
493
486
  this.editor.onCtrlZ = () => this.handleCtrlZ();
494
487
  this.editor.onShiftTab = () => this.cycleThinkingLevel();
495
- this.editor.onCtrlP = () => this.cycleModel();
488
+ this.editor.onCtrlP = () => this.cycleModel("forward");
489
+ this.editor.onShiftCtrlP = () => this.cycleModel("backward");
490
+ this.editor.onCtrlL = () => this.showModelSelector();
496
491
  this.editor.onCtrlO = () => this.toggleToolOutputExpansion();
497
492
  this.editor.onCtrlT = () => this.toggleThinkingBlockVisibility();
498
493
  this.editor.onCtrlG = () => this.openExternalEditor();
@@ -510,8 +505,8 @@ export class InteractiveMode {
510
505
  if (!text)
511
506
  return;
512
507
  // Handle slash commands
513
- if (text === "/thinking") {
514
- this.showThinkingSelector();
508
+ if (text === "/settings") {
509
+ this.showSettingsSelector();
515
510
  this.editor.setText("");
516
511
  return;
517
512
  }
@@ -560,16 +555,6 @@ export class InteractiveMode {
560
555
  this.editor.setText("");
561
556
  return;
562
557
  }
563
- if (text === "/queue") {
564
- this.showQueueModeSelector();
565
- this.editor.setText("");
566
- return;
567
- }
568
- if (text === "/theme") {
569
- this.showThemeSelector();
570
- this.editor.setText("");
571
- return;
572
- }
573
558
  if (text === "/new") {
574
559
  this.editor.setText("");
575
560
  await this.handleClearCommand();
@@ -587,16 +572,6 @@ export class InteractiveMode {
587
572
  }
588
573
  return;
589
574
  }
590
- if (text === "/autocompact") {
591
- this.handleAutocompactCommand();
592
- this.editor.setText("");
593
- return;
594
- }
595
- if (text === "/show-images") {
596
- this.showShowImagesSelector();
597
- this.editor.setText("");
598
- return;
599
- }
600
575
  if (text === "/debug") {
601
576
  this.handleDebugCommand();
602
577
  this.editor.setText("");
@@ -1052,9 +1027,9 @@ export class InteractiveMode {
1052
1027
  this.showStatus(`Thinking level: ${newLevel}`);
1053
1028
  }
1054
1029
  }
1055
- async cycleModel() {
1030
+ async cycleModel(direction) {
1056
1031
  try {
1057
- const result = await this.session.cycleModel();
1032
+ const result = await this.session.cycleModel(direction);
1058
1033
  if (result === null) {
1059
1034
  const msg = this.session.scopedModels.length > 0 ? "Only one model in scope" : "Only one model available";
1060
1035
  this.showStatus(msg);
@@ -1202,59 +1177,74 @@ export class InteractiveMode {
1202
1177
  this.ui.setFocus(focus);
1203
1178
  this.ui.requestRender();
1204
1179
  }
1205
- showThinkingSelector() {
1180
+ showSettingsSelector() {
1206
1181
  this.showSelector((done) => {
1207
- const selector = new ThinkingSelectorComponent(this.session.thinkingLevel, this.session.getAvailableThinkingLevels(), (level) => {
1208
- this.session.setThinkingLevel(level);
1209
- this.footer.updateState(this.session.state);
1210
- this.updateEditorBorderColor();
1211
- done();
1212
- this.showStatus(`Thinking level: ${level}`);
1213
- }, () => {
1214
- done();
1215
- this.ui.requestRender();
1216
- });
1217
- return { component: selector, focus: selector.getSelectList() };
1218
- });
1219
- }
1220
- showQueueModeSelector() {
1221
- this.showSelector((done) => {
1222
- const selector = new QueueModeSelectorComponent(this.session.queueMode, (mode) => {
1223
- this.session.setQueueMode(mode);
1224
- done();
1225
- this.showStatus(`Queue mode: ${mode}`);
1226
- }, () => {
1227
- done();
1228
- this.ui.requestRender();
1229
- });
1230
- return { component: selector, focus: selector.getSelectList() };
1231
- });
1232
- }
1233
- showThemeSelector() {
1234
- const currentTheme = this.settingsManager.getTheme() || "dark";
1235
- this.showSelector((done) => {
1236
- const selector = new ThemeSelectorComponent(currentTheme, (themeName) => {
1237
- const result = setTheme(themeName, true);
1238
- this.settingsManager.setTheme(themeName);
1239
- this.ui.invalidate();
1240
- done();
1241
- if (result.success) {
1242
- this.showStatus(`Theme: ${themeName}`);
1243
- }
1244
- else {
1245
- this.showError(`Failed to load theme "${themeName}": ${result.error}\nFell back to dark theme.`);
1246
- }
1247
- }, () => {
1248
- done();
1249
- this.ui.requestRender();
1250
- }, (themeName) => {
1251
- const result = setTheme(themeName, true);
1252
- if (result.success) {
1182
+ const selector = new SettingsSelectorComponent({
1183
+ autoCompact: this.session.autoCompactionEnabled,
1184
+ showImages: this.settingsManager.getShowImages(),
1185
+ queueMode: this.session.queueMode,
1186
+ thinkingLevel: this.session.thinkingLevel,
1187
+ availableThinkingLevels: this.session.getAvailableThinkingLevels(),
1188
+ currentTheme: this.settingsManager.getTheme() || "dark",
1189
+ availableThemes: getAvailableThemes(),
1190
+ hideThinkingBlock: this.hideThinkingBlock,
1191
+ collapseChangelog: this.settingsManager.getCollapseChangelog(),
1192
+ }, {
1193
+ onAutoCompactChange: (enabled) => {
1194
+ this.session.setAutoCompactionEnabled(enabled);
1195
+ this.footer.setAutoCompactEnabled(enabled);
1196
+ },
1197
+ onShowImagesChange: (enabled) => {
1198
+ this.settingsManager.setShowImages(enabled);
1199
+ for (const child of this.chatContainer.children) {
1200
+ if (child instanceof ToolExecutionComponent) {
1201
+ child.setShowImages(enabled);
1202
+ }
1203
+ }
1204
+ },
1205
+ onQueueModeChange: (mode) => {
1206
+ this.session.setQueueMode(mode);
1207
+ },
1208
+ onThinkingLevelChange: (level) => {
1209
+ this.session.setThinkingLevel(level);
1210
+ this.footer.updateState(this.session.state);
1211
+ this.updateEditorBorderColor();
1212
+ },
1213
+ onThemeChange: (themeName) => {
1214
+ const result = setTheme(themeName, true);
1215
+ this.settingsManager.setTheme(themeName);
1253
1216
  this.ui.invalidate();
1217
+ if (!result.success) {
1218
+ this.showError(`Failed to load theme "${themeName}": ${result.error}\nFell back to dark theme.`);
1219
+ }
1220
+ },
1221
+ onThemePreview: (themeName) => {
1222
+ const result = setTheme(themeName, true);
1223
+ if (result.success) {
1224
+ this.ui.invalidate();
1225
+ this.ui.requestRender();
1226
+ }
1227
+ },
1228
+ onHideThinkingBlockChange: (hidden) => {
1229
+ this.hideThinkingBlock = hidden;
1230
+ this.settingsManager.setHideThinkingBlock(hidden);
1231
+ for (const child of this.chatContainer.children) {
1232
+ if (child instanceof AssistantMessageComponent) {
1233
+ child.setHideThinkingBlock(hidden);
1234
+ }
1235
+ }
1236
+ this.chatContainer.clear();
1237
+ this.rebuildChatFromMessages();
1238
+ },
1239
+ onCollapseChangelogChange: (collapsed) => {
1240
+ this.settingsManager.setCollapseChangelog(collapsed);
1241
+ },
1242
+ onCancel: () => {
1243
+ done();
1254
1244
  this.ui.requestRender();
1255
- }
1245
+ },
1256
1246
  });
1257
- return { component: selector, focus: selector.getSelectList() };
1247
+ return { component: selector, focus: selector.getSettingsList() };
1258
1248
  });
1259
1249
  }
1260
1250
  showModelSelector() {
@@ -1308,7 +1298,7 @@ export class InteractiveMode {
1308
1298
  }
1309
1299
  showSessionSelector() {
1310
1300
  this.showSelector((done) => {
1311
- const sessions = SessionManager.list(this.sessionManager.getCwd());
1301
+ const sessions = SessionManager.list(this.sessionManager.getCwd(), this.sessionManager.getSessionDir());
1312
1302
  const selector = new SessionSelectorComponent(sessions, async (sessionPath) => {
1313
1303
  done();
1314
1304
  await this.handleResumeSession(sessionPath);
@@ -1640,37 +1630,6 @@ export class InteractiveMode {
1640
1630
  }
1641
1631
  await this.executeCompaction(customInstructions, false);
1642
1632
  }
1643
- handleAutocompactCommand() {
1644
- const newState = !this.session.autoCompactionEnabled;
1645
- this.session.setAutoCompactionEnabled(newState);
1646
- this.footer.setAutoCompactEnabled(newState);
1647
- this.showStatus(`Auto-compaction: ${newState ? "on" : "off"}`);
1648
- }
1649
- showShowImagesSelector() {
1650
- // Only available if terminal supports images
1651
- const caps = getCapabilities();
1652
- if (!caps.images) {
1653
- this.showWarning("Your terminal does not support inline images");
1654
- return;
1655
- }
1656
- this.showSelector((done) => {
1657
- const selector = new ShowImagesSelectorComponent(this.settingsManager.getShowImages(), (newValue) => {
1658
- this.settingsManager.setShowImages(newValue);
1659
- // Update all existing tool execution components with new setting
1660
- for (const child of this.chatContainer.children) {
1661
- if (child instanceof ToolExecutionComponent) {
1662
- child.setShowImages(newValue);
1663
- }
1664
- }
1665
- done();
1666
- this.showStatus(`Inline images: ${newValue ? "on" : "off"}`);
1667
- }, () => {
1668
- done();
1669
- this.ui.requestRender();
1670
- });
1671
- return { component: selector, focus: selector.getSelectList() };
1672
- });
1673
- }
1674
1633
  async executeCompaction(customInstructions, isAuto = false) {
1675
1634
  // Stop loading animation
1676
1635
  if (this.loadingAnimation) {