{
  "name": "@vanillagreen/pi-qol",
  "version": "2.5.0",
  "description": "Pi quality-of-life extension: compact statusline/π prompt, reliable multiline input, styled pasted-image chips, session naming/search/context import, scheduled prompts, handoff, permission prompts, notifications, custom compaction, and a collapsed-thinking timer.",
  "license": "MIT",
  "keywords": [
    "pi-package",
    "pi",
    "coding-agent",
    "qol",
    "editor"
  ],
  "pi": {
    "extensions": [
      "./extensions/qol.ts"
    ],
    "image": "https://raw.githubusercontent.com/vanillagreencom/kendex/main/pi-extensions/pi-qol/assets/settings-panel.png"
  },
  "scripts": {
    "test": "bun test ./tests"
  },
  "kendex": {
    "extensionManager": {
      "displayName": "QOL",
      "settings": [
        {
          "key": "enabled",
          "label": "Enable QOL editor helpers",
          "description": "Install the QOL editor component and /qol helper command. Disable and reload to leave Pi editor behavior unchanged.",
          "type": "boolean",
          "default": true,
          "category": "General",
          "apply": "reload",
          "requiresReload": true
        },
        {
          "key": "gatewayPrivacy.enabled",
          "label": "Require gateway privacy controls",
          "description": "Require zero data retention and no prompt training for main-agent and QOL model requests through OpenRouter and Vercel AI Gateway. Overrides weaker privacy values and keeps other routing options. On a gateway session model, QOL writes compaction and branch summaries in place of Pi's built-in ones, which cannot carry these controls, and a failed summary cancels instead of falling back to Pi's.",
          "type": "boolean",
          "default": false,
          "category": "Privacy",
          "apply": "live"
        },
        {
          "key": "glyphStyle",
          "label": "Glyph style",
          "description": "Glyphs for kendex-owned UI chrome. Unicode is the default; ASCII uses plain box/status/separator characters for terminal compatibility. pi-tool-renderer's global override wins when set.",
          "type": "enum",
          "enumValues": [
            "unicode",
            "ascii"
          ],
          "default": "unicode",
          "category": "Rendering",
          "apply": "live"
        },
        {
          "key": "statusline.enabled",
          "label": "Show compact statusline",
          "description": "Render the QOL statusline row with repo, branch, model, thinking level, and context percentage. Disable when another statusline/footer extension owns this UI.",
          "type": "boolean",
          "default": true,
          "category": "Statusline",
          "apply": "reload",
          "requiresReload": true
        },
        {
          "key": "statusline.showProvider",
          "label": "Show model provider",
          "description": "Show a readable provider name before the model, for example Copilot / GPT 6 Astra.",
          "type": "boolean",
          "default": true,
          "category": "Statusline",
          "apply": "live"
        },
        {
          "key": "statusline.showAccount",
          "label": "Show account email",
          "description": "Append the Claude login this session authenticated as, as the Claude bridge reports it. Nothing is shown before the first turn, or when the request used an API key or a third-party backend such as Bedrock or Vertex.",
          "type": "boolean",
          "default": true,
          "category": "Statusline",
          "apply": "live"
        },
        {
          "key": "replaceFooter",
          "label": "Replace built-in footer",
          "description": "Hide Pi default footer while the compact QOL statusline is enabled.",
          "type": "boolean",
          "default": true,
          "category": "Statusline",
          "apply": "reload",
          "requiresReload": true
        },
        {
          "key": "compactPrompt",
          "label": "Use π prompt editor",
          "description": "Replace the main editor with the compact π prompt editor. Disable to use QOL newline/image helpers with Pi default editor chrome.",
          "type": "boolean",
          "default": true,
          "category": "Statusline",
          "apply": "reload",
          "requiresReload": true
        },
        {
          "key": "showSessionNameTitle",
          "label": "Show session name title",
          "description": "Show the named session above the prompt. In tmux, update the current pane title to \"π <session>\" and enable a pane border title padded with non-breaking spaces so tmux does not trim it. The title refreshes as soon as Pi reports a session metadata change.",
          "type": "boolean",
          "default": true,
          "category": "Statusline",
          "apply": "reload",
          "requiresReload": true
        },
        {
          "key": "showSessionNameWindow",
          "label": "Sync session name to tmux window name",
          "description": "Also rename the current tmux window to \"π <session>\" so the session name shows in the tmux status line. Disables tmux automatic-rename for this window while active and restores the original window name and automatic-rename setting on shutdown.",
          "type": "boolean",
          "default": true,
          "category": "Statusline",
          "apply": "reload",
          "requiresReload": true
        },
        {
          "key": "inputBottomPaddingLines",
          "label": "Input bottom padding",
          "description": "Blank lines below the compact prompt. Default is 0 for no extra space.",
          "type": "number",
          "default": 0,
          "category": "Statusline",
          "apply": "reload",
          "requiresReload": true
        },
        {
          "key": "gitRefreshTimeoutMs",
          "label": "Git refresh timeout",
          "description": "Timeout for each git metadata command used by the statusline.",
          "type": "number",
          "default": 1500,
          "category": "Statusline Git",
          "apply": "live"
        },
        {
          "key": "showDirtyMarker",
          "label": "Show dirty marker",
          "description": "Append * to the branch badge when the worktree is dirty.",
          "type": "boolean",
          "default": true,
          "category": "Statusline Git",
          "apply": "live"
        },
        {
          "key": "permissionGate.enabled",
          "label": "Prompt before risky bash commands",
          "description": "Ask for confirmation before bash tool calls matching the configured command list. Off by default; enable explicitly if you want this extra prompt.",
          "type": "boolean",
          "default": false,
          "category": "Permission Gate",
          "apply": "live"
        },
        {
          "key": "permissionGate.commands",
          "label": "Commands to prompt for",
          "description": "Comma-separated command fragments to confirm before running. Literal entries are case-insensitive and whitespace-tolerant; regex entries may use /pattern/flags. Default prompts only for rm -Rf.",
          "type": "string",
          "default": "rm -Rf",
          "category": "Permission Gate",
          "apply": "live"
        },
        {
          "key": "permissionGate.previewLines",
          "label": "Approval preview lines",
          "description": "Maximum command lines rendered in the permission approval prompt. Longer commands show a head/tail preview with an omitted-line marker.",
          "type": "number",
          "default": 12,
          "category": "Permission Gate",
          "apply": "live"
        },
        {
          "key": "permissionGate.previewChars",
          "label": "Approval preview characters",
          "description": "Maximum command preview characters rendered in the permission approval prompt to avoid flooding or flickering the terminal.",
          "type": "number",
          "default": 1200,
          "category": "Permission Gate",
          "apply": "live"
        },
        {
          "key": "newlineOnShiftEnter",
          "label": "shift+enter inserts newline",
          "description": "Intercept distinguishable shift+enter/shift+return in the prompt editor and insert a newline instead of submitting.",
          "type": "boolean",
          "default": true,
          "category": "Input",
          "apply": "reload",
          "requiresReload": true
        },
        {
          "key": "newlineFallbackKey",
          "label": "Fallback newline key",
          "description": "Alternate newline key for terminals that cannot distinguish shift+enter. alt+enter is reserved for Pi follow-up messages.",
          "type": "enum",
          "enumValues": [
            "ctrl+j",
            "none"
          ],
          "default": "ctrl+j",
          "category": "Input",
          "apply": "reload",
          "requiresReload": true
        },
        {
          "key": "pendingQueue.asciiGreen",
          "label": "Style pending queue preview",
          "description": "Render Pi's built-in steering/follow-up queue preview in ANSI green with a heavy left queue bar, while keeping the alt+up restore hint in the theme hint color with a two-space indent.",
          "type": "boolean",
          "default": true,
          "category": "Input",
          "apply": "live"
        },
        {
          "key": "showImageChips",
          "label": "Style image chips",
          "description": "Render [Image #N] placeholders and existing pasted image paths in the editor as visually distinct chips. Pi still owns attachment state.",
          "type": "boolean",
          "default": true,
          "category": "Images",
          "apply": "live"
        },
        {
          "key": "showAttachmentCountInStatus",
          "label": "Show attachment count",
          "description": "Show a lightweight status badge when the draft contains image placeholders.",
          "type": "boolean",
          "default": true,
          "category": "Images",
          "apply": "live"
        },
        {
          "key": "enableSessionNameCommand",
          "label": "Enable /rename command",
          "description": "Register the /rename command for setting or viewing the current session's friendly name in Pi's session selector.",
          "type": "boolean",
          "default": true,
          "category": "Session",
          "apply": "reload",
          "requiresReload": true
        },
        {
          "key": "sessionAutoRename.enabled",
          "label": "Auto-name new sessions",
          "description": "Automatically generate a friendly session name from the first user prompt when the session does not already have a name.",
          "type": "boolean",
          "default": true,
          "category": "Session Auto-Rename",
          "apply": "live"
        },
        {
          "key": "sessionAutoRename.model",
          "label": "Auto-rename model",
          "description": "Model used for title generation. Use provider/model, current, or cheapest. Default is a fast OpenAI Codex model that supports ChatGPT OAuth via /login openai-codex.",
          "type": "string",
          "default": "openai-codex/gpt-5.4-mini",
          "category": "Session Auto-Rename",
          "apply": "live"
        },
        {
          "key": "sessionAutoRename.fallbackModel",
          "label": "Auto-rename fallback model",
          "description": "Model to try if the primary model is unavailable. Use current, provider/model, cheapest, none, or off.",
          "type": "string",
          "default": "current",
          "category": "Session Auto-Rename",
          "apply": "live"
        },
        {
          "key": "sessionAutoRename.fallback",
          "label": "Deterministic fallback",
          "description": "Fallback naming strategy if all model calls fail: title-case words from the first prompt, a truncated prompt, or none.",
          "type": "enum",
          "enumValues": [
            "words",
            "truncate",
            "none"
          ],
          "default": "words",
          "category": "Session Auto-Rename",
          "apply": "live"
        },
        {
          "key": "sessionAutoRename.prefix",
          "label": "Auto-rename prefix",
          "description": "Optional static prefix added before generated names, e.g. a project label. Leave empty for no prefix.",
          "type": "string",
          "default": "",
          "category": "Session Auto-Rename",
          "apply": "live"
        },
        {
          "key": "sessionAutoRename.maxInputChars",
          "label": "Auto-rename input cap",
          "description": "Maximum first-prompt characters sent to the naming model. Long prompts keep the beginning and end.",
          "type": "number",
          "default": 2000,
          "category": "Session Auto-Rename",
          "apply": "live"
        },
        {
          "key": "sessionAutoRename.maxNameChars",
          "label": "Auto-rename title length",
          "description": "Maximum characters for generated session names, including any prefix.",
          "type": "number",
          "default": 80,
          "category": "Session Auto-Rename",
          "apply": "live"
        },
        {
          "key": "sessionAutoRename.maxTokens",
          "label": "Auto-rename output tokens",
          "description": "Maximum output tokens requested from the naming model, reasoning tokens included.",
          "type": "number",
          "default": 128,
          "category": "Session Auto-Rename",
          "apply": "live"
        },
        {
          "key": "sessionAutoRename.timeoutMs",
          "label": "Auto-rename timeout",
          "description": "Per-model timeout in milliseconds before falling back to the next model or deterministic title.",
          "type": "number",
          "default": 12000,
          "category": "Session Auto-Rename",
          "apply": "live"
        },
        {
          "key": "sessionAutoRename.prompt",
          "label": "Auto-rename prompt override",
          "description": "Optional prompt template. Use {{message}} for the first user prompt; when empty, QOL uses its built-in concise title prompt.",
          "type": "string",
          "default": "",
          "category": "Session Auto-Rename Advanced",
          "apply": "live"
        },
        {
          "key": "sessionAutoRename.notify",
          "label": "Notify on auto-rename",
          "description": "Show a Pi UI notification when QOL auto-generates a session name. Manual /qol rename always notifies.",
          "type": "boolean",
          "default": false,
          "category": "Session Auto-Rename Advanced",
          "apply": "live"
        },
        {
          "key": "sessionAutoRename.debug",
          "label": "Debug auto-rename",
          "description": "Show model/auth/fallback diagnostics for automatic session naming.",
          "type": "boolean",
          "default": false,
          "category": "Session Auto-Rename Advanced",
          "apply": "live"
        },
        {
          "key": "enableHandoffCommand",
          "label": "Enable /handoff command",
          "description": "Register the handoff command to generate a focused prompt and create a new session with the prompt as a draft.",
          "type": "boolean",
          "default": true,
          "category": "Session",
          "apply": "reload",
          "requiresReload": true
        },
        {
          "key": "enableScheduleCommand",
          "label": "Enable /schedule command",
          "description": "Register /schedule for timer-based user messages, e.g. /schedule 20m retry after rate reset. The timer runs in the extension without invoking the model immediately.",
          "type": "boolean",
          "default": true,
          "category": "Session",
          "apply": "reload",
          "requiresReload": true
        },
        {
          "key": "rateLimitAutoResume.enabled",
          "label": "Auto-resume after rate limits",
          "description": "When Pi detects a rate-limit stop with a reset time, schedule a follow-up user message at the reset time so the agent can continue if nothing else has continued since.",
          "type": "boolean",
          "default": false,
          "category": "Rate Limit Recovery",
          "apply": "live"
        },
        {
          "key": "rateLimitAutoResume.message",
          "label": "Auto-resume message",
          "description": "User message sent after a detected rate limit resets.",
          "type": "string",
          "default": "Hit API rate limit. It has been reset. Continue",
          "category": "Rate Limit Recovery",
          "apply": "live"
        },
        {
          "key": "rateLimitAutoResume.bufferSeconds",
          "label": "Reset buffer seconds",
          "description": "Seconds to wait after the reported reset time before sending the auto-resume message.",
          "type": "number",
          "default": 10,
          "category": "Rate Limit Recovery",
          "apply": "live"
        },
        {
          "key": "rateLimitAutoResume.maxDelayMinutes",
          "label": "Maximum auto-resume delay",
          "description": "Ignore detected reset times farther away than this many minutes, to avoid stale or malformed schedules.",
          "type": "number",
          "default": 1440,
          "category": "Rate Limit Recovery",
          "apply": "live"
        },
        {
          "key": "rateLimitAutoResume.notify",
          "label": "Notify on auto-resume",
          "description": "Show Pi UI notices when rate-limit auto-resume is scheduled, sent, or fails.",
          "type": "boolean",
          "default": true,
          "category": "Rate Limit Recovery",
          "apply": "live"
        },
        {
          "key": "handoffReviewPrompt",
          "label": "Review handoff prompt",
          "description": "Open an editor to review or edit the generated handoff prompt before creating the new session.",
          "type": "boolean",
          "default": true,
          "category": "Session",
          "apply": "live"
        },
        {
          "key": "enableContextCommand",
          "label": "Enable /context command",
          "description": "Register the /context command to show inline context-window usage with estimated Pi/model category breakdowns.",
          "type": "boolean",
          "default": true,
          "category": "Context",
          "apply": "reload",
          "requiresReload": true
        },
        {
          "key": "sessionSearch.enabled",
          "label": "Enable session search",
          "description": "Register the previous-session search palette, preview, resume, and context-import actions.",
          "type": "boolean",
          "default": true,
          "category": "Session Search",
          "apply": "reload",
          "requiresReload": true
        },
        {
          "key": "sessionSearch.shortcutKey",
          "label": "Session search shortcut",
          "description": "Keyboard shortcut to open the session search overlay. Default f2 avoids Pi's built-in ctrl+f and alt+f editor bindings. Set to none to disable.",
          "type": "string",
          "default": "f2",
          "category": "Session Search",
          "apply": "reload",
          "requiresReload": true
        },
        {
          "key": "sessionSearch.resultLimit",
          "label": "Session search result limit",
          "description": "Maximum number of matching prompts returned by the search palette.",
          "type": "number",
          "default": 40,
          "category": "Session Search",
          "apply": "live"
        },
        {
          "key": "sessionSearch.maxVisible",
          "label": "Visible session rows",
          "description": "Maximum number of sessions shown at once in the overlay before scrolling.",
          "type": "number",
          "default": 8,
          "category": "Session Search",
          "apply": "live"
        },
        {
          "key": "sessionSearch.previewSnippets",
          "label": "Preview snippets",
          "description": "Maximum matching snippets shown on the selected session preview screen.",
          "type": "number",
          "default": 6,
          "category": "Session Search",
          "apply": "live"
        },
        {
          "key": "sessionSearch.overlayWidth",
          "label": "Overlay width",
          "description": "Preferred session search overlay width in terminal columns.",
          "type": "number",
          "default": 104,
          "category": "Session Search",
          "apply": "live"
        },
        {
          "key": "sessionSearch.messageMaxVisible",
          "label": "Visible user prompt rows",
          "description": "Maximum number of one-line user prompts shown at once in the session prompt browser before scrolling.",
          "type": "number",
          "default": 12,
          "category": "Session Search",
          "apply": "live"
        },
        {
          "key": "sessionSearch.cacheTtlSeconds",
          "label": "Session cache TTL",
          "description": "Seconds a loaded session index is kept in memory before it is released; the next search opens at once and reloads it. 0 keeps the index until the session ends.",
          "type": "number",
          "default": 300,
          "category": "Session Search",
          "apply": "live"
        },
        {
          "key": "sessionSearch.summaryModel",
          "label": "Session summary model",
          "description": "Model used to summarize an imported previous session. Use current, provider/model, or a bare model id. Defaults to current so no extra provider setup is required.",
          "type": "string",
          "default": "current",
          "category": "Session Search Summary",
          "apply": "live"
        },
        {
          "key": "sessionSearch.summaryMaxTokens",
          "label": "Session summary max output tokens",
          "description": "Maximum tokens requested from the session-search summarizer.",
          "type": "number",
          "default": 4096,
          "category": "Session Search Summary",
          "apply": "live"
        },
        {
          "key": "sessionSearch.summaryInputMaxChars",
          "label": "Summary input character cap",
          "description": "Maximum previous-session transcript characters sent to the summarizer; long sessions keep the beginning and end.",
          "type": "number",
          "default": 180000,
          "category": "Session Search Summary",
          "apply": "live"
        },
        {
          "key": "notification.enabled",
          "label": "Enable notifications",
          "description": "Send terminal/tmux notifications for ready/input-needed, questions, completed task lists, and critical information.",
          "type": "boolean",
          "default": true,
          "category": "Notifications",
          "apply": "live"
        },
        {
          "key": "notification.onAgentReady",
          "label": "Notify when ready",
          "description": "Notify when an agent turn finishes and Pi is ready for input.",
          "type": "boolean",
          "default": true,
          "category": "Notifications Triggers",
          "apply": "live"
        },
        {
          "key": "notification.onDirectionNeeded",
          "label": "Notify when direction needed",
          "description": "Use stronger notification wording when the last assistant message appears to ask for direction, confirmation, or a choice.",
          "type": "boolean",
          "default": true,
          "category": "Notifications Triggers",
          "apply": "live"
        },
        {
          "key": "notification.onQuestion",
          "label": "Notify for question popups",
          "description": "Notify when pi-questions opens a structured question that requires user input.",
          "type": "boolean",
          "default": true,
          "category": "Notifications Triggers",
          "apply": "live"
        },
        {
          "key": "notification.onTaskComplete",
          "label": "Notify when all tasks complete",
          "description": "Notify after the agent turn ends when tasks_write completes the task panel.",
          "type": "boolean",
          "default": true,
          "category": "Notifications Triggers",
          "apply": "live"
        },
        {
          "key": "notification.onCritical",
          "label": "Notify critical info",
          "description": "Notify for assistant messages containing critical, blocked, security, rate-limit, or cannot-proceed signals. Tool failures are intentionally ignored.",
          "type": "boolean",
          "default": true,
          "category": "Notifications Triggers",
          "apply": "live"
        },
        {
          "key": "notification.bell",
          "label": "Terminal bell",
          "description": "Emit BEL (\\u0007) for standard terminal/tmux audible or visual bell behavior.",
          "type": "boolean",
          "default": true,
          "category": "Notifications Channels",
          "apply": "live"
        },
        {
          "key": "notification.muteBellSound",
          "label": "Mute bell sound",
          "description": "Keep notifications enabled while suppressing QOL-emitted BEL bytes; OSC terminal notifications use ST terminators where supported.",
          "type": "boolean",
          "default": false,
          "category": "Notifications Channels",
          "apply": "live"
        },
        {
          "key": "notification.bellWhenActive",
          "label": "Bell when tmux window active",
          "description": "Also emit BEL when the source tmux window is already active. Disabled by default to avoid noisy bells while you are looking at the Pi window; native terminal notifications still run so Ghostty can decide based on app focus.",
          "type": "boolean",
          "default": false,
          "category": "Notifications Channels",
          "apply": "live"
        },
        {
          "key": "notification.native",
          "label": "Native terminal notification",
          "description": "Send OSC 777/OSC 99 terminal notifications, or Windows toast in Windows Terminal/WSL.",
          "type": "boolean",
          "default": true,
          "category": "Notifications Channels",
          "apply": "live"
        },
        {
          "key": "notification.tmux",
          "label": "tmux display-message",
          "description": "When running inside tmux, also show a transient tmux status-line message. Disabled by default because tmux displays it on the active client, not necessarily as a source-window mark.",
          "type": "boolean",
          "default": false,
          "category": "Notifications Channels",
          "apply": "live"
        },
        {
          "key": "notification.tmuxWindowMark",
          "label": "Mark tmux window",
          "description": "Optionally prefix the source tmux window name when notifying. Disabled by default because BEL should let tmux apply your normal bell styling.",
          "type": "boolean",
          "default": false,
          "category": "Notifications Channels",
          "apply": "live"
        },
        {
          "key": "notification.tmuxWindowMarkText",
          "label": "tmux window mark text",
          "description": "Prefix used when marking the source tmux window.",
          "type": "string",
          "default": "!",
          "category": "Notifications Tuning",
          "apply": "live"
        },
        {
          "key": "notification.tmuxWindowMarkDurationMs",
          "label": "tmux window mark duration",
          "description": "Milliseconds before automatically clearing the tmux window mark. 0 keeps it until next input/agent start/reset.",
          "type": "number",
          "default": 0,
          "category": "Notifications Tuning",
          "apply": "live"
        },
        {
          "key": "notification.tmuxNativeClientTty",
          "label": "tmux native via client TTY",
          "description": "When inside tmux, send OSC terminal notifications directly to attached tmux client TTYs so Ghostty/system notifications still appear for inactive source windows.",
          "type": "boolean",
          "default": true,
          "category": "Notifications Channels",
          "apply": "live"
        },
        {
          "key": "notification.tmuxPassthrough",
          "label": "tmux OSC passthrough",
          "description": "Wrap OSC terminal notifications in tmux passthrough sequences so supported terminals outside tmux can receive them.",
          "type": "boolean",
          "default": true,
          "category": "Notifications Channels",
          "apply": "live"
        },
        {
          "key": "notification.piUi",
          "label": "Also show Pi UI notice",
          "description": "Also call ctx.ui.notify for QOL external notifications. Disabled by default to avoid duplicate in-session noise.",
          "type": "boolean",
          "default": false,
          "category": "Notifications Channels",
          "apply": "live"
        },
        {
          "key": "notification.oscProtocol",
          "label": "Terminal notification protocol",
          "description": "OSC notification protocol. auto uses Kitty OSC 99 when KITTY_WINDOW_ID is present, otherwise OSC 777.",
          "type": "enum",
          "enumValues": [
            "auto",
            "osc777",
            "osc99",
            "off"
          ],
          "default": "auto",
          "category": "Notifications Channels",
          "apply": "live"
        },
        {
          "key": "notification.cooldownSeconds",
          "label": "Notification cooldown",
          "description": "Minimum seconds between duplicate notifications of the same kind.",
          "type": "number",
          "default": 8,
          "category": "Notifications Tuning",
          "apply": "live"
        },
        {
          "key": "notification.title",
          "label": "Notification title",
          "description": "Title used for external terminal notifications.",
          "type": "string",
          "default": "Pi",
          "category": "Notifications Tuning",
          "apply": "live"
        },
        {
          "key": "notification.readyMessage",
          "label": "Ready message",
          "description": "Body text for normal agent-done notifications.",
          "type": "string",
          "default": "Ready for input",
          "category": "Notifications Tuning",
          "apply": "live"
        },
        {
          "key": "notification.bodyMaxChars",
          "label": "Notification body length",
          "description": "Maximum body characters for external notifications.",
          "type": "number",
          "default": 240,
          "category": "Notifications Tuning",
          "apply": "live"
        },
        {
          "key": "notification.tmuxMessageDurationMs",
          "label": "tmux message duration",
          "description": "Milliseconds to display tmux status-line notification messages.",
          "type": "number",
          "default": 5000,
          "category": "Notifications Tuning",
          "apply": "live"
        },
        {
          "key": "compaction.customEnabled",
          "label": "Custom compaction summaries",
          "description": "Use QOL's custom compaction hook instead of Pi's default summary generation. Manual/auto compaction still uses Pi's normal trigger settings.",
          "type": "boolean",
          "default": false,
          "category": "Compaction",
          "apply": "live"
        },
        {
          "key": "compaction.model",
          "label": "Compaction model",
          "description": "Summarizer model as provider/model, a bare model id, or current. Default uses Pi's active model.",
          "type": "string",
          "default": "current",
          "category": "Compaction",
          "apply": "live"
        },
        {
          "key": "compaction.profile",
          "label": "Compaction detail profile",
          "description": "Summary detail level for custom compaction and branch summaries.",
          "type": "enum",
          "enumValues": [
            "concise",
            "balanced",
            "exhaustive"
          ],
          "default": "balanced",
          "category": "Compaction",
          "apply": "live"
        },
        {
          "key": "compaction.maxTokens",
          "label": "Compaction max output tokens",
          "description": "Maximum tokens requested from the custom compaction summarizer.",
          "type": "number",
          "default": 8192,
          "category": "Compaction",
          "apply": "live"
        },
        {
          "key": "compaction.includePreviousSummary",
          "label": "Include previous summary",
          "description": "Pass the previous compaction summary to the custom summarizer for iterative continuity.",
          "type": "boolean",
          "default": true,
          "category": "Compaction",
          "apply": "live"
        },
        {
          "key": "compaction.fallbackToDefault",
          "label": "Fallback to Pi default compaction",
          "description": "If custom compaction fails, let Pi run its default compaction instead of cancelling the compaction. Ignored while gatewayPrivacy.enabled covers the session model, since Pi's compaction would send the transcript without the privacy controls.",
          "type": "boolean",
          "default": true,
          "category": "Compaction",
          "apply": "live"
        },
        {
          "key": "compaction.notify",
          "label": "Show compaction notifications",
          "description": "Show QOL notifications when custom, remote, branch, or idle compaction starts/fails/completes.",
          "type": "boolean",
          "default": true,
          "category": "Compaction",
          "apply": "live"
        },
        {
          "key": "compaction.remoteEnabled",
          "label": "Remote compaction endpoint",
          "description": "Use a remote HTTP summarization endpoint before falling back to the configured model. Endpoint receives { systemPrompt, prompt, maxTokens } and returns { summary }.",
          "type": "boolean",
          "default": false,
          "category": "Compaction Remote",
          "apply": "live"
        },
        {
          "key": "compaction.remoteEndpoint",
          "label": "Remote endpoint URL",
          "description": "HTTP endpoint for remote compaction summaries. Leave empty to use the configured compaction model.",
          "type": "string",
          "default": "",
          "category": "Compaction Remote",
          "apply": "live"
        },
        {
          "key": "compaction.branchSummaryEnabled",
          "label": "Custom branch summaries",
          "description": "When /tree navigation requests a branch summary, generate it through QOL's custom summarizer.",
          "type": "boolean",
          "default": false,
          "category": "Compaction Branching",
          "apply": "live"
        },
        {
          "key": "compaction.idleEnabled",
          "label": "Idle compaction trigger",
          "description": "Trigger Pi compaction after the session sits idle above the configured token threshold. Never fires while Pi's own compaction.enabled is false. Inspired by oh-my-pi idle compaction.",
          "type": "boolean",
          "default": false,
          "category": "Compaction Idle",
          "apply": "live"
        },
        {
          "key": "compaction.idleThresholdTokens",
          "label": "Idle token threshold",
          "description": "Token count above which QOL idle compaction can trigger when thresholdTokens and thresholdPercent are unset.",
          "type": "number",
          "default": 200000,
          "category": "Compaction Idle",
          "apply": "live"
        },
        {
          "key": "compaction.idleTimeoutSeconds",
          "label": "Idle compaction delay",
          "description": "Seconds to wait idle before triggering QOL idle compaction.",
          "type": "number",
          "default": 300,
          "category": "Compaction Idle",
          "apply": "live"
        },
        {
          "key": "compaction.thresholdTokens",
          "label": "QOL compaction token limit",
          "description": "Optional fixed token limit for QOL idle compaction. Set -1 to use percentage or idle token threshold.",
          "type": "number",
          "default": -1,
          "category": "Compaction Threshold",
          "apply": "live"
        },
        {
          "key": "compaction.thresholdPercent",
          "label": "QOL compaction percent limit",
          "description": "Optional context-window percentage for QOL idle compaction. Set -1 to use idle token threshold.",
          "type": "number",
          "default": -1,
          "category": "Compaction Threshold",
          "apply": "live"
        },
        {
          "key": "compaction.budgetGuardEnabled",
          "label": "Long-session budget guard",
          "description": "Start bounded compaction when an active session crosses the configured context budget, while giving Pi's built-in compaction priority. Designed for long autonomous runs so transcript growth cannot silently hit provider or buffer limits. Fires once per threshold crossing, not in a loop. Never fires while Pi's own compaction.enabled is false.",
          "type": "boolean",
          "default": true,
          "category": "Compaction Budget Guard",
          "apply": "live"
        },
        {
          "key": "compaction.budgetPercent",
          "label": "Budget guard percent",
          "description": "Context-window percentage that triggers the budget guard. Set -1 to disable percent-based triggering and rely on the absolute token limit instead.",
          "type": "number",
          "default": 85,
          "category": "Compaction Budget Guard",
          "apply": "live"
        },
        {
          "key": "compaction.budgetTokens",
          "label": "Budget guard token limit",
          "description": "Absolute token count that triggers the budget guard. Set -1 to use only the percentage threshold.",
          "type": "number",
          "default": -1,
          "category": "Compaction Budget Guard",
          "apply": "live"
        },
        {
          "key": "compaction.maxInputChars",
          "label": "Chunked compaction input cap",
          "description": "Maximum serialized characters sent to the summarizer in a single request. Inputs larger than this are split into chunks, summarized chunk-by-chunk, then merged via a summary-of-summaries pass so the compaction request payload itself cannot blow past provider buffer limits. Set 0 to disable chunking.",
          "type": "number",
          "default": 240000,
          "category": "Compaction Budget Guard",
          "apply": "live"
        },
        {
          "key": "compaction.handoffArtifactEnabled",
          "label": "Write pre-compaction handoff artifact",
          "description": "When enabled, write a JSON handoff artifact under ~/.pi/agent/kendex/sessions/<session>/pi-qol/handoff/ containing the previous summary, last task state, and recently-referenced file/artifact paths. Each write creates a stamped file and a latest.json pointer. Applies to every budget-guard compaction (regardless of compaction.customEnabled), and to manual/idle compactions only when compaction.customEnabled is on. Write failures surface a QOL warning and a handoffArtifactError field in the compaction details.",
          "type": "boolean",
          "default": true,
          "category": "Compaction Budget Guard",
          "apply": "live"
        },
        {
          "key": "compaction.transcriptRiskWarnChars",
          "label": "Transcript-risk warn budget (chars)",
          "description": "Show a transcript-risk warning in /context when the serialized payload of the messages-to-send exceeds this many characters, even if the token count is still below the model context window. Set 0 to disable the warning.",
          "type": "number",
          "default": 600000,
          "category": "Compaction Budget Guard",
          "apply": "live"
        },
        {
          "key": "thinkingLabel.text",
          "label": "Hidden thinking label",
          "description": "Label shown when thinking blocks are hidden. Defaults to a Nerd Font brain icon.",
          "type": "string",
          "default": " ",
          "category": "Thinking",
          "apply": "reload",
          "requiresReload": true
        },
        {
          "key": "thinkingTimer.enabled",
          "label": "Show thinking timer",
          "description": "Show live elapsed time next to collapsed hidden-thinking labels and leave the final duration after thinking ends. Uses a defensive internal renderer patch and fails back to Pi's default label if Pi internals change.",
          "type": "boolean",
          "default": true,
          "category": "Thinking",
          "apply": "live"
        },
        {
          "key": "workingIndicator.mode",
          "label": "Working indicator mode",
          "description": "Control Pi's built-in streaming spinner. The default 'static' shows a single non-animating dot that keeps the working indicator visible without a timer. 'animated' restores Pi's spinner, which ticks every 80ms; in terminals where rendered content overflows the viewport (chat scrolled past the top, /tree open, etc.) each tick can trigger a pi-tui full-screen redraw and visible flash. It appears before the project name when the QOL statusline is enabled, or in Pi's working row otherwise.",
          "type": "enum",
          "enumValues": [
            "animated",
            "static"
          ],
          "default": "static",
          "category": "Thinking",
          "apply": "reload",
          "requiresReload": true
        }
      ]
    }
  },
  "peerDependencies": {
    "@earendil-works/pi-agent-core": "*",
    "@earendil-works/pi-ai": "*",
    "@earendil-works/pi-coding-agent": ">=0.86.0",
    "@earendil-works/pi-tui": "*"
  },
  "files": [
    "extensions/",
    "scripts/",
    "assets/",
    "README.md",
    "package.json"
  ],
  "repository": {
    "type": "git",
    "url": "git+https://github.com/vanillagreencom/kendex.git",
    "directory": "pi-extensions/pi-qol"
  },
  "homepage": "https://github.com/vanillagreencom/kendex/tree/main/pi-extensions/pi-qol",
  "bugs": {
    "url": "https://github.com/vanillagreencom/kendex/issues"
  },
  "author": "vanillagreen",
  "publishConfig": {
    "access": "public"
  },
  "engines": {
    "node": ">=22.19.0"
  },
  "peerDependenciesMeta": {
    "@earendil-works/pi-agent-core": {
      "optional": true
    },
    "@earendil-works/pi-ai": {
      "optional": true
    },
    "@earendil-works/pi-coding-agent": {
      "optional": true
    },
    "@earendil-works/pi-tui": {
      "optional": true
    }
  }
}
