// @generated by scripts/build-runtime.mjs; do not edit. // @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader. // src/command-contract.ts var STARSHIP_SUBCOMMANDS = [ { value: "settings", label: "settings", description: "Customize the footer TOML" }, { value: "status", label: "status", description: "Show configuration health and source" }, { value: "help", label: "help", description: "Show configuration help" } ]; function completeStarshipArguments(prefix) { const normalized = prefix.trim().toLowerCase(); const matches = STARSHIP_SUBCOMMANDS.filter((item) => item.value.startsWith(normalized)); return matches.length > 0 ? [...matches] : null; } // src/config.ts import { randomUUID } from "node:crypto"; import { existsSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs"; import { createRequire } from "node:module"; import { dirname, join } from "node:path"; // src/format/style.ts function isFillChunk(chunk) { return "type" in chunk && chunk.type === "fill"; } var NAMED_COLORS = /* @__PURE__ */ new Set([ "black", "red", "green", "yellow", "blue", "purple", "cyan", "white", "bright-black", "bright-red", "bright-green", "bright-yellow", "bright-blue", "bright-purple", "bright-cyan", "bright-white" ]); var FOREGROUND_CODES = { black: 30, red: 31, green: 32, yellow: 33, blue: 34, purple: 35, cyan: 36, white: 37, "bright-black": 90, "bright-red": 91, "bright-green": 92, "bright-yellow": 93, "bright-blue": 94, "bright-purple": 95, "bright-cyan": 96, "bright-white": 97 }; function isValidStyle(styleString, palette = {}) { return parseStyleResult(styleString, palette).valid; } function parseStyleResult(styleString, palette = {}) { const tokens = styleString.split(/\s+/u).filter(Boolean).map(normalizeStyleToken); if (tokens.some(({ token, foreground }) => foreground && token === "none")) { return { valid: true, style: void 0 }; } const style = {}; for (const { token, foreground } of tokens) { if (applyModifier(style, token)) continue; if (token === "prev_fg" || token === "prev_bg") { const source = token === "prev_fg" ? "foreground" : "background"; if (foreground) style.foregroundPrevious = source; else style.backgroundPrevious = source; continue; } const color = parseColor(token, palette); if (!foreground && !color) { delete style.background; continue; } if (!color) return { valid: false, style: void 0 }; if (foreground) style.foreground = color; else style.background = color; } return { valid: true, style }; } function parseStyle(styleString, palette = {}) { return parseStyleResult(styleString, palette).style; } function normalizeStyleToken(rawToken) { let token = rawToken.toLowerCase(); if (token.startsWith("fg:")) { return { token: token.replace(/^(?:fg:)+/u, ""), foreground: true }; } if (token.startsWith("bg:")) { token = token.replace(/^(?:bg:)+/u, ""); return { token, foreground: false }; } return { token, foreground: true }; } function applyModifier(style, token) { switch (token) { case "bold": style.bold = true; return true; case "italic": style.italic = true; return true; case "underline": style.underline = true; return true; case "dimmed": style.dimmed = true; return true; case "inverted": style.inverted = true; return true; case "blink": style.blink = true; return true; case "hidden": style.hidden = true; return true; case "strikethrough": style.strikethrough = true; return true; default: return false; } } function parseColor(token, palette = {}) { const paletteValue = Object.hasOwn(palette, token) ? palette[token] : void 0; if (paletteValue !== void 0) return parseColor(paletteValue.toLowerCase(), {}); if (NAMED_COLORS.has(token)) { return { kind: "named", name: token }; } if (/^\d{1,3}$/u.test(token)) { const value = Number(token); return value <= 255 ? { kind: "fixed", value } : void 0; } const rgb = /^#([0-9a-f]{6})$/iu.exec(token); if (!rgb?.[1]) return void 0; return { kind: "rgb", red: Number.parseInt(rgb[1].slice(0, 2), 16), green: Number.parseInt(rgb[1].slice(2, 4), 16), blue: Number.parseInt(rgb[1].slice(4, 6), 16) }; } function renderChunksToAnsi(chunks) { const runs = []; let previous; for (const chunk of chunks) { if (isFillChunk(chunk)) continue; const style = resolveStyle(chunk.style, previous); const last = runs.at(-1); if (chunk.text && last && stylesEqual(last.style, style)) last.text += chunk.text; else if (chunk.text) runs.push({ text: chunk.text, style }); previous = style; } return runs.map(({ text, style }) => { const codes = ansiCodes(style); return codes.length > 0 ? `\x1B[${codes.join(";")}m${text}\x1B[0m` : text; }).join(""); } function stylesEqual(left, right) { return JSON.stringify(left) === JSON.stringify(right); } function resolveStyle(style, previous) { if (!style) return {}; const { foregroundPrevious, backgroundPrevious, ...resolved } = style; return { ...resolved, foreground: resolveColor(style.foreground, foregroundPrevious, previous), background: resolveColor(style.background, backgroundPrevious, previous) }; } function resolveColor(fallback, source, previous) { if (!source || !previous) return fallback; return source === "foreground" ? previous.foreground : previous.background; } function ansiCodes(style) { const codes = []; if (style.foreground) codes.push(...colorCodes(style.foreground, false)); if (style.background) codes.push(...colorCodes(style.background, true)); if (style.bold) codes.push("1"); if (style.dimmed) codes.push("2"); if (style.italic) codes.push("3"); if (style.underline) codes.push("4"); if (style.blink) codes.push("5"); if (style.inverted) codes.push("7"); if (style.hidden) codes.push("8"); if (style.strikethrough) codes.push("9"); return codes; } function colorCodes(color, background) { if (color.kind === "named") { const foreground = FOREGROUND_CODES[color.name]; return [`${background ? foreground + 10 : foreground}`]; } if (color.kind === "fixed") return [background ? "48" : "38", "5", `${color.value}`]; return [background ? "48" : "38", "2", `${color.red}`, `${color.green}`, `${color.blue}`]; } // src/format/formatter.ts var FormatSyntaxError = class extends Error { offset; constructor(message, offset) { super(`${message} at offset ${offset}`); this.name = "FormatSyntaxError"; this.offset = offset; } }; var FUNCTIONAL = /* @__PURE__ */ new Set(["[", "]", "(", ")", "\\", "$"]); function parseFormat(format) { const parser = new FormatParser(format); const nodes = parser.parseNodes(); if (!parser.done()) throw new FormatSyntaxError("Unexpected character", parser.offset()); return nodes; } var FormatParser = class { constructor(input) { this.input = input; } input; index = 0; offset() { return this.index; } done() { return this.index === this.input.length; } parseNodes(end) { const nodes = []; let text = ""; const flushText = () => { if (!text) return; nodes.push({ type: "text", value: text }); text = ""; }; while (!this.done()) { const current = this.input[this.index]; if (current === end) { flushText(); this.index += 1; return nodes; } if (current === "\\") { const escaped = this.input[this.index + 1]; if (!escaped || !FUNCTIONAL.has(escaped)) { throw new FormatSyntaxError("Invalid escape", this.index); } text += escaped; this.index += 2; continue; } if (current === "$") { flushText(); nodes.push({ type: "variable", name: this.parseVariable() }); continue; } if (current === "[") { flushText(); this.index += 1; const children = this.parseNodes("]"); if (this.input[this.index] !== "(") { throw new FormatSyntaxError("Text group requires a style", this.index); } this.index += 1; nodes.push({ type: "group", children, style: this.parseStyleNodes() }); continue; } if (current === "(") { flushText(); this.index += 1; nodes.push({ type: "conditional", children: this.parseNodes(")") }); continue; } if (current === "]" || current === ")") { throw new FormatSyntaxError(`Unexpected ${current}`, this.index); } text += current; this.index += 1; } if (end) throw new FormatSyntaxError(`Missing ${end}`, this.index); flushText(); return nodes; } parseVariable() { const start = this.index; this.index += 1; if (this.input[this.index] === "{") { this.index += 1; const nameStart = this.index; while (!this.done() && this.input[this.index] !== "}") { const current = this.input[this.index]; if (!current || FUNCTIONAL.has(current) || current === "{") { throw new FormatSyntaxError("Invalid scoped variable", this.index); } this.index += 1; } if (this.done() || this.index === nameStart) { throw new FormatSyntaxError("Unclosed scoped variable", start); } const name = this.input.slice(nameStart, this.index); this.index += 1; return name; } const match = /^[A-Za-z_][A-Za-z0-9_]*/u.exec(this.input.slice(this.index)); if (!match) throw new FormatSyntaxError("Invalid variable", start); this.index += match[0].length; return match[0]; } parseStyleNodes() { const nodes = []; let text = ""; const flushText = () => { if (!text) return; nodes.push({ type: "text", value: text }); text = ""; }; while (!this.done()) { const current = this.input[this.index]; if (current === ")") { flushText(); this.index += 1; return nodes; } if (current === "$") { flushText(); nodes.push({ type: "variable", name: this.parseVariable() }); continue; } if (current === "(" || current === "[" || current === "]" || current === "\\") { throw new FormatSyntaxError("Invalid style character", this.index); } text += current; this.index += 1; } throw new FormatSyntaxError("Missing )", this.index); } }; function formatVariables(nodes) { const variables = /* @__PURE__ */ new Set(); for (const node of nodes) { if (node.type === "variable") variables.add(node.name); else if (node.type === "group" || node.type === "conditional") { for (const variable of formatVariables(node.children)) variables.add(variable); } } return variables; } function styleVariables(nodes) { const variables = /* @__PURE__ */ new Set(); for (const node of nodes) { if (node.type === "group") { for (const part of node.style) { if (part.type === "variable") variables.add(part.name); } } if (node.type === "group" || node.type === "conditional") { for (const variable of styleVariables(node.children)) variables.add(variable); } } return variables; } function renderFormat(nodes, options) { return renderNodes(nodes, options, void 0); } function renderNodes(nodes, options, inheritedStyle) { const chunks = []; for (const node of nodes) { switch (node.type) { case "text": chunks.push({ text: node.value, style: inheritedStyle }); break; case "variable": chunks.push(...chunksForValue(ownValue(options.variables, node.name), inheritedStyle)); break; case "conditional": if (conditionalVisible(node.children, options.variables)) { chunks.push(...renderNodes(node.children, options, inheritedStyle)); } break; case "group": { const styleString = node.style.map( (part) => part.type === "text" ? part.value : ownValue(options.styleVariables, part.name) ?? "" ).join(""); const style = parseStyle(styleString, options.palette); const rendered = renderNodes(node.children, options, style); if (node.children.length === 0) chunks.push({ text: "", style }); else chunks.push(...rendered); break; } } } return chunks; } function ownValue(record, key) { return record && Object.hasOwn(record, key) ? record[key] : void 0; } function chunksForValue(value, inheritedStyle) { if (value === void 0) return []; if (typeof value === "string") return [{ text: value, style: inheritedStyle }]; return value.map( (chunk) => isFillChunk(chunk) ? { type: "fill", pattern: chunk.pattern.map((part) => ({ ...part, style: part.style ?? inheritedStyle })) } : { ...chunk, style: chunk.style ?? inheritedStyle } ); } function conditionalVisible(nodes, variables) { for (const variable of formatVariables(nodes)) { const value = ownValue(variables, variable); if (typeof value === "string" ? value.length > 0 : value?.some((chunk) => isFillChunk(chunk) || chunk.text.length > 0)) { return true; } } return false; } // src/modules/types.ts function defineModule(definition) { return definition; } // src/modules/activity.ts var activityModule = defineModule({ name: "activity", variables: ["symbol", "state", "tool", "count", "text"], defaults: { format: "[ $text ]($style)", symbol: "\u2699", style: "bold yellow", disabled: false }, values: ({ runtime, symbol }) => { const active = [...runtime.activeTools.entries()]; if (active.length > 0) { const [tool = "tool", count2 = 1] = active[0] ?? []; const suffix = count2 > 1 ? `\xD7${count2}` : ""; const more = active.length > 1 ? `+${active.length - 1}` : ""; return { state: "active", tool, count: `${count2}`, text: `${symbol} ${tool}${suffix}${more}` }; } if (runtime.isStreaming) { return { state: "thinking", tool: "", count: "0", text: `${symbol} thinking` }; } if (runtime.lastCompletedTool) { return { state: "completed", tool: runtime.lastCompletedTool, count: "0", text: `${symbol} completed ${runtime.lastCompletedTool}` }; } return { state: "idle", tool: "", count: "0", text: `${symbol} idle` }; } }); // src/modules/brand.ts var brandModule = defineModule({ name: "brand", variables: ["symbol"], defaults: { format: "[ $symbol ]($style)", symbol: "\u03C0", style: "bold white", disabled: false }, values: () => ({}) }); // src/modules/helpers.ts function formatCount(value) { if (value < 1e3) return `${value}`; if (value < 1e6) return `${(value / 1e3).toFixed(value < 1e4 ? 1 : 0)}k`; return `${(value / 1e6).toFixed(1)}m`; } // src/modules/cache.ts var cacheModule = defineModule({ name: "cache", variables: ["symbol", "rate", "read", "write"], defaults: { format: "[$symbol (CH$rate )]($style)", symbol: "\u{1F4E6}", style: "bold green", disabled: true }, values: ({ runtime }) => { const { cacheRead, cacheWrite, latestCacheHitRate } = runtime.tokenTotals; if (cacheRead === 0 && cacheWrite === 0) return void 0; const rate = latestCacheHitRate === void 0 ? "" : `${latestCacheHitRate.toFixed(1)}%`; return { rate, read: cacheRead > 0 ? formatCount(cacheRead) : "", write: cacheWrite > 0 ? formatCount(cacheWrite) : "" }; } }); // src/modules/workspace-helpers.ts function workspaceModuleValues(name, context) { const values = context.runtime.workspace?.modules[name]; if (!values) return void 0; return { ...values }; } // src/modules/cloud.ts function cloudModule(definition) { return defineModule({ name: definition.name, variables: ["symbol", ...definition.variables], defaults: { format: definition.format, symbol: definition.symbol, style: definition.style, disabled: false }, options: definition.options, values: (context) => workspaceModuleValues(definition.name, context) }); } var awsModule = cloudModule({ name: "aws", variables: ["profile", "region"], format: "on [$symbol($profile )(\\($region\\) )]($style)", symbol: "\u2601\uFE0F ", style: "bold yellow", options: { profile_aliases: { kind: "string-map", default: {} }, region_aliases: { kind: "string-map", default: {} } } }); var gcloudModule = cloudModule({ name: "gcloud", variables: ["active", "account", "domain", "project", "region"], format: "on [$symbol$project]($style) ", symbol: "\u2601\uFE0F ", style: "bold blue", options: { project_aliases: { kind: "string-map", default: {} }, region_aliases: { kind: "string-map", default: {} } } }); var azureModule = cloudModule({ name: "azure", variables: ["subscription", "username"], format: "on [$symbol$subscription]($style) ", symbol: "\u{F0805} ", style: "blue bold", options: { subscription_aliases: { kind: "string-map", default: {} }, show_username: { kind: "boolean", default: false } } }); var openstackModule = cloudModule({ name: "openstack", variables: ["cloud", "project"], format: "on [$symbol$cloud( \\($project\\))]($style) ", symbol: "\u2601\uFE0F ", style: "bold yellow", options: { cloud_aliases: { kind: "string-map", default: {} }, project_aliases: { kind: "string-map", default: {} } } }); var cloudModules = [awsModule, gcloudModule, azureModule, openstackModule]; // src/modules/display.ts function resolveDisplayStyle(display, value) { if (value === null || value === void 0 || !Number.isFinite(value)) return void 0; let selected; for (const entry of display) { if (entry.threshold > value) continue; if (!selected || entry.threshold >= selected.threshold) selected = entry; } return selected && !selected.hidden ? selected.style : void 0; } // src/modules/context.ts var contextModule = defineModule({ name: "context", variables: ["symbol", "percentage", "tokens", "window"], defaults: { format: "[$symbol ctx $percentage ]($style)", symbol: "\u{1FA9F}", style: "none", disabled: false }, displayDefaults: [ { threshold: 0, style: "bold green", hidden: true }, { threshold: 30, style: "bold green", hidden: false }, { threshold: 60, style: "bold yellow", hidden: false }, { threshold: 80, style: "bold red", hidden: false } ], styleVariables: ["style"], resolveStyleVariables: ({ runtime, display }) => { const style = resolveDisplayStyle(display, runtime.contextUsage?.percent); return style === void 0 ? void 0 : { style }; }, values: ({ runtime }) => { const percent = runtime.contextUsage?.percent; return { percentage: percent === null || percent === void 0 ? "?" : `${percent.toFixed(1)}%`, tokens: formatCount(runtime.contextUsage?.tokens ?? 0), window: formatCount(runtime.contextUsage?.contextWindow ?? 0) }; } }); // src/modules/cost.ts var costModule = defineModule({ name: "cost", variables: ["symbol", "cost", "subscription"], defaults: { format: "[ $symbol \\$$cost( $subscription) ]($style)", symbol: "\u{1F4B8}", style: "none", disabled: false }, displayDefaults: [ { threshold: 0, style: "bold green", hidden: true }, { threshold: 1, style: "bold yellow", hidden: false }, { threshold: 5, style: "bold red", hidden: false } ], styleVariables: ["style"], resolveStyleVariables: ({ runtime, display }) => { const style = resolveDisplayStyle(display, runtime.tokenTotals.cost); return style === void 0 ? void 0 : { style }; }, values: ({ runtime }) => ({ cost: formatCost(runtime.tokenTotals.cost), subscription: runtime.usingSubscription ? "(sub)" : "" }) }); function formatCost(value) { return value.toFixed(value >= 1 ? 2 : 3); } // src/modules/deployment.ts var directDetection = { detect_files: { kind: "string-array", default: [] }, detect_extensions: { kind: "string-array", default: [] }, detect_folders: { kind: "string-array", default: [] } }; var dockerContextModule = defineModule({ name: "docker_context", variables: ["symbol", "context"], defaults: { format: "via [$symbol$context]($style) ", symbol: "\uF308 ", style: "blue bold", disabled: false }, options: { ...directDetection, only_with_files: { kind: "boolean", default: false } }, values: (context) => workspaceModuleValues("docker_context", context) }); var kubernetesModule = defineModule({ name: "kubernetes", variables: ["symbol", "context", "namespace", "cluster", "user"], defaults: { format: "on [$symbol$context( \\($namespace\\))]($style) ", symbol: "\u2638 ", style: "cyan bold", disabled: false }, options: { context_aliases: { kind: "string-map", default: {} }, namespace_aliases: { kind: "string-map", default: {} }, cluster_aliases: { kind: "string-map", default: {} }, user_aliases: { kind: "string-map", default: {} }, max_config_files: { kind: "integer", default: 8, minimum: 1, maximum: 32 } }, values: (context) => workspaceModuleValues("kubernetes", context) }); var terraformModule = defineModule({ name: "terraform", variables: ["symbol", "workspace", "version"], defaults: { format: "via [$symbol$workspace]($style) ", symbol: "\u{1F4A0} ", style: "bold 105", disabled: false }, options: { ...directDetection, version_format: { kind: "string", default: "v$raw" } }, values: (context) => workspaceModuleValues("terraform", context) }); var deploymentModules = [dockerContextModule, kubernetesModule, terraformModule]; // src/modules/truncation.ts import { sep } from "node:path"; var graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" }); function graphemes(value) { return [...graphemeSegmenter.segment(value)].map(({ segment }) => segment); } function firstGrapheme(value) { return graphemes(value)[0] ?? ""; } function truncateLeadingGraphemes(value, length, symbol) { if (length <= 0) return value; const parts = graphemes(value); if (parts.length <= length) return value; return `${parts.slice(0, length).join("")}${firstGrapheme(symbol)}`; } function toSlashPath(value) { return sep === "\\" ? value.replaceAll("\\", "/") : value; } function truncatePathComponents(value, length) { if (length <= 0) return { value, truncated: false }; const normalized = toSlashPath(value); const components = normalized.split("/").filter((component) => component.length > 0); if (components.length <= length) return { value: normalized, truncated: false }; return { value: components.slice(-length).join("/"), truncated: true }; } function useNativePathSeparator(value) { return sep === "/" ? value : value.replaceAll("/", sep); } // src/modules/development.ts function developmentModule(definition) { return defineModule({ name: definition.name, variables: ["symbol", ...definition.variables], defaults: { format: definition.format, symbol: definition.symbol, style: definition.style, disabled: false }, options: definition.options, values: (context) => workspaceModuleValues(definition.name, context) }); } var directDetection2 = { detect_files: { kind: "string-array", default: [] }, detect_extensions: { kind: "string-array", default: [] }, detect_folders: { kind: "string-array", default: [] } }; var miseModule = developmentModule({ name: "mise", variables: ["health"], format: "via [$symbol$health]($style) ", symbol: "mise ", style: "bold purple", options: directDetection2 }); var direnvModule = developmentModule({ name: "direnv", variables: ["rc_path", "allowed", "loaded"], format: "[$symbol$loaded]($style) ", symbol: "direnv ", style: "bold bright-yellow", options: directDetection2 }); var condaModule = defineModule({ name: "conda", variables: ["symbol", "environment"], defaults: { format: "via [$symbol$environment]($style) ", symbol: "\u{1F152} ", style: "green bold", disabled: false }, options: { ignore_base: { kind: "boolean", default: true }, truncation_length: { kind: "integer", default: 1, minimum: 0, maximum: 1e6 } }, values: (context) => { const values = workspaceModuleValues("conda", context); const environment = values?.environment; if (!environment) return void 0; const length = typeof context.options.truncation_length === "number" ? context.options.truncation_length : 1; return { environment: truncatePathComponents(environment, length).value }; } }); var pixiModule = developmentModule({ name: "pixi", variables: ["version", "environment", "project_name"], format: "via [$symbol$environment]($style) ", symbol: "\u{1F9DA} ", style: "yellow bold", options: { ...directDetection2, version_format: { kind: "string", default: "v$raw" }, show_default_environment: { kind: "boolean", default: false } } }); var nixShellModule = developmentModule({ name: "nix_shell", variables: ["state", "name", "level"], format: "via [$symbol$state( \\($name\\))]($style) ", symbol: "\uF313 ", style: "bold blue" }); var guixShellModule = developmentModule({ name: "guix_shell", variables: ["state"], format: "via [$symbol]($style) ", symbol: "\u{1F403} ", style: "yellow bold" }); var developmentModules = [ miseModule, direnvModule, condaModule, pixiModule, nixShellModule, guixShellModule ]; // src/modules/directory.ts import { basename, isAbsolute, relative, sep as sep2 } from "node:path"; import { sanitizeTerminalText } from "@narumitw/pi-tui-kit/terminal-text"; var directoryModule = defineModule({ name: "directory", variables: ["symbol", "path", "full_path"], defaults: { format: "[ $symbol $path ]($style)", symbol: "\u{1F4C1}", style: "cyan bold", disabled: false }, options: { truncation_length: { kind: "integer", default: 3, minimum: 0, maximum: 1e6 }, truncate_to_repo: { kind: "boolean", default: true }, fish_style_pwd_dir_length: { kind: "integer", default: 0, minimum: 0, maximum: 1e3 }, truncation_symbol: { kind: "string", default: "" }, home_symbol: { kind: "string", default: "~" }, use_os_path_sep: { kind: "boolean", default: true }, substitutions: { kind: "string-map", default: {} } }, values: ({ runtime, options }) => { const fullPath = runtime.cwd; const home = runtime.homeDir; const repoRoot = runtime.gitRoot; const homeSymbol = stringOption(options, "home_symbol", "~"); const truncateToRepo = booleanOption(options, "truncate_to_repo", true); const substitutions = mapOption(options, "substitutions"); const homeContracted = contractPath(fullPath, home, homeSymbol); const repoContracted = truncateToRepo && repoRoot && (!home || !samePath(repoRoot, home)) ? contractRepositoryPath(fullPath, repoRoot) : void 0; let path = sanitizeTerminalText(repoContracted ?? homeContracted); let truncated = repoContracted !== void 0; for (const [from, to] of Object.entries(substitutions)) { if (from) path = path.replaceAll(from, to); } const componentResult = truncatePathComponents( path, numberOption(options, "truncation_length", 3) ); path = componentResult.value; truncated ||= componentResult.truncated; if (truncated) { const fishLength = numberOption(options, "fish_style_pwd_dir_length", 0); if (fishLength > 0 && Object.keys(substitutions).length === 0) { path = `${fishPrefix(homeContracted, path, fishLength)}${path}`; } else { path = `${stringOption(options, "truncation_symbol", "")}${path}`; } } if (booleanOption(options, "use_os_path_sep", true)) path = useNativePathSeparator(path); const fallback = basename(fullPath) || fullPath; return { path: sanitizeTerminalText(path || fallback), full_path: sanitizeTerminalText(fullPath) }; } }); function contractPath(path, root, replacement) { if (!root) return toSlashPath(path); const child = relativeWithin(path, root); if (child === void 0) return toSlashPath(path); return child ? `${replacement}/${child}` : replacement; } function contractRepositoryPath(path, root) { const child = relativeWithin(path, root); if (child === void 0) return void 0; const name = basename(root) || toSlashPath(root); return child ? `${name}/${child}` : name; } function relativeWithin(path, root) { const child = relative(root, path); if (child === "") return ""; if (child === ".." || child.startsWith(`..${sep2}`) || isAbsolute(child)) return void 0; return toSlashPath(child); } function samePath(left, right) { return relative(left, right) === ""; } function fishPrefix(source, displayed, length) { const prefix = source.endsWith(displayed) ? source.slice(0, -displayed.length) : ""; if (!prefix) return ""; return prefix.split("/").map((component) => abbreviateComponent(component, length)).join("/"); } function abbreviateComponent(component, length) { if (!component) return ""; const parts = graphemes(component); if (parts.length <= length) return component; return component.startsWith(".") ? parts.slice(0, length + 1).join("") : parts.slice(0, length).join(""); } function numberOption(options, name, fallback) { const value = options[name]; return typeof value === "number" ? value : fallback; } function stringOption(options, name, fallback) { const value = options[name]; return typeof value === "string" ? value : fallback; } function booleanOption(options, name, fallback) { const value = options[name]; return typeof value === "boolean" ? value : fallback; } function mapOption(options, name) { const value = options[name]; return value && typeof value === "object" && !Array.isArray(value) ? value : {}; } // src/modules/execution.ts var osModule = defineModule({ name: "os", variables: ["symbol", "type", "name", "version", "edition", "codename"], defaults: { format: "[$symbol($name )]($style)", symbol: "", style: "bold white", disabled: true }, options: { symbols: { kind: "string-map", default: { linux: "\u{1F427} ", macos: "\u{1F34E} ", windows: "\uE62A ", wsl: "\uF31A " } } }, values: (context) => workspaceModuleValues("os", context) }); var containerModule = defineModule({ name: "container", variables: ["symbol", "name", "type"], defaults: { format: "[$symbol$name]($style) ", symbol: "\u2B22 ", style: "bold red dimmed", disabled: false }, values: (context) => workspaceModuleValues("container", context) }); var hostnameModule = defineModule({ name: "hostname", variables: ["symbol", "hostname", "ssh_symbol"], defaults: { format: "[$ssh_symbol$hostname]($style) in ", symbol: "", style: "bold dimmed green", disabled: false }, options: { ssh_only: { kind: "boolean", default: true }, trim_at: { kind: "string", default: "." }, aliases: { kind: "string-map", default: {} } }, values: (context) => workspaceModuleValues("hostname", context) }); var usernameModule = defineModule({ name: "username", variables: ["symbol", "user"], defaults: { format: "[$user]($style) in ", symbol: "", style: "none", disabled: false }, styleDefaults: { style_user: "yellow bold", style_root: "red bold" }, styleVariables: ["style"], resolveStyleVariables: ({ runtime, styles }) => ({ style: runtime.workspace?.styleSelectors?.username === "root" ? styles.style_root ?? "" : styles.style_user ?? "" }), options: { show_always: { kind: "boolean", default: false }, aliases: { kind: "string-map", default: {} }, detect_env_vars: { kind: "string-array", default: [] } }, values: (context) => workspaceModuleValues("username", context) }); var executionModules = [ osModule, containerModule, hostnameModule, usernameModule ]; // src/modules/extension-status.ts var extensionStatusModule = defineModule({ name: "extension_status", variables: ["symbol", "statuses", "count"], defaults: { format: "[$statuses]($style)", symbol: "", style: "dimmed white", disabled: false }, values: ({ runtime, extensionStatus }) => { const statuses = [...runtime.extensionStatuses.entries()].filter(([key, value]) => key !== "starship" && value.trim()).map(([key, value]) => formatExtensionStatus(key, value, extensionStatus.icons)).slice(0, extensionStatus.maxStatuses); if (statuses.length === 0) return void 0; return { statuses: statuses.join(extensionStatus.separator), count: `${statuses.length}` }; } }); function formatExtensionStatus(key, value, configuredIcons) { const status = splitExtensionStatusIcon(stripExtensionStatusPrefix(key, value)); const icon = extensionStatusIcon(key, status.icon, configuredIcons); const text = simplifyExtensionStatusText(status.text); return icon ? `${icon} ${text}` : text; } function extensionStatusIcon(key, leadingIcon, configuredIcons) { if (Object.hasOwn(configuredIcons, key)) return configuredIcons[key] ?? ""; const namespaceIcon = configuredNamespaceIcon(key, configuredIcons); if (namespaceIcon !== void 0) return namespaceIcon; const fallbackIcon = Object.hasOwn(configuredIcons, "fallback") ? configuredIcons.fallback : void 0; return leadingIcon ?? fallbackIcon ?? "\u{1F50C}"; } function configuredNamespaceIcon(key, configuredIcons) { let match; for (const [selector, icon] of Object.entries(configuredIcons)) { if (!selector.endsWith(":*")) continue; const base = selector.slice(0, -2); if (!base || !key.startsWith(`${base}:`)) continue; if (!match || base.length > match.baseLength) match = { baseLength: base.length, icon }; } return match?.icon; } function splitExtensionStatusIcon(value) { const trimmed = value.trim(); const [first, ...rest] = trimmed.split(/\s+/u); if (first && isEmojiOnlyToken(first)) return { icon: first, text: rest.join(" ") }; return { text: trimmed }; } function isEmojiOnlyToken(value) { return /^(?=.*(?:\p{Extended_Pictographic}|\p{Regional_Indicator}|[0-9#*]\ufe0f?\u20e3))(?:\p{Extended_Pictographic}|\p{Emoji_Modifier}|\p{Regional_Indicator}|\u200d|\ufe0f|[0-9#*]\ufe0f?\u20e3)+$/u.test( value ); } function stripExtensionStatusPrefix(key, value) { return value.trim().replace(new RegExp(`^${escapeRegExp(key)}\\s*:\\s*`, "iu"), ""); } function simplifyExtensionStatusText(value) { return value.trim().replace(/\bready\b/giu, "\u2713").replace(/\bmissing\b/giu, "\u2717").replace(/,\s*/g, " ").replace(/\s+\([^)]*\)\s*$/u, "").replace(/\s+/gu, " "); } function escapeRegExp(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } // src/modules/fill.ts var fillModule = defineModule({ name: "fill", variables: ["symbol"], defaults: { format: "[$symbol]($style)", symbol: " ", style: "bold black", disabled: false }, layout: "fill", values: () => ({}) }); // src/modules/git/branch.ts var gitBranchModule = defineModule({ name: "git_branch", variables: ["symbol", "branch", "remote_name", "remote_branch"], defaults: { format: "[ $symbol $branch ]($style)", symbol: "\u{1F33F}", style: "bold purple", disabled: false }, options: { // Starship uses i64::MAX as its effective no-truncation default. Zero is the // equivalent stable TOML representation in pi-starship's bounded integer schema. truncation_length: { kind: "integer", default: 0, minimum: 0, maximum: 1e6 }, truncation_symbol: { kind: "string", default: "\u2026" } }, values: ({ runtime, options }) => { const branch = runtime.gitBranchDetails; const name = branch?.name ?? runtime.gitBranch; if (!name) return void 0; const length = typeof options.truncation_length === "number" ? options.truncation_length : 0; const symbol = typeof options.truncation_symbol === "string" ? options.truncation_symbol : "\u2026"; return { branch: truncateLeadingGraphemes(name, length, symbol), remote_name: truncateLeadingGraphemes(branch?.remoteName ?? "", length, symbol), remote_branch: truncateLeadingGraphemes(branch?.remoteBranch ?? "", length, symbol) }; } }); // src/modules/git/commit.ts var DEFAULT_HASH_LENGTH = 7; var gitCommitModule = defineModule({ name: "git_commit", variables: ["symbol", "hash", "tag"], defaults: { format: "[ ($hash) ]($style)", symbol: "", style: "green bold", disabled: false }, options: { commit_hash_length: { kind: "integer", default: DEFAULT_HASH_LENGTH, minimum: 0, maximum: 64 } }, values: ({ runtime, options }) => { const commit = runtime.gitCommit; if (!commit) return void 0; const hashLength = typeof options.commit_hash_length === "number" ? options.commit_hash_length : DEFAULT_HASH_LENGTH; return { hash: commit.hash.slice(0, hashLength), tag: commit.tag ? ` \u{1F3F7} ${commit.tag}` : "" }; } }); // src/modules/git/metrics.ts var gitMetricsModule = defineModule({ name: "git_metrics", variables: ["symbol", "added", "deleted"], defaults: { format: "([+$added]($added_style) )([-$deleted]($deleted_style) )", symbol: "", style: "none", disabled: true }, styleDefaults: { added_style: "bold green", deleted_style: "bold red" }, styleVariables: ["added_style", "deleted_style"], resolveStyleVariables: ({ styles }) => styles, values: ({ runtime }) => { const metrics = runtime.gitMetrics; if (!metrics || metrics.added === 0 && metrics.deleted === 0) return void 0; return { added: metrics.added > 0 ? metrics.added.toString() : "", deleted: metrics.deleted > 0 ? metrics.deleted.toString() : "" }; } }); // src/modules/git/state.ts var gitStateModule = defineModule({ name: "git_state", variables: ["symbol", "state", "progress_current", "progress_total"], defaults: { format: "[ ($state( $progress_current/$progress_total)) ]($style)", symbol: "", style: "bold yellow", disabled: false }, values: ({ runtime }) => { const state = runtime.gitState; if (!state) return void 0; return { state: state.state, progress_current: state.progressCurrent?.toString() ?? "", progress_total: state.progressTotal?.toString() ?? "" }; } }); // src/modules/git/status.ts var gitStatusModule = defineModule({ name: "git_status", variables: [ "symbol", "all_status", "ahead_behind", "ahead", "behind", "up_to_date", "diverged", "conflicted", "stashed", "deleted", "renamed", "modified", "typechanged", "staged", "untracked", "worktree_added", "worktree_deleted", "worktree_modified", "worktree_typechanged", "index_added", "index_deleted", "index_modified", "index_typechanged" ], defaults: { format: "[$all_status( $ahead_behind) ]($style)", symbol: "", style: "red bold", disabled: false }, values: ({ runtime }) => { if (!runtime.gitBranch || !runtime.gitStatus) return void 0; const status = runtime.gitStatus; const ahead = count("\u21E1", status.ahead); const behind = count("\u21E3", status.behind); const diverged = status.ahead > 0 && status.behind > 0 ? `\u21D5\u21E1${formatCount(status.ahead)}\u21E3${formatCount(status.behind)}` : ""; const values = { ahead, behind, up_to_date: "", diverged, ahead_behind: diverged || ahead || behind, conflicted: count("=", status.conflicted), stashed: count("$", status.stashed), deleted: count("\u2718", status.deleted), renamed: count("\xBB", status.renamed), modified: count("!", status.modified), typechanged: count("T", status.typechanged), staged: count("+", status.staged), untracked: count("?", status.untracked), worktree_added: count("A", status.worktreeAdded), worktree_deleted: count("D", status.worktreeDeleted), worktree_modified: count("M", status.worktreeModified), worktree_typechanged: count("T", status.worktreeTypechanged), index_added: count("A", status.indexAdded), index_deleted: count("D", status.indexDeleted), index_modified: count("M", status.indexModified), index_typechanged: count("T", status.indexTypechanged) }; const allStatus = [ values.conflicted, values.stashed, values.deleted, values.renamed, values.modified, values.typechanged, values.staged, values.untracked ].filter(Boolean).join(" "); return allStatus || values.ahead_behind ? { ...values, all_status: allStatus } : void 0; } }); function count(symbol, value) { return value > 0 ? `${symbol}${formatCount(value)}` : ""; } // src/modules/git/worktree.ts var gitWorktreeModule = defineModule({ name: "git_worktree", variables: ["symbol", "name", "path"], defaults: { format: "[ $symbol $name ]($style)", symbol: "\u{1F333}", style: "cyan bold", disabled: false }, values: ({ runtime }) => runtime.gitWorktree ? { name: runtime.gitWorktree.name, path: runtime.gitWorktree.path } : void 0 }); // src/modules/github-pr.ts var githubPrModule = defineModule({ name: "github_pr", variables: ["symbol", "number", "link", "state", "checks", "review", "status"], defaults: { format: "[ $symbol$link( \xB7 $status) ]($style)", symbol: "PR ", style: "bold blue", disabled: false }, values: ({ runtime }) => { const snapshot = runtime.githubPr; if (!snapshot) return void 0; return { number: snapshot.number, link: snapshot.link, state: snapshot.state, checks: snapshot.checks, review: snapshot.review, status: snapshot.status }; } }); // src/modules/languages.ts var detectionOptions = { version_format: { kind: "string", default: "v$raw" }, detect_files: { kind: "string-array", default: [], allowNegative: true }, detect_extensions: { kind: "string-array", default: [], allowNegative: true }, detect_folders: { kind: "string-array", default: [], allowNegative: true } }; function languageModule(definition) { return defineModule({ name: definition.name, variables: ["symbol", ...definition.variables], defaults: { format: definition.format, symbol: definition.symbol, style: definition.style, disabled: false }, options: detectionOptions, values: (context) => workspaceModuleValues(definition.name, context) }); } var nodejsModule = languageModule({ name: "nodejs", variables: ["version", "engines_version"], format: "via [$symbol($version )]($style)", symbol: "\uE718 ", style: "bold green" }); var pythonModule = languageModule({ name: "python", variables: ["version", "virtualenv", "pyenv_prefix"], format: "via [$symbol$pyenv_prefix($version )(\\($virtualenv\\) )]($style)", symbol: "\uE235 ", style: "yellow bold" }); var rustModule = languageModule({ name: "rust", variables: ["version", "numver", "toolchain"], format: "via [$symbol($version )]($style)", symbol: "\uE7A8 ", style: "bold red" }); var golangModule = languageModule({ name: "golang", variables: ["version", "mod_version"], format: "via [$symbol($version )]($style)", symbol: "\uE627 ", style: "bold cyan" }); var bunModule = languageModule({ name: "bun", variables: ["version"], format: "via [$symbol($version )]($style)", symbol: "\u{1F35E} ", style: "bold red" }); var denoModule = languageModule({ name: "deno", variables: ["version"], format: "via [$symbol($version )]($style)", symbol: "\u{1F995} ", style: "green bold" }); var languageModules = [ nodejsModule, pythonModule, rustModule, golangModule, bunModule, denoModule ]; // src/modules/model.ts import { sanitizeTerminalText as sanitizeTerminalText2 } from "@narumitw/pi-tui-kit/terminal-text"; var TRUNCATION_DIRECTIONS = ["start", "middle", "end"]; var graphemeSegmenter2 = new Intl.Segmenter(void 0, { granularity: "grapheme" }); var modelModule = defineModule({ name: "model", variables: ["symbol", "model"], defaults: { format: "[$symbol $model ]($style)", symbol: "\u{1F916}", style: "bold blue", disabled: false }, options: { truncation_length: { kind: "integer", default: 0, minimum: 0, maximum: 1e3 }, truncation_symbol: { kind: "string", default: "\u2026" }, truncation_direction: { kind: "string-enum", default: "end", values: TRUNCATION_DIRECTIONS }, model_aliases: { kind: "string-map", default: {} } }, values: ({ runtime, options }) => { if (!runtime.model) return void 0; const length = typeof options.truncation_length === "number" ? options.truncation_length : 0; const symbol = typeof options.truncation_symbol === "string" ? options.truncation_symbol : "\u2026"; const direction = isTruncationDirection(options.truncation_direction) ? options.truncation_direction : "end"; const aliases = options.model_aliases; const aliasMap = aliases && typeof aliases === "object" && !Array.isArray(aliases) ? aliases : void 0; const alias = aliasMap && Object.hasOwn(aliasMap, runtime.model.id) ? aliasMap[runtime.model.id] : void 0; return { model: truncateModel(alias ?? shortenModel(runtime.model.id), length, symbol, direction) }; } }); function truncateModel(model, length, symbol, direction) { const safeModel = sanitizeTerminalText2(model); if (length === 0) return safeModel; const graphemes2 = [...graphemeSegmenter2.segment(safeModel)].map(({ segment }) => segment); if (graphemes2.length <= length) return safeModel; const safeSymbol = sanitizeTerminalText2(symbol); switch (direction) { case "start": return `${safeSymbol}${graphemes2.slice(-length).join("")}`; case "middle": { const headLength = Math.ceil(length / 2); const tailLength = Math.floor(length / 2); const tail = tailLength > 0 ? graphemes2.slice(-tailLength).join("") : ""; return `${graphemes2.slice(0, headLength).join("")}${safeSymbol}${tail}`; } case "end": return `${graphemes2.slice(0, length).join("")}${safeSymbol}`; } } function isTruncationDirection(value) { return TRUNCATION_DIRECTIONS.includes(value); } function shortenModel(model) { return model.replace(/^claude-/u, "").replace(/^gpt-/u, "gpt ").replace(/-20\d{6}$/u, "").replace(/-latest$/u, ""); } // src/modules/package.ts var packageModule = defineModule({ name: "package", variables: ["symbol", "version", "source"], defaults: { format: "via [$symbol$version]($style) ", symbol: "\u{1F4E6} ", style: "bold 208", disabled: false }, options: { version_format: { kind: "string", default: "v$raw" } }, values: (context) => workspaceModuleValues("package", context) }); // src/modules/provider.ts var providerModule = defineModule({ name: "provider", variables: ["symbol", "provider"], defaults: { format: "[$symbol $provider ]($style)", symbol: "\u{1F50C}", style: "bold blue", disabled: false }, values: ({ runtime }) => runtime.model ? { provider: runtime.model.provider } : void 0 }); // src/modules/thinking.ts var thinkingModule = defineModule({ name: "thinking", variables: ["symbol", "level"], defaults: { format: "[$symbol $level ]($style)", symbol: "\u{1F9E0}", style: "bold purple", disabled: false }, values: ({ runtime }) => ({ level: runtime.thinkingLevel }) }); // src/modules/time.ts var timeModule = defineModule({ name: "time", variables: ["symbol", "time"], defaults: { format: "[$symbol $time ]($style)", symbol: "\u{1F552}", style: "bold yellow", disabled: false }, values: ({ runtime }) => ({ time: formatTime(runtime.now) }) }); function formatTime(now) { return `${now.getHours().toString().padStart(2, "0")}:${now.getMinutes().toString().padStart(2, "0")}`; } // src/modules/tokens.ts var tokensModule = defineModule({ name: "tokens", variables: ["symbol", "input", "output", "total"], defaults: { format: "[$symbol \u2191$input \u2193$output ]($style)", symbol: "\u{1F522}", style: "bold cyan", disabled: false }, values: ({ runtime }) => ({ input: formatCount(runtime.tokenTotals.input), output: formatCount(runtime.tokenTotals.output), total: formatCount(runtime.tokenTotals.input + runtime.tokenTotals.output) }) }); // src/modules/turn.ts var turnModule = defineModule({ name: "turn", variables: ["symbol", "count"], defaults: { format: "[$symbol #$count ]($style)", symbol: "\u{1F501}", style: "bold purple", disabled: false }, values: ({ runtime }) => ({ count: `${runtime.turnCount}` }) }); // src/modules/catalog.ts var MODULE_IMPLEMENTATIONS = [ brandModule, providerModule, modelModule, thinkingModule, directoryModule, gitWorktreeModule, gitBranchModule, githubPrModule, gitCommitModule, gitStateModule, gitMetricsModule, gitStatusModule, packageModule, ...languageModules, ...developmentModules, ...deploymentModules, ...cloudModules, ...executionModules, activityModule, contextModule, tokensModule, cacheModule, costModule, timeModule, turnModule, fillModule, // Keep arbitrary third-party statuses after the native modules. extensionStatusModule ]; var MODULE_DESCRIPTIONS = { activity: "Current Pi activity or most recently completed tool.", aws: "Active AWS profile and region.", azure: "Active Azure subscription and optional username.", brand: "pi-starship brand mark.", bun: "Bun version detected in the current workspace.", cache: "Prompt-cache usage and latest cache hit rate.", conda: "Active Conda environment.", container: "Current container or remote development environment.", context: "Current model context-window usage.", cost: "Reported estimated session cost or subscription state.", deno: "Deno version detected in the current workspace.", directory: "Current working directory.", direnv: "Current direnv loading and permission state.", docker_context: "Active Docker context.", extension_status: "Statuses published through Pi's extension-neutral status map.", fill: "Flexible spacing that aligns content within the footer width.", gcloud: "Active Google Cloud project, account, and region.", git_branch: "Current Git branch and upstream identity.", git_commit: "Current Git commit hash or tag.", git_metrics: "Added and deleted lines in the current Git worktree.", git_state: "Current Git operation such as merge, rebase, or cherry-pick.", git_status: "Current Git worktree and index status summary.", git_worktree: "Current linked Git worktree identity.", github_pr: "Current branch's GitHub pull request state, checks, and review.", golang: "Go version detected in the current workspace.", guix_shell: "Current Guix shell state.", hostname: "Current host name, normally shown for remote sessions.", kubernetes: "Active Kubernetes context, namespace, cluster, and user.", mise: "Current mise configuration health.", model: "Current Pi model.", nix_shell: "Current Nix shell state, name, and nesting level.", nodejs: "Node.js version detected in the current workspace.", openstack: "Active OpenStack cloud and project.", os: "Current operating system identity.", package: "Current workspace package name and version.", pixi: "Active Pixi environment and project.", provider: "Current Pi model provider.", python: "Python version, virtual environment, and pyenv state.", rust: "Rust toolchain detected in the current workspace.", terraform: "Active Terraform workspace and version.", thinking: "Current Pi thinking level or streaming state.", time: "Current local time.", tokens: "Session input and output token totals.", turn: "Current user-turn count.", username: "Current user identity when configured to display." }; var MODULE_DEFINITIONS = MODULE_IMPLEMENTATIONS.map( (definition) => ({ ...definition, description: MODULE_DESCRIPTIONS[definition.name] }) ); var MODULE_NAMES = MODULE_IMPLEMENTATIONS.map( (definition) => definition.name ); // src/config.ts var CONFIG_FILE_NAME = "pi-starship.toml"; var MODULE_CONTENT_VARIABLES = Object.fromEntries( MODULE_DEFINITIONS.map((definition) => [definition.name, definition.variables]) ); var BUILT_IN_FORMAT_DOCUMENT = String.raw`format = """ $brand\ $model\ $thinking\ $directory\ $git_branch\ $git_status\ $activity\ $context\ $time"""`; var BUILT_IN_FORMAT = "$brand$model$thinking$directory$git_branch$git_status$activity$context$time"; var BUILT_IN_MODULES = Object.fromEntries( MODULE_DEFINITIONS.map(({ name, defaults, styleDefaults, displayDefaults, options }) => [ name, { ...defaults, formatAst: parseFormat(defaults.format), styles: { ...styleDefaults }, display: structuredClone(displayDefaults ?? []), options: Object.fromEntries( Object.entries(options ?? {}).map(([key, schema]) => [ key, cloneOptionValue(schema.default) ]) ) } ]) ); var BUILT_IN_CONFIG = { format: BUILT_IN_FORMAT, formatAst: parseFormat(BUILT_IN_FORMAT), palette: void 0, palettes: {}, modules: BUILT_IN_MODULES, extensionStatus: { separator: " \u2022 ", maxStatuses: 5, icons: {} } }; var BUILT_IN_EXAMPLE = `# Native Pi modules with Starship-compatible format and style syntax. ${BUILT_IN_FORMAT_DOCUMENT} `; var require2 = createRequire(import.meta.url); var parseTomlImplementation; function parseToml(document) { parseTomlImplementation ??= require2("smol-toml").parse; return parseTomlImplementation(document); } function settingsFilePath(agentDir) { return join(agentDir, CONFIG_FILE_NAME); } function loadStarshipConfig(settingsPath) { let rawDocument; try { rawDocument = readFileSync(settingsPath, "utf8"); } catch (error) { if (error.code === "ENOENT" && !existsSync(settingsPath)) { return { config: cloneBuiltInConfig(), source: "built-in", settingsPath, diagnostics: [] }; } return { config: cloneBuiltInConfig(), source: "built-in", settingsPath, diagnostics: [diagnostic("error", "", `Unable to read settings: ${formatError(error)}`)] }; } let parsed; try { parsed = parseToml(rawDocument); } catch (error) { return { config: cloneBuiltInConfig(), source: "built-in", settingsPath, rawDocument, diagnostics: [diagnostic("error", "", `Unable to parse TOML: ${formatError(error)}`)] }; } const normalized = normalizeConfig(parsed); return { ...normalized, source: "user", settingsPath, rawDocument }; } function normalizeConfig(value) { const config = cloneBuiltInConfig(); const diagnostics = []; if (!isRecord(value)) { return { config, diagnostics: [diagnostic("error", "", "Settings must contain a TOML table")] }; } const knownRoot = /* @__PURE__ */ new Set(["format", "palette", "palettes", ...MODULE_NAMES]); for (const key of Object.keys(value)) { if (!knownRoot.has(key)) diagnostics.push(unknownDiagnostic(key)); } if (value.format !== void 0) { if (typeof value.format !== "string") { diagnostics.push(typeDiagnostic("format", "string")); } else { try { config.formatAst = parseFormat(value.format); config.format = value.format; } catch (error) { diagnostics.push( diagnostic("warning", "format", `Invalid format; using built-in: ${formatError(error)}`) ); } } } if (value.palettes !== void 0) { if (!isRecord(value.palettes)) { diagnostics.push(typeDiagnostic("palettes", "table")); } else { for (const [paletteName, paletteValue] of Object.entries(value.palettes)) { if (!isRecord(paletteValue)) { diagnostics.push(typeDiagnostic(`palettes.${paletteName}`, "table")); continue; } const palette2 = {}; for (const [name, color] of Object.entries(paletteValue)) { if (typeof color !== "string" || !parseColor(color.toLowerCase())) { diagnostics.push( diagnostic( "warning", `palettes.${paletteName}.${name}`, "Palette colors must be named, ANSI 0-255, or #RRGGBB" ) ); continue; } setOwn(palette2, name, color); } setOwn(config.palettes, paletteName, palette2); } } } if (value.palette !== void 0) { if (typeof value.palette !== "string") diagnostics.push(typeDiagnostic("palette", "string")); else { config.palette = value.palette; if (!Object.hasOwn(config.palettes, value.palette)) { diagnostics.push( diagnostic("warning", "palette", `Unknown palette ${JSON.stringify(value.palette)}`) ); } } } for (const name of MODULE_NAMES) { const moduleValue = value[name]; if (moduleValue === void 0) continue; if (!isRecord(moduleValue)) { diagnostics.push(typeDiagnostic(name, "table")); continue; } normalizeModule(name, moduleValue, config, diagnostics); } validateFormatVariables( config.formatAst, /* @__PURE__ */ new Set([...MODULE_NAMES, "all"]), "format", diagnostics ); validateStyleVariables(config.formatAst, /* @__PURE__ */ new Set(), "format", diagnostics); const palette = activePalette(config); validateLiteralStyles(config.formatAst, palette, "format", diagnostics); for (const definition of MODULE_DEFINITIONS) { const name = definition.name; const module = config.modules[name]; validateFormatVariables( module.formatAst, new Set(MODULE_CONTENT_VARIABLES[name]), `${name}.format`, diagnostics ); validateStyleVariables( module.formatAst, new Set(definition.styleVariables ?? ["style"]), `${name}.format`, diagnostics ); validateLiteralStyles(module.formatAst, palette, `${name}.format`, diagnostics); if (definition.styleDefaults) { for (const field of Object.keys(definition.styleDefaults)) { validateModuleStyleField(name, field, module, palette, diagnostics); } } else if (!definition.displayDefaults) { validateModuleStyleField(name, "style", module, palette, diagnostics); } } return { config, diagnostics }; } function normalizeModule(name, value, config, diagnostics) { const definition = MODULE_DEFINITIONS.find((candidate) => candidate.name === name); if (!definition) return; const optionSchemas = definition.options ?? {}; const known = /* @__PURE__ */ new Set(["format", "symbol", "disabled", ...Object.keys(optionSchemas)]); if (definition.styleDefaults) { for (const field of Object.keys(definition.styleDefaults)) known.add(field); } else if (!definition.displayDefaults) known.add("style"); if (definition.displayDefaults) known.add("display"); if (name === "extension_status") { known.add("separator"); known.add("max_statuses"); known.add("icons"); } for (const key of Object.keys(value)) { if (!known.has(key)) diagnostics.push(unknownDiagnostic(`${name}.${key}`)); } const module = config.modules[name]; if (value.format !== void 0) { if (typeof value.format !== "string") diagnostics.push(typeDiagnostic(`${name}.format`, "string")); else { try { module.formatAst = parseFormat(value.format); module.format = value.format; } catch (error) { diagnostics.push( diagnostic( "warning", `${name}.format`, `Invalid format; using module default: ${formatError(error)}` ) ); } } } if (value.symbol !== void 0) { if (typeof value.symbol !== "string") { diagnostics.push(typeDiagnostic(`${name}.symbol`, "string")); } else module.symbol = value.symbol; } if (definition.styleDefaults) { for (const field of Object.keys(definition.styleDefaults)) { if (value[field] === void 0) continue; if (typeof value[field] !== "string") { diagnostics.push(typeDiagnostic(`${name}.${field}`, "string")); } else module.styles[field] = value[field]; } } else if (!definition.displayDefaults && value.style !== void 0) { if (typeof value.style !== "string") { diagnostics.push(typeDiagnostic(`${name}.style`, "string")); } else module.style = value.style; } if (definition.displayDefaults && value.display !== void 0) { module.display = normalizeDisplay( name, value.display, definition.displayDefaults, activePalette(config), diagnostics ); } if (value.disabled !== void 0) { if (typeof value.disabled !== "boolean") { diagnostics.push(typeDiagnostic(`${name}.disabled`, "boolean")); } else module.disabled = value.disabled; } for (const [key, schema] of Object.entries(optionSchemas)) { if (value[key] === void 0) continue; const normalized = normalizeModuleOption(value[key], schema); if (normalized.ok) module.options[key] = normalized.value; else diagnostics.push(diagnostic("warning", `${name}.${key}`, normalized.message)); } if (name !== "extension_status") return; if (value.separator !== void 0) { if (typeof value.separator !== "string") { diagnostics.push(typeDiagnostic("extension_status.separator", "string")); } else config.extensionStatus.separator = value.separator; } if (value.max_statuses !== void 0) { if (typeof value.max_statuses !== "number" || !Number.isInteger(value.max_statuses) || value.max_statuses < 0 || value.max_statuses > 100) { diagnostics.push( diagnostic( "warning", "extension_status.max_statuses", "Expected an integer from 0 through 100" ) ); } else config.extensionStatus.maxStatuses = value.max_statuses; } if (value.icons !== void 0) { if (!isRecord(value.icons)) diagnostics.push(typeDiagnostic("extension_status.icons", "table")); else { config.extensionStatus.icons = Object.fromEntries( Object.entries(value.icons).flatMap(([key, icon]) => { if (typeof icon === "string") return [[key, icon]]; diagnostics.push(typeDiagnostic(`extension_status.icons.${key}`, "string")); return []; }) ); } } } function validateConfigDocument(settingsPath, rawDocument) { let parsed; try { parsed = parseToml(rawDocument); } catch (error) { throw new Error(`Unable to parse TOML: ${formatError(error)}`); } const normalized = normalizeConfig(parsed); if (normalized.diagnostics.some((item) => item.severity === "error")) { throw new Error(normalized.diagnostics.map((item) => item.message).join("\n")); } return { ...normalized, source: "user", settingsPath, rawDocument }; } function atomicSaveConfigDocument(settingsPath, rawDocument, overrides = {}) { const validated = validateConfigDocument(settingsPath, rawDocument); return { ...validated, fileIdentity: atomicWriteConfigDocument(settingsPath, rawDocument, overrides) }; } function atomicRestoreConfigDocument(settingsPath, rawDocument, overrides = {}) { atomicWriteConfigDocument(settingsPath, rawDocument, overrides); } function removeConfigDocumentIfMatches(settingsPath, expectedRawDocument, expectedIdentity, overrides = {}) { const quarantinePath = join( dirname(settingsPath), `.${CONFIG_FILE_NAME}.${randomUUID()}.rollback` ); const before = lstatSync(settingsPath); if (before.dev !== expectedIdentity.dev || before.ino !== expectedIdentity.ino) { throw new Error("Starship settings changed concurrently; the newer file was preserved"); } renameSync(settingsPath, quarantinePath); const quarantined = lstatSync(quarantinePath); const quarantinedSavedFile = quarantined.isFile() && !quarantined.isSymbolicLink() && quarantined.dev === expectedIdentity.dev && quarantined.ino === expectedIdentity.ino; if (quarantinedSavedFile && readFileSync(quarantinePath, "utf8") === expectedRawDocument) { (overrides.rmSync ?? rmSync)(quarantinePath); return; } if (quarantinedSavedFile && !pathEntryExists(settingsPath)) { try { renameSync(quarantinePath, settingsPath); } catch { } } throw new Error("Starship settings changed concurrently; the newer file was preserved"); } function atomicWriteConfigDocument(settingsPath, rawDocument, overrides) { const fs = { mkdirSync, writeFileSync, renameSync, rmSync, ...overrides }; const replaceExisting = pathEntryExists(settingsPath); fs.mkdirSync(dirname(settingsPath), { recursive: true }); const tempPath = join(dirname(settingsPath), `.${CONFIG_FILE_NAME}.${randomUUID()}.tmp`); try { fs.writeFileSync(tempPath, rawDocument, { encoding: "utf8", flag: "wx" }); const info = lstatSync(tempPath); if (!replaceExisting && pathEntryExists(settingsPath)) { throw new Error(`${CONFIG_FILE_NAME} was created concurrently; reopen settings and retry.`); } fs.renameSync(tempPath, settingsPath); return { dev: info.dev, ino: info.ino }; } finally { try { fs.rmSync(tempPath, { force: true }); } catch { } } } function cloneBuiltInConfig() { return { ...BUILT_IN_CONFIG, formatAst: structuredClone(BUILT_IN_CONFIG.formatAst), palettes: Object.fromEntries( Object.entries(BUILT_IN_CONFIG.palettes).map(([name, colors]) => [name, { ...colors }]) ), modules: Object.fromEntries( MODULE_NAMES.map((name) => [ name, { ...BUILT_IN_CONFIG.modules[name], formatAst: structuredClone(BUILT_IN_CONFIG.modules[name].formatAst), styles: { ...BUILT_IN_CONFIG.modules[name].styles }, display: structuredClone(BUILT_IN_CONFIG.modules[name].display), options: structuredClone(BUILT_IN_CONFIG.modules[name].options) } ]) ), extensionStatus: { ...BUILT_IN_CONFIG.extensionStatus, icons: { ...BUILT_IN_CONFIG.extensionStatus.icons } } }; } function validateFormatVariables(ast, allowed, path, diagnostics) { for (const variable of formatVariables(ast)) { if (allowed.has(variable)) continue; diagnostics.push( diagnostic( "warning", path, `Unknown variable ${JSON.stringify(variable)} in ${path} was ignored` ) ); } } function validateStyleVariables(ast, allowed, path, diagnostics) { for (const variable of styleVariables(ast)) { if (allowed.has(variable)) continue; diagnostics.push( diagnostic( "warning", path, `Unknown style variable ${JSON.stringify(variable)} in ${path} was ignored` ) ); } } function validateModuleStyleField(name, field, module, palette, diagnostics) { const style = field === "style" ? module.style : module.styles[field]; if (style === void 0 || isValidStyle(style, palette)) return; diagnostics.push( diagnostic( "warning", `${name}.${field}`, `Invalid style ${JSON.stringify(style)}; using the module default` ) ); if (field === "style") module.style = BUILT_IN_CONFIG.modules[name].style; else { const fallback = BUILT_IN_CONFIG.modules[name].styles[field]; if (fallback !== void 0) module.styles[field] = fallback; } } function normalizeDisplay(name, value, defaults, palette, diagnostics) { if (!Array.isArray(value)) { diagnostics.push(typeDiagnostic(`${name}.display`, "array of tables")); return defaults.map((entry) => ({ ...entry })); } const result = []; for (const [index, entry] of value.entries()) { const path = `${name}.display.${index}`; if (!isRecord(entry)) { diagnostics.push(typeDiagnostic(path, "table")); continue; } for (const field of Object.keys(entry)) { if (!(/* @__PURE__ */ new Set(["threshold", "style", "hidden"])).has(field)) { diagnostics.push(unknownDiagnostic(`${path}.${field}`)); } } let valid = true; if (typeof entry.threshold !== "number" || !Number.isFinite(entry.threshold)) { diagnostics.push(diagnostic("warning", `${path}.threshold`, "Expected a finite number")); valid = false; } if (typeof entry.style !== "string" || !isValidStyle(entry.style, palette)) { diagnostics.push( diagnostic("warning", `${path}.style`, "Expected a valid Starship style string") ); valid = false; } if (typeof entry.hidden !== "boolean") { diagnostics.push(typeDiagnostic(`${path}.hidden`, "boolean")); valid = false; } if (valid) { result.push({ threshold: entry.threshold, style: entry.style, hidden: entry.hidden }); } } if (result.length > 0) return result; diagnostics.push( diagnostic("warning", `${name}.display`, "Expected at least one valid entry; using defaults") ); return defaults.map((entry) => ({ ...entry })); } function validateLiteralStyles(ast, palette, path, diagnostics) { for (const node of ast) { if (node.type === "group" && node.style.every((part) => part.type === "text")) { const style = node.style.map((part) => part.type === "text" ? part.value : "").join(""); if (!isValidStyle(style, palette)) { diagnostics.push( diagnostic( "warning", path, `Invalid literal style ${JSON.stringify(style)}; rendered unstyled` ) ); } } if (node.type === "group" || node.type === "conditional") { validateLiteralStyles(node.children, palette, path, diagnostics); } } } function normalizeModuleOption(value, schema) { switch (schema.kind) { case "string": return typeof value === "string" && (schema.allowEmpty !== false || value.length > 0) ? { ok: true, value } : { ok: false, message: "Expected a non-empty string; using the default value" }; case "string-enum": return typeof value === "string" && schema.values.includes(value) ? { ok: true, value } : { ok: false, message: `Expected one of: ${schema.values.join(", ")}; using the default value` }; case "boolean": return typeof value === "boolean" ? { ok: true, value } : { ok: false, message: "Expected boolean; using the default value" }; case "integer": return typeof value === "number" && Number.isInteger(value) && value >= schema.minimum && value <= schema.maximum ? { ok: true, value } : { ok: false, message: `Expected an integer from ${schema.minimum} through ${schema.maximum}; using the default value` }; case "string-array": { if (!Array.isArray(value) || value.some( (item) => typeof item !== "string" || item.length === 0 || !schema.allowNegative && item.startsWith("!") )) { return { ok: false, message: "Expected an array of valid strings; using the default value" }; } return { ok: true, value: [...value] }; } case "string-map": { if (!isRecord(value) || Object.values(value).some((item) => typeof item !== "string")) { return { ok: false, message: "Expected a table of strings; using the default value" }; } const result = {}; for (const [key, item] of Object.entries(value)) setOwn(result, key, item); return { ok: true, value: result }; } } } function cloneOptionValue(value) { return typeof value === "object" ? structuredClone(value) : value; } function setOwn(record, key, value) { Object.defineProperty(record, key, { value, writable: true, enumerable: true, configurable: true }); } function isRecord(value) { return typeof value === "object" && value !== null && !Array.isArray(value); } function typeDiagnostic(path, type) { return diagnostic("warning", path, `Expected ${type}; using the default value`); } function unknownDiagnostic(path) { return diagnostic("warning", path, `Unknown setting ${JSON.stringify(path)} was ignored`); } function diagnostic(severity, path, message) { return { severity, path, message }; } function pathEntryExists(path) { try { lstatSync(path); return true; } catch (error) { if (error.code === "ENOENT") return false; throw error; } } function formatError(error) { return error instanceof Error ? error.message : String(error); } function activePalette(config) { return ownPalette(config.palettes, config.palette) ?? {}; } function ownPalette(palettes, name) { return name !== void 0 && Object.hasOwn(palettes, name) ? palettes[name] : void 0; } // src/modules/inspection.ts import { stripVTControlCharacters } from "node:util"; // src/modules/render.ts import { visibleWidth } from "@earendil-works/pi-tui"; function renderStatusline(config, runtime, width = 80) { const palette = activePalette(config); const modules = {}; const layoutModules = {}; for (const name of MODULE_NAMES) { modules[name] = []; layoutModules[name] = []; } for (const definition of MODULE_DEFINITIONS) { const name = definition.name; const module = config.modules[name]; const values = definition.values(valueContext(config, name, runtime)); if (!values) continue; const styleVariables2 = definition.resolveStyleVariables ? definition.resolveStyleVariables({ runtime, values, style: module.style, styles: module.styles, display: module.display }) : { style: module.style }; if (!styleVariables2) continue; const contentValues = Object.fromEntries( definition.variables.flatMap( (variable) => variable !== "symbol" && Object.hasOwn(values, variable) ? [[variable, values[variable]]] : [] ) ); const rendered = renderModule(module, contentValues, styleVariables2, palette); modules[name] = rendered; layoutModules[name] = definition.layout === "fill" && !module.disabled ? [{ type: "fill", pattern: rendered }] : rendered; } const explicitModules = formatVariables(config.formatAst); const all = MODULE_NAMES.flatMap( (name) => explicitModules.has(name) || config.modules[name].disabled ? [] : layoutModules[name] ); const rootVariables = { all }; for (const name of MODULE_NAMES) rootVariables[name] = layoutModules[name]; const layout = renderFormat(config.formatAst, { variables: rootVariables, palette }); const chunks = resolveFillLayout(layout, width); return { ansi: renderChunksToAnsi(chunks), chunks, modules }; } function valueContext(config, name, runtime) { return { runtime, symbol: config.modules[name].symbol, options: config.modules[name].options, extensionStatus: config.extensionStatus }; } function renderModule(module, values, styleVariables2, palette) { if (module.disabled) return []; return renderFormat(module.formatAst, { variables: { symbol: module.symbol, ...values }, styleVariables: styleVariables2, palette }).filter((chunk) => !isFillChunk(chunk)); } function reachableModuleRequirements(config) { const rootVariables = formatVariables(config.formatAst); const includeAll = rootVariables.has("all"); const requirements = /* @__PURE__ */ new Map(); for (const definition of MODULE_DEFINITIONS) { if (config.modules[definition.name].disabled) continue; if (!includeAll && !rootVariables.has(definition.name)) continue; requirements.set(definition.name, formatVariables(config.modules[definition.name].formatAst)); } return requirements; } function resolveFillLayout(layout, width) { const lines = splitLogicalLines(layout); const resolved = []; for (const [lineIndex, line] of lines.entries()) { const fills = line.filter(isFillChunk); const fixedWidth = line.reduce( (total, chunk) => total + (isFillChunk(chunk) ? 0 : visibleWidth(renderChunksToAnsi([chunk]))), 0 ); const remaining = Math.max(0, width - fixedWidth); const base = fills.length > 0 ? Math.floor(remaining / fills.length) : 0; let remainder = fills.length > 0 ? remaining % fills.length : 0; for (const chunk of line) { if (!isFillChunk(chunk)) { resolved.push(chunk); continue; } const allocation = base + (remainder > 0 ? 1 : 0); if (remainder > 0) remainder -= 1; resolved.push(...expandFill(chunk.pattern, allocation)); } if (lineIndex < lines.length - 1) resolved.push({ text: "\n" }); } return resolved; } function splitLogicalLines(layout) { const lines = [[]]; for (const chunk of layout) { if (isFillChunk(chunk)) { lines.at(-1)?.push(chunk); continue; } const parts = chunk.text.split("\n"); for (const [index, text] of parts.entries()) { if (text) lines.at(-1)?.push({ ...chunk, text }); if (index < parts.length - 1) lines.push([]); } } return lines; } function expandFill(pattern, width) { if (width <= 0) return []; const patternWidth = visibleWidth(renderChunksToAnsi(pattern)); if (patternWidth <= 0) return [{ text: " ".repeat(width), style: pattern[0]?.style }]; const repetitions = Math.floor(width / patternWidth); const result = []; for (let index = 0; index < repetitions; index += 1) { result.push(...pattern.map((chunk) => ({ ...chunk }))); } const remainder = width - repetitions * patternWidth; if (remainder > 0) result.push({ text: " ".repeat(remainder), style: pattern.at(-1)?.style }); return result; } // src/modules/inspection.ts function inspectStatuslineModules(config, runtime, width = 80) { const rendered = renderStatusline(config, runtime, width); const requirements = reachableModuleRequirements(config); const rootVariables = formatVariables(config.formatAst); const includeAll = rootVariables.has("all"); const modules = MODULE_DEFINITIONS.map((definition) => { const module = config.modules[definition.name]; const reachable = requirements.has(definition.name); const preview = plainPreview( rendered.modules[definition.name].map((chunk) => chunk.text).join("") ); const state = module.disabled ? "Disabled" : !reachable ? "Not in format" : preview.length > 0 ? "Showing" : "Empty"; return moduleInspection( config, definition, state, preview, includeAll || rootVariables.has(definition.name), reachable ); }); return { modules, showing: modules.filter((module) => module.state === "Showing") }; } function inspectUnavailableModules(config) { const requirements = reachableModuleRequirements(config); const rootVariables = formatVariables(config.formatAst); const includeAll = rootVariables.has("all"); return { modules: MODULE_DEFINITIONS.map((definition) => { const module = config.modules[definition.name]; const reachable = requirements.has(definition.name); const state = module.disabled ? "Disabled" : reachable ? "Unavailable" : "Not in format"; return moduleInspection( config, definition, state, "", includeAll || rootVariables.has(definition.name), reachable ); }), showing: [] }; } function moduleInspection(config, definition, state, preview, rootReferenced, reachable) { const module = config.modules[definition.name]; return { name: definition.name, description: definition.description, state, preview, variables: [...definition.variables], styleFields: ["style", ...Object.keys(module.styles)], displayRules: module.display.map( (rule) => `${rule.threshold}: ${rule.hidden ? "hidden" : rule.style || "unstyled"}` ), rootReferenced, reachable, reason: inspectionReason(state) }; } function inspectionReason(state) { switch (state) { case "Showing": return "Rendered in the current footer."; case "Empty": return "Referenced by the root format, but the current snapshot produced no output."; case "Disabled": return "Disabled by this module's configuration."; case "Not in format": return "Not referenced by the root format or $all."; case "Unavailable": return "Current footer inspection is unavailable; no collector failure was inferred."; } } function plainPreview(value) { return Array.from(stripVTControlCharacters(value), (character) => { const codePoint = character.codePointAt(0) ?? 0; if (character === "\n") return character; return codePoint <= 31 || codePoint >= 127 && codePoint <= 159 ? " " : character; }).join(""); } export { STARSHIP_SUBCOMMANDS, completeStarshipArguments, MODULE_DEFINITIONS, BUILT_IN_EXAMPLE, settingsFilePath, loadStarshipConfig, validateConfigDocument, atomicSaveConfigDocument, atomicRestoreConfigDocument, removeConfigDocumentIfMatches, renderStatusline, reachableModuleRequirements, inspectStatuslineModules, inspectUnavailableModules }; //# sourceMappingURL=chunk-GS2UE7CF.ts.map