// @generated by scripts/build-runtime.mjs; do not edit. // @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader. import { BUILT_IN_EXAMPLE, MODULE_DEFINITIONS, STARSHIP_SUBCOMMANDS, atomicRestoreConfigDocument, atomicSaveConfigDocument, completeStarshipArguments, inspectUnavailableModules, loadStarshipConfig, removeConfigDocumentIfMatches, validateConfigDocument } from "./chunk-GS2UE7CF.js"; // src/commands.ts import { defineMenu, runMenu } from "@narumitw/pi-tui-kit"; // src/command-configuration.ts import { sanitizeTerminalText } from "@narumitw/pi-tui-kit/terminal-text"; // src/command-preview.ts import { stripVTControlCharacters } from "node:util"; import { Key, matchesKey, truncateToWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui"; import { runCustomInteraction } from "@narumitw/pi-tui-kit"; var RESERVED_HOST_ROWS = 3; async function showPreviewActionMenu(ctx, title, body, items, signal, isCurrent = () => !signal?.aborted) { if (signal?.aborted || !isCurrent()) return { kind: "closed" }; const result = await runCustomInteraction(ctx, { signal, isCurrent, create: ({ tui, theme, keybindings, complete }) => { let selectedIndex = 0; let scrollOffset = 0; let lastMaximumScroll = 0; let lastViewportSize = 1; let disposed = false; const requestRender = () => { if (!disposed) tui.requestRender(); }; const moveSelection = (delta) => { if (items.length === 0) return; selectedIndex = (selectedIndex + delta + items.length) % items.length; requestRender(); }; const movePreview = (offset) => { scrollOffset = Math.max(0, Math.min(offset, lastMaximumScroll)); requestRender(); }; return { render(width) { const safeWidth = Math.max(1, width); const terminalRows = Number.isFinite(tui.terminal.rows) ? Math.floor(tui.terminal.rows) : 24; const availableRows = Math.max(1, terminalRows - RESERVED_HOST_ROWS); const bodyLines = body(safeWidth).flatMap( (line) => line ? wrapTextWithAnsi(line, safeWidth) : [""] ); const layout = allocateLayout(availableRows, items.length, bodyLines.length); lastViewportSize = layout.previewRows; lastMaximumScroll = Math.max(0, bodyLines.length - layout.previewRows); scrollOffset = Math.max(0, Math.min(scrollOffset, lastMaximumScroll)); const actionStart = actionWindowStart(selectedIndex, items.length, layout.actionRows); const actionLines = items.slice(actionStart, actionStart + layout.actionRows).map((item, index) => { const absoluteIndex = actionStart + index; const prefix = absoluteIndex === selectedIndex ? "\u2192 " : " "; return theme.fg( absoluteIndex === selectedIndex ? "accent" : "text", `${prefix}${safeDisplayText(item.label)}` ); }); const position = layout.positionRows ? [theme.fg("dim", previewPosition(scrollOffset, layout.previewRows, bodyLines.length))] : []; const lines = [ ...layout.titleRows ? [theme.fg("accent", theme.bold(safeDisplayText(title)))] : [], ...bodyLines.slice(scrollOffset, scrollOffset + layout.previewRows), ...position, ...actionLines, ...layout.hintRows ? [theme.fg("dim", previewHint(keybindings))] : [] ]; return lines.map((line) => truncateToWidth(line, safeWidth, "")); }, invalidate() { }, handleInput(data) { if (disposed) return; if (matchesKey(data, Key.ctrl("c"))) { complete({ kind: "closed" }); } else if (keybindings.matches(data, "tui.select.cancel")) { complete({ kind: "cancelled" }); } else if (keybindings.matches(data, "tui.select.up")) { moveSelection(-1); } else if (keybindings.matches(data, "tui.select.down")) { moveSelection(1); } else if (keybindings.matches(data, "tui.select.pageUp")) { movePreview(scrollOffset - lastViewportSize); } else if (keybindings.matches(data, "tui.select.pageDown")) { movePreview(scrollOffset + lastViewportSize); } else if (matchesKey(data, Key.home)) { movePreview(0); } else if (matchesKey(data, Key.end)) { movePreview(lastMaximumScroll); } else if (keybindings.matches(data, "tui.select.confirm")) { const item = items[selectedIndex]; if (item) complete({ kind: "selected", value: item.value }); } }, dispose() { if (disposed) return; disposed = true; } }; } }); if (result.kind === "completed") return result.value; if (result.kind === "error") throw result.error; if (result.kind === "stale" && (signal?.aborted || !isCurrent())) return { kind: "closed" }; return void 0; } function allocateLayout(availableRows, actionCount, bodyLineCount) { if (availableRows === 1) { return { titleRows: 0, previewRows: 0, positionRows: 0, actionRows: 1, hintRows: 0 }; } const titleRows = availableRows >= 3 ? 1 : 0; const hintRows = availableRows >= 3 ? 1 : 0; const minimumPreviewRows = bodyLineCount > 0 && availableRows >= 4 ? 1 : 0; const availableActionRows = Math.max( 1, availableRows - titleRows - hintRows - minimumPreviewRows ); const actionRows = Math.min(Math.max(1, actionCount), availableActionRows); let previewRows = Math.max(0, availableRows - titleRows - hintRows - actionRows); const positionRows = previewRows >= 2 && bodyLineCount > previewRows ? 1 : 0; previewRows -= positionRows; return { titleRows, previewRows, positionRows, actionRows, hintRows }; } function actionWindowStart(selectedIndex, itemCount, viewportSize) { if (itemCount <= viewportSize) return 0; return Math.max(0, Math.min(selectedIndex, itemCount - viewportSize)); } function previewPosition(offset, viewportSize, lineCount) { if (lineCount === 0) return "0/0"; return `${offset + 1}-${Math.min(lineCount, offset + viewportSize)}/${lineCount}`; } function previewHint(keybindings) { const up = bindingText(keybindings, "tui.select.up"); const down = bindingText(keybindings, "tui.select.down"); const confirm = bindingText(keybindings, "tui.select.confirm"); const cancel = bindingText(keybindings, "tui.select.cancel", "ctrl+c"); const pageUp = bindingText(keybindings, "tui.select.pageUp"); const pageDown = bindingText(keybindings, "tui.select.pageDown"); return [ ...up || down ? [`${[up, down].filter(Boolean).join("/")} navigate`] : [], ...confirm ? [`${confirm} select`] : [], ...cancel ? [`${cancel} discard`] : [], "ctrl+c close", ...pageUp || pageDown ? [`${[pageUp, pageDown].filter(Boolean).join("/")} preview`] : [] ].join(" \u2022 "); } function bindingText(keybindings, binding, excluded) { return keybindings.getKeys(binding).filter((key) => key !== excluded).map((key) => { if (key === "up") return "\u2191"; if (key === "down") return "\u2193"; if (key === "escape") return "esc"; if (key === "return") return "enter"; return safeDisplayText(key); }).join("/"); } function safeDisplayText(value) { return Array.from(stripVTControlCharacters(value), (character) => { const codePoint = character.codePointAt(0) ?? 0; const control = codePoint <= 31 || codePoint >= 127 && codePoint <= 159; return control ? "" : character; }).join(""); } // src/effective-config.ts import { stringify } from "smol-toml"; function projectEffectiveConfig(config) { const projected = { format: config.format }; if (config.palette !== void 0) projected.palette = config.palette; projected.palettes = Object.fromEntries( sortedEntries(config.palettes).map(([name, colors]) => [name, sortedRecord(colors)]) ); for (const definition of MODULE_DEFINITIONS) { const module = config.modules[definition.name]; const table = { format: module.format, symbol: module.symbol }; if (definition.styleDefaults) { for (const field of Object.keys(definition.styleDefaults)) { table[field] = module.styles[field] ?? ""; } } else if (!definition.displayDefaults) { table.style = module.style; } if (definition.displayDefaults) { table.display = module.display.map((entry) => ({ ...entry })); } table.disabled = module.disabled; for (const key of Object.keys(definition.options ?? {})) { table[key] = cloneOptionValue(module.options[key]); } if (definition.name === "extension_status") { table.separator = config.extensionStatus.separator; table.max_statuses = config.extensionStatus.maxStatuses; table.icons = sortedRecord(config.extensionStatus.icons); } projected[definition.name] = table; } return projected; } function serializeEffectiveConfig(config) { return stringify(projectEffectiveConfig(config)); } function cloneOptionValue(value) { if (value === void 0) throw new Error("Missing normalized module option"); if (Array.isArray(value)) return [...value]; if (typeof value === "object") { return sortedRecord(value); } return value; } function sortedRecord(values) { return Object.fromEntries(sortedEntries(values)); } function sortedEntries(values) { return Object.entries(values).sort( ([left], [right]) => left < right ? -1 : left > right ? 1 : 0 ); } // src/presets/bracketed-segments.ts var BRACKETED_SEGMENTS_PRESET = String.raw`# Font-safe bracketed segments inspired by Starship. format = "$brand$model$thinking$directory$git_branch$git_status$activity$context$time" [brand] format = '[\[$symbol\]]($style) ' style = "bold white" [model] format = '[\[$symbol $model\]]($style) ' symbol = "AI" style = "bold blue" [thinking] format = '[\[$symbol $level\]]($style) ' symbol = "think" style = "bold purple" [directory] format = '[\[$symbol $path\]]($style) ' symbol = "dir" style = "cyan bold" [git_branch] format = '[\[$symbol $branch\]]($style) ' symbol = "git" style = "bold purple" [git_status] format = '[\[$all_status( $ahead_behind)\]]($style) ' style = "red bold" [activity] format = '[\[$text\]]($style) ' symbol = "run" style = "bold yellow" [context] format = '[\[$symbol $percentage\]]($style) ' symbol = "ctx" [time] format = '[\[$time\]]($style)' symbol = "" style = "bold yellow" `; // src/presets/catppuccin-powerline.ts var CATPPUCCIN_POWERLINE_PRESET = `# Pi-native adaptation of Starship's Catppuccin Powerline preset. format = "[\uE0B6](red)$brand$model[\uE0B0](bg:peach fg:red)$directory[\uE0B0](bg:yellow fg:peach)$git_branch$git_status[\uE0B0](fg:yellow bg:green)$thinking$activity[\uE0B0](fg:green bg:sapphire)$context[\uE0B0](fg:sapphire bg:lavender)$time[\uE0B4](fg:lavender)" palette = "catppuccin_mocha" [brand] format = "[ $symbol ]($style)" symbol = "\uE285" style = "fg:crust bg:red bold" [model] format = "[$model ]($style)" symbol = "" style = "fg:crust bg:red bold" [directory] format = "[ $path ]($style)" symbol = "" style = "fg:crust bg:peach bold" truncation_length = 3 truncation_symbol = "\u2026/" [git_branch] format = "[ $symbol $branch ]($style)" symbol = "\uF418" style = "fg:crust bg:yellow bold" [git_status] format = "[$all_status$ahead_behind ]($style)" style = "fg:crust bg:yellow bold" [thinking] format = "[ $symbol $level ]($style)" symbol = "\u{F051F}" style = "fg:crust bg:green bold" [activity] format = "[ $text ]($style)" symbol = "\u{F046E}" style = "fg:crust bg:green bold" [context] format = "[ $symbol $percentage ](fg:crust bg:sapphire bold)" symbol = "\u{F035B}" [[context.display]] threshold = 0 style = "sapphire" hidden = false [time] format = "[ \uF43A $time ]($style)" symbol = "" style = "fg:crust bg:lavender bold" [palettes.catppuccin_mocha] red = "#f38ba8" peach = "#fab387" yellow = "#f9e2af" green = "#a6e3a1" sapphire = "#74c7ec" lavender = "#b4befe" crust = "#11111b" [palettes.catppuccin_frappe] red = "#e78284" peach = "#ef9f76" yellow = "#e5c890" green = "#a6d189" sapphire = "#85c1dc" lavender = "#babbf1" crust = "#232634" [palettes.catppuccin_latte] red = "#d20f39" peach = "#fe640b" yellow = "#df8e1d" green = "#40a02b" sapphire = "#209fb5" lavender = "#7287fd" crust = "#dce0e8" [palettes.catppuccin_macchiato] red = "#ed8796" peach = "#f5a97f" yellow = "#eed49f" green = "#a6da95" sapphire = "#7dc4e4" lavender = "#b7bdf8" crust = "#181926" `; // src/presets/gruvbox-rainbow.ts var GRUVBOX_RAINBOW_PRESET = `# Pi-native adaptation of Starship's Gruvbox Rainbow preset. format = "[\uE0B6](color_orange)$brand$model[\uE0B0](bg:color_yellow fg:color_orange)$directory[\uE0B0](fg:color_yellow bg:color_aqua)$git_branch$git_status[\uE0B0](fg:color_aqua bg:color_blue)$thinking$activity[\uE0B0](fg:color_blue bg:color_bg3)$context[\uE0B0](fg:color_bg3 bg:color_bg1)$time[\uE0B4](fg:color_bg1)" palette = "gruvbox_dark" [palettes.gruvbox_dark] color_fg0 = "#fbf1c7" color_bg1 = "#3c3836" color_bg3 = "#665c54" color_blue = "#458588" color_aqua = "#689d6a" color_green = "#98971a" color_orange = "#d65d0e" color_purple = "#b16286" color_red = "#cc241d" color_yellow = "#d79921" [brand] format = "[ $symbol ]($style)" symbol = "\uE285" style = "fg:color_fg0 bg:color_orange bold" [model] format = "[$model ]($style)" symbol = "" style = "fg:color_fg0 bg:color_orange bold" [directory] format = "[ $path ]($style)" symbol = "" style = "fg:color_fg0 bg:color_yellow bold" truncation_length = 3 truncation_symbol = "\u2026/" [git_branch] format = "[ $symbol $branch ]($style)" symbol = "\uF418" style = "fg:color_fg0 bg:color_aqua bold" [git_status] format = "[$all_status$ahead_behind ]($style)" style = "fg:color_fg0 bg:color_aqua bold" [thinking] format = "[ $symbol $level ]($style)" symbol = "\u{F051F}" style = "fg:color_fg0 bg:color_blue bold" [activity] format = "[ $text ]($style)" symbol = "\u{F046E}" style = "fg:color_fg0 bg:color_blue bold" [context] format = "[ $symbol $percentage ](fg:color_fg0 bg:color_bg3 bold)" symbol = "\u{F035B}" [[context.display]] threshold = 0 style = "color_bg3" hidden = false [time] format = "[ \uF43A $time ]($style)" symbol = "" style = "fg:color_fg0 bg:color_bg1 bold" `; // src/presets/jetpack.ts var JETPACK_PRESET = `# Pi-native adaptation of Starship's Jetpack preset. # Left-side session activity and right-side workspace context meet at $fill. format = "$activity$context$fill$directory$git_branch$git_state$git_status$time" [activity] format = "[\u25C4 $text ]($style)" symbol = "" style = "italic white" [context] format = "[\u25EF $percentage]($style) " symbol = "" [[context.display]] threshold = 0 style = "bold purple" hidden = false [fill] symbol = " " style = "none" [directory] format = "[$path]($style) " symbol = "" style = "italic blue" truncation_length = 2 truncation_symbol = "\u25A1 " home_symbol = "\u2302" [git_branch] format = "[$symbol$branch]($style)" symbol = "\u25B3 " style = "italic bright-blue" truncation_length = 11 truncation_symbol = "\u22EF" [git_state] format = "([\u23AA$state $progress_current/$progress_total\u23A5]($style))" symbol = "" style = "italic bright-purple" [git_status] format = "([\u23AA$all_status$ahead_behind\u23A5]($style))" style = "bold italic bright-blue" [time] format = "[ $time]($style)" symbol = "" style = "italic dimmed white" `; // src/presets/minimal.ts var MINIMAL_PRESET = `# A compact, font-safe Pi footer. format = "$model$directory$git_branch$activity" [model] format = "[$model]($style) " symbol = "" style = "bold blue" [directory] format = "[$path]($style) " symbol = "" style = "cyan bold" [git_branch] format = "[git:$branch]($style) " symbol = "" style = "bold purple" [activity] format = "[$text]($style)" symbol = "*" style = "bold yellow" `; // src/presets/nerd-font-symbols.ts var NERD_FONT_SYMBOLS_PRESET = `# Balanced Pi footer with Nerd Font symbols. format = "$brand$model$thinking$directory$git_branch$git_status$activity$context$time" [brand] symbol = "\uE285" [model] symbol = "\u{F06A9}" [thinking] symbol = "\u{F051F}" [directory] symbol = "\u{F024B}" [git_branch] symbol = "\uF418" [activity] symbol = "\u{F046E}" [context] symbol = "\u{F035B}" [time] symbol = "\uF43A" `; // src/presets/no-empty-icons.ts var NO_EMPTY_ICONS_PRESET = `# Pi-native adaptation of Starship's No Empty Icons preset. # Each label lives inside the same conditional group as its value. format = "$model$thinking$directory$git_branch$git_status$activity$context$time" [model] format = "([model $model]($style) )" symbol = "" style = "bold blue" [thinking] format = "([thinking $level]($style) )" symbol = "" style = "bold purple" [directory] format = "([in $path]($style) )" symbol = "" style = "cyan bold" [git_branch] format = "([on $branch]($style) )" symbol = "" style = "bold purple" [git_status] format = "([$all_status$ahead_behind]($style) )" style = "red bold" [activity] format = "([while $text]($style) )" symbol = "" style = "bold yellow" [context] format = "([using $percentage context]($style) )" symbol = "" [[context.display]] threshold = 0 style = "bold green" hidden = false [time] format = "([at $time]($style))" symbol = "" style = "bold yellow" `; // src/presets/no-nerd-font.ts var NO_NERD_FONT_PRESET = `# Pi-native adaptation of Starship's No Nerd Font preset. format = "$brand$model$thinking$directory$git_branch$git_status$activity$context$time" [brand] format = "[$symbol]($style) " symbol = "\u2726" style = "bold white" [model] format = "[$symbol $model]($style) " symbol = "\u25C6" style = "bold blue" [thinking] format = "[$symbol $level]($style) " symbol = "\u25C7" style = "bold purple" [directory] format = "[$symbol $path]($style) " symbol = "\u2302" style = "cyan bold" [git_branch] format = "[$symbol $branch]($style) " symbol = "\u2387" style = "bold purple" [git_status] format = "[$all_status( $ahead_behind)]($style) " style = "red bold" [activity] format = "[$text]($style) " symbol = "\u25CF" style = "bold yellow" [context] format = "[$symbol $percentage]($style) " symbol = "\u25CC" [[context.display]] threshold = 0 style = "bold green" hidden = false [time] format = "[$symbol $time]($style)" symbol = "\u25F4" style = "bold yellow" `; // src/presets/no-runtime-versions.ts var NO_RUNTIME_VERSIONS_PRESET = `# Pi-native adaptation of Starship's No Runtime Versions preset. # Model and thinking modules retain presence symbols while hiding version-like details. format = "$brand$model$thinking$directory$git_branch$git_status$activity$context$time" [brand] format = "[$symbol]($style) " symbol = "\u03C0" style = "bold white" [model] format = "[$symbol]($style) " symbol = "AI" style = "bold blue" [thinking] format = "[$symbol]($style) " symbol = "think" style = "bold purple" [directory] format = "[$path]($style) " symbol = "" style = "cyan bold" [git_branch] format = "[$symbol$branch]($style) " symbol = "git:" style = "bold purple" [git_status] format = "[$all_status( $ahead_behind)]($style) " style = "red bold" [activity] format = "[$symbol]($style) " symbol = "run" style = "bold yellow" [context] format = "[$percentage]($style) " symbol = "" [[context.display]] threshold = 0 style = "bold green" hidden = false [time] format = "[$time]($style)" symbol = "" style = "bold yellow" `; // src/presets/pastel-powerline.ts var PASTEL_POWERLINE_PRESET = `# Pi-native adaptation of Starship's Pastel Powerline preset. format = "[\uE0B6](#9A348E)$brand$model[\uE0B0](bg:#DA627D fg:#9A348E)$directory[\uE0B0](fg:#DA627D bg:#FCA17D)$git_branch$git_status[\uE0B0](fg:#FCA17D bg:#86BBD8)$thinking$activity[\uE0B0](fg:#86BBD8 bg:#06969A)$context[\uE0B0](fg:#06969A bg:#33658A)$time[\uE0B4](fg:#33658A)" [brand] format = "[ $symbol ]($style)" symbol = "\uE285" style = "fg:#ffffff bg:#9A348E bold" [model] format = "[$model ]($style)" symbol = "" style = "fg:#ffffff bg:#9A348E bold" [directory] format = "[ $path ]($style)" symbol = "" style = "fg:#ffffff bg:#DA627D bold" truncation_length = 3 truncation_symbol = "\u2026/" [git_branch] format = "[ $symbol $branch ]($style)" symbol = "\uF418" style = "fg:#1f1f1f bg:#FCA17D bold" [git_status] format = "[$all_status$ahead_behind ]($style)" style = "fg:#1f1f1f bg:#FCA17D bold" [thinking] format = "[ $symbol $level ]($style)" symbol = "\u{F051F}" style = "fg:#1f1f1f bg:#86BBD8 bold" [activity] format = "[ $text ]($style)" symbol = "\u{F046E}" style = "fg:#1f1f1f bg:#86BBD8 bold" [context] format = "[ $symbol $percentage ](fg:#ffffff bg:#06969A bold)" symbol = "\u{F035B}" [[context.display]] threshold = 0 style = "#06969A" hidden = false [time] format = "[ \u2665 $time ]($style)" symbol = "" style = "fg:#ffffff bg:#33658A bold" `; // src/presets/plain-text-symbols.ts var PLAIN_TEXT_SYMBOLS_PRESET = `# Pi-native adaptation of Starship's Plain Text Symbols preset. format = "$brand$model$thinking$directory$git_branch$git_status$activity$context$time" [brand] format = "[$symbol]($style) " symbol = "pi" style = "bold white" [model] format = "[$symbol $model]($style) " symbol = "model" style = "bold blue" [thinking] format = "[$symbol $level]($style) " symbol = "thinking" style = "bold purple" [directory] format = "[$symbol $path]($style) " symbol = "dir" style = "cyan bold" [git_branch] format = "[$symbol $branch]($style) " symbol = "git" style = "bold purple" truncation_symbol = "..." [git_status] format = "[$all_status( $ahead_behind)]($style) " style = "red bold" [activity] format = "[$text]($style) " symbol = "active" style = "bold yellow" [context] format = "[$symbol $percentage]($style) " symbol = "context" [[context.display]] threshold = 0 style = "bold green" hidden = false [time] format = "[$symbol $time]($style)" symbol = "time" style = "bold yellow" `; // src/presets/pure-preset.ts var PURE_PRESET = `# Pi-native adaptation of Starship's Pure preset. format = "$directory$git_branch$git_state$git_status$activity\\n$model$thinking$context$fill$time" [directory] format = "[$path]($style) " symbol = "" style = "blue" [git_branch] format = "[$branch]($style) " symbol = "" style = "bright-black" [git_state] format = '([($state( $progress_current/$progress_total))]($style) )' symbol = "" style = "bright-black" [git_status] format = "[(*$all_status)](218) [$ahead_behind]($style) " style = "cyan" [activity] format = "[$text]($style) " symbol = "" style = "yellow" [model] format = "[$model]($style) " symbol = "" style = "bright-black" [thinking] format = "[$level]($style) " symbol = "" style = "bright-black" [context] format = "[$percentage]($style)" symbol = "" [[context.display]] threshold = 0 style = "bright-black" hidden = false [fill] symbol = " " style = "none" [time] format = "[$time]($style)" symbol = "" style = "bright-black" `; // src/presets/tokyo-night.ts var TOKYO_NIGHT_PRESET = `# Pi-native adaptation of Starship's Tokyo Night preset. format = "[\u2591\u2592\u2593](#a3aed2)$brand$model[\uE0B4](bg:#769ff0 fg:#a3aed2)$directory[\uE0B4](fg:#769ff0 bg:#394260)$git_branch$git_status[\uE0B4](fg:#394260 bg:#212736)$thinking$activity[\uE0B4](fg:#212736 bg:#1d2230)$context$time[\uE0B4](fg:#1d2230)" [brand] format = "[ $symbol ]($style)" symbol = "\uE285" style = "fg:#090c0c bg:#a3aed2 bold" [model] format = "[$model ]($style)" symbol = "" style = "fg:#090c0c bg:#a3aed2 bold" [directory] format = "[ $path ]($style)" symbol = "" style = "fg:#e3e5e5 bg:#769ff0 bold" truncation_length = 3 truncation_symbol = "\u2026/" [git_branch] format = "[ $symbol $branch ]($style)" symbol = "\uF418" style = "fg:#769ff0 bg:#394260 bold" [git_status] format = "[$all_status$ahead_behind ]($style)" style = "fg:#769ff0 bg:#394260 bold" [thinking] format = "[ $symbol $level ]($style)" symbol = "\u{F051F}" style = "fg:#769ff0 bg:#212736 bold" [activity] format = "[ $text ]($style)" symbol = "\u{F046E}" style = "fg:#769ff0 bg:#212736 bold" [context] format = "[ $symbol $percentage ](fg:#a0a9cb bg:#1d2230 bold)" symbol = "\u{F035B}" [[context.display]] threshold = 0 style = "#1d2230" hidden = false [time] format = "[\uF43A $time ]($style)" symbol = "" style = "fg:#a0a9cb bg:#1d2230 bold" `; // src/presets/catalog.ts var STARSHIP_PRESETS = [ { id: "minimal", label: "Minimal", description: "Compact Pi essentials \xB7 font-safe", requiresNerdFont: false, rawDocument: MINIMAL_PRESET }, { id: "bracketed-segments", label: "Bracketed Segments", description: "Balanced Pi and Git details in brackets \xB7 font-safe", requiresNerdFont: false, rawDocument: BRACKETED_SEGMENTS_PRESET }, { id: "catppuccin-powerline", label: "Catppuccin Powerline", description: "Mocha connected color blocks \xB7 requires Nerd Font", requiresNerdFont: true, rawDocument: CATPPUCCIN_POWERLINE_PRESET }, { id: "gruvbox-rainbow", label: "Gruvbox Rainbow", description: "Warm Gruvbox connected segments \xB7 requires Nerd Font", requiresNerdFont: true, rawDocument: GRUVBOX_RAINBOW_PRESET }, { id: "jetpack", label: "Jetpack", description: "Airy geometric left/right layout \xB7 font-safe", requiresNerdFont: false, rawDocument: JETPACK_PRESET }, { id: "nerd-font-symbols", label: "Nerd Font Symbols", description: "Balanced default layout with icon-rich symbols \xB7 requires Nerd Font", requiresNerdFont: true, rawDocument: NERD_FONT_SYMBOLS_PRESET }, { id: "no-empty-icons", label: "No Empty Icons", description: "Conditional labels never appear without values \xB7 font-safe", requiresNerdFont: false, rawDocument: NO_EMPTY_ICONS_PRESET }, { id: "no-nerd-font", label: "No Nerd Font", description: "Portable Unicode symbols without private-use glyphs \xB7 font-safe", requiresNerdFont: false, rawDocument: NO_NERD_FONT_PRESET }, { id: "no-runtime-versions", label: "No Runtime Versions", description: "Presence indicators without model or thinking details \xB7 font-safe", requiresNerdFont: false, rawDocument: NO_RUNTIME_VERSIONS_PRESET }, { id: "pastel-powerline", label: "Pastel Powerline", description: "Pastel connected color blocks \xB7 requires Nerd Font", requiresNerdFont: true, rawDocument: PASTEL_POWERLINE_PRESET }, { id: "plain-text-symbols", label: "Plain Text Symbols", description: "Plain words replace pictograms \xB7 font-safe", requiresNerdFont: false, rawDocument: PLAIN_TEXT_SYMBOLS_PRESET }, { id: "pure-preset", label: "Pure Preset", description: "Clean two-line workspace and session context \xB7 font-safe", requiresNerdFont: false, rawDocument: PURE_PRESET }, { id: "tokyo-night", label: "Tokyo Night", description: "Cool connected color blocks \xB7 requires Nerd Font", requiresNerdFont: true, rawDocument: TOKYO_NIGHT_PRESET } ]; function getStarshipPreset(id) { const preset = STARSHIP_PRESETS.find((candidate) => candidate.id === id); if (!preset) throw new Error(`Unknown pi-starship preset: ${id}`); return preset; } function presetForDocument(rawDocument) { return STARSHIP_PRESETS.find((preset) => preset.rawDocument === rawDocument); } // src/command-configuration.ts var RELOAD_ACTIONS = { apply: "apply", cancel: "cancel" }; function configurationPresentation(loaded) { const healthyMissing = isHealthyMissing(loaded); const savedBuiltIn = loaded.source === "user" && loaded.rawDocument === BUILT_IN_EXAMPLE; const activePreset = presetForDocument(loaded.rawDocument); const fallback = loaded.source === "built-in" && loaded.diagnostics.length > 0; return { state: healthyMissing ? "Built-in defaults" : savedBuiltIn ? "Saved built-in configuration" : activePreset ? `${activePreset.label} preset` : fallback ? "Built-in fallback" : "Custom configuration", source: healthyMissing ? "No settings file" : activePreset ? "Bundled preset" : loaded.source === "user" ? "User file" : "Built-in fallback", health: configurationHealth(loaded), restoreDisabled: healthyMissing || savedBuiltIn, restoreDescription: healthyMissing ? "Already using defaults \xB7 no file to replace" : savedBuiltIn ? "Built-in configuration already saved" : fallback ? "Preview before replacing invalid settings" : "Preview before replacing the document" }; } function configurationMenuScreen(loaded) { const presentation = configurationPresentation(loaded); return { kind: "actions", title: "Configuration", lines: [`${presentation.state} \xB7 ${presentation.health}`], items: [ { id: "configuration-overview", label: "Overview", description: "State, source, path, health, and warnings", to: "configuration-overview" }, { id: "configuration-effective", label: "Effective configuration", description: "Normalized public TOML currently in use", to: "configuration-effective" }, { id: "configuration-document", label: "Settings document", description: isHealthyMissing(loaded) ? "No settings file \xB7 built-in defaults are active" : "Exact loaded UTF-8 text \xB7 read-only", to: "configuration-document" }, { id: "configuration-reload", label: "Reload from disk\u2026", description: "Validate and preview external changes", action: "configuration-reload" } ], hint: "back" }; } function configurationOverviewScreen(loaded, settingsPath) { const presentation = configurationPresentation(loaded); return { kind: "detail", title: "Configuration overview", lines: [ `State: ${presentation.state}`, `Source: ${presentation.source}`, `Path: ${sanitizeTerminalText(settingsPath)}`, ...diagnosticLines(loaded, true) ], hint: "back" }; } function effectiveConfigurationScreen(loaded) { const presentation = configurationPresentation(loaded); return { kind: "review", title: "Effective configuration", lines: [ `${presentation.state} \xB7 ${presentation.health}`, "Normalized public TOML; comments and unknown fields are intentionally excluded." ], content: serializeEffectiveConfig(loaded.config), format: { kind: "code", language: "toml" }, viewportSize: "adaptive", hint: "back" }; } function settingsDocumentScreen(loaded, settingsPath) { const path = sanitizeTerminalText(settingsPath); if (loaded.rawDocument !== void 0) { return { kind: "review", title: "Settings document", lines: ["Exact loaded UTF-8 text \xB7 display controls sanitized \xB7 read-only", `Path: ${path}`], content: loaded.rawDocument, format: { kind: "code", language: "toml" }, viewportSize: "adaptive", hint: "back" }; } return { kind: "detail", title: "Settings document", lines: [ ...isHealthyMissing(loaded) ? [ "No settings document exists.", "Built-in defaults are active; this read created no file." ] : ["The settings document could not be loaded.", ...diagnosticLines(loaded, false)], `Path: ${path}` ], hint: "back" }; } async function reloadConfiguration(ctx, options, owner) { if (!isCurrentOwner(owner)) return "stay"; const previous = options.getLoaded(); const revision = options.getLoadedRevision?.(); const candidate = readReloadCandidate(options); if (!candidate.ok) { ctx.ui.notify(`Footer reload was blocked: ${safeError(candidate.error)}`, "error"); return "stay"; } if (sameLoadedDocument(previous, candidate.loaded)) { ctx.ui.notify("The current disk configuration is already loaded.", "info"); return "stay"; } const selection = await showPreviewActionMenu( ctx, "Reload preview", (width) => reloadPreviewBody(ctx, options, candidate.loaded, width), [ { value: RELOAD_ACTIONS.apply, label: "Apply reloaded configuration\u2026" }, { value: RELOAD_ACTIONS.cancel, label: "Cancel" } ], owner.signal, owner.isCurrent ); if (!isCurrentOwner(owner) || activeRevisionChanged(options, revision)) return "stay"; if (selection?.kind === "closed") return "close"; if (selectedReloadAction(selection) !== RELOAD_ACTIONS.apply) return "stay"; const confirmed = await ctx.ui.confirm( "Apply configuration from disk?", candidate.loaded.rawDocument === void 0 ? "Use built-in defaults for this session? No settings file will be created." : "Apply the validated settings document to this session without changing its bytes?" ); if (!isCurrentOwner(owner) || activeRevisionChanged(options, revision) || !confirmed) { return "stay"; } const fresh = readReloadCandidate(options); if (!fresh.ok) { ctx.ui.notify( `Footer reload was blocked after confirmation: ${safeError(fresh.error)}. The previous footer remains active.`, "error" ); return "stay"; } if (!sameLoadedDocument(candidate.loaded, fresh.loaded)) { ctx.ui.notify( "The settings document changed after preview. The previous footer remains active; reload again to review the latest version.", "warning" ); return "stay"; } try { options.apply(fresh.loaded, ctx); } catch (error) { let rollbackError; try { options.apply(previous, ctx); } catch (rollback) { rollbackError = rollback; } ctx.ui.notify( rollbackError ? `Footer reload failed: ${safeError(error)}. Restoring the previous configuration also failed: ${safeError(rollbackError)}.` : `Footer reload failed: ${safeError(error)}. The previous configuration was restored.`, "error" ); return "stay"; } const warnings = fresh.loaded.diagnostics.length; ctx.ui.notify( warnings === 0 ? "Footer configuration reloaded and applied." : `Footer configuration reloaded and applied with ${warnings} warning${warnings === 1 ? "" : "s"}.`, "info" ); return "applied"; } function readReloadCandidate(options) { let loaded; try { loaded = (options.read ?? loadStarshipConfig)(options.settingsPath); } catch (error) { return { ok: false, error: formatError(error) }; } const errors = loaded.diagnostics.filter((item) => item.severity === "error"); if (errors.length > 0) { return { ok: false, error: errors.map((item) => item.message).join("; ") }; } return { ok: true, loaded }; } function reloadPreviewBody(ctx, options, loaded, width) { const presentation = configurationPresentation(loaded); let preview; try { preview = options.renderPreview?.(loaded, width, ctx) ?? [ "Live preview is unavailable until the footer is ready." ]; } catch (error) { preview = [`Preview unavailable: ${safeError(formatError(error))}`]; } return [ `Candidate: ${presentation.state}`, `Source: ${presentation.source}`, `Path: ${sanitizeTerminalText(options.settingsPath)}`, loaded.rawDocument === void 0 ? "Applying uses built-in defaults and creates no settings file." : "Applying changes only the active session; the settings document bytes stay unchanged.", ...diagnosticLines(loaded, true), "", ...preview ]; } function diagnosticLines(loaded, includeSummary) { const diagnostics = loaded.diagnostics.slice(0, 8).map( (item) => `${sanitizeTerminalText(item.path || "root")}: ${sanitizeTerminalText(item.message)}` ); const remaining = loaded.diagnostics.length - diagnostics.length; return [ ...includeSummary ? [`Health: ${configurationHealth(loaded)}`] : [], ...diagnostics.length > 0 ? diagnostics : ["No configuration warnings."], ...remaining > 0 ? [`${remaining} additional warnings not shown.`] : [] ]; } function configurationHealth(loaded) { const errors = loaded.diagnostics.filter((item) => item.severity === "error").length; if (errors > 0) return `${errors} error${errors === 1 ? "" : "s"}`; const warnings = loaded.diagnostics.length; return warnings === 0 ? "Healthy" : `${warnings} warning${warnings === 1 ? "" : "s"}`; } function isHealthyMissing(loaded) { return loaded.source === "built-in" && loaded.rawDocument === void 0 && loaded.diagnostics.length === 0; } function sameLoadedDocument(left, right) { return left.source === right.source && left.rawDocument === right.rawDocument && JSON.stringify(left.diagnostics) === JSON.stringify(right.diagnostics); } function activeRevisionChanged(options, revision) { return revision !== void 0 && options.getLoadedRevision?.() !== revision; } function isCurrentOwner(owner) { return !owner.signal.aborted && owner.isCurrent(); } function selectedReloadAction(result) { return result?.kind === "selected" ? result.value : null; } function safeError(value) { return sanitizeTerminalText(typeof value === "string" ? value : formatError(value)); } function formatError(error) { return error instanceof Error ? error.message : String(error); } // src/command-inspector.ts import { stripVTControlCharacters as stripVTControlCharacters2 } from "node:util"; function formatFooterExplanation(inspection) { if (!inspection) { return safeLines([ "Footer inspection is unavailable until the TUI footer is ready.", "No collection work was started." ]); } if (inspection.showing.length === 0) { return safeLines([ "No modules are currently showing.", "Open Modules to inspect empty, disabled, or unreachable modules." ]); } return safeLines( inspection.showing.flatMap((module, index) => [ ...index > 0 ? [""] : [], module.name, ...previewLines(module.preview), module.description ]) ); } function previewLines(preview) { const lines = preview ? preview.split("\n") : ["(no text)"]; return lines.map((line, index) => `${index === 0 ? "Value: " : " "}${line}`); } function safeLines(lines) { return lines.map(safeDisplayText2).join("\n"); } function safeDisplayText2(value) { return Array.from(stripVTControlCharacters2(value), (character) => { const codePoint = character.codePointAt(0) ?? 0; return codePoint <= 31 || codePoint >= 127 && codePoint <= 159 ? "" : character; }).join(""); } // src/command-preset-picker.ts import { runLiveChoice } from "@narumitw/pi-tui-kit"; async function showPresetPicker(ctx, options) { const current = options.presets.find((preset) => preset.id === options.activePresetId)?.label ?? "Custom configuration"; const result = await runLiveChoice(ctx, { title: `Presets \xB7 current: ${current}`, items: options.presets.map((preset) => ({ id: preset.id, label: preset.label, description: preset.id === options.activePresetId ? `Currently applied \xB7 ${preset.description}` : preset.description, details: [ `Selected: ${preset.label} \xB7 ${preset.requiresNerdFont ? "requires Nerd Font" : "font-safe"}` ], confirmationDisabled: preset.id === options.activePresetId, confirmationDisabledReason: "Already applied; press e to customize" })), currentItemId: options.activePresetId, initialItemId: options.initialPresetId ?? options.activePresetId, viewportSize: Math.min(options.presets.length, 10), hint: "back", navigationLabel: "live preview", confirmLabel: "apply", shortcuts: [{ id: "customize", keys: ["e", "shift+e"], label: "customize" }], onSelectionChange: ({ item }) => { const preset = options.presets.find((candidate) => candidate.id === item.id); if (preset) options.preview(preset); }, signal: options.signal, isCurrent: options.isCurrent }); if (result.kind === "selected") return { kind: "apply", presetId: result.itemId }; if (result.kind === "shortcut") { return { kind: "customize", presetId: result.itemId }; } if (result.kind === "closed" && result.reason === "back") return { kind: "back" }; return { kind: "close" }; } // src/commands.ts var MAIN_ACTIONS = { customize: "customize", presets: "presets", explain: "explain", modules: "modules", configuration: "configuration", help: "help", restore: "restore" }; var PREVIEW_ACTIONS = { continue: "continue", edit: "edit", cancel: "cancel" }; function registerStarshipCommand(pi, options) { pi.registerCommand("starship", { description: "Customize or inspect the native Starship-style footer", getArgumentCompletions: completeStarshipArguments, handler: (args, ctx) => handleStarshipCommand(args, ctx, options) }); } async function handleStarshipCommand(args, ctx, options) { const normalized = args.trim(); if (!normalized) { if (ctx.mode === "tui") await showMainMenu(ctx, options); else showHelp(ctx, options.settingsPath); return; } const [subcommand = "", ...trailing] = normalized.split(/\s+/u); const route = subcommand.toLowerCase(); if (trailing.length > 0 || !STARSHIP_SUBCOMMANDS.some((item) => item.value === route)) { if (canNotify(ctx)) { const reason = trailing.length > 0 ? `Unexpected arguments for /starship ${safeText(route)}.` : `Unknown /starship subcommand: ${safeText(route)}.`; ctx.ui.notify(`${reason} Usage: /starship [settings|status|help]`, "warning"); } return; } switch (route) { case "settings": await editSettings(ctx, options); return; case "status": showStatus(ctx, options); return; case "help": showHelp(ctx, options.settingsPath); return; } } async function showMainMenu(ctx, options) { const fallbackController = new AbortController(); const owner = options.getMenuOwner?.() ?? { signal: fallbackController.signal, isCurrent: () => !fallbackController.signal.aborted }; const menu = defineMenu({ start: "main", screens: { main: () => { const loaded = options.getLoaded(); const presentation = configurationPresentation(loaded); return { kind: "actions", title: "pi-starship", lines: [`${presentation.state} \xB7 ${presentation.health}`], items: [ { id: MAIN_ACTIONS.customize, label: "Customize footer", description: `${presentation.state} \xB7 preview before applying`, action: "customize" }, { id: MAIN_ACTIONS.presets, label: "Presets", description: "Browse and live-preview bundled footer starting points", action: "presets" }, { id: MAIN_ACTIONS.explain, label: "Explain footer", description: "Why each visible module appears", to: "explain" }, { id: MAIN_ACTIONS.modules, label: "Modules", description: "Browse supported modules and current states", to: "modules" }, { id: MAIN_ACTIONS.configuration, label: "Configuration", description: presentation.health, to: "configuration" }, { id: MAIN_ACTIONS.help, label: "Help", description: "Formats, modules, and commands", to: "help" }, { id: MAIN_ACTIONS.restore, label: "Restore built-in\u2026", description: presentation.restoreDescription, disabled: presentation.restoreDisabled, action: "restore" } ], hint: "close" }; }, explain: () => ({ kind: "review", title: "Explain footer", content: formatFooterExplanation(options.getInspection?.()), format: { kind: "text" }, viewportSize: "adaptive", hint: "back" }), modules: () => { const inspection = options.getInspection?.() ?? inspectUnavailableModules(options.getLoaded().config); return { kind: "browse", title: "Modules", items: inspection.modules.map((module) => ({ id: module.name, label: module.name, statusText: module.state, description: module.description, searchText: [...module.variables, ...module.styleFields, ...module.displayRules].join( " " ), details: [ `Root: ${module.rootReferenced ? "Referenced" : "Not referenced"}`, `Reachable: ${module.reachable ? "Yes" : "No"}`, ...modulePreviewDetails(module.preview), `Reason: ${module.reason}`, `Variables: ${module.variables.join(", ") || "none"}`, `Style fields: ${module.styleFields.join(", ") || "none"}`, `Display rules: ${module.displayRules.join(" \xB7 ") || "none"}` ] })), viewportSize: "adaptive", hint: "back" }; }, configuration: () => configurationMenuScreen(options.getLoaded()), "configuration-overview": () => configurationOverviewScreen(options.getLoaded(), options.settingsPath), "configuration-effective": () => effectiveConfigurationScreen(options.getLoaded()), "configuration-document": () => settingsDocumentScreen(options.getLoaded(), options.settingsPath), help: () => ({ kind: "detail", title: "pi-starship help", lines: [ "Customize footer opens the TOML editor, then previews and confirms before saving.", "Presets live-previews the cursor in the footer; Enter confirms apply and e customizes first.", "Explain footer breaks down the modules currently showing from the existing snapshot.", "Modules searches every supported module and explains its current read-only state.", "Configuration separates overview, effective TOML, loaded settings text, and safe disk reload.", `Settings: ${safeText(options.settingsPath)}`, "Docs: https://github.com/narumiruna/pi-extensions/tree/main/packages/pi-starship" ], hint: "back" }) }, actions: { customize: async () => { const result = await editSettings(ctx, options); return result === "applied" || result === "close" ? { kind: "close" } : { kind: "stay" }; }, presets: async () => { const result = await choosePreset(ctx, options, owner); return result === "applied" || result === "close" ? { kind: "close" } : { kind: "stay" }; }, "configuration-reload": async () => { const result = await reloadConfiguration(ctx, options, owner); return result === "close" ? { kind: "close" } : { kind: "stay" }; }, restore: async () => { const presentation = configurationPresentation(options.getLoaded()); if (presentation.restoreDisabled) { ctx.ui.notify(presentation.restoreDescription, "info"); return { kind: "stay" }; } const result = await restoreBuiltIn(ctx, options); return result === "applied" || result === "close" ? { kind: "close" } : { kind: "stay" }; } } }); try { await runMenu(ctx, menu, { getState: () => void 0, signal: owner.signal, isCurrent: owner.isCurrent }); } finally { fallbackController.abort(new DOMException("Starship menu closed", "AbortError")); } } async function editSettings(ctx, options) { const owner = workflowOwner(options); if (!isCurrentOwner2(owner)) return "cancel"; if (ctx.mode !== "tui") { if (ctx.hasUI) ctx.ui.notify(`Edit settings manually: ${options.settingsPath}`, "info"); return "cancel"; } let draft = options.getLoaded().rawDocument ?? BUILT_IN_EXAMPLE; while (true) { const edited = await ctx.ui.editor("Customize footer \u2014 close to preview", draft); if (!isCurrentOwner2(owner) || edited === void 0) return "cancel"; draft = edited; let validated; try { validated = (options.validate ?? validateConfigDocument)(options.settingsPath, draft); } catch (error) { ctx.ui.notify(`Footer draft is invalid: ${safeText(formatError2(error))}`, "error"); const action = await showPreviewActionMenu( ctx, "Configuration needs attention", () => [safeText(formatError2(error)), "The current footer has not changed."], [ { value: PREVIEW_ACTIONS.edit, label: "Continue editing" }, { value: PREVIEW_ACTIONS.cancel, label: "Discard draft" } ], owner.signal, owner.isCurrent ); if (!isCurrentOwner2(owner)) return "cancel"; if (action?.kind === "closed") return "close"; if (selectedPreviewAction(action) === PREVIEW_ACTIONS.edit) continue; return "cancel"; } const result = await reviewAndApply(ctx, options, validated, { kind: "customize" }, owner); if (result === "edit") continue; return result; } } async function choosePreset(ctx, options, owner) { const activePreset = presetForDocument(options.getLoaded().rawDocument); const selection = await showPresetPicker(ctx, { presets: STARSHIP_PRESETS, activePresetId: activePreset?.id, initialPresetId: activePreset?.id ?? STARSHIP_PRESETS[0]?.id, signal: owner.signal, isCurrent: owner.isCurrent, preview(preset2) { const loaded = (options.validate ?? validateConfigDocument)( options.settingsPath, preset2.rawDocument ); options.preview?.(loaded, ctx); } }); if (!isCurrentOwner2(owner)) { options.preview?.(void 0, ctx); return "cancel"; } if (selection.kind === "back" || selection.kind === "close") { options.preview?.(void 0, ctx); return selection.kind === "close" ? "close" : "cancel"; } const preset = getStarshipPreset(selection.presetId); try { return await applyPreset( ctx, options, preset, selection.kind === "customize" ? "customize" : "confirm" ); } finally { options.preview?.(void 0, ctx); } } async function applyPreset(ctx, options, preset, start = "review") { const owner = workflowOwner(options); if (!isCurrentOwner2(owner)) return "cancel"; let draft = preset.rawDocument; if (start === "customize") { const edited = await ctx.ui.editor(`Customize ${preset.label} preset`, draft); if (!isCurrentOwner2(owner) || edited === void 0) return "cancel"; draft = edited; } while (true) { let validated; try { validated = (options.validate ?? validateConfigDocument)(options.settingsPath, draft); } catch (error) { ctx.ui.notify(`Preset draft is invalid: ${safeText(formatError2(error))}`, "error"); const action = await showPreviewActionMenu( ctx, "Preset needs attention", () => [safeText(formatError2(error)), "The current footer has not changed."], [ { value: PREVIEW_ACTIONS.edit, label: "Continue editing" }, { value: PREVIEW_ACTIONS.cancel, label: "Choose another preset" } ], owner.signal, owner.isCurrent ); if (!isCurrentOwner2(owner)) return "cancel"; if (action?.kind === "closed") return "close"; if (selectedPreviewAction(action) !== PREVIEW_ACTIONS.edit) return "cancel"; const edited2 = await ctx.ui.editor(`Customize ${preset.label} preset`, draft); if (!isCurrentOwner2(owner) || edited2 === void 0) return "cancel"; draft = edited2; continue; } const result = await reviewAndApply( ctx, options, validated, { kind: "preset", preset }, owner, start === "confirm" ); if (result !== "edit") return result; const edited = await ctx.ui.editor(`Customize ${preset.label} preset`, draft); if (!isCurrentOwner2(owner) || edited === void 0) return "cancel"; draft = edited; } } async function restoreBuiltIn(ctx, options) { const owner = workflowOwner(options); if (!isCurrentOwner2(owner)) return "cancel"; const validated = (options.validate ?? validateConfigDocument)( options.settingsPath, BUILT_IN_EXAMPLE ); const result = await reviewAndApply(ctx, options, validated, { kind: "restore" }, owner); return result === "edit" ? "cancel" : result; } async function reviewAndApply(ctx, options, validated, intent, owner, skipReview = false) { let reviewRequired = !skipReview; while (true) { const directAttempt = !reviewRequired; if (reviewRequired) { const selection = await showPreviewActionMenu( ctx, reviewTitle(intent), (width) => reviewPreviewBody(ctx, options, validated, width, intent), [ { value: PREVIEW_ACTIONS.continue, label: continueLabel(intent) }, ...intent.kind === "restore" ? [] : [ { value: PREVIEW_ACTIONS.edit, label: intent.kind === "preset" ? "Customize before applying" : "Continue editing" } ], { value: PREVIEW_ACTIONS.cancel, label: intent.kind === "restore" ? "Cancel" : intent.kind === "preset" ? "Choose another preset" : "Discard draft" } ], owner.signal, owner.isCurrent ); if (!isCurrentOwner2(owner)) return "cancel"; if (selection?.kind === "closed") return "close"; const selected = selectedPreviewAction(selection); if (selected === PREVIEW_ACTIONS.edit) return "edit"; if (selected !== PREVIEW_ACTIONS.continue) return "cancel"; } reviewRequired = true; const confirmed = await ctx.ui.confirm( confirmationTitle(intent), confirmationMessage(options.settingsPath, intent) ); if (!isCurrentOwner2(owner)) return "cancel"; if (!confirmed) { if (directAttempt) return "cancel"; continue; } const save = options.save ?? atomicSaveConfigDocument; const previous = options.getLoaded(); let saved; try { saved = save(options.settingsPath, validated.rawDocument ?? BUILT_IN_EXAMPLE); } catch (error) { ctx.ui.notify( `Footer settings were not saved: ${safeText(formatError2(error))}. The previous footer remains active.`, "error" ); if (directAttempt) return "cancel"; continue; } try { options.apply(saved, ctx); } catch (error) { const rollbackError = restorePreviousConfiguration(ctx, options, previous, saved); ctx.ui.notify( rollbackError ? `Footer settings could not be applied: ${safeText(formatError2(error))}. Restoring the previous configuration also failed: ${safeText(formatError2(rollbackError))}.` : `Footer settings could not be applied: ${safeText(formatError2(error))}. The previous configuration was restored.`, "error" ); if (directAttempt) return "cancel"; continue; } const warningSuffix = saved.diagnostics.length > 0 ? ` (${saved.diagnostics.length} warning${saved.diagnostics.length === 1 ? "" : "s"})` : ""; ctx.ui.notify(`${successMessage(intent)}${warningSuffix}.`, "info"); return "applied"; } } function reviewTitle(intent) { switch (intent.kind) { case "customize": return "Footer preview"; case "restore": return "Restore preview"; case "preset": return `${intent.preset.label} preset preview`; } } function continueLabel(intent) { switch (intent.kind) { case "customize": return "Apply changes\u2026"; case "restore": return "Replace with built-in\u2026"; case "preset": return `Apply ${intent.preset.label} preset\u2026`; } } function confirmationTitle(intent) { switch (intent.kind) { case "customize": return "Apply footer changes?"; case "restore": return "Restore built-in footer?"; case "preset": return `Apply ${intent.preset.label} preset?`; } } function confirmationMessage(settingsPath, intent) { if (intent.kind === "customize") return "Save this configuration and apply it immediately?"; const replacement = intent.kind === "restore" ? "the built-in configuration" : `the ${intent.preset.label} preset`; return `Replace ${safeText(settingsPath)} entirely with ${replacement}? All custom settings, unknown fields, and comments will be removed. No backup is kept after success.`; } function successMessage(intent) { switch (intent.kind) { case "customize": return "Footer settings saved and applied"; case "restore": return "Built-in footer restored and applied"; case "preset": return `${intent.preset.label} preset saved and applied`; } } function modulePreviewDetails(preview) { const lines = preview ? preview.split("\n") : ["(no current preview)"]; return lines.map((line, index) => `${index === 0 ? "Preview: " : " "}${line}`); } function workflowOwner(options) { if (options.getMenuOwner) return options.getMenuOwner(); const controller = new AbortController(); return { signal: controller.signal, isCurrent: () => true }; } function isCurrentOwner2(owner) { return !owner.signal.aborted && owner.isCurrent(); } function restorePreviousConfiguration(ctx, options, previous, saved) { try { if (saved.rawDocument === void 0 || saved.fileIdentity === void 0) { throw new Error("The saved settings document identity is unavailable"); } removeConfigDocumentIfMatches(options.settingsPath, saved.rawDocument, saved.fileIdentity); if (previous.rawDocument !== void 0) { (options.restore ?? atomicRestoreConfigDocument)(options.settingsPath, previous.rawDocument); } options.apply(previous, ctx); return void 0; } catch (error) { return error; } } function reviewPreviewBody(ctx, options, loaded, width, intent) { if (intent.kind === "restore") return restorePreviewBody(ctx, options, loaded, width); if (intent.kind === "customize") return previewBody(ctx, options, loaded, width); const current = configurationPresentation(options.getLoaded()); return [ `Preset: ${intent.preset.label}`, `Requirement: ${intent.preset.requiresNerdFont ? "Nerd Font" : "No special font"}`, `Current: ${current.state}`, `Path: ${safeText(options.settingsPath)}`, "Applying replaces the entire settings document, including custom settings, unknown fields, and comments.", "No backup is kept after a successful apply.", "", ...previewBody(ctx, options, loaded, width) ]; } function restorePreviewBody(ctx, options, loaded, width) { const current = configurationPresentation(options.getLoaded()); return [ `Current: ${current.state}`, `Path: ${safeText(options.settingsPath)}`, "The entire settings document will be replaced, including custom settings, unknown fields, and comments.", "No backup is kept after a successful restore.", "", ...previewBody(ctx, options, loaded, width) ]; } function previewBody(ctx, options, loaded, width) { let lines; try { lines = options.renderPreview?.(loaded, width, ctx) ?? [ "Live preview is unavailable until the footer is ready.", "The draft is valid and can still be applied." ]; } catch (error) { lines = [`Preview unavailable: ${safeText(formatError2(error))}`]; } const warning = loaded.diagnostics.length === 0 ? "Draft validation: Healthy" : `Draft validation: ${loaded.diagnostics.length} warning${loaded.diagnostics.length === 1 ? "" : "s"}`; return [...lines, "", warning]; } function selectedPreviewAction(result) { return result?.kind === "selected" ? result.value : null; } function showStatus(ctx, options) { if (!canNotify(ctx)) return; const loaded = options.getLoaded(); const diagnostics = loaded.diagnostics.slice(0, 5).map((item) => `${safeText(item.path || "root")}: ${safeText(item.message)}`).join("; "); ctx.ui.notify( [ `pi-starship source: ${loaded.source}`, `path: ${options.settingsPath}`, diagnostics ? `warnings: ${diagnostics}` : "warnings: none" ].join("\n"), loaded.diagnostics.length > 0 ? "warning" : "info" ); } function showHelp(ctx, settingsPath) { if (!canNotify(ctx)) return; ctx.ui.notify( [ "/starship \u2014 customize, live-preview presets, explain, or inspect the footer in TUI mode", "/starship settings \u2014 customize, preview, and apply TOML", "/starship status \u2014 show source, path, and warnings", "/starship help \u2014 show this help", `Settings: ${settingsPath}`, "Format/module docs: https://github.com/narumiruna/pi-extensions/tree/main/packages/pi-starship" ].join("\n"), "info" ); } function canNotify(ctx) { return ctx.mode === "tui" || ctx.hasUI; } function safeText(value) { return Array.from(value, (character) => { const codePoint = character.codePointAt(0) ?? 0; const unsafe = codePoint <= 8 || codePoint >= 11 && codePoint <= 31 || codePoint >= 127 && codePoint <= 159; return unsafe ? `\\u${codePoint.toString(16).padStart(4, "0")}` : character; }).join(""); } function formatError2(error) { return error instanceof Error ? error.message : String(error); } export { handleStarshipCommand, registerStarshipCommand }; //# sourceMappingURL=commands-X3VM2UGT.ts.map