// @generated by scripts/build-runtime.mjs; do not edit. // @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader. import { PRIVATE_STYLE_SELECTOR } from "./chunks/chunk-2BNGNI6Y.js"; import { createFileSystem } from "./chunks/chunk-PV5KIAAP.js"; import { completeStarshipArguments, inspectStatuslineModules, loadStarshipConfig, reachableModuleRequirements, renderStatusline, settingsFilePath } from "./chunks/chunk-GS2UE7CF.js"; // src/pi-starship.ts import { homedir, hostname, userInfo } from "node:os"; import { getAgentDir } from "@earendil-works/pi-coding-agent"; import { wrapTextWithAnsi } from "@earendil-works/pi-tui"; // src/modules/git/runtime.ts import { existsSync, readFileSync } from "node:fs"; import { basename, isAbsolute, join, resolve } from "node:path"; var GIT_TIMEOUT_MS = 3e3; async function readGitSnapshot(pi, cwd, options) { const statusPromise = pi.exec( "git", [ "--no-optional-locks", "status", "--porcelain=v2", "--branch", "--show-stash", "--untracked-files=normal" ], { cwd, signal: options.signal, timeout: GIT_TIMEOUT_MS } ); const metadataPromise = pi.exec( "git", ["rev-parse", "--path-format=absolute", "--show-toplevel", "--git-common-dir", "--git-dir"], { cwd, signal: options.signal, timeout: GIT_TIMEOUT_MS } ); const metricsPromise = options.includeMetrics ? pi.exec("git", ["--no-optional-locks", "diff", "--shortstat", "HEAD", "--"], { cwd, signal: options.signal, timeout: GIT_TIMEOUT_MS }) : Promise.resolve(void 0); const tagPromise = options.includeTag ? pi.exec("git", ["describe", "--tags", "--exact-match", "HEAD"], { cwd, signal: options.signal, timeout: GIT_TIMEOUT_MS }) : Promise.resolve(void 0); const [statusResult, metadataResult, metricsResult, tagResult] = await Promise.all([ statusPromise, metadataPromise, metricsPromise, tagPromise ]); if (statusResult.code !== 0 || statusResult.killed) return void 0; const parsed = parseGitStatusPorcelainV2(statusResult.stdout); const metadata = metadataResult.code === 0 && !metadataResult.killed ? parseGitRepositoryMetadata(metadataResult.stdout) : void 0; const commit = withTag(parsed.commit, tagResult); const metrics = metricsResult && metricsResult.code === 0 && !metricsResult.killed ? parseGitDiffShortstat(metricsResult.stdout) : void 0; return { ...parsed, root: metadata?.root, commit, state: metadata ? parseGitState(metadata.gitDirectory) : void 0, metrics, worktree: metadata?.worktree }; } function parseGitStatusPorcelainV2(output) { const status = emptyGitStatus(); let branchName; let commitHash; let upstream; for (const line of output.split(/\r?\n/u)) { if (!line) continue; if (line.startsWith("# branch.oid ")) { const value = line.slice("# branch.oid ".length).trim(); if (value && value !== "(initial)") commitHash = value; continue; } if (line.startsWith("# branch.head ")) { branchName = line.slice("# branch.head ".length).trim(); continue; } if (line.startsWith("# branch.upstream ")) { upstream = line.slice("# branch.upstream ".length).trim(); continue; } if (line.startsWith("# branch.ab ")) { const match = /^# branch\.ab \+(\d+) -(\d+)$/u.exec(line); status.ahead = match?.[1] ? Number(match[1]) : 0; status.behind = match?.[2] ? Number(match[2]) : 0; continue; } if (line.startsWith("# stash ")) { const count = Number(line.slice("# stash ".length)); status.stashed = Number.isSafeInteger(count) && count > 0 ? count : 0; continue; } addPorcelainV2Status(status, line); } finalizeStatus(status); const detached = branchName === "(detached)"; const remote = splitUpstream(upstream); const branch = branchName ? { name: detached ? "HEAD" : branchName, ...remote, detached } : void 0; const commit = commitHash ? { hash: commitHash, detached } : void 0; return { branch, commit, status }; } function parseGitDiffShortstat(output) { return { added: parseShortstatMetric(output, "insertion(+)", "insertions(+)") ?? 0, deleted: parseShortstatMetric(output, "deletion(-)", "deletions(-)") ?? 0 }; } function parseShortstatMetric(output, singularLabel, pluralLabel) { for (const field of output.split(",")) { const trimmed = field.trim(); const separator = trimmed.indexOf(" "); if (separator <= 0) continue; const label = trimmed.slice(separator + 1).trimStart(); if (label !== singularLabel && label !== pluralLabel) continue; const countText = trimmed.slice(0, separator); for (const character of countText) { if (character < "0" || character > "9") return void 0; } const count = Number(countText); return Number.isSafeInteger(count) ? count : void 0; } return void 0; } function parseGitState(gitDirectory) { const rebaseMerge = join(gitDirectory, "rebase-merge"); if (existsSync(rebaseMerge)) return operationWithProgress("REBASING", rebaseMerge, "msgnum", "end"); const rebaseApply = join(gitDirectory, "rebase-apply"); if (existsSync(rebaseApply)) { const state = existsSync(join(rebaseApply, "applying")) ? "AM" : existsSync(join(rebaseApply, "rebasing")) ? "REBASING" : "AM/REBASE"; return operationWithProgress(state, rebaseApply, "next", "last"); } if (existsSync(join(gitDirectory, "MERGE_HEAD"))) return { state: "MERGING" }; if (existsSync(join(gitDirectory, "REVERT_HEAD"))) return { state: "REVERTING" }; if (existsSync(join(gitDirectory, "CHERRY_PICK_HEAD"))) return { state: "CHERRY-PICKING" }; if (existsSync(join(gitDirectory, "BISECT_LOG"))) return { state: "BISECTING" }; return void 0; } function parseGitRepositoryMetadata(output) { const lines = output.trimEnd().split(/\r?\n/u); if (lines.length !== 3) return void 0; const [path, commonDir, gitDirectory] = lines; if (!path || !commonDir || !gitDirectory) return void 0; if (![path, commonDir, gitDirectory].every(isAbsolute)) return void 0; return { root: path, gitDirectory, worktree: samePath(commonDir, gitDirectory) ? void 0 : { name: basename(path) || path, path } }; } function addPorcelainV2Status(status, line) { const kind = line[0]; if (kind === "?") { status.untracked += 1; return; } if (kind === "u") { status.conflicted += 1; return; } if (kind !== "1" && kind !== "2") return; const indexStatus = line[2] ?? "."; const worktreeStatus = line[3] ?? "."; addNormalStatus(status, indexStatus, worktreeStatus); if (kind === "2") status.renamed += 1; } function addNormalStatus(status, indexStatus, worktreeStatus) { if (indexStatus === "A") status.indexAdded += 1; if (indexStatus === "D") status.indexDeleted += 1; if (indexStatus === "M") status.indexModified += 1; if (indexStatus === "T") status.indexTypechanged += 1; if (worktreeStatus === "A") status.worktreeAdded += 1; if (worktreeStatus === "D") status.worktreeDeleted += 1; if (worktreeStatus === "M") status.worktreeModified += 1; if (worktreeStatus === "T") status.worktreeTypechanged += 1; } function finalizeStatus(status) { status.deleted = status.worktreeDeleted + status.indexDeleted; status.modified = status.worktreeModified + status.worktreeAdded; status.staged = status.indexModified + status.indexAdded + status.indexTypechanged; status.typechanged = status.worktreeTypechanged; } function emptyGitStatus() { return { ahead: 0, behind: 0, stashed: 0, conflicted: 0, deleted: 0, renamed: 0, modified: 0, staged: 0, typechanged: 0, untracked: 0, worktreeAdded: 0, worktreeDeleted: 0, worktreeModified: 0, worktreeTypechanged: 0, indexAdded: 0, indexDeleted: 0, indexModified: 0, indexTypechanged: 0 }; } function splitUpstream(upstream) { if (!upstream) return {}; const separator = upstream.indexOf("/"); if (separator <= 0 || separator === upstream.length - 1) return { remoteBranch: upstream }; return { remoteName: upstream.slice(0, separator), remoteBranch: upstream.slice(separator + 1) }; } function withTag(commit, result) { if (!commit || !result || result.code !== 0 || result.killed) return commit; const tag = result.stdout.trim(); return tag ? { ...commit, tag: tag.split(/\r?\n/u)[0] } : commit; } function operationWithProgress(state, directory, currentName, totalName) { const progressCurrent = readPositiveInteger(join(directory, currentName)); const progressTotal = readPositiveInteger(join(directory, totalName)); return progressCurrent !== void 0 && progressTotal !== void 0 ? { state, progressCurrent, progressTotal } : { state }; } function readPositiveInteger(path) { try { const value = Number(readFileSync(path, "utf8").trim()); return Number.isSafeInteger(value) && value > 0 ? value : void 0; } catch { return void 0; } } function samePath(left, right) { const normalizedLeft = resolve(left); const normalizedRight = resolve(right); return process.platform === "win32" ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase() : normalizedLeft === normalizedRight; } function gitSnapshotEqual(left, right) { return JSON.stringify(left) === JSON.stringify(right); } // src/runtime/command.ts import { spawn } from "node:child_process"; var FORCE_KILL_DELAY_MS = 250; var execWorkspaceCommand = async (command, args, options) => new Promise((resolve2) => { if (options.signal?.aborted) { resolve2({ stdout: "", stderr: "", code: 1, killed: true }); return; } let child; try { child = spawn(command, args, { cwd: options.cwd, detached: process.platform !== "win32", env: explicitEnvironment(options.environment), shell: false, stdio: ["ignore", "pipe", "pipe"], windowsHide: true }); } catch { resolve2({ stdout: "", stderr: "", code: 1, killed: false }); return; } const stdout = []; const stderr = []; let outputBytes = 0; let killed = false; let settled = false; let forceKillTimer; let timeoutTimer; const finish = (code) => { if (settled) return; settled = true; if (timeoutTimer) clearTimeout(timeoutTimer); if (forceKillTimer) clearTimeout(forceKillTimer); options.signal?.removeEventListener("abort", terminate); resolve2({ stdout: Buffer.concat(stdout).toString("utf8"), stderr: Buffer.concat(stderr).toString("utf8"), code, killed }); }; const signalProcessTree = (signal) => { const pid = child.pid; if (pid === void 0) return; if (process.platform !== "win32") { try { process.kill(-pid, signal); return; } catch { child.kill(signal); return; } } try { const killer = spawn("taskkill.exe", ["/pid", String(pid), "/t", "/f"], { stdio: "ignore", windowsHide: true }); killer.once("error", () => child.kill(signal)); killer.unref(); } catch { child.kill(signal); } }; function terminate() { if (killed || settled) return; killed = true; signalProcessTree("SIGTERM"); forceKillTimer = setTimeout(() => { signalProcessTree("SIGKILL"); child.stdout?.destroy(); child.stderr?.destroy(); finish(1); }, FORCE_KILL_DELAY_MS); } const collect = (target) => (data) => { const chunk = Buffer.isBuffer(data) ? data : Buffer.from(data); const remaining = Math.max(0, options.maxOutputBytes - outputBytes); if (remaining > 0) target.push(chunk.subarray(0, remaining)); outputBytes += chunk.byteLength; if (outputBytes > options.maxOutputBytes) terminate(); }; child.stdout?.on("data", collect(stdout)); child.stderr?.on("data", collect(stderr)); child.once("error", () => { if (!killed) finish(1); }); child.once("close", (code) => { if (!killed) finish(code ?? 1); }); options.signal?.addEventListener("abort", terminate, { once: true }); if (options.signal?.aborted) terminate(); timeoutTimer = setTimeout(terminate, options.timeout); }); function explicitEnvironment(source) { const result = /* @__PURE__ */ Object.create(null); for (const [name, value] of Object.entries(source)) { if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(name) || value === void 0 || value.includes("\0")) { continue; } Object.defineProperty(result, name, { value, writable: true, enumerable: true, configurable: true }); } return result; } // src/runtime/github-pr.ts var GH_TIMEOUT_MS = 1e4; var MAX_GH_OUTPUT_BYTES = 128 * 1024; var MAX_URL_LENGTH = 4096; var MAX_CHECKS = 1e3; var MAX_FIELD_LENGTH = 128; var RFC3339_TIMESTAMP = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d{1,9})?(?:Z|[+-](\d{2}):(\d{2}))$/u; var TERMINAL_PR_LIFETIME_MS = 24 * 60 * 60 * 1e3; var GH_PR_FIELDS = [ "number", "isDraft", "url", "state", "closedAt", "mergedAt", "reviewDecision", "statusCheckRollup" ]; function ghPrViewInvocation(platform = process.platform) { return { command: platform === "win32" ? "gh.exe" : "gh", args: ["pr", "view", "--json", GH_PR_FIELDS.join(",")] }; } function githubPrEnvironment(source = process.env) { const environment = /* @__PURE__ */ Object.create(null); for (const [name, value] of Object.entries(source)) { const normalized = name.toUpperCase(); if (normalized === "GH_HOST" || normalized === "GH_REPO") continue; Object.defineProperty(environment, name, { value, writable: true, enumerable: true, configurable: true }); } return environment; } async function queryGithubPr(exec, cwd, signal, now = Date.now(), options = {}) { const invocation = ghPrViewInvocation(options.platform); try { const result = await exec(invocation.command, invocation.args, { cwd, signal, timeout: GH_TIMEOUT_MS, maxOutputBytes: MAX_GH_OUTPUT_BYTES, environment: githubPrEnvironment(options.environment) }); if (result.killed || result.code !== 0) return void 0; if (Buffer.byteLength(result.stdout) > MAX_GH_OUTPUT_BYTES) return void 0; return buildGithubPrSnapshot(JSON.parse(result.stdout), now); } catch { return void 0; } } function buildGithubPrSnapshot(value, now = Date.now()) { try { const pr = record(value); const number = positiveInteger(pr.number); const isDraft = requiredBoolean(pr.isDraft); const url = requiredBoundedString(pr.url, MAX_URL_LENGTH, true); const state = pullRequestState(pr.state); const closedAt = optionalTimestamp(pr.closedAt); const mergedAt = optionalTimestamp(pr.mergedAt); const reviewDecision = reviewDecisionValue(pr.reviewDecision); const checks = summarizeChecks(pr.statusCheckRollup); const expiresAt = terminalExpiry(state, closedAt, mergedAt); if (state !== "OPEN" && (expiresAt === void 0 || now >= expiresAt)) return void 0; const presentationState = state === "MERGED" ? "merged" : state === "CLOSED" ? "closed" : isDraft ? "draft" : "open"; const checksText = formatChecks(checks); const review = formatReview(reviewDecision); const status = compactStatus(presentationState, checks, checksText, review); const snapshot = { number: String(number), link: osc8Link(url, `#${number}`), state: presentationState, checks: checksText, review, status, ...expiresAt === void 0 ? {} : { expiresAt } }; return Object.freeze(snapshot); } catch { return void 0; } } function githubPrSnapshotEqual(left, right) { return JSON.stringify(left) === JSON.stringify(right); } function terminalExpiry(state, closedAt, mergedAt) { if (state === "OPEN") return void 0; const terminalAt = state === "MERGED" ? mergedAt : closedAt; return terminalAt === void 0 ? void 0 : terminalAt + TERMINAL_PR_LIFETIME_MS; } function summarizeChecks(value) { if (!Array.isArray(value) || value.length > MAX_CHECKS) throw new Error("Invalid PR checks"); const summary = { failed: 0, pending: 0, total: value.length }; for (const item of value) { const check = record(item); const state = optionalUppercase(check.state); const status = optionalUppercase(check.status); const conclusion = optionalUppercase(check.conclusion); if (state === "SUCCESS") continue; if (state === "FAILURE" || state === "ERROR") { summary.failed += 1; continue; } if (state === "PENDING" || state === "EXPECTED") { summary.pending += 1; continue; } if (status && status !== "COMPLETED") { summary.pending += 1; continue; } if (conclusion === "SUCCESS" || conclusion === "SKIPPED" || conclusion === "NEUTRAL") { continue; } if (conclusion === "FAILURE" || conclusion === "CANCELLED" || conclusion === "TIMED_OUT" || conclusion === "ACTION_REQUIRED" || conclusion === "STARTUP_FAILURE") { summary.failed += 1; continue; } summary.pending += 1; } return summary; } function formatChecks(checks) { if (checks.total === 0) return "-"; const passed = checks.total - checks.failed - checks.pending; return [ compactCount("\u2713", passed), compactCount("\xD7", checks.failed), compactCount("\u2026", checks.pending) ].filter(Boolean).join(" "); } function compactCount(symbol, count) { return count > 0 ? `${symbol}${count}` : ""; } function formatReview(review) { if (review === "APPROVED") return "R\u2713"; if (review === "CHANGES_REQUESTED") return "R\xD7"; if (review === "REVIEW_REQUIRED") return "R?"; return ""; } function compactStatus(state, checks, checksText, review) { if (state === "merged") return "M"; if (state === "closed") return "C"; if (state === "draft") return "D"; if (checks.failed > 0) return compactCount("\xD7", checks.failed); if (review === "R\xD7") return review; if (checks.pending > 0) return compactCount("\u2026", checks.pending); if (review === "R\u2713" || review === "R?") return review; return checksText; } function osc8Link(value, label) { if (hasTerminalControls(value)) return label; try { const url = new URL(value); if (url.protocol !== "http:" && url.protocol !== "https:" || url.username || url.password) { return label; } const normalized = url.toString(); if (normalized.length > MAX_URL_LENGTH || hasTerminalControls(normalized)) return label; return `\x1B]8;;${normalized}\x07${label}\x1B]8;;\x07`; } catch { return label; } } function hasTerminalControls(value) { return Array.from(value).some((character) => { const codePoint = character.codePointAt(0) ?? 0; return codePoint <= 31 || codePoint >= 127 && codePoint <= 159 || codePoint >= 55296 && codePoint <= 57343; }); } function record(value) { if (typeof value !== "object" || value === null || Array.isArray(value)) { throw new Error("Expected PR object"); } return value; } function positiveInteger(value) { if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) { throw new Error("Invalid PR number"); } return value; } function requiredBoolean(value) { if (typeof value !== "boolean") throw new Error("Invalid PR boolean"); return value; } function requiredBoundedString(value, maxLength, allowEmpty = false) { if (typeof value !== "string" || value.length > maxLength || !allowEmpty && value.length === 0) { throw new Error("Invalid PR string"); } return value; } function optionalUppercase(value) { if (value === null || value === void 0 || value === "") return void 0; return requiredBoundedString(value, MAX_FIELD_LENGTH).toUpperCase(); } function optionalTimestamp(value) { if (value === null || value === void 0 || value === "") return void 0; const text = requiredBoundedString(value, MAX_FIELD_LENGTH); const match = RFC3339_TIMESTAMP.exec(text); if (!match) throw new Error("Invalid PR timestamp"); const [ , yearText, monthText, dayText, hourText, minuteText, secondText, offsetHourText, offsetMinuteText ] = match; const year = Number(yearText); const month = Number(monthText); const day = Number(dayText); const hour = Number(hourText); const minute = Number(minuteText); const second = Number(secondText); const offsetHour = Number(offsetHourText ?? 0); const offsetMinute = Number(offsetMinuteText ?? 0); if (month < 1 || month > 12 || day < 1 || day > daysInMonth(year, month) || hour > 23 || minute > 59 || second > 59 || offsetHour > 23 || offsetMinute > 59) { throw new Error("Invalid PR timestamp"); } const timestamp = Date.parse(text); if (!Number.isFinite(timestamp)) throw new Error("Invalid PR timestamp"); return timestamp; } function daysInMonth(year, month) { if (month === 2) { const leapYear = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0); return leapYear ? 29 : 28; } return month === 4 || month === 6 || month === 9 || month === 11 ? 30 : 31; } function pullRequestState(value) { if (value === "OPEN" || value === "CLOSED" || value === "MERGED") return value; throw new Error("Invalid PR state"); } function reviewDecisionValue(value) { if (value === null || value === void 0 || value === "" || value === "UNKNOWN") return ""; if (value === "APPROVED" || value === "CHANGES_REQUESTED" || value === "REVIEW_REQUIRED") { return value; } throw new Error("Invalid PR review decision"); } // src/runtime/refresh-controller.ts var AsyncRefreshController = class { constructor(options) { this.options = options; } options; generation; requestId = 0; active; pending; current; start(generation) { this.cancelActive("Refresh generation replaced"); this.generation = generation; this.requestId += 1; this.pending = void 0; this.current = void 0; } clear() { this.current = void 0; } request(input) { if (this.generation === void 0) return; const request = { generation: this.generation, requestId: ++this.requestId, input }; if (this.active) { this.pending = request; return; } this.run(request); } stop() { this.generation = void 0; this.requestId += 1; this.pending = void 0; this.current = void 0; this.cancelActive("Refresh controller stopped"); } run(request) { if (!this.isCurrentTarget(request)) return; const active = { request, controller: new AbortController() }; this.active = active; void this.options.read(request.input, active.controller.signal).then((snapshot) => { if (this.active !== active || !this.isCurrentRequest(request)) return; if (this.options.equal(this.current, snapshot)) return; this.current = snapshot; this.options.publish(snapshot); }).catch((error) => { if (this.active === active && this.isCurrentRequest(request) && !active.controller.signal.aborted) { this.options.onError?.(error); } }).finally(() => { if (this.active !== active) return; this.active = void 0; const pending = this.pending; this.pending = void 0; if (pending) this.run(pending); }); } cancelActive(reason) { this.active?.controller.abort(new DOMException(reason, "AbortError")); } isCurrentTarget(request) { return this.generation !== void 0 && request.generation === this.generation; } isCurrentRequest(request) { return this.isCurrentTarget(request) && request.requestId === this.requestId; } }; // src/runtime/workspace.ts async function collectWorkspaceSnapshot(input) { const requirements = reachableModuleRequirements(input.config); if (input.signal?.aborted || !hasWorkspaceRequirement(requirements)) return freezeSnapshot({}); const fs = createFileSystem(input); let listing; const context = { input, fs, requirements, entries() { listing ??= fs.readDirectory(input.cwd); return listing; }, options(name) { return input.config.modules[name].options; }, needs(name, variable) { const variables = requirements.get(name); return Boolean(variables && (variable === void 0 || variables.has(variable))); } }; const modules = {}; if (requirements.has("package")) { const { collectPackage } = await import("./chunks/package-WM7PK5OH.js"); if (input.signal?.aborted) return freezeSnapshot({}); const packageValues = await collectPackage(context); if (input.signal?.aborted) return freezeSnapshot({}); if (packageValues) modules.package = packageValues; } for (const descriptor of COLLECTOR_GROUPS) { if (!descriptor.modules.some((name) => requirements.has(name))) continue; if (input.signal?.aborted) return freezeSnapshot({}); const collector = await descriptor.load(); if (input.signal?.aborted) return freezeSnapshot({}); mergeModules(modules, await collector(context)); } return input.signal?.aborted ? freezeSnapshot({}) : freezeSnapshot(modules); } function workspaceSnapshotEqual(left, right) { return JSON.stringify(left) === JSON.stringify(right); } function hasWorkspaceRequirement(requirements) { return [...requirements.keys()].some((name) => !BUILT_IN_ONLY_MODULES.has(name)); } var COLLECTOR_GROUPS = [ { modules: ["nodejs", "python", "rust", "golang", "bun", "deno"], load: async () => (await import("./chunks/languages-RGO3PWDR.js")).collectLanguages }, { modules: ["mise", "direnv", "pixi", "conda", "nix_shell", "guix_shell"], load: async () => (await import("./chunks/development-N7EARF34.js")).collectDevelopment }, { modules: ["docker_context", "kubernetes", "terraform"], load: async () => (await import("./chunks/deployment-RDEDDCLG.js")).collectDeployment }, { modules: ["aws", "gcloud", "azure", "openstack"], load: async () => (await import("./chunks/cloud-RFUL56GP.js")).collectCloud }, { modules: ["container", "hostname", "os", "username"], load: async () => (await import("./chunks/execution-YGXH4DK6.js")).collectExecution } ]; var BUILT_IN_ONLY_MODULES = /* @__PURE__ */ new Set([ "brand", "provider", "model", "thinking", "directory", "git_worktree", "git_branch", "github_pr", "git_commit", "git_state", "git_metrics", "git_status", "activity", "context", "tokens", "cost", "time", "turn", "fill", "extension_status" ]); function mergeModules(target, source) { for (const [name, values] of Object.entries(source)) { Object.defineProperty(target, name, { value: values, writable: true, enumerable: true, configurable: true }); } } function freezeSnapshot(modules) { const styleSelectors = {}; for (const [name, values] of Object.entries(modules)) { const selector = Object.hasOwn(values, PRIVATE_STYLE_SELECTOR) ? values[PRIVATE_STYLE_SELECTOR] : void 0; delete values[PRIVATE_STYLE_SELECTOR]; if (selector !== void 0) styleSelectors[name] = selector; Object.freeze(values); } return Object.freeze({ modules: Object.freeze(modules), ...Object.keys(styleSelectors).length > 0 ? { styleSelectors: Object.freeze(styleSelectors) } : {} }); } // src/usage.ts function summarizeFooterUsage(entries) { const totals = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 }; for (const entry of entries) { let usage; if (entry.type === "message" && entry.message.role === "assistant") { usage = entry.message.usage; const input = usage.input ?? 0; const cacheRead = usage.cacheRead ?? 0; const cacheWrite = usage.cacheWrite ?? 0; const promptTokens = input + cacheRead + cacheWrite; totals.latestCacheHitRate = promptTokens > 0 ? cacheRead / promptTokens * 100 : void 0; } else if (entry.type === "message" && entry.message.role === "toolResult") { usage = entry.message.usage; } else if (entry.type === "compaction" || entry.type === "branch_summary") { usage = entry.usage; } if (!usage) continue; totals.input += usage.input ?? 0; totals.output += usage.output ?? 0; totals.cacheRead += usage.cacheRead ?? 0; totals.cacheWrite += usage.cacheWrite ?? 0; totals.cost += usage.cost?.total ?? 0; } return totals; } // src/pi-starship.ts var REFRESH_INTERVAL_MS = 3e4; var GITHUB_PR_REFRESH_INTERVAL_MS = 6e4; var EVENT_DEBOUNCE_MS = 250; var MAX_TIMER_DELAY_MS = 2147483647; var starshipCommandsPromise; function loadStarshipCommands() { if (!starshipCommandsPromise) { starshipCommandsPromise = import("./chunks/commands-X3VM2UGT.js").catch((error) => { starshipCommandsPromise = void 0; throw error; }); } return starshipCommandsPromise; } function piStarship(pi, options = {}) { let loaded; let loadedRevision = 0; let previewLoaded; const runtime = { activeTools: /* @__PURE__ */ new Map(), isStreaming: false, thinkingLevel: "off" }; let sessionGeneration = 0; let menuController = new AbortController(); let sessionOwner; let activeTarget; let eventDebounceTimer; let githubPrRefreshTimer; let githubPrExpiryTimer; let githubPrGeneration = 0; const refresh = () => runtime.requestRender?.(); const githubPrExec = options.githubPrExec ?? execWorkspaceCommand; const gitController = new AsyncRefreshController({ async read(input, signal) { const requirements = reachableModuleRequirements(input.config); const gitReachable = [...requirements.keys()].some((name) => name.startsWith("git_")) || requirements.has("directory") && input.config.modules.directory.options.truncate_to_repo === true; if (!gitReachable) return void 0; try { return await readGitSnapshot(pi, input.cwd, { includeMetrics: requirements.has("git_metrics"), includeTag: requirements.get("git_commit")?.has("tag") ?? false, signal }); } catch { return void 0; } }, equal: gitSnapshotEqual, publish(snapshot) { runtime.git = snapshot; refresh(); } }); const githubPrController = new AsyncRefreshController({ read: (input, signal) => queryGithubPr(githubPrExec, input.cwd, signal), equal: githubPrSnapshotEqual, publish(snapshot) { runtime.githubPr = snapshot; scheduleGithubPrExpiry(snapshot); refresh(); } }); const workspaceController = new AsyncRefreshController({ async read(input, signal) { try { return await collectWorkspaceSnapshot({ ...input, signal }); } catch { return { modules: {} }; } }, equal: workspaceSnapshotEqual, publish(snapshot) { runtime.workspace = snapshot; refresh(); } }); const clearDebounce = () => { if (!eventDebounceTimer) return; clearTimeout(eventDebounceTimer); eventDebounceTimer = void 0; }; const isActiveTarget = (target) => activeTarget?.cwd === target.cwd && activeTarget.generation === target.generation && activeTarget.sessionManager === target.sessionManager && target.generation === sessionGeneration; const requestRefresh = (target, reason = "event") => { if (!loaded || !isActiveTarget(target)) return; gitController.request({ cwd: target.cwd, config: loaded.config }); workspaceController.request( workspaceInput(target.cwd, loaded.config, reason, runtime.workspace) ); }; const scheduleRefresh = (ctx) => { const target = activeTarget; if (!target || target.cwd !== ctx.cwd || target.sessionManager !== ctx.sessionManager) { return; } clearDebounce(); eventDebounceTimer = setTimeout(() => { eventDebounceTimer = void 0; requestRefresh(target); }, EVENT_DEBOUNCE_MS); }; function restartLocalControllers(target) { if (!isActiveTarget(target)) return; gitController.start(target.generation); workspaceController.start(target.generation); } function clearGithubPrTimers() { if (githubPrRefreshTimer) clearInterval(githubPrRefreshTimer); if (githubPrExpiryTimer) clearTimeout(githubPrExpiryTimer); githubPrRefreshTimer = void 0; githubPrExpiryTimer = void 0; } function stopGithubPr() { clearGithubPrTimers(); githubPrController.stop(); runtime.githubPr = void 0; } function githubPrReachable() { return Boolean(loaded && reachableModuleRequirements(loaded.config).has("github_pr")); } function startGithubPr(target) { if (!isActiveTarget(target)) return false; stopGithubPr(); if (!githubPrReachable()) return false; githubPrController.start(++githubPrGeneration); githubPrRefreshTimer = setInterval(() => { requestGithubPr(target); }, GITHUB_PR_REFRESH_INTERVAL_MS); githubPrRefreshTimer.unref?.(); return true; } function requestGithubPr(target) { if (!isActiveTarget(target) || !githubPrReachable()) return; githubPrController.request({ cwd: target.cwd }); } function scheduleGithubPrExpiry(snapshot) { if (githubPrExpiryTimer) clearTimeout(githubPrExpiryTimer); githubPrExpiryTimer = void 0; if (snapshot?.expiresAt === void 0) return; const target = activeTarget; if (!target) return; const delay = snapshot.expiresAt - Date.now(); if (delay <= 0) { runtime.githubPr = void 0; githubPrController.clear(); refresh(); return; } githubPrExpiryTimer = setTimeout( () => { githubPrExpiryTimer = void 0; if (!isActiveTarget(target) || runtime.githubPr !== snapshot) return; scheduleGithubPrExpiry(snapshot); }, Math.min(delay, MAX_TIMER_DELAY_MS) ); githubPrExpiryTimer.unref?.(); } const installFooter = (ctx) => { const generation = ++sessionGeneration; sessionOwner = ctx.sessionManager; previewLoaded = void 0; menuController.abort(new DOMException("Starship session context replaced", "AbortError")); menuController = new AbortController(); const target = { cwd: ctx.cwd, generation, sessionManager: ctx.sessionManager }; clearDebounce(); gitController.stop(); workspaceController.stop(); stopGithubPr(); runtime.git = void 0; runtime.workspace = void 0; runtime.requestRender = void 0; runtime.renderPreview = void 0; runtime.inspect = void 0; runtime.footerWidth = void 0; activeTarget = ctx.mode === "tui" ? target : void 0; ctx.ui.setStatus("starship", void 0); if (!activeTarget || !loaded) return; gitController.start(generation); workspaceController.start(generation); startGithubPr(target); ctx.ui.setFooter((tui, _theme, footerData) => { runtime.requestRender = () => tui.requestRender(); runtime.renderPreview = (preview, width) => { const snapshot = runtimeSnapshot(ctx, footerData, runtime); return wrapFormattedStatusline( renderStatusline(preview.config, snapshot, width).ansi, width ); }; runtime.inspect = (current) => inspectStatuslineModules( current.config, runtimeSnapshot(ctx, footerData, runtime), runtime.footerWidth ?? 80 ); const unsubscribe = footerData.onBranchChange(() => { if (!isActiveTarget(target)) return; runtime.git = void 0; restartLocalControllers(target); clearDebounce(); startGithubPr(target); requestRefresh(target); requestGithubPr(target); tui.requestRender(); }); const timer = setInterval(() => { if (!isActiveTarget(target)) return; clearDebounce(); requestRefresh(target, "periodic"); tui.requestRender(); }, REFRESH_INTERVAL_MS); let disposed = false; return { dispose() { if (disposed) return; disposed = true; unsubscribe(); clearInterval(timer); if (isActiveTarget(target)) { activeTarget = void 0; clearDebounce(); gitController.stop(); workspaceController.stop(); stopGithubPr(); runtime.git = void 0; runtime.workspace = void 0; runtime.requestRender = void 0; runtime.renderPreview = void 0; runtime.inspect = void 0; runtime.footerWidth = void 0; } }, invalidate() { }, render(width) { runtime.footerWidth = width; const current = previewLoaded ?? loaded; if (!current) return []; const snapshot = runtimeSnapshot(ctx, footerData, runtime); return wrapFormattedStatusline( renderStatusline(current.config, snapshot, width).ansi, width ); } }; }); requestRefresh(target, "initial"); requestGithubPr(target); }; const configPath = settingsFilePath(getAgentDir()); const commandOptions = { settingsPath: configPath, getLoaded: () => loaded ?? loadStarshipConfig(configPath), getLoadedRevision: () => loadedRevision, getInspection: () => { const current = loaded ?? loadStarshipConfig(configPath); return runtime.inspect?.(current); }, getMenuOwner: () => { const generation = sessionGeneration; return { signal: menuController.signal, isCurrent: () => generation === sessionGeneration && !menuController.signal.aborted }; }, apply(next, ctx) { if (sessionOwner !== ctx.sessionManager) { throw new Error("Starship session context was replaced"); } previewLoaded = void 0; loaded = next; loadedRevision += 1; const target = activeTarget; if (target) { restartLocalControllers(target); startGithubPr(target); requestRefresh(target); requestGithubPr(target); } refresh(); }, preview(next, ctx) { if (sessionOwner !== ctx.sessionManager) return; previewLoaded = next; refresh(); }, renderPreview(preview, width) { return runtime.renderPreview?.(preview, width) ?? [ "Live preview is unavailable until the footer is ready." ]; } }; pi.registerCommand("starship", { description: "Customize or inspect the native Starship-style footer", getArgumentCompletions: completeStarshipArguments, handler: async (args, ctx) => { const commands = await loadStarshipCommands(); if (sessionOwner !== ctx.sessionManager) return; await commands.handleStarshipCommand(args, ctx, commandOptions); } }); pi.on("session_start", (_event, ctx) => { loaded = loadStarshipConfig(configPath); loadedRevision += 1; if (loaded.diagnostics.length > 0 && (ctx.mode === "tui" || ctx.hasUI)) { ctx.ui.notify(formatDiagnostics(loaded), "warning"); } runtime.thinkingLevel = pi.getThinkingLevel(); installFooter(ctx); }); pi.on("session_tree", (_event, ctx) => { installFooter(ctx); refresh(); }); pi.on("session_shutdown", (_event, ctx) => { if (sessionOwner !== ctx.sessionManager) return; sessionOwner = void 0; previewLoaded = void 0; sessionGeneration += 1; menuController.abort(new DOMException("Starship session shut down", "AbortError")); activeTarget = void 0; clearDebounce(); gitController.stop(); workspaceController.stop(); stopGithubPr(); runtime.git = void 0; runtime.workspace = void 0; runtime.requestRender = void 0; runtime.renderPreview = void 0; runtime.inspect = void 0; runtime.footerWidth = void 0; ctx.ui.setFooter(void 0); ctx.ui.setStatus("starship", void 0); }); pi.on("model_select", () => refresh()); pi.on("thinking_level_select", (event) => { runtime.thinkingLevel = event.level; refresh(); }); pi.on("agent_start", () => { runtime.isStreaming = true; refresh(); }); pi.on("agent_end", (_event, ctx) => { runtime.isStreaming = false; scheduleRefresh(ctx); const target = activeTarget; if (target?.sessionManager === ctx.sessionManager) requestGithubPr(target); refresh(); }); pi.on("turn_start", () => { runtime.isStreaming = true; refresh(); }); pi.on("turn_end", (_event, ctx) => { scheduleRefresh(ctx); refresh(); }); pi.on("tool_execution_start", (event) => { runtime.activeTools.set(event.toolName, (runtime.activeTools.get(event.toolName) ?? 0) + 1); refresh(); }); pi.on("tool_execution_end", (event, ctx) => { const count = runtime.activeTools.get(event.toolName) ?? 0; if (count <= 1) runtime.activeTools.delete(event.toolName); else runtime.activeTools.set(event.toolName, count - 1); runtime.lastCompletedTool = event.toolName; scheduleRefresh(ctx); refresh(); }); } function workspaceInput(cwd, config, reason, previous) { return { cwd, config, environment: allowlistedEnvironment(config), homeDir: homedir(), platform: process.platform, hostname: hostname(), username: safeUsername(), exec: execWorkspaceCommand, reason, previous }; } var ENVIRONMENT_ALLOWLIST = [ "AWS_CONFIG_FILE", "AWS_DEFAULT_PROFILE", "AWS_DEFAULT_REGION", "AWS_PROFILE", "AWS_REGION", "AZURE_CONFIG_DIR", "CLOUDSDK_ACTIVE_CONFIG_NAME", "CLOUDSDK_CONFIG", "CODESPACES", "CONDA_DEFAULT_ENV", "DOCKER_CONFIG", "DOCKER_CONTEXT", "GUIX_ENVIRONMENT", "IN_NIX_SHELL", "KUBECONFIG", "LOGNAME", "NIX_SHELL_LEVEL", "NIX_SHELL_NAME", "OS_CLIENT_CONFIG_FILE", "OS_CLOUD", "OS_PROJECT_NAME", "PATH", "PIXI_ENVIRONMENT_NAME", "PIXI_PROJECT_NAME", "PYENV_VERSION", "REMOTE_CONTAINERS", "RUSTC", "RUSTUP_TOOLCHAIN", "SSH_CONNECTION", "SSH_TTY", "TF_DATA_DIR", "TF_WORKSPACE", "USER", "USERNAME", "VIRTUAL_ENV", "WSL_DISTRO_NAME" ]; function allowlistedEnvironment(config) { const result = {}; const configured = config.modules.username.options.detect_env_vars; const names = /* @__PURE__ */ new Set([ ...ENVIRONMENT_ALLOWLIST, ...Array.isArray(configured) ? configured.filter((name) => typeof name === "string") : [] ]); for (const name of names) result[name] = process.env[name]; return result; } function safeUsername() { try { return userInfo().username; } catch { return process.env.USER ?? process.env.USERNAME ?? ""; } } function runtimeSnapshot(ctx, footerData, runtime) { return { cwd: ctx.cwd, homeDir: homedir(), gitRoot: runtime.git?.root, model: ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : void 0, thinkingLevel: runtime.thinkingLevel, turnCount: userTurnCount(ctx), activeTools: runtime.activeTools, isStreaming: runtime.isStreaming, lastCompletedTool: runtime.lastCompletedTool, contextUsage: ctx.getContextUsage() ?? void 0, tokenTotals: summarizeFooterUsage(ctx.sessionManager.getEntries()), usingSubscription: isSubscriptionBacked(ctx), gitBranch: runtime.git?.branch?.name ?? footerData.getGitBranch(), gitBranchDetails: runtime.git?.branch, gitCommit: runtime.git?.commit, gitState: runtime.git?.state, gitMetrics: runtime.git?.metrics, gitStatus: runtime.git?.status, gitWorktree: runtime.git?.worktree, githubPr: runtime.githubPr, workspace: runtime.workspace, extensionStatuses: footerData.getExtensionStatuses(), now: /* @__PURE__ */ new Date() }; } function userTurnCount(ctx) { return ctx.sessionManager.getBranch().filter((entry) => entry.type === "message" && entry.message.role === "user").length; } function isSubscriptionBacked(ctx) { const model = ctx.model; return model !== void 0 && (model.provider === "kimi-coding" || ctx.modelRegistry.isUsingOAuth(model)); } function formatDiagnostics(loaded) { const details = loaded.diagnostics.slice(0, 5).map((item) => item.message); const remaining = loaded.diagnostics.length - details.length; return [ `pi-starship settings: ${details.join("; ")}`, ...remaining > 0 ? [`+${remaining} more`] : [] ].join(" "); } function wrapFormattedStatusline(format, width) { if (width <= 0) return []; return wrapTextWithAnsi(format, width); } export { piStarship as default }; //# sourceMappingURL=index.ts.map