@mariozechner/pi-coding-agent
Version:
Coding agent CLI with read, bash, edit, write tools and session management
311 lines • 14 kB
JavaScript
import { Container, getCapabilities, SelectList, SettingsList, Spacer, Text, } from "@mariozechner/pi-tui";
import { getSelectListTheme, getSettingsListTheme, theme } from "../theme/theme.js";
import { DynamicBorder } from "./dynamic-border.js";
const SETTINGS_SUBMENU_SELECT_LIST_LAYOUT = {
minPrimaryColumnWidth: 12,
maxPrimaryColumnWidth: 32,
};
const THINKING_DESCRIPTIONS = {
off: "No reasoning",
minimal: "Very brief reasoning (~1k tokens)",
low: "Light reasoning (~2k tokens)",
medium: "Moderate reasoning (~8k tokens)",
high: "Deep reasoning (~16k tokens)",
xhigh: "Maximum reasoning (~32k tokens)",
};
/**
* A submenu component for selecting from a list of options.
*/
class SelectSubmenu extends Container {
selectList;
constructor(title, description, options, currentValue, onSelect, onCancel, onSelectionChange) {
super();
// Title
this.addChild(new Text(theme.bold(theme.fg("accent", title)), 0, 0));
// Description
if (description) {
this.addChild(new Spacer(1));
this.addChild(new Text(theme.fg("muted", description), 0, 0));
}
// Spacer
this.addChild(new Spacer(1));
// Select list
this.selectList = new SelectList(options, Math.min(options.length, 10), getSelectListTheme(), SETTINGS_SUBMENU_SELECT_LIST_LAYOUT);
// Pre-select current value
const currentIndex = options.findIndex((o) => o.value === currentValue);
if (currentIndex !== -1) {
this.selectList.setSelectedIndex(currentIndex);
}
this.selectList.onSelect = (item) => {
onSelect(item.value);
};
this.selectList.onCancel = onCancel;
if (onSelectionChange) {
this.selectList.onSelectionChange = (item) => {
onSelectionChange(item.value);
};
}
this.addChild(this.selectList);
// Hint
this.addChild(new Spacer(1));
this.addChild(new Text(theme.fg("dim", " Enter to select · Esc to go back"), 0, 0));
}
handleInput(data) {
this.selectList.handleInput(data);
}
}
/**
* Main settings selector component.
*/
export class SettingsSelectorComponent extends Container {
settingsList;
constructor(config, callbacks) {
super();
const supportsImages = getCapabilities().images;
const items = [
{
id: "autocompact",
label: "Auto-compact",
description: "Automatically compact context when it gets too large",
currentValue: config.autoCompact ? "true" : "false",
values: ["true", "false"],
},
{
id: "steering-mode",
label: "Steering mode",
description: "Enter while streaming queues steering messages. 'one-at-a-time': deliver one, wait for response. 'all': deliver all at once.",
currentValue: config.steeringMode,
values: ["one-at-a-time", "all"],
},
{
id: "follow-up-mode",
label: "Follow-up mode",
description: "Alt+Enter queues follow-up messages until agent stops. 'one-at-a-time': deliver one, wait for response. 'all': deliver all at once.",
currentValue: config.followUpMode,
values: ["one-at-a-time", "all"],
},
{
id: "transport",
label: "Transport",
description: "Preferred transport for providers that support multiple transports",
currentValue: config.transport,
values: ["sse", "websocket", "auto"],
},
{
id: "hide-thinking",
label: "Hide thinking",
description: "Hide thinking blocks in assistant responses",
currentValue: config.hideThinkingBlock ? "true" : "false",
values: ["true", "false"],
},
{
id: "collapse-changelog",
label: "Collapse changelog",
description: "Show condensed changelog after updates",
currentValue: config.collapseChangelog ? "true" : "false",
values: ["true", "false"],
},
{
id: "quiet-startup",
label: "Quiet startup",
description: "Disable verbose printing at startup",
currentValue: config.quietStartup ? "true" : "false",
values: ["true", "false"],
},
{
id: "install-telemetry",
label: "Install telemetry",
description: "Send an anonymous version/update ping after changelog-detected updates",
currentValue: config.enableInstallTelemetry ? "true" : "false",
values: ["true", "false"],
},
{
id: "double-escape-action",
label: "Double-escape action",
description: "Action when pressing Escape twice with empty editor",
currentValue: config.doubleEscapeAction,
values: ["tree", "fork", "none"],
},
{
id: "tree-filter-mode",
label: "Tree filter mode",
description: "Default filter when opening /tree",
currentValue: config.treeFilterMode,
values: ["default", "no-tools", "user-only", "labeled-only", "all"],
},
{
id: "thinking",
label: "Thinking level",
description: "Reasoning depth for thinking-capable models",
currentValue: config.thinkingLevel,
submenu: (currentValue, done) => new SelectSubmenu("Thinking Level", "Select reasoning depth for thinking-capable models", config.availableThinkingLevels.map((level) => ({
value: level,
label: level,
description: THINKING_DESCRIPTIONS[level],
})), currentValue, (value) => {
callbacks.onThinkingLevelChange(value);
done(value);
}, () => done()),
},
{
id: "theme",
label: "Theme",
description: "Color theme for the interface",
currentValue: config.currentTheme,
submenu: (currentValue, done) => new SelectSubmenu("Theme", "Select color theme", config.availableThemes.map((t) => ({
value: t,
label: t,
})), currentValue, (value) => {
callbacks.onThemeChange(value);
done(value);
}, () => {
// Restore original theme on cancel
callbacks.onThemePreview?.(currentValue);
done();
}, (value) => {
// Preview theme on selection change
callbacks.onThemePreview?.(value);
}),
},
];
// Only show image toggle if terminal supports it
if (supportsImages) {
// Insert after autocompact
items.splice(1, 0, {
id: "show-images",
label: "Show images",
description: "Render images inline in terminal",
currentValue: config.showImages ? "true" : "false",
values: ["true", "false"],
});
}
// Image auto-resize toggle (always available, affects both attached and read images)
items.splice(supportsImages ? 2 : 1, 0, {
id: "auto-resize-images",
label: "Auto-resize images",
description: "Resize large images to 2000x2000 max for better model compatibility",
currentValue: config.autoResizeImages ? "true" : "false",
values: ["true", "false"],
});
// Block images toggle (always available, insert after auto-resize-images)
const autoResizeIndex = items.findIndex((item) => item.id === "auto-resize-images");
items.splice(autoResizeIndex + 1, 0, {
id: "block-images",
label: "Block images",
description: "Prevent images from being sent to LLM providers",
currentValue: config.blockImages ? "true" : "false",
values: ["true", "false"],
});
// Skill commands toggle (insert after block-images)
const blockImagesIndex = items.findIndex((item) => item.id === "block-images");
items.splice(blockImagesIndex + 1, 0, {
id: "skill-commands",
label: "Skill commands",
description: "Register skills as /skill:name commands",
currentValue: config.enableSkillCommands ? "true" : "false",
values: ["true", "false"],
});
// Hardware cursor toggle (insert after skill-commands)
const skillCommandsIndex = items.findIndex((item) => item.id === "skill-commands");
items.splice(skillCommandsIndex + 1, 0, {
id: "show-hardware-cursor",
label: "Show hardware cursor",
description: "Show the terminal cursor while still positioning it for IME support",
currentValue: config.showHardwareCursor ? "true" : "false",
values: ["true", "false"],
});
// Editor padding toggle (insert after show-hardware-cursor)
const hardwareCursorIndex = items.findIndex((item) => item.id === "show-hardware-cursor");
items.splice(hardwareCursorIndex + 1, 0, {
id: "editor-padding",
label: "Editor padding",
description: "Horizontal padding for input editor (0-3)",
currentValue: String(config.editorPaddingX),
values: ["0", "1", "2", "3"],
});
// Autocomplete max visible toggle (insert after editor-padding)
const editorPaddingIndex = items.findIndex((item) => item.id === "editor-padding");
items.splice(editorPaddingIndex + 1, 0, {
id: "autocomplete-max-visible",
label: "Autocomplete max items",
description: "Max visible items in autocomplete dropdown (3-20)",
currentValue: String(config.autocompleteMaxVisible),
values: ["3", "5", "7", "10", "15", "20"],
});
// Clear on shrink toggle (insert after autocomplete-max-visible)
const autocompleteIndex = items.findIndex((item) => item.id === "autocomplete-max-visible");
items.splice(autocompleteIndex + 1, 0, {
id: "clear-on-shrink",
label: "Clear on shrink",
description: "Clear empty rows when content shrinks (may cause flicker)",
currentValue: config.clearOnShrink ? "true" : "false",
values: ["true", "false"],
});
// Add borders
this.addChild(new DynamicBorder());
this.settingsList = new SettingsList(items, 10, getSettingsListTheme(), (id, newValue) => {
switch (id) {
case "autocompact":
callbacks.onAutoCompactChange(newValue === "true");
break;
case "show-images":
callbacks.onShowImagesChange(newValue === "true");
break;
case "auto-resize-images":
callbacks.onAutoResizeImagesChange(newValue === "true");
break;
case "block-images":
callbacks.onBlockImagesChange(newValue === "true");
break;
case "skill-commands":
callbacks.onEnableSkillCommandsChange(newValue === "true");
break;
case "steering-mode":
callbacks.onSteeringModeChange(newValue);
break;
case "follow-up-mode":
callbacks.onFollowUpModeChange(newValue);
break;
case "transport":
callbacks.onTransportChange(newValue);
break;
case "hide-thinking":
callbacks.onHideThinkingBlockChange(newValue === "true");
break;
case "collapse-changelog":
callbacks.onCollapseChangelogChange(newValue === "true");
break;
case "quiet-startup":
callbacks.onQuietStartupChange(newValue === "true");
break;
case "install-telemetry":
callbacks.onEnableInstallTelemetryChange(newValue === "true");
break;
case "double-escape-action":
callbacks.onDoubleEscapeActionChange(newValue);
break;
case "tree-filter-mode":
callbacks.onTreeFilterModeChange(newValue);
break;
case "show-hardware-cursor":
callbacks.onShowHardwareCursorChange(newValue === "true");
break;
case "editor-padding":
callbacks.onEditorPaddingXChange(parseInt(newValue, 10));
break;
case "autocomplete-max-visible":
callbacks.onAutocompleteMaxVisibleChange(parseInt(newValue, 10));
break;
case "clear-on-shrink":
callbacks.onClearOnShrinkChange(newValue === "true");
break;
}
}, callbacks.onCancel, { enableSearch: true });
this.addChild(this.settingsList);
this.addChild(new DynamicBorder());
}
getSettingsList() {
return this.settingsList;
}
}
//# sourceMappingURL=settings-selector.js.map